20 février 20266 min de lecture #freebsd

Les IDs de FreeBSD

Une plongée dans le comportement des UID/GID/SVUID de FreeBSD

Si vous avez déjà travaillé avec UNIX (ou du moins des systèmes conformes à la SUS), vous connaissez très probablement déjà les UID/GID.

Bien sûr, c’est l’un des concepts les plus simples à saisir qui soit : vous avez un identifiant utilisateur, et un identifiant de groupe, assignés à votre utilisateur

Mais comme la plupart des choses sous UNIX, le fonctionnement interne est un peu plus compliqué qu’il n’y paraît

Cet article se base sur FreeBSD stable/15 @ afaf984ae0dc

ID utilisateur, ID utilisateur effectif et ID utilisateur réel #

Jetons un œil à la représentation d’un processus par le noyau : sys/sys/proc.h. Bien sûr, j’ai éludé une bonne partie des champs non pertinents.

Parmi la centaine de champs de la structure, on trouve :

c
/*
 * Process structure.
 */
struct proc {
	TAILQ_HEAD(, thread) p_threads;	/* (c) all threads. */
	struct ucred	*p_ucred;	/* (c) Process owner's identity. */
	struct pwddesc	*p_pd;		/* (b) Cwd, chroot, jail, umask */
	pid_t		p_pid;		/* (b) Process identifier. */
	struct proc	*p_pptr;	/* (c + e) Pointer to parent process. */
	LIST_HEAD(, proc) p_children;	/* (e) Pointer to list of children. */
};

Chaque processus possède une liste de threads :

c
/*
 * Kernel runnable context (thread).
 * This is what is put to sleep and reactivated.
 * Thread context.  Processes may have multiple threads.
 */
struct thread {
	lwpid_t		td_tid;		/* (b) Thread ID. */
	struct ucred	*td_realucred;	/* (k) Reference to credentials. */
	struct ucred	*td_ucred;	/* (k) Used credentials, temporarily switchable. */
};

(Cette structure est également éludée pour plus de concision)

Ce qui est intéressant, c’est la structure ucred, car elle définit les identifiants d’un thread / processus

c
/*
 * Credentials.
 *
 * Please do not inspect cr_uid directly to determine superuserness.  The
 * priv(9) interface should be used to check for privilege.
 *
 * Lock reference:
 *      c - cr_mtx
 *
 * Unmarked fields are constant after creation.
 *
 * See "Credential management" comment in kern_prot.c for more information.
 */
struct ucred {
	struct mtx cr_mtx;
	long	cr_ref;			/* (c) reference count */
	u_int	cr_users;		/* (c) proc + thread using this cred */
	u_int	cr_flags;		/* credential flags */
	struct auditinfo_addr	cr_audit;	/* Audit properties. */
	int	cr_ngroups;		/* number of supplementary groups */
#define	cr_startcopy cr_uid
	uid_t	cr_uid;			/* effective user id */
	uid_t	cr_ruid;		/* real user id */
	uid_t	cr_svuid;		/* saved user id */
	gid_t	cr_gid;			/* effective group id */
	gid_t	cr_rgid;		/* real group id */
	gid_t	cr_svgid;		/* saved group id */
	struct uidinfo	*cr_uidinfo;	/* per euid resource consumption */
	struct uidinfo	*cr_ruidinfo;	/* per ruid resource consumption */
	struct prison	*cr_prison;	/* jail(2) */
	struct loginclass	*cr_loginclass; /* login class */
	void		*cr_pspare2[2];	/* general use 2 */
#define	cr_endcopy	cr_label
	struct label	*cr_label;	/* MAC label */
	gid_t	*cr_groups;		/* groups */
	int	cr_agroups;		/* Available groups */
	/* storage for small groups */
	gid_t   cr_smallgroups[CRED_SMALLGROUPS_NB];
};

On a :

  • cr_uid : identifiant utilisateur effectif ((E)UID)
  • cr_ruid: identifiant utilisateur réel (RUID)
  • cr_svuid: identifiant utilisateur sauvegardé (SVUID)

Et leur équivalent pour les groupes

Maintenant, il faut comprendre comment chacun d’entre eux est utilisé.

Quand et comment sont-ils mis à jour ? #

EUID #

L’identifiant utilisateur effectif, également connu sous le nom d’UID, semble être le plus simple

Création de processus #

Lorsqu’un appel système execve est déclenché, le noyau vérifie d’abord les permissions du fichier et ouvre le fichier (exec_check_permissions) puis mappe la première page (exec_map_first_page)

Il crée une structure image_params qui servira de base à la création du processus et du thread

Il vérifie ensuite la présence des bits SUID/SGID sur le fichier. Si c’est le cas, il passe en mode credential_changing.

En mode credential_changing (alias SUID) #

les euid et egid du image_param nouvellement créé seront définis à partir de ceux lus dans le fichier (lus pendant exec_check_permissions !), selon les bits SUID/SGID respectivement.

Mode !credential_changing #

Les euid et egid sont définis à partir des identifiants du processus parent.

Dans les deux cas, svuid et svgid seront ensuite copiés à partir de euid et egid

Au final, on aura toujours :

  • uid == svuid
  • gid == svgid

