Skip to main content

FreeBSDs IDs

·1210 words·6 mins
Table of Contents

If you have been working with UNIX (Or at least SUS-compliant systems), you most probably already know about UID/GID.

Of course, it’s one of the most easy to grasp concepts there is: You have a User Identifier, and a Group Identifier, assigned to your user

But as most things UNIX, the inner workings are a bit more complicated than it seems

This article is based on FreeBSD stable/15 @ afaf984ae0dc

User ID, Effective User ID and Real User ID
#

Let’s have a look at the kernel’s representation of a process: sys/sys/proc.h. Of course, I’ve elipsed a fair amount of unrelated fields.

Among the hundred of fields in the structure, we can find:

/*
 * 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. */
};

Each process has a list of threads:

/*
 * 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. */
};

(This struct is also elipsed for brevity)

What’s interesting is the ucred structure, because it defines the credentials for a thread / proc

/*
 * 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];
};

We have:

  • cr_uid : Effective User ID ((E)UID)
  • cr_ruid: Real User ID (RUID)
  • cr_svuid: Saved User ID (SVUID)

And their equivalent for groups

Now, we need to understand how each of these is used.

When and how are they updated ?
#

EUID
#

The effective user ID, also known as the UID seems to be the simplest

Process creation
#

When a execve syscall is triggered, the kernel first checks the permissions of the file and opens the file (exec_check_permissions) and then, maps the first page (exec_map_first_page)

It creates an image_params structure that will be the basis for the creation of the process & thread

It then checks for the presence of SUID/SGID bits on the file. If so, it will switch to credential_changing mode.

In credential_changing mode (aka SUID)
#

the euid and egid of the newly created image_param will be set to those read from the file (Read during exec_check_permissions !), depending on the SUID/SGID bits respectively.

!credential_changing mode
#

The euid and egid are set from the parent’s process credentials.

In both case, svuid & svgid will then be copied from the euid and egid

In the end, it will always be:

  • uid == svuid
  • gid == svgid

You can read the source in sys/kern/kern_exec.c in the do_execve function

kern_setcred
#

There is one way to change the credentials of a process. Through the kern_setcred function (in sys/kern/kern_prot.c)), we can update a proc’s credentials.

Please note that while the function takes a thread* as it’s first argument, it’s indeed the proc that will be updated.

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

Also, every credential can be changed through this calls.

	/*
	 * 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);

The changes are however verified in two ways:

  • Through MAC for granular access control. Eg. Make sure that the process has the right to update it’s values to a given set of new values
  • Through priv_check_cred, to make sure that the process has the rights to update it’s credentials

Anyway, MAC will probably be the subject of another post

Once everything is checked and is in order, then the new privileges are copied in place of the old ones.

sys_setuid & sys_setreuid & sys_resuid
#

These 3 functions all update the euid, via change_euid.

The difference is simple:

  • sys_setuid : Update the euid
  • sys_setreuid : Updates euid and reuid. If the Real User ID (ruid) doesn’t match the new UID, then update the svuid
  • sys_setresuid : Updates euid, reuid and svuid

The role of (E)UID
#

From a farther standpoint, cr_uid appears \(403\) times in FreeBSD across \(114\) files. Which in itself doesn’t mean much.

But, it’s much more than cr_ruid with \(147\) matches, or cr_svuid with \(43\) matches.

  • It’s used by MAC when allowing a PRIV(ilege).
  • Generally, it’s who the process will be to the eye of the OS and everyone else

RUID: Real User ID
#

Process creation
#

The RUID is copied from the creating process.

/*
 * 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); 
            // ...
		}
    }

Whether RUID == EUID depends on the SUID/SGID bits.

Saved User ID
#

Process creation
#

When a SUID binary starts, the origin EUID is saved here before the EUID is changed.

Privilege dropping
#

The svuid is saved on process creation.

When you call setuid to change the UID of a process, the EUID is updated and the cr_svuid remains unchanged. When you call again setuid(uid_t uid), if uid == cr_svuid, you are allowed to return to your original uid

Sources
#

Raphaël Fleury-le veso
Author
Raphaël Fleury-le veso
24 years old. Low-level software & hardware enthusiast