Libseccomp: RFE : valeurs de retour d'erreur définies plus précisément

Créé le 7 oct. 2017  ·  24Commentaires  ·  Source: seccomp/libseccomp

En examinant les valeurs de retour d'erreur de libseccomp concernant les appels système, il n'est pas possible de déterminer laquelle des raisons suivantes a causé les erreurs :

  1. syscall n'existe pas sur certaines arches
  2. syscall ne peut pas être mis en correspondance sur certaines arches (parce qu'il est multiplexé, pensez socket/socketcall)
  3. autres cas d'erreur

Lors de la construction du filtre seccomp, l'appelant peut considérer certaines de ces raisons comme fatales mais pas les autres, donc des informations d'erreur plus détaillées (ensemble de valeurs d'erreur plus large que -EINVAL ) seraient nécessaires.

Voir aussi systemd PR 6952 .

enhancement prioritmedium

Commentaire le plus utile

Grand merci. J'ai un patchset à moitié cuit que je vais finir et soumettre en tant que PR pour examen.

Tous les 24 commentaires

Salut @topimiettinen , désolé d'avoir mis si longtemps à en arriver là, mais je pense qu'il est temps de régler ce problème.

@drakenclimber celui-ci va être un doozy. Toutes les API libseccomp devraient avoir des pages de manuel à ce stade (si ce n'est pas le cas, nous devons créer un problème pour cela), toutes les pages de manuel ayant des commentaires "valeurs négatives en cas d'erreur" dans la section VALEUR RENVOYÉE. Je pense que nous devons faire ce qui suit :

  • auditer manuellement chaque appel d'API pour générer une liste de valeurs de retour possibles
  • décider si ces valeurs de retour ont un sens, modifier le code si ce n'est pas le cas
  • documentez chaque valeur de retour possible dans la page de manuel associée avec une brève explication de ce que le code d'erreur indique

Les pensées?

Salut @topimiettinen , désolé d'avoir mis si longtemps à en arriver là, mais je pense qu'il est temps de régler ce problème.

@drakenclimber celui-ci va être un doozy. Toutes les API libseccomp devraient avoir des pages de manuel à ce stade (si ce n'est pas le cas, nous devons créer un problème pour cela), toutes les pages de manuel ayant des commentaires "valeurs négatives en cas d'erreur" dans la section VALEUR RENVOYÉE. Je pense que nous devons faire ce qui suit :

* 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

Les pensées?

Dang.... Je suis d'accord à contrecœur avec tout ce que vous avez écrit ci-dessus, en particulier l'effort requis :). Obtenir les bons codes de retour, puis les documenter sera un très gros effort.

Et oui, même si ce n'est pas un travail glamour, je pense que c'est essentiel. J'ai travaillé sur des trucs de cgroup et nous avons récemment rencontré une implémentation de conteneur qui a complètement mal compris une fonctionnalité de cgroup ... mais il n'y avait pas de document définitif dans le noyau ou ailleurs pour guider correctement le chemin, donc ils l'ont fait fonctionner au mieux ils pourraient.

C'est formidable de voir de l'activité sur cette question! Je ne suis pas contre un examen approfondi, mais la demande initiale était limitée uniquement pour pouvoir distinguer différents modes de défaillance, ce qui est quelque peu orthogonal. L'examen serait certainement utile, même préalable dans une certaine mesure, je pense.

C'est formidable de voir de l'activité sur cette question! Je ne suis pas contre un examen approfondi, mais la demande initiale était limitée uniquement pour pouvoir distinguer différents modes de défaillance, ce qui est quelque peu orthogonal. L'examen serait certainement utile, même préalable dans une certaine mesure, je pense.

Nous devons faire l'examen à un moment donné, et cela pourrait aussi bien être maintenant. Plus nous attendons, moins les codes d'erreur seront utiles pour les appelants et tout l'intérêt de libseccomp est de rendre ce truc plus facile à utiliser :)

Dang.... Je suis d'accord à contrecœur avec tout ce que vous avez écrit ci-dessus, en particulier l'effort requis :). Obtenir les bons codes de retour, puis les documenter sera un très gros effort.

Oui, c'est l'une des raisons pour lesquelles ce problème est resté si longtemps, mais je l'ai reporté assez longtemps (au moins @drakenclimber peut dire qu'il l'a reporté depuis moins d'un an !). Plus tard dans la journée (demain ?), je diviserai cela en morceaux / problèmes multiples (avec quelques suggestions) pour le rendre un peu plus facile à aborder en morceaux.

Comme un peu positif, il semble que libseccomp n'utilise vraiment que neuf valeurs errno uniques (selon une vérification très grossière):

# 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

... cela devrait aider à réduire un peu l'espace du problème, surtout si nous pouvons convenir d'une valeur sémantique commune pour chaque code d'erreur dans la bibliothèque (ce que nous devrions certainement faire).

C'est un peu plus tard que prévu, mais voici une liste complète des fonctions composant l'API 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 ces fonctions, nous n'avons qu'à nous préoccuper des fonctions retournant "int".

Regroupements possibles de fonctions qui doivent avoir des chemins de code et des valeurs de retour similaires.

  • groupe 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)
  • Groupe 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)
  • Groupe 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)
  • Groupe 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, ...)
  • Groupe 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)
  • Groupe F
