# cred —— 进程的凭证（权限身份）

> 这是 [../task-struct.md](/concepts/process/task-struct.md) 里 `task->cred` 那一项的展开。`cred`(credentials)是内核判断"这个进程能干什么"的依据:它是谁(uid/gid)、属于哪些组、有哪些特权(capabilities)。谁能读某个文件、能不能 bind 低端口、能不能重启系统,全看这里。本篇讲 `cred` 的结构、四种 uid 为什么存在、setuid 怎么"变身"、capabilities 怎么细分 root 权限。

## 零、一句话认知：cred 回答"这个进程以谁的身份、有多大权限运行"

内核每做一次权限检查(打开文件、发信号、绑端口……),都拿当前进程的 `cred` 去比对。`cred` 主要装三样：

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<a>> #E3F2FD
  BorderColor<<a>> #1976D2
  BackgroundColor<<b>> #C8E6C9
  BorderColor<<b>> #388E3C
  BackgroundColor<<c>> #FFE0B2
  BorderColor<<c>> #EF6C00
}
rectangle "cred (进程凭证)" as CRED {
  rectangle "用户/组 ID\nuid/gid(real/effective/saved/fs)" <<a>> as UID
  rectangle "附加组\nsupplementary groups[]" <<b>> as GRP
  rectangle "capabilities\n细分的特权位集\n(Permitted/Effective/Inheritable...)" <<c>> as CAP
}
note bottom of UID : 判文件属主/普通权限
note bottom of CAP : 判特权操作\n(把'root 万能'切成几十个开关)
@enduml
```

> **核心记忆**:`cred` = **身份(uid/gid + 附加组)** + **特权(capabilities)**。传统 Unix 只看 uid(0 = root 万能);现代 Linux 用 capabilities 把 root 的"万能"拆成几十个独立特权位,按需授予,最小权限。

## 一、四种 uid：为什么不止一个

一个进程的用户身份其实有**四个** uid(gid 同理),各有用途：

| uid | 全称 | 作用 |
|-----|------|------|
| **real uid (ruid)** | 真实用户 | "我本来是谁"——启动这个进程的用户 |
| **effective uid (euid)** | 有效用户 | **权限检查实际用的就是它**——"我现在以谁的身份行事" |
| **saved uid (suid)** | 保存的 | 暂存一个 uid,方便在 euid 间来回切(提权/降权后能还原) |
| **fs uid (fsuid)** | 文件系统 | 专用于文件访问检查(Linux 特有,历史遗留,一般跟随 euid) |

### 为什么要 real / effective / saved 三个：setuid 程序

经典例子 `passwd`:普通用户运行它却要改 `/etc/shadow`(只有 root 能写)。靠**setuid 位**：

```plantuml
@startuml
skinparam shadowing false
skinparam sequence {
  ParticipantBackgroundColor #E3F2FD
  ParticipantBorderColor     #1976D2
  ArrowColor                 #37474F
}
participant "用户 alice(uid=1000)\n运行 /usr/bin/passwd" as A
participant "内核 execve" as K
participant "passwd 进程" as P
A -> K : 执行 passwd\n(文件有 setuid 位,属主 root)
K -> P : 设 ruid=1000(alice)\n**euid=0(root)** ← setuid 位生效\nsuid=0
note over P : 权限检查看 euid=0 → 能写 /etc/shadow
P -> P : 改完密码后可把 euid 降回 1000\n(用 saved uid 还原)
@enduml
```

- **setuid 位**(文件权限的 `s`):`execve` 一个带 setuid 位的程序,**euid 被设成文件属主的 uid**(如 root),于是普通用户运行时临时获得属主权限。
- **saved uid 的用途**:程序可以在 euid=0 和 euid=1000 之间切换(需要特权时提到 0,平时降到 1000 减少风险),`saved` 存着"另一个身份"供还原。**最小权限原则**:setuid 程序应尽快把 euid 降回去,只在必要时提权。

## 二、capabilities：把"root 万能"拆成几十个开关

传统模型:**uid=0 就是 root,能干一切**——太粗暴,一个 setuid-root 程序被攻破就等于全盘沦陷。Linux **capabilities** 把 root 的特权拆成几十个独立的位,按需授予：

| capability | 允许的特权操作 |
|-----------|---------------|
| `CAP_NET_BIND_SERVICE` | bind 1024 以下的低端口(不必是 root) |
| `CAP_NET_ADMIN` | 配置网络(网卡、路由、防火墙) |
| `CAP_SYS_ADMIN` | 一大堆管理操作(mount 等)——最"胖"、最接近万能 |
| `CAP_SYS_NICE` | 提升调度优先级/设实时优先级(见 [../scheduling.md](/concepts/process/scheduling.md)) |
| `CAP_SYS_PTRACE` | ptrace 别的进程(调试器、strace) |
| `CAP_DAC_OVERRIDE` | 无视文件读写权限检查 |
| `CAP_CHOWN` | 改文件属主 |

- **进程持有的 capability 分几套**:`Permitted`(允许拥有的上限)、`Effective`(当前生效的)、`Inheritable`(可传给 exec 后的程序)、`Bounding`(边界上限)、`Ambient`。权限检查看 `Effective` 集。
- **好处**:给一个 web server `CAP_NET_BIND_SERVICE` 就能监听 80 端口,而**不必以 root 运行**——被攻破也只多这一个特权,不是整台机器。这是现代服务(和容器)最小权限的基石。

```bash
getcap /usr/bin/ping        # 看文件被授予了哪些 capability
setcap cap_net_raw+ep ./myprog   # 给程序授 raw socket 权限(需 root)
cat /proc/<pid>/status | grep Cap  # CapPrm/CapEff/CapInh/CapBnd 位掩码
capsh --decode=<hex>        # 把 Cap 位掩码解码成名字
```

## 三、凭证怎么变、什么时候变

- **`setuid`/`setgid`/`seteuid`/`setresuid`**:进程主动改自己的 uid(受权限约束——非特权进程只能在 real/effective/saved 之间切)。
- **`execve`**:普通 exec 保留凭证;**setuid/setgid 程序**的 exec 会按文件位设置 euid/egid。
- **fork/线程**:子进程/线程**继承**父的凭证(见 [../process-creation.md](/concepts/process/process-creation.md))。
- **`cred` 是写时复制的**:内核用引用计数共享 `cred`,要改时复制一份新的——避免每次检查都拷贝。

## 四、观测

```bash
cat /proc/<pid>/status | grep -E 'Uid|Gid|Groups|Cap'
#   Uid:  1000  1000  1000  1000     ← real / effective / saved / fs
#   Gid:  1000  1000  1000  1000
#   Groups: 4 24 27 1000             ← 附加组
#   CapEff: 0000000000000000         ← 当前生效的 capabilities(0=无特权)
id                                   # 当前 shell 的 uid/gid/组
ps -o pid,euser,ruser,cmd            # 按有效/真实用户列进程
```

- `Uid:` 行四个数正是 real/effective/saved/fs——**euid(第二个)才是权限检查用的**。
- `CapEff` 非 0 = 该进程持有特权;root 进程通常满位,普通服务应尽量少。

## 五、和其它文档的关系

- **父篇**:[../task-struct.md](/concepts/process/task-struct.md)(cred 是 task 的资源对象)。
- **继承**:[../process-creation.md](/concepts/process/process-creation.md)(fork/exec 时凭证怎么传)。
- **特权与其它子系统**:[../scheduling.md](/concepts/process/scheduling.md)(`CAP_SYS_NICE` 设实时优先级)、[../../proc/kernel-tuning.md](/tools/proc/kernel-tuning.md)(改 sysctl 多需特权)、[../namespaces-cgroups... 见](/concepts/process/task-resources/namespaces-cgroups.md)(user namespace 让容器内"看似 root")。
- **观测**:[../../proc/procfs.md](/tools/proc/procfs.md)(`/proc/<pid>/status` 的 Uid/Cap)。

## 六、一句话总结

> **`cred` 是进程的权限身份:用户/组 ID + 附加组 + capabilities。uid 有四个——real(本来是谁)、effective(权限检查实际用的)、saved(供提权/降权来回切)、fs(文件专用);setuid 程序靠 execve 把 euid 设成文件属主(如 root)实现临时提权,用完应降回(最小权限)。capabilities 把"root 万能"拆成几十个独立特权位(如 CAP_NET_BIND_SERVICE 绑低端口),按需授予,不必真给 root——现代服务和容器最小权限的基石。透过 `/proc/<pid>/status` 的 Uid/CapEff 观测。**
