Libseccomp: RFE: valores de retorno de error definidos con mayor precisión

Creado en 7 oct. 2017  ·  24Comentarios  ·  Fuente: seccomp/libseccomp

Al observar los valores de retorno de error de libseccomp sobre las llamadas al sistema, no es posible determinar cuál de las siguientes razones causó los errores:

  1. syscall no existe en algún arco
  2. syscall no se puede emparejar en algún arco (porque está multiplexado, piense en socket/socketcall)
  3. otros casos de error

Al construir el filtro seccomp, la persona que llama puede considerar algunas de estas razones fatales pero no las otras, por lo que se necesitaría información de error más detallada (un conjunto más amplio de valores de error que solo -EINVAL ).

Véase también systemd PR 6952 .

enhancement prioritmedium

Comentario más útil

Muchas gracias. Tengo un conjunto de parches a medio hacer que terminaré y enviaré como relaciones públicas para su revisión.

Todos 24 comentarios

Hola @topimiettinen , lamento haber tardado tanto en llegar a esto, pero creo que es hora de que arreglemos esto.

@drakenclimber este va a ser genial. Todas las API de libseccomp deberían tener páginas de manual en este punto (si no es así, necesitamos crear un problema para eso), con todas las páginas de manual con algunos comentarios ondulados a mano de "valores negativos en caso de error" en la sección VALOR DEVUELTO. Creo que tenemos que hacer lo siguiente:

  • auditar manualmente cada llamada API para generar una lista de posibles valores de retorno
  • decidir si estos valores devueltos tienen sentido, modificar el código si no lo tienen
  • documente cada posible valor de retorno en la página de manual asociada con una breve explicación de lo que indica el código de error

¿Pensamientos?

Hola @topimiettinen , lamento haber tardado tanto en llegar a esto, pero creo que es hora de que arreglemos esto.

@drakenclimber este va a ser genial. Todas las API de libseccomp deberían tener páginas de manual en este punto (si no es así, necesitamos crear un problema para eso), con todas las páginas de manual con algunos comentarios ondulados a mano de "valores negativos en caso de error" en la sección VALOR DEVUELTO. Creo que tenemos que hacer lo siguiente:

* manually audit each API call to generate a list of possible return values

* decide if these return values make sense, modify the code if they don't

* document each possible return value in the associated manpage with a brief explanation of what the error code indicates

¿Pensamientos?

Dang... Estoy de acuerdo a regañadientes con todo lo que escribiste anteriormente, especialmente con el esfuerzo requerido :). Obtener los códigos de retorno correctos y luego documentarlos será un gran esfuerzo.

Y sí, aunque no es un trabajo glamoroso, creo que es crítico. He estado trabajando en algunas cosas de cgroup y recientemente nos encontramos con una implementación de contenedor que malinterpretó por completo una función de cgroup... pero no había un documento definitivo en el kernel ni en ningún otro lugar para guiar adecuadamente el camino, por lo que hicieron que funcionara de la mejor manera ellos podrían.

¡Es genial ver actividad en este tema! No estoy en contra de una revisión exhaustiva, pero la solicitud original se limitó solo a poder distinguir diferentes modos de falla, lo cual es algo ortogonal. La revisión ciertamente ayudaría, incluso creo que es un requisito previo hasta cierto punto.

¡Es genial ver actividad en este tema! No estoy en contra de una revisión exhaustiva, pero la solicitud original se limitó solo a poder distinguir diferentes modos de falla, lo cual es algo ortogonal. La revisión ciertamente ayudaría, incluso creo que es un requisito previo hasta cierto punto.

Tenemos que hacer la revisión en algún momento, y bien podría ser ahora. Cuanto más esperemos, menos útiles serán los códigos de error para las personas que llaman y el objetivo de libseccomp es hacer que esto sea más fácil de usar :)

Dang... Estoy de acuerdo a regañadientes con todo lo que escribiste anteriormente, especialmente con el esfuerzo requerido :). Obtener los códigos de retorno correctos y luego documentarlos será un gran esfuerzo.