int seccomp_load(const scmp_filter_ctx ctx)
int seccomp_export_bpf(const scmp_filter_ctx ctx, int fd)

... si la fonction n'est pas dans l'un des groupes ci-dessus, elle est probablement unique dans son chemin de code et/ou sa valeur de retour.

Le groupe C renvoie uniquement __NR_SCMP_ERROR.

Le groupe D peut renvoyer l'un des éléments EINVAL, EPERM, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

seccomp_load() peut renvoyer les valeurs EINVAL, ENOMEM, ESRCH et également errno à partir des appels système prctl() (uniquement EACCES, EFAULT, EINVAL pour PR_SET_NO_NEW_PRIVS et PR_SET_SECCOMP) et seccomp() (EACCES, EFAULT, EINVAL, ENOMEM, ESRCH) selon leur manuel pages.

Quand il s'agit de libseccomp transmettant les erreurs d'appel système à l'appelant, par exemple prctl() et seccomp(), je pense que nous avons juste besoin de les cacher derrière une seule valeur errno (peut-être ENOSYS ?) afin que libseccomp ne soit pas affecté par tout changement dans le noyau (ou différences ABI).

Si cela devient un problème pour le débogage, nous pourrions peut-être introduire un nouvel attr qui transmettrait la valeur errno directement à l'appelant.

Peut-être, mais alors ce serait bien de s'assurer qu'il n'y a pas de rupture ABI/API si les utilisateurs de libseccomp attendent déjà certaines valeurs errno. Le problème initial était que certains appels système n'existaient que sur certaines architectures (comme ugetrlimit uniquement sur x86_32) mais cela ne pouvait pas être distingué d'un nom d'appel système mal tapé, donc des codes d'erreur différents (éventuellement synthétiques) seraient nécessaires .

Eh bien, comme nous l'avons dit précédemment, nous ne garantissons actuellement pas vraiment de valeurs errno spécifiques, juste des "valeurs négatives en cas d'échec", donc même s'il serait malheureux de casser les utilisateurs qui font actuellement des hypothèses sur des valeurs errno spécifiques, je pense changer les choses fournir une garantie errno solide entre les versions du noyau et les ABI à l'avenir est un compromis valable.

Eh bien, comme nous l'avons dit précédemment, nous ne garantissons actuellement pas vraiment de valeurs errno spécifiques, juste des "valeurs négatives en cas d'échec", donc même s'il serait malheureux de casser les utilisateurs qui font actuellement des hypothèses sur des valeurs errno spécifiques, je pense changer les choses fournir une garantie errno solide entre les versions du noyau et les ABI à l'avenir est un compromis valable.

Je suis d'accord.

Une fois que cette évaluation est terminée et que nous mettons à jour les valeurs errno en cas d'échec, je me sentirais plus à l'aise de faire une sorte de garantie des valeurs errno renvoyées.

