Cómo evitar que un proceso escriba archivos

13

Quiero ejecutar un comando en Linux de una manera que no pueda crear ni abrir ningún archivo para escribir. Todavía debería poder leer archivos de manera normal (por lo que un chroot vacío no es una opción), y aún así poder escribir en archivos ya abiertos (especialmente stdout).

Puntos de bonificación si todavía es posible escribir archivos en ciertos directorios (es decir, el directorio actual).

Estoy buscando una solución que sea de proceso local, es decir, que no implique configurar cosas como AppArmor o SELinux para todo el sistema, ni privilegios de root. Sin embargo, puede implicar la instalación de sus módulos de kernel.

Estaba mirando las capacidades y estas habrían sido agradables y fáciles, si hubiera una capacidad para crear archivos. ulimit es otro enfoque que sería conveniente, si cubriera este caso de uso.

Joachim Breitner
fuente
Demasiados programas suponen que son capaces de escribir archivos de forma habitual (y fallan de manera extraña cuando no pueden). stracete dice qué archivos está abriendo el programa. ¿Por qué quieres hacer esto? ¿Es un programa específico, o quieres esto para probar u otra cosa? ¿Puede ejecutar el programa como un usuario / grupo que no tiene permiso para escribir en casi todas partes, excepto en el directorio actual? Las distribuciones modernas de Linux utilizan la idea de un grupo para cada usuario, por lo que debería ser relativamente fácil de configurar.
vonbrand
Es un programa especial (Isabelle) que ya interpreta el código de una manera segura (sin ejecución de código arbitrario), pero aún permite que el código cree archivos en lugares arbitrarios. Como el código no es confiable, quiero evitar que esto suceda (abortando el programa). El programa ya se ejecuta como un usuario especial, pero me sentiría más seguro si el código no pudiera cambiar, digamos, / tmp o lugares similares.
Joachim Breitner
Puede agregar un nuevo usuario para ejecutar la aplicación.
ctrl-alt-delor

Respuestas:

9

¿Qué tal crear un chroot vacío, luego montar en bind el sistema de archivos principal como de solo lectura dentro del chroot?

Probablemente debería ser algo como esto para crear un montaje de enlace de solo lectura:

mount --bind /foo/ /path/to/chroot/
mount -o remount,ro /path/to/chroot/

Puede enlazar y montar otros directorios a los que desea que la cárcel también tenga acceso de escritura. Tenga cuidado si necesita enlazar-montar directorios especiales (/ dev /, / proc /, / sys /), montarlos tal cual puede ser inseguro.

Lie Ryan
fuente
Nuevamente, necesita privilegios de root y otra "configuración global". Pero una opción, sí.
Joachim Breitner
¿Es /foo/la ruta al sistema de archivos principal?
Wayne Conrad
5

Parece que la herramienta adecuada para este trabajo se fseccompbasa en el sync-ignoringcódigo f de Bastian Blank. Se me ocurrió este archivo relativamente pequeño que hace que todos sus hijos no puedan abrir un archivo para escribir:

/*
 * Copyright (C) 2013 Joachim Breitner <[email protected]>
 *
 * Based on code Copyright (C) 2013 Bastian Blank <[email protected]>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#define _GNU_SOURCE 1
#include <errno.h>
#include <fcntl.h>
#include <seccomp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define filter_rule_add(action, syscall, count, ...) \
  if (seccomp_rule_add(filter, action, syscall, count, ##__VA_ARGS__)) abort();

static int filter_init(void)
{
  scmp_filter_ctx filter;

  if (!(filter = seccomp_init(SCMP_ACT_ALLOW))) abort();
  if (seccomp_attr_set(filter, SCMP_FLTATR_CTL_NNP, 1)) abort();
  filter_rule_add(SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY));
  filter_rule_add(SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR));
  return seccomp_load(filter);
}

int main(__attribute__((unused)) int argc, char *argv[])
{
  if (argc <= 1)
  {
    fprintf(stderr, "usage: %s COMMAND [ARG]...\n", argv[0]);
    return 2;
  }

  if (filter_init())
  {
    fprintf(stderr, "%s: can't initialize seccomp filter\n", argv[0]);
    return 1;
  }

  execvp(argv[1], &argv[1]);

  if (errno == ENOENT)
  {
    fprintf(stderr, "%s: command not found: %s\n", argv[0], argv[1]);
    return 127;
  }

  fprintf(stderr, "%s: failed to execute: %s: %s\n", argv[0], argv[1], strerror(errno));
  return 1;
}

Aquí puede ver que todavía es posible leer archivos:

[jojo@kirk:1] Wed, der 06.03.2013 um 12:58 Uhr Keep Smiling :-)
> ls test
ls: cannot access test: No such file or directory
> echo foo > test
bash: test: Permission denied
> ls test
ls: cannot access test: No such file or directory
> touch test
touch: cannot touch 'test': Permission denied
> head -n 1 no-writes.c # reading still works
/*

No impide eliminar archivos, moverlos u otras operaciones de archivos además de abrir, pero eso podría agregarse.

Una herramienta que permite esto sin tener que escribir código C es syscall_limiter .

Joachim Breitner
fuente
44
Tenga en cuenta que el enfoque seguro es incluir en la lista blanca las llamadas al sistema, no incluirlas en la lista negra. Si se niega demasiado, se pueden usar ayudantes externos sin caja para ayudar al programa. Con LD_PRELOAD, estos ayudantes pueden hacerse transparentes para el programa que ejecutamos.
Vi.
4

¿Consideraría escribir un sustituto para la open(…)función y cargarlo usando LD_PRELOAD?

Leonid
fuente
2
Probablemente quieras decir open... Bueno, consideraría usar una solución existente que use este enfoque, sí.
Joachim Breitner
2
Hay algo como esto en github.com/certik/restrict , pero se configura compilando y no parece tener un uso generalizado.
Joachim Breitner
Sí, lo siento, mi error, actualizando la respuesta ... Pero me parece que también tendrás que sustituirla por una write(…).
Leonid
En cuanto a github.com/certik/restrict , sí, tienes toda la razón.
Leonid
3

La solución más simple es probablemente un programa contenedor que crea un nuevo espacio de nombres del sistema de archivos con los sistemas de archivos relevantes montados de solo lectura y luego ejecuta el programa que está tratando de restringir.

Esto es lo que systemdhace cuando usa ReadOnlyDirectories=para marcar ciertos directorios como de solo lectura para un servicio. También hay un unsharecomando util-linuxque puede hacer el trabajo de crear un nuevo espacio de nombres, por lo que puede hacer algo como:

unshare -m <wrapper>

donde wrapperentonces solo tendría que volver a montar los sistemas de archivos según sea necesario antes de iniciar el programa de destino real.

El único problema es que debes ser rootpara crear el nuevo espacio de nombres ...

TomH
fuente
Pensé en esto. ¿Pero es esto posible sin ser root? ¿Hay un script / programa listo para eso disponible?
Joachim Breitner
1
Sí, parece que necesitas ser root, al menos con el kernel 3.7.
TomH
Estaba buscando más en esta solución. Es posible vincular-montar / recursivamente a un nuevo /, pero no y marcarlo recursivamente como de solo lectura.
Joachim Breitner
2

Podrías ejecutarlo en un chroot, montando versiones especiales de /tmp y dentro. Quizás systemd sea ​​de ayuda, y particularmente systemd-nspawn (1) , que se parece a lo que quieres.

vonbrand
fuente
2

Una máquina virtual permitiría que el script escriba en cualquier lugar sin afectar el sistema host, e inspeccionar en dónde realmente está tratando de escribir, lo que parece ser el objetivo.

Por ejemplo, puede iniciar fácilmente Arch Linux con

kvm -boot d -m 512 -cdrom archlinux-*.iso
l0b0
fuente
1
Todavía quiero ejecutar el programa en la máquina actual para evitar tener que configurar un nuevo sistema, un nuevo entorno, etc. Una máquina virtual es demasiado pesada para mi caso de uso.
Joachim Breitner
2

Hacer una configuración inicial como root es realmente la forma más fácil. Específicamente, un chroot en un montaje de enlace de solo lectura es el camino de menor resistencia.

Puede usar bindfs en lugar de mount --bindcrear la vista de solo lectura sin necesidad de ser root. Sin embargo, debe hacer algo como root para evitar el acceso a otros archivos, como chroot.

Otro enfoque es LD_PRELOADuna biblioteca que se engancha en la apertura de archivos y se niega a permitir la escritura. Esto no requiere privilegios especiales. Desde una perspectiva de seguridad, esto se puede omitir, pero está bien para su caso de uso donde solo necesita contener una característica específica y no un código nativo arbitrario. Sin embargo, no conozco una biblioteca existente para esto. LD_PRELOADtambién podría usarse para limitar el programa a la vista de solo lectura creada con mount --bindo bindfs; De nuevo, no sé de una biblioteca existente.

En Debian y derivados, puede configurar un entorno schroot . Schroot es una raíz setuid y necesita ser configurada como root, pero puede ser ejecutada por cualquier usuario autorizado.

Un método que no requiere ninguna cooperación desde la raíz es ejecutar el proceso en una máquina virtual. Puede configurar KVM o VirtualBox, o Linux en modo de usuario . Es un poco pesado y significará un consumo de memoria adicional, pero no debería afectar significativamente la velocidad del cálculo simbólico en bruto.

¿Cómo "encarcelar" un proceso sin ser root? podría proporcionar algo de inspiración.

Gilles 'SO- deja de ser malvado'
fuente
1

Una forma de evitar al menos que el proceso escriba los archivos (pero no los cree) es llamar ulimit -f 0primero. Esto anulará el proceso tan pronto como intente escribir en un archivo, pero aún es posible crear archivos vacíos.

Joachim Breitner
fuente