简而言之,Linux特权(功能)

本文的翻译是专门为Linux Administrator课程的学生准备的。


功能的使用越来越多,这在很大程度上要归功于SystemD,Docker和Kubernetes等编排器。 但是,在我看来,该文档有点难以理解,而且特权实现的某些部分对我来说有些混乱,因此我决定在此简短文章中分享我的最新知识。



最重要的特权链接是功能(7)手册页。 但是她不太适合初次认识。

工艺能力


普通用户的权限非常有限,而“ root”用户的权限非常广泛。 尽管以“ root”身份运行的进程通常不需要所有root特权。

为了减少root用户特权,POSIX(POSIX功能)许可权提供了一种方法来限制允许进程及其后代执行的特权系统操作组。 本质上,它们将所有“根”权限划分为一组单独的特权。 功能的概念在POSIX 1003.1e的草案中于1997年进行了描述。

在Linux上,每个进程(任务)都有五个包含权限位的64位数字 (集合)(在Linux 2.6.25之前是32位),可以在以下位置查看
  / proc / <pid> /状态 


CapInh: 00000000000004c0 CapPrm: 00000000000004c0 CapEff: 00000000000004c0 CapBnd: 00000000000004c0 CapAmb: 0000000000000000 

这些数字(此处以十六进制表示)是表示权限集的位图。 这是他们的全名:

  • 可继承 -后代可以继承的权限
  • 允许 -任务可以使用的权限。
  • 有效 -当前有效权限
  • 边界 -在Linux 2.6.25之前,边界集是所有线程共有的系统范围的属性,旨在描述不能扩展权限的集合。 当前,这是每个任务的集合,并且仅是execve逻辑的一部分,详细信息如下。
  • Ambient (Linux 4.3外部版本)-已添加,以便更轻松地向用户提供非root权限,而无需使用setuid或文件权限(稍后会详细介绍)。

如果任务要求特权操作(例如,绑定到<1024的端口),则内核将检查CAP_NET_BIND_SERVICE的当前边界集。 如果已安装,则操作将继续。 否则,该操作将被EPERM拒绝(不允许进行操作)。 这些CAP_在内核源代码CAP_ ,并按顺序编号,因此CAP_NET_BIND_SERVICE等于10,意味着位1 << 10 = 0x400(这是我前面的示例中的十六进制数字“ 4”)。

当前定义的权限的完整的人类可读列表可以在当前功能(7)手册页中找到(此处的列表仅供参考)。

此外,还有一个libcap库可简化管理和授权检查。 除了库API之外 ,该软件包还包括capsh实用程序,该实用程序除其他功能外,还允许您显示凭据。

 # capsh --print Current: = cap_setgid,cap_setuid,cap_net_bind_service+eip Bounding set = cap_setgid,cap_setuid,cap_net_bind_service Ambient set = Securebits: 00/0x0/1'b0 secure-noroot: no (unlocked) secure-no-suid-fixup: no (unlocked) secure-keep-caps: no (unlocked) secure-no-ambient-raise: no (unlocked) uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video) 

这里有一些令人困惑的地方:

  • 当前 -以cap_to_text(3)格式显示 capsh进程的有效,继承和可用特权。 在这种格式下,权限被列为权限组“capability[,capability…]+(e|i|p)” ,其中“e”表示有效, “i”继承的, “p”可用。 正如您可能已经猜到的那样“,”列表没有以“,”符号分隔(cap_setgid+eip, cap_setuid+eip) 。 逗号将权限分割为一个操作组。 然后,将实际的操作组列表用空格分隔。 具有两个操作组的另一个示例是“= cap_sys_chroot+ep cap_net_bind_service+eip” 。 并且以下两组动作“= cap_net_bind_service+e cap_net_bind_service+ip”将编码与一个“cap_net_bind_service+eip”相同的值。
  • 边界集/环境集 。 更令人困惑的是,这两行仅包含这些集合中定义的权限列表,以空格分隔。 这里不使用cap_to_text格式,因为它不包含可用,有效和继承的权限集,而仅包含一组(绑定/环境)权限。
  • Securebits :以十进制/十六进制/ Verilog格式显示任务的securebits标志(是的,每个人都希望在这里看到,从每个系统管理员对自己的FPGAASIC编程的角度来看 ,这是非常清楚的)。 以下是安全位的状态。 实际标志在securebits.h中定义为SECBIT_* ,并在功能(7)中进行了描述。
  • 该实用程序缺少“ NoNewPrivs”信息的显示,可以在以下位置查看
      / proc / <pid> /状态 
    。 尽管它在与文件权限一起使用时会直接影响权限,但仅在prctl(2)中提到了该权限(下面将更详细地介绍)。 NoNewPrivs的描述如下: “在no_new_privs为1的情况下,execve(2)承诺不授予不调用execve(2)就无法完成的操作的特权(例如,处理set-user-IDset-group-IDset-group-ID并禁用文件权限处理) 安装后,无法重置no_new_privs属性。 该属性的值由通过fork(2)和clone(2)创建的后代继承,并通过execve(2)存储。” 当Pod securityContext中的allowPrivilegeEscalation为false时,Kubernetes将此标志设置为1。


通过execve(2)启动新进程时,将使用功能(7)中指定的公式转换子进程的权限:

 P'(ambient) = (file is privileged) ? 0 : P(ambient) P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient) P'(effective) = F(effective) ? P'(permitted) : P'(ambient) P'(inheritable) = P(inheritable) [ie, unchanged] P'(bounding) = P(bounding) [ie, unchanged] where: P() denotes the value of a thread capability set before the execve(2) -      execve(2) P'() denotes the value of a thread capability set after the execve(2) -      execve(2) F() denotes a file capability set -   