Vous pouvez lire le code source dans sys/kern/kern_exec.c, dans la fonction do_execve

kern_setcred #

Il existe un moyen de modifier les identifiants d’un processus. Via la fonction kern_setcred (dans sys/kern/kern_prot.c)), on peut mettre à jour les identifiants d’un proc.

Veuillez noter que bien que la fonction prenne un thread* comme premier argument, c’est bien le proc qui sera mis à jour.

c
struct proc *const p = td->td_proc;
// ... 
cred_set = proc_set_cred_enforce_proc_lim(p, new_cred);

De plus, chaque identifiant peut être modifié via ces appels.

c
	/*
	 * Change user IDs.
	 */
	if (flags & SETCREDF_UID)
		change_euid(new_cred, uip);
	if (flags & SETCREDF_RUID)
		change_ruid(new_cred, ruip);
	if (flags & SETCREDF_SVUID)
		change_svuid(new_cred, wcred->sc_svuid);

	/*
	 * Change groups.
	 */
	if (flags & SETCREDF_SUPP_GROUPS)
		crsetgroups_internal(new_cred, wcred->sc_supp_groups_nb,
		    wcred->sc_supp_groups);
	if (flags & SETCREDF_GID)
		change_egid(new_cred, wcred->sc_gid);
	if (flags & SETCREDF_RGID)
		change_rgid(new_cred, wcred->sc_rgid);
	if (flags & SETCREDF_SVGID)
		change_svgid(new_cred, wcred->sc_svgid);

Les modifications sont cependant vérifiées de deux façons :

  • Via MAC pour un contrôle d’accès granulaire. Par exemple, s’assurer que le processus a le droit de mettre à jour ses valeurs vers un nouvel ensemble de valeurs données
  • Via priv_check_cred, pour s’assurer que le processus a le droit de mettre à jour ses identifiants

Quoi qu’il en soit, MAC fera probablement l’objet d’un autre article

Une fois que tout est vérifié et en ordre, les nouveaux privilèges sont copiés à la place des anciens.

sys_setuid & sys_setreuid & sys_resuid #

Ces 3 fonctions mettent toutes à jour l’euid, via change_euid.

La différence est simple :

  • sys_setuid : met à jour l’euid
  • sys_setreuid : met à jour l’euid et le reuid. Si l’identifiant utilisateur réel (ruid) ne correspond pas au nouvel UID, alors le svuid est mis à jour
  • sys_setresuid : met à jour l’euid, le reuid et le svuid

Le rôle de l’(E)UID #

D’un point de vue plus large, cr_uid apparaît \(403\) fois dans FreeBSD à travers \(114\) fichiers. Ce qui en soi ne signifie pas grand-chose.

Mais c’est bien plus que cr_ruid avec \(147\) occurrences, ou cr_svuid avec \(43\) occurrences.

  • Il est utilisé par MAC lors de l’autorisation d’un PRIV(ilège).
  • De manière générale, c’est ce que le processus sera aux yeux de l’OS et de tout le monde

RUID : identifiant utilisateur réel #

Création de processus #

Le RUID est copié depuis le processus créateur.

c
/*
 * In-kernel implementation of execve().  All arguments are assumed to be
 * userspace pointers from the passed thread.
 */
static int
do_execve(struct thread *td, struct image_args *args, struct mac *mac_p,
    struct vmspace *oldvmspace)
{
	struct proc *p = td->td_proc;
	struct ucred *oldcred;
    // ...
    oldcred = p->p_ucred;
    // ...
    if (credential_changing &&
#ifdef CAPABILITY_MODE
	    ((oldcred->cr_flags & CRED_FLAG_CAPMODE) == 0) &&
#endif
	    (imgp->vp->v_mount->mnt_flag & MNT_NOSUID) == 0 &&
	    (p->p_flag & P_TRACED) == 0) {
		imgp->newcred = crdup(oldcred);
        // ...
    } else {

		if (oldcred->cr_svuid != oldcred->cr_uid ||
		    oldcred->cr_svgid != oldcred->cr_gid) {
            // ...
			imgp->newcred = crdup(oldcred); 
            // ...
		}
    }

Le fait que RUID == EUID dépend des bits SUID/SGID.

Identifiant utilisateur sauvegardé #

Création de processus #

Quand un binaire SUID démarre, l’EUID d’origine est sauvegardé ici avant que l’EUID ne soit modifié.

Abandon de privilèges #

Le svuid est sauvegardé à la création du processus.

Quand vous appelez setuid pour changer l’UID d’un processus, l’EUID est mis à jour et le cr_svuid reste inchangé. Quand vous appelez à nouveau setuid(uid_t uid), si uid == cr_svuid, vous êtes autorisé à revenir à votre uid d’origine

Sources #

Plus d'articles

Comment utiliser PGO et LTO avec Meson et Clang

Un guide pas à pas testé pour activer le Profile Guided Optimization et le Link Time Optimization dans un projet Meson compilé avec Clang, y compris l'étape de fusion llvm-profdata que la plupart des guides oublient.

5 min

Partie 2 : Profiling, PGO & LTO

Mesurer PGO et LTO sur une simulation SPH en C++ avec perf et des flamegraphs, et pourquoi ils n'ont presque rien apporté.

8 min