Sí, esta es una de las razones por las que este problema se ha pospuesto durante tanto tiempo, pero lo he pospuesto lo suficiente (¡al menos @drakenclimber puede decir que lo ha estado posponiendo por menos de un año!). Más tarde hoy (¿mañana?) dividiré esto en partes/problemas múltiples (con algunas sugerencias) para que sea un poco más fácil abordarlo en partes.

Como algo positivo, parece que libseccomp realmente solo usa nueve valores únicos de errno (según una verificación muy cruda):

# grep -e "-E[A-Z0-9]\+" src/*.{h,c} | sed 's/.*-\(E[A-Z0-9]\+\).*/\1/' | sort -u
EACCES
EDOM
EEXIST
EFAULT
EINVAL
ENOMEM
EOPNOTSUPP
EPERM
ESRCH

... esto debería ayudar a reducir un poco el espacio del problema, especialmente si podemos acordar un valor semántico común para cada código de error en la biblioteca (lo que definitivamente deberíamos hacer).

Esto es un poco más tarde de lo previsto, pero aquí hay una lista completa de las funciones que componen la API de libseccomp:

const struct scmp_version *seccomp_version(void)
unsigned int seccomp_api_get(void)
int seccomp_api_set(unsigned int level)
scmp_filter_ctx seccomp_init(uint32_t def_action)
int seccomp_reset(scmp_filter_ctx ctx, uint32_t def_action)
void seccomp_release(scmp_filter_ctx ctx)
int seccomp_merge(scmp_filter_ctx ctx_dst, scmp_filter_ctx ctx_src)
uint32_t seccomp_arch_resolve_name(const char *arch_name)
uint32_t seccomp_arch_native(void)
int seccomp_arch_exist(const scmp_filter_ctx ctx, uint32_t arch_token)
int seccomp_arch_add(scmp_filter_ctx ctx, uint32_t arch_token)
int seccomp_arch_remove(scmp_filter_ctx ctx, uint32_t arch_token)
int seccomp_load(const scmp_filter_ctx ctx)
int seccomp_attr_get(const scmp_filter_ctx ctx, enum scmp_filter_attr attr, uint32_t *value)
int seccomp_attr_set(scmp_filter_ctx ctx, enum scmp_filter_attr attr, uint32_t value)
char *seccomp_syscall_resolve_num_arch(uint32_t arch_token, int num)
int seccomp_syscall_resolve_name_arch(uint32_t arch_token, const char *name)
int seccomp_syscall_resolve_name_rewrite(uint32_t arch_token, const char *name)
int seccomp_syscall_resolve_name(const char *name)
int seccomp_syscall_priority(scmp_filter_ctx ctx, int syscall, uint8_t priority)
int seccomp_rule_add_array(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, const struct scmp_arg_cmp *arg_array)
int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...)
int seccomp_rule_add_exact_array(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, const struct scmp_arg_cmp *arg_array)
int seccomp_rule_add_exact(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...)
int seccomp_notify_alloc(struct seccomp_notif **req, struct seccomp_notif_resp **resp)
void seccomp_notify_free(struct seccomp_notif *req, struct seccomp_notif_resp *resp)
int seccomp_notify_receive(int fd, struct seccomp_notif *req)
int seccomp_notify_respond(int fd, struct seccomp_notif_resp *resp)
int seccomp_notify_id_valid(int fd, uint64_t id)
int seccomp_notify_fd(const scmp_filter_ctx ctx)
int seccomp_export_pfc(const scmp_filter_ctx ctx, int fd)
int seccomp_export_bpf(const scmp_filter_ctx ctx, int fd)

... de estas funciones, solo tenemos que preocuparnos de que las funciones devuelvan "int".

Posibles agrupaciones de funciones que deberían tener rutas de código y valores de retorno similares.

  • Grupo A
int seccomp_arch_exist(const scmp_filter_ctx ctx, uint32_t arch_token)
int seccomp_arch_add(scmp_filter_ctx ctx, uint32_t arch_token)
int seccomp_arch_remove(scmp_filter_ctx ctx, uint32_t arch_token)
  • Grupo B
int seccomp_attr_get(const scmp_filter_ctx ctx, enum scmp_filter_attr attr, uint32_t *value)
int seccomp_attr_set(scmp_filter_ctx ctx, enum scmp_filter_attr attr, uint32_t value)
  • Grupo C