L'idée de regroupement n'était peut-être pas la meilleure, alors commençons une liste pour garder une trace de tout cela _(je continuerai à mettre à jour cela au fur et à mesure que nous progressons)_ :

  • [x] seccomp_reset
    Retourne actuellement : EINVAL, ENOMEM.

  • [x] seccomp_merge
    Renvoie actuellement : EINVAL, EDOM, EEXIST, ENOMEM.

  • [x] seccomp_arch_exist
    Renvoie actuellement : EINVAL, EEXIST.

  • [x] seccomp_arch_add
    Renvoie actuellement : EINVAL, EEXIST, ENOMEM, EDOM.

  • [x] seccomp_arch_remove
    Renvoie actuellement : EINVAL, EEXIST.

  • [x] seccomp_load
    Renvoie actuellement : EINVAL, ENOMEM, ESRCH, ECANCELED.

  • [x] seccomp_attr_get
    Renvoie actuellement : EINVAL, EEXIST.

  • [x] seccomp_attr_set
    Renvoie actuellement : EINVAL, EACCES, EOPNOTSUPP, EEXIST.

  • [x] seccomp_syscall_resolve_name_arch
    Déjà bien défini, renvoie la valeur syscall ou __NR_SCMP_ERROR en cas d'échec.

  • [x] seccomp_syscall_resolve_name_rewrite
    Déjà bien défini, renvoie la valeur syscall ou __NR_SCMP_ERROR en cas d'échec.

  • [x] seccomp_syscall_resolve_name
    Déjà bien défini, renvoie la valeur syscall ou __NR_SCMP_ERROR en cas d'échec.

  • [x] seccomp_syscall_priority
    Renvoie actuellement : EINVAL, EDOM, EFAULT, ENOMEM.

  • [x] seccomp_rule_add_array
    Renvoie actuellement : EINVAL, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

  • [x] seccomp_rule_add
    Renvoie actuellement : EINVAL, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

  • [x] seccomp_rule_add_exact_array
    Renvoie actuellement : EINVAL, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

  • [x] seccomp_rule_add_exact
    Renvoie actuellement : EINVAL, EOPNOTSUPP, ENOMEM, EDOM, EFAULT, EEXIST.

  • [x] seccomp_notify_alloc
    Renvoie actuellement : EOPNOTSUPP, ENOMEM, EFAULT, ECANCELED. La page de manuel spécifie déjà -1 en cas d'erreur, ce qui se réfère probablement uniquement au numéro d'erreur seccomp().

  • [x] seccomp_notify_receive
    Renvoie actuellement : EOPNOTSUPP et ECANCELED. La page de manuel spécifie déjà -1 en cas d'erreur, ce qui se réfère probablement uniquement au numéro d'erreur seccomp().

  • [x] seccomp_notify_respond
    Renvoie actuellement : EOPNOTSUPP et ECANCELED. La page de manuel spécifie déjà -1 en cas d'erreur, ce qui se réfère probablement uniquement au numéro d'erreur seccomp().

  • [x] seccomp_notify_id_valid
    Renvoie actuellement : EOPNOTSUPP et ECANCELED. La page de manuel spécifie déjà -ENOENT en cas d'erreur (ID invalide), qui fait probablement référence uniquement à l'errno seccomp().

  • [x] seccomp_notify_fd
    Déjà bien défini, renvoie la notification fd.

  • [x] seccomp_export_pfc
    Renvoie actuellement : EINVAL et ECANCELED.

  • [x] seccomp_export_bpf
    Renvoie actuellement : EINVAL, ENOMEM et ECANCELED.

Maintenant que nous avons une liste des fonctions qui renvoient les codes d'erreur, je me sens un peu mieux à ce sujet, d'autant plus que nous sommes déjà assez cohérents avec la façon dont nous utilisons nos codes d'erreur. Ce dernier morceau devrait aider énormément.

Je vais commencer un PR afin que nous puissions commencer à collecter des correctifs et des commentaires sur les changements, je le posterai bientôt ici.

Étant donné la nature plutôt "spéciale" d'ENOSYS, j'ai des réserves quant à l'utilisation de l'errno comme fourre-tout noyau/libc. Je vais devoir jeter un œil à d'autres valeurs, est-ce que quelqu'un a des sentiments/pensées forts à ce sujet ?

Il manque encore beaucoup de choses, principalement des modifications de pages de manuel et des commentaires de code (sans parler des tests), mais vous pouvez consulter la branche suivante pour avoir une idée de ce que je pense :

Étant donné la nature plutôt "spéciale" d'ENOSYS, j'ai des réserves quant à l'utilisation de l'errno comme fourre-tout noyau/libc. Je vais devoir jeter un œil à d'autres valeurs, est-ce que quelqu'un a des sentiments/pensées forts à ce sujet ?

Qu'en est-il de l'EIO ?

Qu'en est-il de l'EIO ?

