ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux权限体系详解:从DAC到SELinux/SEAndroid排查实战

Linux权限体系详解:从DAC到SELinux/SEAndroid排查实战 工作几年你会发现真正挡路的往往不是业务逻辑而是权限体系。Linux 环境里的“权限”并不是单层结构最外层是我们熟悉的文件属主、权限位也就是 DAC自主访问控制在它背后还有一层可能更隐蔽但一旦触发就让人头疼的 MAC强制访问控制而 SELinux 和它在 Android 端的落地版本 SEAndroid就是 MAC 的代表。很多人排查问题时会先怀疑代码、再怀疑端口、最后才想到 SELinux但一套完整的权限排查思路应该在第一步就把这两层区分开。这篇文章我打算把 Linux DAC、SELinux/SEAndroid MAC 这个组合讲明白先说 DAC 的原理和局限再说 SELinux 的核心机制然后单独拆解 SEAndroid 在实际 Android 系统里怎么运作最后给出可复现的排查步骤和踩坑记录。适合正在做服务器加固、Android 系统定制或者经常被 Permission denied 折磨的运维和驱动开发同学。1. 先分清两套门卫DAC 与 MAC 的工作逻辑完全不同1.1 DAC 的核心逻辑是“所有者说了算”Linux 的 DAC 体系里面文件、目录、设备节点都有明确的属主 uid 和属组 gid权限位则由读、写、执行三组 rwx 表示。这套模型最关键的词是“自主”文件所有者可以自己决定把权限放宽到什么程度。你把一个文件chmod 777系统会照做它并不会阻拦你因为 DAC 的设计原则就是所有者自治。这种机制能解决最基本的隔离问题用户 A 不能随便读用户 B 的文件只要 B 的文件权限是 600 或 700。但 DAC 对“谁在操作”这件事并不敏感。它只检查操作者是不是文件属主、属组或其他用户不关心这个进程到底是谁 spawn 出来的也不关心它是不是已经被攻击者控制。所以 DAC 很容易被绕过只要文件属主愿意或者某个进程以 root 身份运行权限位就形同虚设。1.2 MAC 的核心逻辑是“全局规则说了算”MAC 全称 Mandatory Access Control强制访问控制。它和 DAC 最大的区别在于不再由文件属主决定能不能访问而是由系统里预先定义好的全局策略来决定。进程访问一个文件时内核会先看发起者的安全属性、目标客体的安全属性再查策略规则如果策略里没写允许就默认拒绝。以 SELinux 为例它给每个进程打上一个叫“安全上下文”的标签通常叫 domain给文件、目录、端口也打上对应的标签叫 type。访问动作要允许必须满足策略里存在一条显式的 allow 规则。这条规则和文件所有者完全无关即使你有文件的所有权只要 domain 和 type 之间没有规则内核依然会拒绝访问。这就是“强制”的含金量。1.3 为什么 DAC 和 MAC 会同时存在内核在处理请求时不会让 MAC 简单替代 DAC而是两套机制串行检查。先走 VFS 层的常规权限检查也就是 DAC走不过去直接返回 EACCESDAC 通过之后再进入 LSM 钩子如果系统启用了 SELinux就继续做 MAC 检查。任何一个环节拒绝操作就失败。这就是为什么实际排障时经常看到一种现象文件明明已经是 777进程还是报 Permission denied。此时拿ls -l看不出问题真正拦截你的是后面那层 SELinux 在按策略拒绝。对比维度DACMACSELinux权限决策依据文件 owner / group / mode主体 domain 与客体 type 的策略规则谁说了算文件所有者系统策略定义者root 是否受限制基本不受限受限于域内能力与策略典型配置方式chmod / chown / ACLsepolicy、布尔值、安全上下文直观排障入口ls -l、getfaclls -Z、AVC 日志、sesearch记住上面这张表后面遇到问题就能先分类是 DAC 不够还是 MAC 不放行。2. DAC 的细节从权限位到 capability别只在 chmod 上打转2.1 权限位背后的系统调用语义DAC 的 rwx 权限位本质上映射到一组系统调用操作。文件的读权限对应 open 时的 O_RDONLY写权限对应 O_WRONLY/O_RDWR执行权限对应 execve。目录的读权限是列举目录项写权限是在目录里创建或删除文件执行权限则是能否穿越路径到达内部文件。很多人对目录权限有个误区认为文件本身是 644目录是 555就能让所有人读文件。但如果你把目录的执行权限去掉即使文件是 644外部用户也进不了目录路径解析会在中间某一层直接失败。同理粘滞位一般出现在 /tmp 这样的共享目录它限制了删除操作“只有文件属主、目录属主或 root 才能删除目录内的文件”。这是 DAC 模型里少数能弥补多人共享目录问题的机制。2.2 setuid 和 capability 藏着的隐患Linux 传统上会让 root 的 uid0 享有各种特权。setuid程序可以让普通用户临时以文件属主身份跑起来比如/usr/bin/passwd需要修改 /etc/shadow就依赖这个位。可问题也出在这里一旦一个 setuid 程序有漏洞DAC 层面就无法限制它“借用”到的权限。很多人讲 Linux 安全时只会提醒“别给脚本加 setuid 位”但很少解释为什么。因为 setuid 位放在一个可被篡改的脚本上时攻击者可以借这个身份执行任意代码而 DAC 对这种情况毫无拦截能力。后续内核引入 capabilities 以后root 特权被拆成几十个细粒度的能力比如CAP_DAC_OVERRIDE可以直接绕过文件的 DAC 权限检查CAP_NET_BIND_SERVICE可以绑定低端口。这套机制确实让系统比原来精细但它的本质仍是“授权”并不是“限制”。只要进程持有能力它就能做对应操作能力一旦分配过度攻击面一样很大。2.3 ACL 只是 DAC 的扩展不是 MACPOSIX ACL 主要通过setfacl和getfacl管理它允许你给一个文件指定多个用户或多个组的特定权限比传统的属主、属组、其他用户三段式灵活得多。很多新手看到 ACL 里有 mask、有 default ACL会误以为这就是强制访问控制。实际上 ACL 仍然完全是“资源所有者自治”的逻辑只有文件的属主或有CAP_FOWNER能力的进程才能修改 ACL它依然属于 DAC 范畴。在实际部署中ACL 解决的是“跨部门共享目录”这类需求比如一个项目组的日志目录既要让组内同学写又要让运维同学只读用传统权限位很难同时满足ACL 就很方便。但如果你遇到的是安全策略级别的隔离问题ACL 帮不上忙还是要进入 MAC 层去解决。2.4 DAC 的根本局限root 太强、进程权限不收敛DAC 的天然缺陷不在于权限位本身而在于它很难实现最小权限原则。root 用户基本不受常规权限位约束一旦服务以 root 运行哪怕只是一个小漏洞被利用攻击者就可能读取任意文件。虽然可以创建低权限用户来运行服务但这只是从“人”的角度做限制没有从“进程行为”的角度做控制。一个普通用户权限的进程依然可以读取它有权访问的全部数据也可以在这个权限范围内任意横向移动。这也是为什么后来会出现 SELinux、AppArmor 这类 MAC 方案。它们的目标不是替代 DAC而是在 DAC 之上加一层“行为白名单”把进程能碰到的文件、网络、设备都限定在一个很小的范围里。3. SELinux 实践标签、策略与 AVC 日志的完整拆解3.1 看清安全上下文进程和文件各自挂什么标签SELinux 里面最常用的查看命令是带-Z参数的组合例如ps -eZ查看进程上下文ls -Z查看文件上下文。一个典型的上下文长这样system_u:system_r:httpd_t:s0从左到右分别是 SELinux 用户、角色、类型、安全级别。对普通排障来说最需要注意的就是第三段“类型”。进程的类型通常称为“域”比如 httpd_t文件的类型则直接称为“类型”比如 httpd_sys_content_t。内核在做访问判断时主要就是看进程的域和文件的类型。这里的角色和用户并不是普通 Linux 用户的概念。system_u通常指系统资源unconfined_u指不受限制的用户上下文。在多数发行版默认策略下普通进程的域可能是unconfined_t这意味着 SELinux 对它的限制很少而系统服务的域往往被收敛得很窄比如sshd_t只允许访问少量端口和文件。3.2 allow 规则与域转换策略是怎么描述一个“合理动作”的SELinux 的策略规则核心是一条条 allow 语句形式大概是这样allow httpd_t httpd_sys_content_t:file { open read getattr };意思是允许运行在 httpd_t 域中的进程对标记为 httpd_sys_content_t 类型的文件执行 open、read、getattr 操作。这里有一个很容易忽略的点权限集合里的读写和 DAC 的 rw 权限位是两个维度。SELinux 甚至允许策略里区分 map、append、ioctl 这类更细的操作。所以一个文件即使 DAC 层面已经允许写入只要 SELinux 策略没有给 write/append 权限写入照样失败。域转换也是一个重要概念。进程发起 execve 时SELinux 会根据被执文件的安全上下文决定新进程进入哪个域。最常见的是 init 启动服务进入对应域Android 里 Zygote 给每个应用分配独立域也是这么做的。域转换依赖策略里的type_transition规则这也是很多人写 sepolicy 时经常搞不定的点。3.3 一次典型的 AVC 拒绝从日志看问题定位SELinux 拒绝访问时如果 auditd 在运行日志会进入/var/log/audit/audit.log如果没开可以在 dmesg 或 journalctl 里看到。典型日志长这样avc: denied { read } for pid1234 commnginx nameindex.html devsda1 ino3456 scontextsystem_u:system_r:httpd_t:s0 tcontextsystem_u:object_r:user_home_t:s0 tclassfilescontext 是发起访问的进程上下文也就是 httpd_ttcontext 是目标文件上下文tclass 是对象类别这里是 file。看到 tcontext 里的 user_home_t 就能明白Nginx 试图读一个被标记为家目录内容的文件但策略里没有 allow httpd_t user_home_t 这条规则所以被拒。排查时先不要急着加规则。优先级最高的做法是让文件的标签回归正确范围。比如把文件移动到/var/www/html/下再执行restorecon标签就会变成 httpd_sys_content_t问题自然消失。因为 Nginx 是标准服务系统自带的 httpd 策略已经给好了规则不需要你再发明新规则。3.4 文件标签管理restorecon、chcon 与 semanage fcontext 的区别这三条命令是 SELinux 排障中最常用到的但很多人分不清它们。restorecon的作用是“按照系统默认的文件上下文规则恢复标签”。它读取/etc/selinux/policy/contexts/files/file_contexts里的配置把文件标签改成默认值。chcon的作用是“直接修改标签”不过它不修改默认规则。你手动 chcon 改出的标签如果和 file_contexts 不一致下次全盘 relabel 或被 restorecon 覆盖时会被改回去所以它更适合临时验证。semanage fcontext才是“注册一条默认标签规则”的正规操作。比如你想让/data/web下的文件在每次 restorecon 时都被标记成 httpd_sys_content_t可以这样semanage fcontext -a -t httpd_sys_content_t /data/web(/.*)? restorecon -Rv /data/web加了这条规则以后系统在自动 relabel 时也会按这个规则恢复不会被默认的/var/www路径限制住。实际操作中我非常建议大家把习惯改成“能用 restorecon 就不要用 chcon”因为 chcon 改出来的临时标签就像手工补丁重启一次可能就没了。3.5 布尔值比改策略更安全的快速开关SELinux 策略里预置了很多布尔值用来开放或收紧某些场景的权限比如让 Nginx 能代理外部网络连接或者允许 Samba 读写家目录。查看用getsebool -a | grep httpd临时启用并持久化可以这样setsebool -P httpd_can_network_connect on这里-P表示写入策略存储重启仍然生效。布尔值的价值在于它让你不需要写新的 allow 规则而是使用策略作者预留好的开关。但也要注意布尔值打开后影响的是整个域不只是某一台服务器所以从严谨性角度还是要能收就收别把httpd_can_network_connect长期开着。4. SEAndroid 的落地当 SELinux 遇见 Android4.1 SEAndroid 与桌面 Linux SELinux 的差异SEAndroid 本质上是 SELinux 在 Android 平台的一个专门实现但它和 RHEL/CentOS 上的 SELinux 有很明显的使用方式差异。桌面 Linux 的经典场景是管理员看着 AVC 日志去调整策略Android 则是出厂时就把策略固化进系统镜像普通用户根本没有机会修改 sepolicy。策略的编写者通常是设备厂商或 SoC 方案商而不是最终用户。Android 里每个应用和每个系统服务都会被塞进独立的安全域。系统服务一般使用 system_app、system_server 这类域普通应用按签名级别落在 platform_app、untrusted_app、priv_app 等域里。每个域之间默认没有互访权限必须通过策略显式放开。这种“默认拒绝”的严格程度比很多发行版默认的 targeting policy 还要高。4.2 特殊的安全上下文属性服务与 zygote 派生Android 有一个全局属性服务类似共享内存键值对但应用层非常依赖它。SELinux 在 Android 里专门为属性定义了 property_contexts 文件控制哪个域能 set 或 get 属性。你会发现有些应用报 “property_set: Permission denied”往往不是属性权限配置问题而是它的域根本没有资格写对应属性前缀。所有允许写属性的域和属性前缀都要靠策略一条条列出来。另一个容易踩坑的地方是 zygote 派生应用时的域转换。应用进程由 zygote fork但 SELinux 会在 fork 后根据应用的 UID、包名和 seinfo 信息做一次 domain transition落到 untrusted_app 之类独立域。如果这项 transition 配置不完整应用启动时会直接 crash 或者被 kill。业界排查应用起不来的问题第一步就应该是看 logcat 里的 AVC 记录。4.3 Treble 架构下的 vendor 策略拆分Android 8.0 之后引入 Treblesystem 分区和 vendor 分区解耦SELinux 策略也被拆成 platform sepolicy 和 vendor sepolicy 两部分。在启动早期init 会把 system 与 vendor 的 policy 片断合并成完整策略再加载进内核。vendor 进程使用的域通常以 vendor_ 前缀开头避免和 system 域冲突。这种拆分带来一个常见问题厂商在 vendor 侧新增一个 HAL 服务试图访问 system 分区的文件或属性时不仅需要自己域的 allow 规则还要确认 system 侧 neverallow 规则没有挡住它。比如 system 侧不允许 vendor_init 对 system_server 做某些操作vendor 无论如何放行都没有用因为合并策略后 neverallow 会直接把构建或启动阶段卡住。这也是很多定制 ROM 编译不通过的原因之一。4.4 一个 HAL 服务被拦截的真实调试过程假设你写了一个 vendor HAL 服务在 init 里启动后访问/sys/devices/...下的一个节点logcat 或 dmesg 里反复出现这样的 AVCavc: denied { open } for pid234 commvendor.hal.demo namepower_state scontextu:r:vendor_hal_demo_t:s0 tcontextu:object_r:sysfs:s0 tclassfile我的做法通常分四步。第一步先确认这个 sysfs 节点到底该用什么 tag。如果节点路径在 file_contexts 里没有定义默认会落到 sysfs 类型而 HAL 服务几乎不可能对所有 sysfs 都有访问权。第二步检查节点有没有 genfs_contexts 映射因为 sysfs、procfs 这类虚拟文件系统往往在 genfs_contexts 里定义。第三步写规则。基于上面的 AVC最粗暴的是在 vendor policy 里加一条allow vendor_hal_demo_t sysfs:file { open read write }。但这种写法过于宽泛等于给了整个 sysfs 权限。更稳的做法是给这个节点单独定义一个类型比如 power_state_t然后在 file_contexts 里给路径打标签再只允许 HAL 域访问这一类型。第四步重新打包、刷机、验证确认 AVC 消失。这里特别提醒一下不要一上来就把整块 HAL 域设为 permissive。你听不到 AVC 日志的同时也失去了安全屏障问题只是被埋起来了。5. 权限报错排查清单三步定位是 DAC 还是 MAC5.1 第一步先把基础状态搞清楚遇到 Permission denied 或是奇怪的 Operation not permitted 时我建议按下面的顺序冷静走一遍id # 看当前用户和所属组 ls -ld /data/web # 看目录权限位和属主 getfacl /data/web/ # 看是不是有 ACL 影响 ls -Zd /data/web # 看 SELinux 文件标签 getenforce # 看 SELinux 当前模式 ps -eZ | grep nginx # 看进程域这一套命令下来大部分问题能见到眉目。如果文件标签是 var_t 或 user_home_t 一类明显不合适的类型问题基本锁定在 MAC 层如果/data/web属主是 root 而运行用户是 www-data权限位还是 750问题就在 DAC 层。先做分类再治疗。5.2 第二步用 AVC 日志二次确认当 DAC 看起来完全正常但操作还是失败时就要去 audit 日志确认是不是 SELinux。最直接的读取ausearch -m avc -ts recent如果只关心某个进程或文件可以加上 comm 或 path 过滤。也可以用audit2why查看内核给出的简单解释ausearch -m avc -ts recent | audit2why注意audit2allow生成的规则只是一种参考不要 blindly 直接编译加载。一个常见误区是只要你调一次 web 路径问题就用audit2allow -a生成一整包规则结果策略越加越宽松把原本的隔离效果破坏掉。5.3 第三步常见场景速查表现象可能链路检查方向常用修复文件 777 仍报 Permission deniedDAC 通过MAC 拒绝ls -Z、AVC 日志修正文件标签或增加 allow 规则Nginx 默认路径 403换目录后 502新路径文件标签不对ls -Zdsemanage fcontextrestorecon自定义服务启动后无监听端口域内未允许 bind 该端口类型sesearch -A -s domain -c tcp_socket增加端口上下文或开启布尔值容器或 systemd 服务被 kill缺少对 cgroup 或 systemd 文件的访问logcat / journalctl AVC布尔值或策略放行挂载 NFS 后应用无法读写文件系统不支持安全标签ls -Z看标签是否?调整挂载选项或关闭该目录 relabel这张表不能覆盖所有情况但它代表了一套思考框架核心就是“先确认谁在访问、目标是什么标签、策略有没有放行”。排障的时候思路清晰比背命令重要得多。5.4 哪些快捷操作是治标不治本的不少工程师在测试环境图快直接setenforce 0或者在内核参数里加selinux0。结果服务确实能跑问题却并没有定位到根因。你关掉 SELinux 之后原来缺标签、缺策略、错上下文的问题全部被跳过上线后只要机制重新打开故障就会原封不动回来。另一种治标操作是随便chcon -R -t 某个类型到目录上。常见于把某目录 chcon 成 httpd_sys_content_t 后能访问了但之后如果系统做一次 full relabel标签又会被恢复问题再次出现。正确的做法永远是 semanage fcontext 注册默认规则再 restorecon 验证一遍。6. 我的实际体会SELinux 是白名单不是事后补丁我自己的感受是DAC 适合用来做“基础隔离”它简单、高效、足够直观MAC 则是给系统加了一层“行为白名单”。从运维角度看强制访问控制确实会增加排障成本尤其刚开始接触时你会莫名被各种 AVC 日志劝退。但当你经历过一次因为 SELinux 没关导致数据被越权读取的事件复盘就会明白“默认拒绝”这个设计的价值。在 Android 系统定制的项目里SEAndroid 的严格程度往往比服务器端还高。我见过不少同事在 vendor 进程无法访问某个节点时第一反应是建议把整台设备设成 permissive。这个想法很危险Android 应用的攻击面比服务器大得多一旦放开等于把每道门都拆了只留下 DAC 那层薄薄的保护。最后分享一个个人习惯我给服务器或设备交付前会强制自己用“permissive 模式收集日志 - 逐步精确化规则 - 回归 enforcing”这条链路走一遍。permissive 不是让你关安全而是让你在安全机制的保护下看清楚每个访问请求该不该发生。如果你也遇到类似权限问题建议先花半小时把 AVC 日志读透再考虑动策略。这个时间花得值。
返回列表