盒子
盒子
文章目录
  1. Overview
  2. Introduction
  3. Pre-internal
    1. User namespaces
  4. Internal
    1. set*uid
      1. setuid(uid)
      2. setreuid(ruid, euid)
      3. setresuid(ruid, euid, suid)
    2. set*gid
  5. Reference

linux下的访问控制之credentials

Overview

Credentials在Linux中用于访问控制(Access Control),基于uidgidsid,是Linux几种安全措施的一部分。同时,仅用于进程(task)中的Capabilities提供了更细化的权限控制机制。1

Note2

  • uid —— User Identifier,用户标识符,用于辨识用户。又分为euidruidsuidfsuid
    • ruid —— Real UID,真实用户ID,一般称之为uid
    • euid —— Effective UID,有效用户ID。
    • suid —— Saved UID,暂存用户ID。
    • fsuid —— File System UID,文件系统用户ID。
  • gid —— Group Identifier,用户组标识符,用户辨识用户组。也又分为rgidegidsgidfsgid。因每个用户必须是一个组的成员,主组(the primary group)由组数据库中用户条目的数字gid标识。

在v4.12中,与进程访问控制有关的系统调用有以下18个:

getuid setuid
getgid setgid
geteuid getegid
setreuid setregid
getresuid setresuid
getresgid setresgid
getgroups setgroups
getfsuid getfsgid
capget catset

Introduction

一些概念4

  • Real user ID / Real group ID:这些ID决定该进程的所有者是谁。
  • Effective user ID / Effective group ID:内核利用这些ID决定进程对共享资源拥有怎样的访问权,比如:消息队列、共享内存和信号量。尽管大多数的UNIX系统使用这些ID决定文件的访问权,但Linux使用的是独有的filesystem ID
  • Saved set-user-ID / Saved set-group-ID:这两个ID在set-user-IDset-group-ID程序执行后,保存相应的effective ID。因此,一个set-user-ID程序的effective user ID可以在real user IDsaved set-user-ID之间来回切换,从而可以恢复/抛弃特权
  • Filesystem user ID / Filesystem group ID:这些ID用于决定进程对文件与其他共享资源的访问权。进程无论何时更改effective user/group ID,内核也同时更改filesystem user/group ID
  • Supplementary group IDs:它是一组额外的group IDs,也用于文件、共享资源的访问控制。

Note567
Set-user-id / Set-group-id区别于进程中的saved set-user-ID / saved set-group-ID,是文件上的概念。设置一个Saved set-user-ID的意义在于,在execv可执行文件之后,如果可执行文件的set-user-ID位被设置了,进程的effective user ID, saved set-user-ID会设置成可执行文件所有者的uideffective group ID也有类似的操作。下面是内核中与此有关的具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 
* fs/exec.c
*/
prepare_binprm
- bprm_fill_uid
if (mode & S_ISUID) {
bprm->per_clear |= PER_CLEAR_ON_SETID;
bprm->cred->euid = uid;
}

if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
bprm->per_clear |= PER_CLEAR_ON_SETID;
bprm->cred->egid = gid;
}
- security_bprm_set_creds
new->suid = new->fsuid = new->euid;
new->sgid = new->fsgid = new->egid;

举例说明:用户zzz执行了文件a.out,a.out的属主为hzzz且设置了set-user-ID位。现在本进程的real uid为zzz,effective uid = saved uid = hzzz。

进程执行了一会之后,突然想用zzz的权限访问一个文件,于是进程可能会调用setuid(zzz), 此时检测进程的权限,进程的effective uid是hzzz,不是root,所以不能更改real uid(只有root才能更改real uid),所以只能设置effective uid,发现effective uid可以被设置为zzz(因为real uid是zzz),所以函数调用成功,只将effective uid设置成zzz。

现在进程访问完zzz的文件了,又想回到hzzz的环境中执行,所以有可能会调用setuid(hzzz),这次saved uid的作用就表现出来了,因为刚刚只是改变了effective uid, 而saved uid还保存着之前的effective uid,所以可以调用setuid(hzzz)来要回原来的权限。

描述uid/gid转换的有限状态自动机(FSA)在Proceedings of the 11th USENIX Security Symposium中的第10-12页有展示。

关于setuid可以简单记为:在没有特权的情况下,euid / fsuid可以通过setuid设置成ruidsuid

Pre-internal

User namespaces

A new approach to user namespaces3梗概:

容器可以被看做一种轻量级的虚拟化技术。因为与宿主机共享内核,所以比真正的虚拟机运行效率更高。但必须提供一种机制,把全局可见的资源封装进命名空间中,对容器展现只属于自己的那部分资源(比如进程ID、文件系统、网络接口)的视图。

用户命名空间(user namespaces)可以被认为是user/group ID以及相关权限的封装,它允许容器的所有者进程(不一定为root用户)在容器内部以root的身份运行,同时将容器内用户与系统的其余部分隔离。那么,同一个进程怎样才能在不同的上下文中有不同的uid呢?

Eric Biederman提交了一组patch解决了这个问题。这组patch中定义了两种新的数据类型kuid_t(Kernel UID)与kgid_t(Kernel GID)。Kernel UID用于描述进程在内核中的身份,而不管它在容器中可能采用的任何uid;它是用于大多数特权检查的值;并且进程并没有办法知道它的值。

为了kernel ID与user ID、group ID在不同的命名空间中做转换,除了知道Kernel ID之外,还需要知道特定的namespace ID,因此有了下面的一组用于uid的转换函数(gid同样存在):

1
2
3
4
kuid_t make_kuid(struct user_namespace *from, uid_t uid);
uid_t from_kuid(struct user_namespace *to, kuid_t kuid);
uid_t from_kuid_munged(struct user_namespace *to, kuid_t kuid);
bool kuid_has_mapping(struct user_namespace *ns, kuid_t uid);

在Kernel与user ID、group ID之间建立映射是一种特权操作,需要CAP_SETUID, CAP_SETGID标志。

Internal

set*uid

setuid(uid)

1
2
3
4
5
6
7
kuid <- make kuid using uid and namespace
if process has CAP_SETUID priviledge; then:
uid = euid = suid = fsuid = kuid
else if kuid == old_uid || kuid == old_suid; then:
fsuid = euid = kuid
else:
goto error

setreuid(ruid, euid)

1
2
3
4
5
6
7
8
9
kruid <- make kruid using ruid and namespace
keuid <- make keuid using euid and namespace
if kruid != old_uid && kruid != old_euid
&& keuid != old_uid && keuid != old_euid && keuid != old_suid
&& process has no CAP_SETUID priviledge; then:
uid = kruid
euid = suid = fsuid = keuid
else:
goto error

setresuid(ruid, euid, suid)

1
2
3
4
5
6
7
8
9
10
11
12
kruid <- make kruid using ruid and namespace
keuid <- make keuid using euid and namespace
ksuid <- make ksuid using ruid and namespace
if process has no CAP_SETUID
&& kruid != old_uid && kruid != old_euid && kruid != old_suid
&& keuid != old_uid && keuid != old_euid && keuid != old_suid
&& ksuid != old_uid && ksuid != old_suid && ksuid != old_suid; then:
goto error
else:
uid = kruid
euid = fsuid = keuid
suid = ksuid

set*gid

the same as set*uid.

Reference