Je ne sais pas si cela rendrait l'erreur beaucoup plus exploitable. Que diriez-vous plutôt d'étendre l'API avec des fonctions qui n'utilisent pas les valeurs errno, mais par exemple :

  • SCMP_ERROR_UNKNOWN_SYSCALL : l'appel système n'est pas connu de libseccomp : l'appelant peut l'utiliser pour rejeter l'entrée de l'utilisateur (par exemple, faute de frappe dans le nom de l'appel système)
  • SCMP_ERROR_SYSCALL_NOT_FOR_THIS_ARCH : l'appel système est connu de libseccomp mais n'est pas disponible ici : l'appelant peut ignorer cette erreur uniquement pour cette architecture
  • SCMP_ERROR_API_USAGE : libseccomp détecte un problème avec la logique d'appel qui ne devrait pas se produire dans un code correctement écrit : l'appelant pourrait déclencher assert()
  • SCMP_ERROR_KERNEL_OTHER : libseccomp était OK avec l'entrée et la séquence des appels, mais le noyau a renvoyé certaines erreurs bien définies, par exemple ENOMEM, EPERM, ENOSYS : l'appelant doit vérifier l'errno pour action. Dans le cas où la raison de l'erreur est connue par libseccomp comme étant due à une erreur commise par l'appelant (par exemple EFAULT), SCMP_ERROR_API_USAGE pourrait peut-être être utilisé à la place.
  • SCMP_ERROR_KERNEL_API_USAGE : libseccomp était OK avec l'entrée, etc., mais le noyau ne l'aimait pas pour des raisons peu claires et évidentes. Cela pourrait indiquer un changement de noyau, une configuration désactivée, un bogue dans libseccomp ou l'appelant, une entrée utilisateur très étrange, etc. analyse plus approfondie, non assert()able.

Alternativement, l'API avec errnos pourrait rester telle quelle, mais une nouvelle fonction pourrait être utilisée pour demander le code d'erreur ci-dessus.

Peut-être que @poettering ou @keszybz pourraient aussi commenter.

Je ne sais pas si cela rendrait l'erreur beaucoup plus exploitable. Que diriez-vous plutôt d'étendre l'API avec des fonctions qui n'utilisent pas les valeurs errno ...

Eh bien, avant même que nous puissions envisager de faire quelque chose comme ça (et je ne suis pas sûr que nous voulions le faire), nous devons nous installer sur des codes de retour stables et pris en charge. C'est ce sur quoi nous travaillons ici et ce que nous ciblons pour la v2.5.

Obtenons les codes de retour stables/supportés dans la v2.5 et nous pouvons voir comment cela se passe, si nous devons faire quelque chose de plus, nous pouvons l'envisager pour la v2.6.

Qu'en est-il de l'EIO ?

J'ai été retiré pendant un certain temps en raison d'autres priorités de travail et de certains éléments du noyau, mais maintenant que je suis de retour sur libseccomp, je me rends compte que l'utilisation d'EIO ici semble erronée. Permettez-moi de réfléchir un peu plus à cela.

Qu'en est-il d'ECANCELED comme code d'erreur fourre-tout du noyau ?

Je suis curieux de savoir ce que vous en pensez @drakenclimber , vous êtes silencieux à ce sujet depuis un moment.

J'ai été retiré pendant un certain temps en raison d'autres priorités de travail et de certains éléments du noyau, mais maintenant que je suis de retour sur libseccomp, je me rends compte que l'utilisation d'EIO ici semble erronée. Permettez-moi de réfléchir un peu plus à cela.

Pareil ici. Les choses ont été un peu agitées ces derniers temps :/.

Je suis curieux de savoir ce que vous en pensez @drakenclimber , vous êtes silencieux à ce sujet depuis un moment.

Sûr. Je veux relire tout le fil de discussion, puis j'interviendrai.

Qu'en est-il d'ECANCELED comme code d'erreur fourre-tout du noyau ?

J'avoue que je n'étais pas très familier avec ECANCELED. J'ai brièvement parcouru la source du noyau pour son utilisation et j'ai également effectué une recherche sur Google. ECANCELED n'a aucune collision avec les précédentes libseccomp, prctl() ou d'autres API que nous avons utilisées, et je pense qu'il peut raisonnablement encapsuler toute erreur que le noyau nous envoie.

tl;dr - Je suis d'accord avec ECANCELED comme fourre-tout pour les erreurs du noyau.

Grand merci. J'ai un patchset à moitié cuit que je vais finir et soumettre en tant que PR pour examen.

Cette page vous a été utile?
0 / 5 - 0 notes