int seccomp_syscall_resolve_name_arch(uint32_t arch_token, const char *name)
int seccomp_syscall_resolve_name_rewrite(uint32_t arch_token, const char *name)
int seccomp_syscall_resolve_name(const char *name)
  • Grupo D
int seccomp_rule_add_array(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, const struct scmp_arg_cmp *arg_array)
int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...)
int seccomp_rule_add_exact_array(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, const struct scmp_arg_cmp *arg_array)
int seccomp_rule_add_exact(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...)
  • Grupo E
int seccomp_notify_receive(int fd, struct seccomp_notif *req)
int seccomp_notify_respond(int fd, struct seccomp_notif_resp *resp)
int seccomp_notify_id_valid(int fd, uint64_t id)
int seccomp_notify_fd(const scmp_filter_ctx ctx)
  • Grupo F
int seccomp_load(const scmp_filter_ctx ctx)
int seccomp_export_bpf(const scmp_filter_ctx ctx, int fd)

... si la función no está en uno de los grupos anteriores, es probable que sea única en su ruta de código y/o valor de retorno.

El grupo C solo devuelve __NR_SCMP_ERROR.

El grupo D puede devolver uno de EINVAL, EPERM, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

seccomp_load() puede devolver EINVAL, ENOMEM, ESRCH y también valores errno de prctl() (solo EACCES, EFAULT, EINVAL para PR_SET_NO_NEW_PRIVS y PR_SET_SECCOMP) y seccomp() (EACCES, EFAULT, EINVAL, ENOMEM, ESRCH) llamadas al sistema según su manual paginas

Cuando se trata de que libseccomp devuelva los errores de syscall a la persona que llama, por ejemplo, prctl() y seccomp(), creo que solo debemos ocultarlos detrás de un solo valor errno (¿quizás ENOSYS?) para que libseccomp no se vea afectado por cualquier cambio en el núcleo (o diferencias ABI).

Si esto se convierte en un problema para la depuración, tal vez podríamos introducir un nuevo atributo que pasaría el valor errno directamente a la persona que llama.

Posiblemente, pero entonces sería bueno asegurarse de que no haya rotura de ABI/API si los usuarios de libseccomp ya esperan ciertos valores de errno. El problema original era que algunas llamadas al sistema solo existen en algunas arquitecturas (como ugetrlimit solo en x86_32), pero esto no se podía distinguir de un nombre de llamada al sistema mal escrito, por lo que se necesitarían códigos de error diferentes (posiblemente sintéticos). .

Bueno, como dijimos anteriormente, actualmente no garantizamos valores específicos de errno, solo "valores negativos en caso de falla", por lo que aunque sería desafortunado interrumpir a los usuarios que actualmente están haciendo suposiciones sobre valores específicos de errno, creo que cambiar las cosas proporcionar una sólida garantía de errno en todas las versiones del kernel y ABI en el futuro es una compensación que vale la pena.

Bueno, como dijimos anteriormente, actualmente no garantizamos valores específicos de errno, solo "valores negativos en caso de falla", por lo que aunque sería desafortunado interrumpir a los usuarios que actualmente están haciendo suposiciones sobre valores específicos de errno, creo que cambiar las cosas proporcionar una sólida garantía de errno en todas las versiones del kernel y ABI en el futuro es una compensación que vale la pena.

Estoy de acuerdo.

Una vez que se complete esta evaluación y actualicemos los valores de errno en caso de fallas, me sentiría más cómodo haciendo algún tipo de garantía de los valores de errno devueltos.

