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 == svuidgid == 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 theeuidsys_setreuid: Updateseuidandreuid. If the Real User ID (ruid) doesn’t match the new UID, then update thesvuidsys_setresuid: Updateseuid,reuidandsvuid
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#
- FreeBSD Handbook
- FreeBSD stable/15 @
afaf984ae0dc - man 9 ucred