这些规则描述了在所有权限集中(环境/允许/有效/可继承/边界)中每个位执行的操作。 使用标准C语法(&-表示逻辑AND,|-表示逻辑OR)。 P'是一个子进程。 P是当前调用execve(2)的进程。 F是通过execve启动的文件的所谓“文件权限”。

此外,进程可以根据以下规则随时使用libcap以编程方式更改其继承,可访问和有效的集:

  • 如果调用方没有CAP_SETPCAP ,则新的继承集必须是P(继承)和P(可用)的子集
  • (对于Linux 2.6.25)新的继承集应该是P(继承)和P(限制)的子集
  • 新的可用集应该是P(可用)的子集
  • 新的有效集应该是P(有效)的子集


档案权限


有时,权限有限的用户需要运行需要更多特权的文件。 之前是通过在二进制文件中设置setuid位( chmod + s ./executable )来实现的。 此类文件(如果属于root)在由任何用户执行时将具有完全root权限。

但是这种机制给文件提供了太多特权,因此POSIX权限实现了一个称为“文件权限”的概念。 它们存储为扩展文件属性,称为“ security.capability”,因此您需要一个支持扩展属性(ext *,XFS,Raiserfs,Brtfs,overlay2等)的文件系统。 要更改此属性, CAP_SETFCAP权限(在可用的进程权限集中)。

 $ getfattr -m - -d `which ping` # file: usr/bin/ping security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA= $ getcap `which ping` /usr/bin/ping = cap_net_raw+ep 


特殊情况和评论


当然,实际上,并不是所有事情都那么简单,在功能(7)手册页中描述了几种特殊情况。 其中最重要的可能是:

  • 如果安装NoNewPrivs或使用nosuid挂载了文件系统,或者ptrace跟踪了调用execve的进程, 则会忽略setuid位和文件许可权 。 当内核使用no_file_caps选项引导时,文件权限也将被忽略。
  • “哑文件”(capability-dumb)是从setuid文件转换为具有文件许可权但不更改其源代码的文件的二进制文件。 此类文件通常是通过对其设置+ ep权限获得的,例如“setcap cap_net_bind_service+ep ./binary” 。 重要的部分是“ e”-有效。 execve之后,这些权限将被添加到可用的和现有的权限中,因此可执行文件将准备使用特权操作。 相比之下,使用libcap或类似功能的“功能智能”文件可以随时使用cap_set_proc(3) (或capset )来设置“有效”或“继承”位, 前提是该权限已在“负担得起的”工具包。 因此,“ setcap cap_net_bind_service+p ./binary”对于“智能”文件就足够了,因为它可以在调用特权操作之前在有效集中本身设置必要的权限。 请参阅示例代码
  • 具有setuid-root的文件继续工作,当用户以非root身份启动时,将授予所有root特权。 但是,如果设置了文件许可权,则只会授予它们。 您还可以创建一个空权限集的setuid文件,这将使其以UID 0的用户身份运行而没有任何权限。 对于具有root用户的超级用户,使用setuid-root运行文件并设置各种securebits标志(见man)时,有一些特殊情况。
  • 边界集会屏蔽可用权限,但不会继承继承权限。 记住P'(可用)= F(可用)和P(限制)。 如果流在其继承集中权限不在其边界集中,那么它仍然可以通过运行在其继承集中具有许可权的文件在其可用集中获得此许可权 -P'(available)= P(继承)和F(继承)。
  • 执行通过set-user-ID,set-group-ID位更改UID或GID的程序,或者执行设置了任何文件许可权的程序, 将会清除环境设置 。 使用PR_CAP_AMBIENT prctl 权限添加到周围的环境中 。 这些权限应该已经存在于可访问和继承的过程集中
  • 如果UID不为0的进程执行execve(2) ,则将删除其可用和活动集中的所有权限。
  • 如果未设置SECBIT_KEEP_CAPS (或更广泛的SECBIT_NO_SETUID_FIXUP ),并且将UID从0更改为非零,则会从继承,可访问和有效的集中删除所有权限


所以...


如果官方的nginx容器,ingress-nginx或您自己的容器停止或重新启动并出现错误:

bind() to 0.0.0.0:80 failed (13: Permission denied)

...这意味着试图以非特权(非0)用户身份监听端口80,并且当前权限CAP_NET_BIND_SERVICE没有CAP_NET_BIND_SERVICE 。 要获得这些权利,必须使用xattr并设置(使用setcap )对nginx文件的权限至少为cap_net_bind_service+ie 。 此文件许可权将与旧集(通过pod SecurityContext / capability / add / NET_BIND_SERVICE中的边界集指定)组合在一起,还将被放置在可用许可权集中。 结果是cap_net_bind_service+pie

只要将securityContext / allowPrivilegeEscalation设置为true并且docker / rkt存储驱动程序(请参阅docker文档)支持xattrs,这一切就可以使用。

如果nginx在权限方面很聪明,那么cap_net_bind_service+i就足够了。 然后,他可以使用libcap将权限从可用集扩展到有效。 结果收到cap_net_bind_service+pie

除了使用xattr,在非根容器中获取cap_net_bind_service的唯一方法是让Docker设置其外部功能(环境功能)。 但是截至2019年4月,这尚未实施

代码示例


这是使用libcap将CAP_NET_BIND_SERVICE添加到有效权限集中的示例代码 。 该文件需要CAP_BIND_SERVICE+p权限。

参考文献(英文):

Source: https://habr.com/ru/post/zh-CN471802/


All Articles