La idea de agrupar tal vez no fue la mejor, así que comencemos una lista para hacer un seguimiento de todo esto _(Seguiré actualizando esto a medida que avancemos)_:

  • [x] seccomp_restablecer
    Actualmente regresa: EINVAL, ENOMEM.

  • [x] seccomp_merge
    Actualmente regresa: EINVAL, EDOM, EEXIST, ENOMEM.

  • [x] seccomp_arch_exist
    Actualmente devuelve: EINVAL, EEXIST.

  • [x] seccomp_arch_add
    Actualmente regresa: EINVAL, EEXIST, ENOMEM, EDOM.

  • [x] seccomp_arch_remove
    Actualmente devuelve: EINVAL, EEXIST.

  • [x] seccomp_load
    Actualmente regresa: EINVAL, ENOMEM, ESRCH, ECANCELED.

  • [x] seccomp_attr_get
    Actualmente devuelve: EINVAL, EEXIST.

  • [x] seccomp_attr_conjunto
    Actualmente regresa: EINVAL, EACCES, EOPNOTSUPP, EEXIST.

  • [x] seccomp_syscall_resolve_name_arch
    Ya bien definido, devuelve el valor de syscall o __NR_SCMP_ERROR en caso de falla.

  • [x] seccomp_syscall_resolve_name_rewrite
    Ya bien definido, devuelve el valor de syscall o __NR_SCMP_ERROR en caso de falla.

  • [x] seccomp_syscall_resolve_nombre
    Ya bien definido, devuelve el valor de syscall o __NR_SCMP_ERROR en caso de falla.

  • [x] seccomp_syscall_prioridad
    Actualmente regresa: EINVAL, EDOM, EFAULT, ENOMEM.

  • [x] seccomp_rule_add_array
    Actualmente devuelve: EINVAL, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

  • [x] seccomp_rule_add
    Actualmente devuelve: EINVAL, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

  • [x] seccomp_rule_add_exact_array
    Actualmente devuelve: EINVAL, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

  • [x] seccomp_rule_add_exact
    Actualmente devuelve: EINVAL, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

  • [x] seccomp_notify_alloc
    Actualmente regresa: EOPNOTSUPP, ENOMEM, EFAULT, ECANCELED. La página de manual ya especifica -1 en caso de error, lo que probablemente se refiera solo al errno seccomp().

  • [x] seccomp_notify_receive
    Actualmente regresa: EOPNOTSUPP y ECANCELED. La página de manual ya especifica -1 en caso de error, lo que probablemente se refiera solo al errno seccomp().

  • [x] seccomp_notificar_responder
    Actualmente regresa: EOPNOTSUPP y ECANCELED. La página de manual ya especifica -1 en caso de error, lo que probablemente se refiera solo al errno seccomp().

  • [x] seccomp_notify_id_válido
    Actualmente regresa: EOPNOTSUPP y ECANCELED. La página de manual ya especifica -ENOENT en caso de error (ID no válida), que probablemente se refiere solo al errno seccomp().

  • [x] seccomp_notify_fd
    Ya bien definido, devuelve la notificación fd.

  • [x] seccomp_export_pfc
    Actualmente vuelve: EINVAL y ECANCELED.

  • [x] seccomp_export_bpf
    Actualmente regresa: EINVAL, ENOMEM y ECANCELED.

Ahora que tenemos una lista de qué funciones devuelven qué códigos de error, me siento un poco mejor al respecto, especialmente porque ya somos bastante consistentes con la forma en que usamos nuestros códigos de error. Eso último debería ayudar enormemente.

Voy a iniciar un PR para que podamos comenzar a recopilar correcciones y comentarios sobre los cambios, lo publicaré aquí pronto.

Dada la naturaleza bastante "especial" de ENOSYS, tengo mis reservas sobre el uso de errno como nuestro kernel/libc catch-all. Tendré que echar un vistazo a otros valores, ¿alguien tiene algún sentimiento/pensamiento fuerte sobre esto?

Todavía falta mucho, en su mayoría ediciones de la página de manual y comentarios de código (sin mencionar las pruebas), pero puede consultar la siguiente rama para tener una idea de lo que estoy pensando:

Dada la naturaleza bastante "especial" de ENOSYS, tengo mis reservas sobre el uso de errno como nuestro kernel/libc catch-all. Tendré que echar un vistazo a otros valores, ¿alguien tiene algún sentimiento/pensamiento fuerte sobre esto?

¿Qué pasa con EIO?

¿Qué pasa con EIO?

