Quiero saber la diferencia exacta entre las capacidades de Linux y seccomp.
Explicaré el exacto diferencias a continuación, pero aquí está la explicación general:Las capacidades implican varias comprobaciones en las funciones del kernel accesibles mediante llamadas al sistema. Si la verificación falla (es decir, el proceso carece de la capacidad necesaria), la llamada al sistema normalmente se realiza para devolver un error. La verificación se puede realizar justo al comienzo de una llamada al sistema específica o más profundamente en el kernel en áreas a las que se puede acceder a través de múltiples llamadas al sistema diferentes (como escribir en un archivo privilegiado específico).
Seccomp es un filtro de llamadas al sistema que se aplica a todas las llamadas al sistema antes de que se ejecuten. Un proceso puede configurar un filtro que les permita revocar su derecho a ejecutar ciertas llamadas al sistema o argumentos específicos para ciertas llamadas al sistema. El filtro generalmente tiene la forma de un código de bytes eBPF, que el kernel usa para verificar si se permite o no una llamada al sistema para ese proceso. Una vez que se aplica un filtro, no se puede aflojar en absoluto, solo se puede hacer más estricto (suponiendo que las llamadas al sistema responsables de cargar una política seccomp aún estén permitidas).
Tenga en cuenta que algunas llamadas al sistema no pueden restringirse ni por seccomp ni por capacidades porque no son llamadas al sistema reales. Este es el caso de las llamadas vDSO, que son implementaciones en el espacio de usuario de varias llamadas al sistema que no necesitan estrictamente el kernel. Intentando bloquear getcpu()
o gettimeofday()
es inútil por este motivo, ya que un proceso utilizará el vDSO en lugar de la llamada al sistema nativo de todos modos. Afortunadamente, estas llamadas al sistema (y sus implementaciones virtuales asociadas) son en gran medida inofensivas.
También es que las capacidades de Linux usan seccomp internamente o es al revés o ambos son completamente diferentes.
Se implementan de manera completamente diferente internamente. Escribí otra respuesta en otra parte sobre la implementación actual de varias tecnologías de sandboxing explicando en qué se diferencian y para qué sirven.
Capacidades
Muchas llamadas al sistema que hacen cosas privilegiadas pueden incluir una verificación interna para garantizar que el proceso de llamada tenga capacidades suficientes. El núcleo almacena la lista de capacidades que tiene un proceso, y una vez que un proceso elimina capacidades, no puede recuperarlas. Por ejemplo, intentar escribir en /dev/cpu/*/msr
fallará a menos que el proceso llame al open()
syscall tiene CAP_SYS_RAWIO
. Esto se puede ver en el código fuente del kernel responsable de modificar los MSR (características de CPU de bajo nivel):
static int msr_open(struct inode *inode, struct file *file)
{
unsigned int cpu = iminor(file_inode(file));
struct cpuinfo_x86 *c;
if (!capable(CAP_SYS_RAWIO))
return -EPERM;
if (cpu >= nr_cpu_ids || !cpu_online(cpu))
return -ENXIO; /* No such CPU */
c = &cpu_data(cpu);
if (!cpu_has(c, X86_FEATURE_MSR))
return -EIO; /* MSR not supported */
return 0;
}
Algunas llamadas al sistema no se ejecutarán en absoluto si la capacidad correcta no está presente, como vhangup()
:
SYSCALL_DEFINE0(vhangup)
{
if (capable(CAP_SYS_TTY_CONFIG)) {
tty_vhangup_self();
return 0;
}
return -EPERM;
}
Las capacidades se pueden considerar como clases amplias de funcionalidad privilegiada que se pueden eliminar selectivamente de un proceso o usuario. Las funciones específicas que tienen comprobaciones de capacidad varían de una versión de kernel a otra y, a menudo, hay discusiones entre los desarrolladores del kernel sobre si una función determinada debe o no requerir capacidades para ejecutarse. Generalmente , reducir las capacidades de un proceso mejora la seguridad al reducir la cantidad de acciones privilegiadas que puede realizar. Tenga en cuenta que algunas capacidades se consideran equivalentes a la raíz , lo que significa que, incluso si deshabilita todas las demás capacidades, pueden, en algunas condiciones, usarse para recuperar permisos completos. El creador de grsecurity, Brad Spengler, da muchos ejemplos. Un ejemplo obvio sería CAP_SYS_MODULE
que permite cargar módulos de kernel arbitrarios. Otro sería CAP_SYS_ADMIN
que es una capacidad general casi equivalente a root.
Modo 1 segundo
Hay dos tipos de seccomp:modo 1 (estricto) y modo 2 (filtro). El modo 1 es extremadamente restrictivo y, una vez habilitado, solo permite cuatro llamadas al sistema. Estas llamadas al sistema son read()
, write()
, exit()
y rt_sigreturn()
. A un proceso se le envía inmediatamente el fatal SIGKILL
una señal del núcleo si alguna vez intenta usar una llamada al sistema que no está en la lista blanca. Este modo es el modo seccomp original y no requiere generar y enviar bytecode eBPF al kernel. Se realiza una llamada al sistema especial, después de lo cual el modo 1 estará activo durante la vida útil del proceso:seccomp(SECCOMP_SET_MODE_STRICT)
o prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT)
. Una vez activo, no se puede apagar.
El siguiente es un programa de ejemplo que ejecuta de forma segura el código de bytes que devuelve 42:
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
/* "mov al,42; ret" aka "return 42" */
static const unsigned char code[] = "\xb0\x2a\xc3";
void main(void)
{
int fd[2], ret;
/* spawn child process, connected by a pipe */
pipe(fd);
if (fork() == 0) {
close(fd[0]);
/* enter mode 1 seccomp and execute untrusted bytecode */
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
ret = (*(uint8_t(*)())code)();
/* send result over pipe, and exit */
write(fd[1], &ret, sizeof(ret));
syscall(SYS_exit, 0);
} else {
close(fd[1]);
/* read the result from the pipe, and print it */
read(fd[0], &ret, sizeof(ret));
printf("untrusted bytecode returned %d\n", ret);
}
}
El modo 1 es el modo original y se agregó con el fin de hacer posible la ejecución de código de bytes no confiable para cálculos sin procesar. Un proceso de intermediario bifurcaría a un niño (y posiblemente configuraría la comunicación a través de conductos), y el niño habilitaría seccomp, impidiendo que haga otra cosa que leer y escribir en descriptores de archivos que ya están abiertos y salir. Este proceso secundario podría ejecutar un código de bytes que no sea de confianza de forma segura. No mucha gente usó este modo, pero antes de que Linus pudiera quejarse lo suficientemente fuerte como para eliminarlo, el equipo de Google Chrome expresó su deseo de usarlo para su navegador. Esto creó un interés renovado en seccomp y lo salvó de una muerte prematura.
Modo 2 segundos
El segundo modo, filtro, también llamado seccomp-bpf, permite que el proceso envíe una política de filtro de grano fino al núcleo, permitiendo o denegando llamadas al sistema completas, o argumentos o rangos de argumentos de llamada al sistema específicos. La política también especifica lo que sucede en caso de una infracción (por ejemplo, ¿debe eliminarse el proceso o simplemente debe denegarse la llamada al sistema?) y si la infracción debe registrarse o no. Debido a que las llamadas al sistema de Linux se mantienen en registros y, por lo tanto, solo pueden ser números enteros, es imposible filtrar el contenido de la memoria al que podría apuntar un argumento de llamada al sistema. Por ejemplo, aunque puede evitar open()
de ser llamado con el O_RDWR
con capacidad de escritura o O_WRONLY
banderas, no puede incluir en la lista blanca la ruta individual que está abierta. La razón de esto es que, en segundo lugar, la ruta no es más que un puntero a la memoria que contiene la ruta del sistema de archivos terminada en nulo. No hay forma de garantizar que la memoria que contiene la ruta no haya sido modificada por un subproceso hermano entre la aprobación de la verificación de seccomp y la desreferencia del puntero, a menos que se coloque en la memoria de solo lectura y se niegue el acceso a las llamadas al sistema relacionadas con la memoria. A menudo es necesario utilizar LSM como AppArmor.
Este es un programa de ejemplo que usa el modo 2 seccomp para garantizar que solo pueda imprimir su PID actual. Este programa utiliza la biblioteca libseccomp que facilita la creación de filtros eBPF seccomp, aunque también es posible hacerlo de la manera difícil sin ninguna biblioteca de abstracción.
#include <seccomp.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
void main(void)
{
/* initialize the libseccomp context */
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
/* allow exiting */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
/* allow getting the current pid */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
/* allow changing data segment size, as required by glibc */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
/* allow writing up to 512 bytes to fd 1 */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
SCMP_A0(SCMP_CMP_EQ, 1),
SCMP_A2(SCMP_CMP_LE, 512));
/* if writing to any other fd, return -EBADF */
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EBADF), SCMP_SYS(write), 1,
SCMP_A0(SCMP_CMP_NE, 1));
/* load and enforce the filters */
seccomp_load(ctx);
seccomp_release(ctx);
printf("this process is %d\n", getpid());
}
El modo 2 seccomp se creó porque el modo 1 obviamente tenía sus limitaciones. No todas las tareas se pueden separar en un proceso de código de bytes puro que podría ejecutarse en un proceso secundario y comunicarse a través de conductos o memoria compartida. Este modo tiene muchas más características y su funcionalidad continúa ampliándose lentamente. Sin embargo, todavía tiene sus desventajas. El uso seguro del modo 2 seccomp requiere una comprensión profunda de las llamadas al sistema (quiere bloquear kill()
de matar a otros procesos? Para mal, puedes matar procesos con fcntl()
¡también!). También es frágil, ya que los cambios en la libc subyacente pueden causar roturas. La glibc open()
La función, por ejemplo, ya no siempre usa la llamada al sistema de ese nombre y en su lugar puede usar openat()
, incumpliendo las políticas que incluyeron solo las primeras en la lista blanca.