No sé si eso haría que el error fuera mucho más procesable. ¿Qué tal si, en cambio, extendiéramos la API con funciones que no usan valores errno, pero por ejemplo:

  • SCMP_ERROR_UNKNOWN_SYSCALL: libseccomp no conoce la llamada al sistema: la persona que llama puede usar esto para rechazar la entrada del usuario (por ejemplo, error tipográfico en el nombre de la llamada al sistema)
  • SCMP_ERROR_SYSCALL_NOT_FOR_THIS_ARCH: libseccomp conoce la llamada al sistema pero no está disponible aquí: la persona que llama puede ignorar este error solo para esta arquitectura
  • SCMP_ERROR_API_USAGE: libseccomp detecta un problema con la lógica de llamada que no debería ocurrir en el código escrito correctamente: la persona que llama podría activar la afirmación ()
  • SCMP_ERROR_KERNEL_OTHER: libseccomp estuvo bien con la entrada y la secuencia de llamadas, pero el kernel devolvió ciertos errores bien definidos, por ejemplo, ENOMEM, EPERM, ENOSYS: la persona que llama debe verificar el errno para la acción. En caso de que libseccomp sepa que el motivo del error se debe a un error cometido por la persona que llama (por ejemplo, EFAULT), tal vez se podría usar SCMP_ERROR_API_USAGE en su lugar.
  • SCMP_ERROR_KERNEL_API_USAGE: libseccomp estuvo bien con la entrada, etc., pero al kernel no le gustó por razones no tan claras y obvias. Esto podría indicar un cambio en el kernel, una configuración deshabilitada, un error en libseccomp o en la persona que llama, una entrada de usuario muy rara, etc. análisis adicional, no asertivo () capaz.

Alternativamente, la API con errnos podría permanecer como está, pero podría usarse una nueva función para solicitar el código de error anterior.

Quizás @poettering o @keszybz también podrían comentar.

No sé si eso haría que el error fuera mucho más procesable. ¿Qué tal si, en cambio, extendiéramos la API con funciones que no usan valores errno...

Bien, antes de que pudiéramos siquiera considerar hacer algo así (y no estoy seguro de que queramos hacer eso), debemos decidirnos por códigos de retorno estables y compatibles. Eso es en lo que estamos trabajando aquí y en lo que apuntamos para v2.5.

Obtengamos los códigos de retorno estables/compatibles en v2.5 y podemos ver cómo funciona, si necesitamos hacer algo adicional, podemos considerarlo para v2.6.

¿Qué pasa con EIO?

Me quitaron esto por un tiempo debido a otras prioridades de trabajo y algunas cosas del núcleo, pero ahora que estoy de vuelta en libseccomp me doy cuenta de que usar EIO aquí parece incorrecto. Déjame pensar un poco más sobre esto.

¿Qué pasa con ECANCELED como el código de error general del kernel?

Tengo curiosidad por lo que piensas @drakenclimber , has estado callado sobre esto por un tiempo.

Me quitaron esto por un tiempo debido a otras prioridades de trabajo y algunas cosas del núcleo, pero ahora que estoy de vuelta en libseccomp me doy cuenta de que usar EIO aquí parece incorrecto. Déjame pensar un poco más sobre esto.

Aquí igual. Las cosas han estado un poco agitadas últimamente :/.

Tengo curiosidad por lo que piensas @drakenclimber , has estado callado sobre esto por un tiempo.

Por supuesto. Quiero leer todo el hilo de nuevo, y luego intervendré.

¿Qué pasa con ECANCELED como el código de error general del kernel?

Reconozco que no estaba tremendamente familiarizado con ECANCELED. Miré brevemente a través de la fuente del kernel para su uso y también hice una búsqueda en Google. ECANCELED no tiene colisiones con libseccomp, prctl() u otras API anteriores que hemos usado, y creo que puede encapsular razonablemente cualquier error que el kernel nos arroje.

tl;dr - Estoy bien con ECANCELED como nuestro catch-all para los errores del kernel.

Muchas gracias. Tengo un conjunto de parches a medio hacer que terminaré y enviaré como relaciones públicas para su revisión.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

diekmann picture diekmann  ·  3Comentarios

drakenclimber picture drakenclimber  ·  10Comentarios

erdumbledore picture erdumbledore  ·  3Comentarios

pcmoore picture pcmoore  ·  20Comentarios

kloetzl picture kloetzl  ·  19Comentarios