当一个对象作用于另一个对象时,Linux执行的安全检查有几个部分:
act(action)
subject ------------------> object
subjective context objective context
在Linux系统中,对象是可以直接由用户空间程序操作的系统中的实体。Linux具有多种可操作的对象,包括:
(1)任务(Tasks):任务表示Linux系统中的进程或线程。与任务相关的一组凭据包括:
用户ID(UID):标识拥有任务的用户。
组ID(GID):标识任务所属的组。
附加组ID(Supplementary group IDs):任务所属的其他附加组。
权限(Capabilities):定义任务可用的特定权限和权限级别。
(2)文件/索引节点(Files/Inodes):文件和索引节点表示文件系统中的对象。与文件/索引节点相关的一组凭据包括:
文件权限:指定文件的访问权限(例如读取、写入、执行),包括文件所有者、组和其他用户的权限。
所有者UID和GID:标识拥有文件的用户和组。
文件特权(File capabilities):定义与文件关联的附加特权,可以扩展传统的POSIX权限。
(3)套接字(Sockets):套接字是网络通信的通信端点。与套接字相关的一组凭据包括:
套接字权限:类似于文件权限,指定套接字的访问权限。
所有者UID和GID:标识拥有套接字的用户和组。
与套接字相关的特权(Socket-related capabilities):与套接字操作相关的附加特权。
(4)消息队列、共享内存段、信号量(Message queues, shared memory segments, semaphores):这些是进程间通信(IPC)机制。与这些IPC对象相关的一组凭据包括:
对象权限:确定对IPC对象的访问权限。
所有者UID和GID:标识拥有IPC对象的用户和组。
与IPC相关的其他特权(Additional IPC-related capabilities):提供特定于操作IPC对象的特权。
(5)密钥(Keys):密钥用于控制对各种内核资源的访问。与密钥相关的一组凭据包括:
密钥权限:指定密钥的访问权限。
所有者UID和GID:标识拥有密钥的用户和组。
与密钥管理相关的特权(Key-specific capabilities):提供与密钥管理相关的附加特权。
这些凭据在Linux系统中起着至关重要的作用,用于执行安全性和访问控制策略,确保用户程序只能根据其分配的权限和特权与对象进行交互。
在大多数对象的凭据中,会有一个子集用于指示该对象的所有权。这用于资源账户和限制(例如磁盘配额和任务资源限制)。
在标准的UNIX文件系统中,例如,这是通过标记在索引节点上的UID(用户ID)来定义的。每个文件和目录都有一个与之关联的索引节点(inode),其中包含了文件的元数据信息,包括所有者UID。通过索引节点上的UID,可以确定文件的所有者是哪个用户。
这种所有权信息对于资源管理和限制非常重要。例如,磁盘配额(disk quotas)可以根据文件所有者的UID来限制用户可以使用的磁盘空间量。任务资源限制(task rlimits)可以根据进程所有者的UID来限制进程可以使用的系统资源,如CPU时间、内存等。
通过使用所有者UID的信息,系统可以对资源进行计量、分配和限制,以确保公平的资源使用和避免滥用。
在这些对象的凭据中,还有一个子集用于指示该对象的"客观环境 - 上下文"(objective context)。这个子集可能与前面提到的所有权凭据的子集相同,也可能不同。在标准UNIX文件中,客观环境由标记在索引节点上的UID和GID定义。
客观环境是在对对象进行操作时进行安全计算的一部分。它用于确定在执行操作时应用的安全策略和权限。通过客观环境的考虑,可以确保在操作对象时,系统会根据对象的客观环境来确定用户的权限和访问级别。
例如,在标准UNIX文件系统中,访问文件时,系统会检查执行操作的用户的UID和GID,以及文件的所有者UID和GID。根据这些客观环境的比较,系统可以确定用户是否具有读取、写入或执行文件的权限。
客观环境的概念用于确保在操作对象时,系统会考虑对象的属性、所有权和其他相关因素,以确定访问和操作的合法性。这有助于维护系统的安全性,并防止未经授权的访问和操作。
在标准UNIX文件中,除了用户ID(UID)和组ID(GID)之外,还有其他因素可以确定对象的客观环境。这些因素包括:
(1)文件权限:文件权限通过文件模式表示,对确定客观环境起着关键作用。文件模式指定了所有者、组和其他用户的访问权限,指明了谁可以读取、写入或执行该文件。
(2)访问控制列表(ACL - Access Control List):ACL提供了比传统文件权限更细粒度的访问控制。ACL允许为特定用户或组指定访问权限,实现更精细的客观环境控制。
(3)文件系统挂载选项:客观环境还可以受到文件系统挂载选项的影响。挂载选项定义了文件系统的各种行为,包括与安全相关的设置,如强制执行特定权限或禁用特定功能。
(4)安全策略和模块:客观环境可能受到系统范围的安全策略和模块的影响,例如SELinux(安全增强型Linux)或AppArmor。这些安全框架根据文件和进程关联的标签、上下文或配置文件实施额外的访问控制和策略。
在系统中,主体(subject)是指对另一个对象进行操作的对象。
大多数对象在系统中是不活动的,它们不对系统内的其他对象进行操作。进程/任务是明显的例外:它们执行操作,访问和操作各种资源。
除了进程/任务之外,其他对象在某些情况下也可以成为主体。例如,一个打开的文件可以使用由调用fcntl(F_SETOWN)的任务给予的UID和EUID发送SIGIO信号给一个任务。在这种情况下,文件结构体将具有主观上下文。
这意味着在某些情况下,不仅进程/任务可以成为主体,还可以是其他对象,如文件。这取决于特定的操作和系统的实现。例如,在上述情况中,文件结构体中的主观上下文(subjective context)包含了与文件相关的信息,允许文件向特定任务发送信号,并基于任务的UID和EUID进行权限控制。
主观上下文的概念用于确保在对象之间的交互中考虑到操作的主体,并根据主体的属性和权限来确定访问和操作的合法性。这有助于维护系统的安全性,并确保对对象的操作是经过授权和合法的。
主体(subject)在安全计算中具有其凭证的额外解释。其凭证的一个子集形成了“主观上下文”(subjective context)。主观上下文在主体执行操作时作为安全计算的一部分使用。
以Linux任务为例,当任务在操作文件时,它具有FSUID(文件系统用户ID)、FSGID(文件系统组ID)和附加组列表。这些凭证与通常构成任务的客观上下文的真实UID和GID是相互独立的。
这意味着在Linux系统中,任务的主观上下文是在特定操作中使用的一组凭证,它们与任务的真实UID和GID不同。主观上下文反映了任务在执行操作时的特定身份和权限。文件系统用户ID(FSUID)、文件系统组ID(FSGID)以及附加组列表提供了任务在操作文件时的临时身份,用于访问和操作文件的安全计算。
通过区分主观上下文和客观上下文,系统能够根据不同的操作和权限需求来进行细粒度的安全计算。这有助于确保任务在进行操作时,基于其主观上下文的凭证来决定访问权限,从而提高系统的安全性和灵活性。
在Linux中,主体(subject)可以对对象执行多种可用操作。可用的操作集取决于主体和对象的性质。
这些操作包括读取、写入、创建和删除文件,以及派生(forking)或发送信号(signalling)和跟踪(tracing)任务等。
下面是一些常见的操作示例:
读取文件:主体可以读取对象(例如文件)中的数据内容。
写入文件:主体可以向对象(例如文件)写入数据内容。
创建文件:主体可以在文件系统中创建新文件。
删除文件:主体可以从文件系统中删除文件。
派生(Forking):主体可以创建子进程,派生出一个新的执行实体。
发送信号(Signalling):主体可以向其他进程发送信号,以通知或影响其行为。
跟踪(Tracing):主体可以对目标进程进行跟踪,以监视其行为或收集调试信息。
这些操作的可用性取决于主体和对象的类型以及相关的权限配置。Linux提供了广泛的操作和功能,允许主体对不同类型的对象执行各种操作。这种灵活性使得可以根据具体需求和安全要求来定义和控制主体对对象的操作行为。
当主体对对象进行操作时,会进行安全计算。这涉及到使用主观上下文、客观上下文和操作,根据这些上下文搜索一个或多个规则集,以确定在给定这些上下文的情况下,主体是否被授予或拒绝以所需方式对对象进行操作的权限。
主要有两个规则来源:
(1)自主访问控制(Discretionary Access Control,DAC):
有时,对象的描述中会包含一组规则。这就是所谓的“访问控制列表”(Access Control List,ACL)。一个Linux文件可以提供多个ACL。
例如,传统的UNIX文件包括一个权限掩码,它是一个简化的ACL,具有三个固定的主体类别(“用户”、“组”和“其他”),每个类别可以被授予特定的权限(如“读取”、“写入”和“执行” - 具体映射到对象的权限)。然而,UNIX文件权限不允许任意指定主体,因此在这方面的用途有限。
Linux文件还可以支持POSIX ACL。这是一个规则列表,为任意主体授予各种权限。
(2)强制访问控制(Mandatory Access Control,MAC):
整个系统可能有一个或多个规则集,适用于所有主体和对象,不考虑它们的来源。SELinux和Smack(LSM安全模块)就是这种情况的例子。
在SELinux和Smack的情况下,每个对象在其凭证中都被赋予一个标签。当请求执行操作时,它们使用主体标签、对象标签和操作,寻找一个规则,该规则表示此操作是授予还是拒绝的。
总体而言,在DAC中,对象本身提供ACL规则,而在MAC中,系统为所有主体和对象应用一组通用规则。这些规则在安全计算过程中用于确定是否授予主体执行操作的权限。通过结合DAC和MAC,系统能够提供更细粒度的访问控制,并确保对对象的访问和操作符合规定的权限和策略。
Linux内核支持以下类型的凭据:
传统UNIX系统中的凭证包括:
(1)真实用户ID(Real User ID)和真实组ID(Real Group ID):这些凭证代表进程或任务的实际用户和组身份。它们定义了进程的客观上下文,并通常继承自启动该进程的用户。
(2)有效用户ID(Effective User ID)和有效组ID(Effective Group ID):这些凭证确定进程在执行时具有的权限和特权。在某些情况下,有效用户ID和有效组ID可能与真实用户ID和真实组ID不同。例如,当进程执行一个具有设置了setuid或setgid权限的程序时,其有效ID可能会变为该程序所有者的ID,从而使进程能够在一定时间内暂时具有该所有者的权限。
(3)保存的用户ID(Saved User ID)和保存的组ID(Saved Group ID):这些凭证用于保留进程临时更改有效ID时的原始用户和组身份。通常在特权提升操作中使用,允许进程在执行特权操作后恢复到其原始身份。
(4)文件系统用户ID(Filesystem User ID)和文件系统组ID(Filesystem Group ID):这些凭证在进程访问文件系统上的文件时使用。它们为文件相关操作提供临时身份更改,与有效ID独立。这允许进程在文件操作中具有不同的权限,同时保留其有效ID用于其他目的。
附加组是进程可以拥有的额外组成员身份,除了主要组(由组ID确定)之外。这些附加组为进程提供了与这些组相关联的权限,超出了主要组的权限。
需要注意的是,虽然有效ID通常被用作主观上下文,真实ID被用作客观上下文,但这并不适用于所有任务。具体凭证的使用可能会根据任务的要求和系统的配置而有所不同。
这些传统UNIX凭证在确定进程的访问权限和特权方面起着重要作用,确保与系统中的对象进行安全且受控的交互。
struct task_struct {
......
/* Process credentials: */
......
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
......
};
当在UNIX系统中讨论能力(capabilities)时,它提供了一种相对于传统UNIX凭证更精细的权限管理方式。能力允许任务根据需要的功能具有特定的特权,而不是完全依赖于具有无限制访问权限的超级用户(root),从而降低了不必要的特权带来的风险。
能力集合由一系列预定义的个别特权组成,可以授予进程。能力的示例包括执行网络操作、修改系统时间、访问原始套接字等。每个能力代表着可以为进程启用或禁用的特定操作或特权。
能力分为不同的集合:
(1)允许的能力集合(Permitted capabilities):这个集合定义了进程可以通过capset()系统调用授予其有效或允许的能力集合的能力。它表示进程可以具有的潜在能力。
(2)有效的能力集合(Effective capabilities):这个集合表示任务实际可以使用的能力。它是允许的能力的子集,确定了当前进程可用的特权。
(3)可继承的能力集合(Inheritable capabilities):这个集合包含可以通过execve()系统调用在创建新进程时传递的能力。当进程执行新程序时,可继承的能力可以传递给新进程,允许它具有一组特定的特权。
(4)能力边界集合(Capability bounding set):这个集合定义了能力在execve()过程中的上限,特别是在以UID 0(root)执行二进制文件时。能力边界集合用于限制能力的继承,确保在执行特权操作时不会无意中继承过多的特权。
能力可以通过对传统UNIX凭证(例如UID、GID)的更改来隐式操作,也可以通过capset()系统调用直接进行操作。通过管理能力,管理员可以向进程或任务授予特定的特权,而不必授予无限制的超级用户访问权限,从而最大程度地降低安全漏洞的潜在影响。
总体而言,能力通过允许对进程授予的特权进行精细控制,减少攻击面并减轻过多特权带来的风险,增强了UNIX系统的安全性和灵活性。
安全管理标志(securebits)是一种仅作用于任务(tasks)的概念。它们用于控制上述凭证在特定操作(例如execve())中的操作和继承方式。与能力类似,安全管理标志并不直接用作客观或主观凭证。
在UNIX系统中,安全管理标志是一组用于管理特权和安全性的标志位。它们控制着进程在特定操作期间的行为和权限。
密钥(keys)和密钥环(keyrings)是由任务(tasks)携带的概念。它们用于携带和缓存无法存储在其他标准UNIX凭证中的安全令牌。密钥和密钥环的作用是使网络文件系统密钥等安全令牌在进程执行的文件访问中可用,而无需普通程序了解涉及的安全细节。
密钥环是一种特殊类型的密钥。它们携带一组其他密钥,并可以通过搜索来查找所需的密钥。每个进程可以订阅多个密钥环:
每线程密钥(Per-thread keying)
每进程密钥环(Per-process keyring)
每会话密钥环(Per-session keyring)
当进程访问一个密钥时,如果该密钥尚不存在,则通常会将其缓存在这些密钥环中,以便将来的访问可以找到该密钥。
要了解如何使用密钥,请参阅Documentation/security/keys/*文档。该文档提供了更多有关使用密钥的信息。
需要注意的是,密钥和密钥环仅由任务携带,并用于进程之间的安全令牌传递和缓存。它们不直接用作标准UNIX凭证的一部分,而是提供了一种在文件访问中使安全令牌可用的机制,而无需普通程序了解安全细节。
LSM(Linux Security Module)是一种允许对任务操作进行额外控制的机制。它在Linux中支持多个LSM选项。
LSM通过为系统中的对象进行标记,并应用一组规则(策略)来限制具有一个标记的任务对具有另一个标记的对象可以进行的操作。
LSM的基本思想是通过在内核中引入一个可插拔的安全模块接口,允许不同的安全模块实现特定的安全策略。每个安全模块都可以对系统中的对象(如文件、进程、网络连接等)进行标记,并定义了一组规则来控制对象之间的访问和操作。
通过使用LSM,可以实现基于标签的访问控制(Label-Based Access Control,LBAC)或多级安全策略,使系统管理员能够根据需求灵活地配置和强化系统的安全性。
目前,Linux支持多个LSM选项,包括但不限于以下几个常见的LSM:
(1)SELinux(Security-Enhanced Linux):基于强制访问控制(Mandatory Access Control,MAC),使用标签来控制对象的访问权限。
(2)AppArmor:基于强制访问控制(MAC),使用路径名来标记对象并应用访问规则。
Smack(Simplified Mandatory Access Control Kernel):基于强制访问控制(MAC),使用标签来标记对象并定义访问策略。
(3)TOMOYO Linux:基于可选访问控制(Optional Access Control,OAC),使用域和访问控制列表(ACL)来控制对象的访问权限。
(4)Yama(Yet Another MAC Implementation):提供一些额外的安全功能,如限制进程间通信、控制进程核心转储等。
通过选择适合特定需求和安全要求的LSM选项,系统管理员可以增加对系统操作的细粒度控制,提高系统的安全性和可靠性。
AF_KEY是一种基于套接字的地址族密钥管理协议,用于网络协议栈中的凭证管理。这个协议在RFC 2367中定义。与直接与任务和文件凭证进行交互不同,AF_KEY保留了系统级别的凭证。
AF_KEY提供了一种机制,用于在网络协议级别管理和操作安全关联、密钥和策略。它允许应用程序或服务与内核进行交互,执行与身份验证、加密和其他安全相关任务有关的操作。
AF_KEY协议在网络协议级别操作,允许系统级别的凭证和安全相关信息单独存储和管理,而不是与特定任务或文件关联。它为应用程序提供了与底层网络协议栈通信的标准接口,以执行与安全关联相关的操作。
AF_KEY在管理网络协议栈的系统级别凭证方面具有特定的作用。它可以在全局级别管理与安全相关的信息,而不仅仅关注特定任务或文件的凭证。
当打开一个文件时,打开任务的主观上下文的一部分会记录在创建的文件结构(files_struct)中。这使得使用该文件结构的操作可以使用这些凭证,而不是发出操作的任务的主观上下文。一个例子是在网络文件系统上打开的文件,打开文件的凭证应该被呈现给服务器,而不管实际进行读取或写入操作的是谁。
struct task_struct {
/* Open file information: */
struct files_struct *files;
}
/*
* Open file table structure
*/
struct files_struct {
.....
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
在Linux中,每个打开的文件都有一个与之相关的文件结构(file struct)。文件结构中包含了与该文件相关的信息和属性,包括文件描述符、文件状态标志、打开模式、文件位置指针等。此外,文件结构还会保存打开文件的任务的主观上下文的一部分。
主观上下文包括与任务相关的凭证信息,如用户标识、组标识、安全上下文等。当一个任务打开一个文件时,一部分主观上下文会被记录在该文件的文件结构中。这意味着任何使用该文件结构进行的操作都会使用该文件结构中存储的凭证信息,而不是任务自身的主观上下文。
这种机制在处理网络文件系统中的文件时非常有用。例如,当在网络文件系统上打开一个文件时,无论实际进行读取或写入操作的用户是谁,打开文件的凭证都应该被传递给服务器。这样可以确保在与服务器进行通信时,使用的是正确的凭证信息,而不是当前任务的凭证。
通过在文件结构中保存打开文件的主观上下文的一部分,可以实现这种凭证的传递和使用,使得对文件的操作能够以特定的凭证身份进行,而不受执行操作的任务的凭证影响。
在Linux中,文件结构(file struct)并不直接存储打开文件的凭证信息。相反,它存储了与文件相关联的索引节点结构(inode struct)的引用。
索引节点结构代表Linux文件系统中的一个具体文件,它包含了文件的各种属性和元数据,包括所有权和权限信息。索引节点结构又引用了相应的特定于文件系统的索引节点数据结构。
struct inode {
......
kuid_t i_uid;
kgid_t i_gid;
......
}
当一个文件被打开时,内核会创建文件结构来表示打开的文件。这个文件结构包含一个指针,指向与该文件相关联的索引节点结构。索引节点结构除了文件特定的属性外,还存储了文件的所有权和权限信息。
要访问打开文件的凭证信息,内核会遍历文件结构,获取指向索引节点结构的指针。通过这个指针,内核可以访问索引节点结构中存储的所有权和权限信息。这些所有权和权限属性代表了与文件相关的凭证,例如用户标识符(UID)、组标识符(GID)和安全上下文。
因此,虽然文件结构本身并不直接存储凭证信息,但它间接引用了索引节点结构,后者保存了打开文件的所有权和权限信息。内核可以通过访问文件结构中的索引节点结构来获取凭证信息。
文件标记在实施访问控制、确保数据安全和防止未经授权访问或恶意操作方面起着重要作用。它们允许操作系统将文件的客观安全上下文(由标记表示)与任务的主观安全上下文(执行的进程)进行比较,以确定是否应允许或拒绝请求的操作。这有助于维护系统的完整性,防止特权滥用,并保护敏感数据免受未经授权的访问。
存储在磁盘上或通过网络获取的文件可能具有注释,构成该文件的客观安全上下文。根据文件系统的类型,这些注释可能包括以下一项或多项:
UNIX UID, GID, mode;
Windows user ID;
Access control list;
LSM security label;
UNIX exec privilege escalation bits (SUID/SGID);
File capabilities exec privilege escalation bits.
以下是有关文件标记及其重要性的更多信息:
(1)UNIX的用户标识符(UID)、组标识符(GID)和访问模式:在类UNIX系统中,每个文件都与一个用户标识符(UID)和组标识符(GID)相关联,这些标识符确定文件的所有权。文件模式指定了所有者、组和其他用户(如读取、写入和执行权限)的访问权限。这些标记有助于执行访问控制,并根据所有权和权限限制文件操作。
(2)Windows用户ID:在Windows操作系统中,文件与用户ID关联,该ID标识拥有文件的用户帐户。Windows使用访问控制列表(ACL)来管理文件权限,通过ACL可以对谁可以访问和执行文件进行细粒度控制。
(3)访问控制列表(ACL):ACL是一些文件系统中使用的附加层文件权限。它们通过为个别用户或组指定权限,超出传统的所有者、组和其他用户权限,实现更细粒度的文件访问控制。ACL允许对文件的访问进行更灵活和详细的管理。
(4)LSM安全标签:Linux Security Modules(LSM)为扩展Linux内核的安全功能提供了一个框架。一些LSM,如SELinux和AppArmor,支持附加到文件上的安全标签。这些标签提供了附加的安全属性和策略,用于管理文件的访问和操作,增强系统的整体安全性。
(5)UNIX的可执行文件特权提升位(SUID/SGID):SUID和SGID位是在类UNIX系统中可设置在可执行文件上的特殊权限位。当文件具有SUID或SGID位设置时,执行该文件的进程会临时继承文件所属者或所属组的特权。这使得进程能够执行需要提升特权的操作,即使执行该进程的用户权限较低。
(6)文件特权提升位:文件特权提供了Linux系统中更精细的特权模型。与仅依赖特权提升位不同,可以为可执行文件分配特定的特权。这些特权授予执行文件的进程个别特权,允许更受控和有限的特权提升机制。
这些标记将与任务的主观安全上下文进行比较,并根据比较结果允许或禁止执行某些操作。在execve()函数的情况下,特权提升位起作用,并且可能允许由可执行文件的注释决定的进程获得额外的特权。
在文件操作中,文件标记用于确定对文件的访问权限。通过比较文件的标记与任务的安全上下文,系统可以决定是否允许执行某些操作。例如,文件的所有权标记(UID、GID)和访问模式可以确定哪个用户或组可以读取、写入或执行文件。访问控制列表和LSM安全标签可以提供更细粒度的访问控制,允许特定用户或进程执行特定操作。
在执行可执行文件时,特权提升位起到重要作用。如果可执行文件具有SUID或SGID标记,执行该文件的进程将获得与文件所有者或所属组相关联的特权。这使得进程能够在执行过程中暂时提升权限,执行一些仅限特定用户或组的操作。
文件标记提供了一种机制,用于在文件操作中实现安全性和访问控制。通过比较文件标记和任务的安全上下文,系统可以决定是否允许执行特定的操作,并在需要时提供特权提升功能。这有助于确保文件的安全性,并限制访问和操作的范围,以防止未经授权的访问和潜在的安全风险。
在Linux中,一个任务的所有凭据都保存在一个引用计数结构体(struct cred)中,通过(uid, gid)或(groups, keys, LSM security)进行访问。每个任务在其task_struct中通过一个名为’cred’的指针指向其凭据。
/*
* The security context of a task
*
* The parts of the context break down into two categories:
*
* (1) The objective context of a task. These parts are used when some other
* task is attempting to affect this one.
*
* (2) The subjective context. These details are used when the task is acting
* upon another object, be that a file, a task, a key or whatever.
*
* Note that some members of this structure belong to both categories - the
* LSM security pointer for instance.
*
* A task has two security pointers. task->real_cred points to the objective
* context that defines that task's actual details. The objective part of this
* context is used whenever that task is acted upon.
*
* task->cred points to the subjective context that defines the details of how
* that task is going to act upon another object. This may be overridden
* temporarily to point to another security context, but normally points to the
* same context as task->real_cred.
*/
struct cred {
atomic_t usage;
.....
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;
struct task_struct {
/* Process credentials: */
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
}
一旦一组凭据已经准备好并提交,除非以下几种情况,否则不能更改:
其引用计数可以更改;
它所指向的 group_info 结构体的引用计数可以更改;
它所指向的 security 数据的引用计数可以更改;
它所指向的任何 keyrings 的引用计数可以更改;
它所指向的任何 keyrings 可以被撤销、过期或其安全属性可以更改;
它所指向的任何 keyrings 的内容可以更改(密钥环的整个目的就是作为一组共享凭据,可由具有适当访问权限的任何人修改)。
要更改cred结构体中的任何内容,必须遵循复制和替换的原则。首先进行复制,然后修改副本,最后使用RCU(读-复制-更新)将任务指针更改为指向新的副本。有一些包装函数可用于帮助执行这个过程。
一个任务只能修改自己的凭据;不再允许一个任务修改另一个任务的凭据。这意味着capset()系统调用不再允许使用除当前进程之外的任何PID。此外,keyctl_instantiate()和keyctl_negate()函数也不再允许在请求进程中附加到特定于进程的密钥环,因为实例化进程可能需要创建它们。
这些机制确保了任务凭据的安全管理,并防止未经授权的修改。通过遵循复制和替换原则,并对凭据修改施加限制,Linux内核维护了系统访问控制机制的完整性和安全性。
一旦一组凭据已经被公开(例如通过调用commit_creds()),除了两个例外情况外,必须将其视为不可变的:
引用计数可以被修改。
虽然无法更改一组凭据的 keyring 订阅,但订阅的 keyring 的内容可以被更改。
为了在编译时捕获意外的凭据修改,struct task_struct和struct file具有_const_指针指向其凭据集。此外,某些函数(如get_cred()和put_cred())在const指针上操作,因此不需要进行类型转换,但需要临时放弃const限定,以便能够修改引用计数。
(1)
任务只能修改自己的凭据的限制简化了读取或替换凭据的过程。由于在这种情况下不需要任何形式的锁定,任务可以直接调用相应的函数来修改自己的凭据。
通过遵循任务只能修改自己凭据的原则,大大简化了并发访问和同步管理的复杂性。这种设计选择简化了实现,并确保每个任务对自己的凭据拥有独占控制权,而无需额外的锁定机制。
// linux-5.4.18/include/linux/cred.h
/**
* current_cred - Access the current task's subjective credentials
*
* Access the subjective credentials of the current task. RCU-safe,
* since nobody else can modify it.
*/
#define current_cred() \
rcu_dereference_protected(current->cred, 1)
调用 current_cred() 以获得指向其凭据结构的指针,并且之后不必释放它。
(2)
有一些方便的包装器用于检索任务凭据的特定方面(在每种情况下都只返回值):
// linux-5.4.18/include/linux/cred.h
#define current_cred_xxx(xxx) \
({ \
current_cred()->xxx; \
})
#define current_uid() (current_cred_xxx(uid))
#define current_gid() (current_cred_xxx(gid))
#define current_euid() (current_cred_xxx(euid))
#define current_egid() (current_cred_xxx(egid))
#define current_suid() (current_cred_xxx(suid))
#define current_sgid() (current_cred_xxx(sgid))
#define current_fsuid() (current_cred_xxx(fsuid))
#define current_fsgid() (current_cred_xxx(fsgid))
#define current_cap() (current_cred_xxx(cap_effective))
#define current_user() (current_cred_xxx(user))
(3)
还有一些方便的包装器,用于检索任务凭据的特定关联对:
#define current_uid_gid(_uid, _gid) \
do { \
const struct cred *__cred; \
__cred = current_cred(); \
*(_uid) = __cred->uid; \
*(_gid) = __cred->gid; \
} while(0)
#define current_euid_egid(_euid, _egid) \
do { \
const struct cred *__cred; \
__cred = current_cred(); \
*(_euid) = __cred->euid; \
*(_egid) = __cred->egid; \
} while(0)
#define current_fsuid_fsgid(_fsuid, _fsgid) \
do { \
const struct cred *__cred; \
__cred = current_cred(); \
*(_fsuid) = __cred->fsuid; \
*(_fsgid) = __cred->fsgid; \
} while(0)
其在从当前任务的凭证中检索这些值之后通过它们的参数返回这些值对。
(4)
此外,还有一个功能用于获取当前进程的当前凭据集的引用:
/**
* get_cred - Get a reference on a set of credentials
* @cred: The credentials to reference
*
* Get a reference on the specified set of credentials. The caller must
* release the reference. If %NULL is passed, it is returned with no action.
*
* This is used to deal with a committed set of credentials. Although the
* pointer is const, this will temporarily discard the const and increment the
* usage count. The purpose of this is to attempt to catch at compile time the
* accidental alteration of a set of credentials that should be considered
* immutable.
*/
static inline const struct cred *get_cred(const struct cred *cred)
{
struct cred *nonconst_cred = (struct cred *) cred;
if (!cred)
return cred;
validate_creds(cred);
nonconst_cred->non_rcu = 0;
return get_new_cred(nonconst_cred);
}
/**
* get_current_cred - Get the current task's subjective credentials
*
* Get the subjective credentials of the current task, pinning them so that
* they can't go away. Accessing the current task's credentials directly is
* not permitted.
*/
#define get_current_cred() \
(get_cred(current_cred()))
以及用于获取对一个实际上不存在于struct-cred中的凭据的引用的函数:
/**
* get_current_user - Get the current task's user_struct
*
* Get the user record of the current task, pinning it so that it can't go
* away.
*/
#define get_current_user() \
({ \
struct user_struct *__u; \
const struct cred *__cred; \
__cred = current_cred(); \
__u = get_uid(__cred->user); \
__u; \
})
/**
* get_current_groups - Get the current task's supplementary group list
*
* Get the supplementary group list of the current task, pinning it so that it
* can't go away.
*/
#define get_current_groups() \
({ \
struct group_info *__groups; \
const struct cred *__cred; \
__cred = current_cred(); \
__groups = get_group_info(__cred->group_info); \
__groups; \
})
分别获得对当前进程的 user accounting structure 和补充组列表的引用。
一旦获得引用,就必须根据需要使用put_cred()、free_uid()或put_group_info()来释放它。
虽然一个任务可以在不需要锁定的情况下访问自己的凭据,但想要访问另一个任务的凭据的任务则不然。它必须使用RCU读锁和RCU_reference()。
当一个任务想要访问另一个任务的凭据时,它必须使用RCU(Read-Copy-Update)读锁和rcu_dereference()函数。
rcu_dereference()函数被封装在以下函数中:
/**
* __task_cred - Access a task's objective credentials
* @task: The task to query
*
* Access the objective credentials of a task. The caller must hold the RCU
* readlock.
*
* The result of this function should not be passed directly to get_cred();
* rather get_task_cred() should be used instead.
*/
#define __task_cred(task) \
rcu_dereference((task)->real_cred)
这个函数应该在RCU读锁的内部使用,就像下面的例子一样:
void foo(struct task_struct *t, struct foo_data *f)
{
const struct cred *tcred;
...
rcu_read_lock();
tcred = __task_cred(t);
f->uid = tcred->uid;
f->gid = tcred->gid;
f->groups = get_group_info(tcred->groups);
rcu_read_unlock();
...
}
如果需要长时间持有另一个任务的凭据,并且可能在此过程中休眠,则调用方应该使用以下函数来获取对这些凭据的引用:
// linux-5.4.18/kernel/cred.c
/**
* get_task_cred - Get another task's objective credentials
* @task: The task to query
*
* Get the objective credentials of a task, pinning them so that they can't go
* away. Accessing a task's credentials directly is not permitted.
*
* The caller must also make sure task doesn't get deleted, either by holding a
* ref on task or by holding tasklist_lock to prevent it from being unlinked.
*/
const struct cred *get_task_cred(struct task_struct *task)
{
const struct cred *cred;
rcu_read_lock();
do {
cred = __task_cred((task));
BUG_ON(!cred);
} while (!get_cred_rcu(cred));
rcu_read_unlock();
return cred;
}
EXPORT_SYMBOL(get_task_cred);
这个函数内部完成了所有的RCU操作。当使用完这些凭据时,调用方必须调用put_cred()函数释放它们。
注意:
__task_cred()的结果不应直接传递给get_cred(),因为这可能与commit_cred()发生竞争条件。
还有一些方便的函数可以访问另一个任务凭据的特定部分,将RCU操作对调用方隐藏起来:
#define task_cred_xxx(task, xxx) \
({ \
__typeof__(((struct cred *)NULL)->xxx) ___val; \
rcu_read_lock(); \
___val = __task_cred((task))->xxx; \
rcu_read_unlock(); \
___val; \
})
#define task_uid(task) (task_cred_xxx((task), uid)) // Task's real UID
#define task_euid(task) (task_cred_xxx((task), euid)) // Task's effective UID
如果调用方在此时已经持有RCU读锁,则应改为使用:
__task_cred(task)->uid
__task_cred(task)->euid
类似地,如果需要访问任务凭据的多个方面,应使用RCU读锁,调用__task_cred()函数,将结果存储在临时指针中,然后从临时指针中调用凭据的各个方面,最后释放锁。这样可以防止多次调用昂贵的RCU操作。
如果需要访问另一个任务凭据的其他单个方面,可以使用以下形式:
#define task_cred_xxx(task, xxx) \
({ \
__typeof__(((struct cred *)NULL)->xxx) ___val; \
rcu_read_lock(); \
___val = __task_cred((task))->xxx; \
rcu_read_unlock(); \
___val; \
})
task_cred_xxx(task, member)
这里的’member’是cred结构体的非指针成员。例如:
uid_t task_cred_xxx(task, suid);
将从任务中检索’struct cred::suid’,并执行适当的RCU操作。对于指针成员,不能使用这种形式,因为它们指向的内容可能在释放RCU读锁的瞬间消失。
正如先前提到的,一个任务只能修改自己的凭据,不能修改其他任务的凭据。这意味着它不需要使用任何锁来修改自己的凭据。
要修改当前进程的凭据,函数应首先调用:
// linux-5.4.18/kernel/cred.c
//struct cred 结构体 slub对象
static struct kmem_cache *cred_jar;
/**
* prepare_creds - Prepare a new set of credentials for modification
*
* Prepare a new set of task credentials for modification. A task's creds
* shouldn't generally be modified directly, therefore this function is used to
* prepare a new copy, which the caller then modifies and then commits by
* calling commit_creds().
*
* Preparation involves making a copy of the objective creds for modification.
*
* Returns a pointer to the new creds-to-be if successful, NULL otherwise.
*
* Call commit_creds() or abort_creds() to clean up.
*/
struct cred *prepare_creds(void)
{
struct task_struct *task = current;
const struct cred *old;
struct cred *new;
validate_process_creds();
//使用 slub 分配器分配 struct cred 结构体 结构体对象
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;
kdebug("prepare_creds() alloc %p", new);
old = task->cred;
memcpy(new, old, sizeof(struct cred));
new->non_rcu = 0;
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_group_info(new->group_info);
get_uid(new->user);
get_user_ns(new->user_ns);
#ifdef CONFIG_KEYS
key_get(new->session_keyring);
key_get(new->process_keyring);
key_get(new->thread_keyring);
key_get(new->request_key_auth);
#endif
#ifdef CONFIG_SECURITY
new->security = NULL;
#endif
if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
goto error;
validate_creds(new);
return new;
error:
abort_creds(new);
return NULL;
}
EXPORT_SYMBOL(prepare_creds);
struct cred *prepare_creds(void);
该函数会锁定current->cred_replace_mutex,然后分配并构建当前进程凭据的副本。如果成功,函数返回时仍然保持互斥锁。如果不成功(内存不足),则返回NULL。
互斥锁防止ptrace()在进行凭据构建和更改的安全检查时更改进程的ptrace状态,因为ptrace状态可能会改变结果,特别是在execve()的情况下。
新的凭据集应适当地进行修改,并进行任何安全检查和钩子。在此时,当前和提议的凭据集都可用,因为current_cred()将返回当前的凭据集。
在替换组列表时,必须在将其添加到凭据之前对新列表进行排序,因为二进制搜索用于测试成员资格。实际上,这意味着在set_groups()或set_current_groups()之前应调用groups_sort()。如果struct group_list是共享的,则不能在其上调用groups_sort(),因为即使数组已经排序,它也可能在排序过程中对元素进行排列。
当凭据集准备好时,应通过调用以下函数将其提交给当前进程:
/**
* commit_creds - Install new credentials upon the current task
* @new: The credentials to be assigned
*
* Install a new set of credentials to the current task, using RCU to replace
* the old set. Both the objective and the subjective credentials pointers are
* updated. This function may not be called if the subjective credentials are
* in an overridden state.
*
* This function eats the caller's reference to the new credentials.
*
* Always returns 0 thus allowing this function to be tail-called at the end
* of, say, sys_setgid().
*/
int commit_creds(struct cred *new)
{
struct task_struct *task = current;
const struct cred *old = task->real_cred;
kdebug("commit_creds(%p{%d,%d})", new,
atomic_read(&new->usage),
read_cred_subscribers(new));
BUG_ON(task->cred != old);
#ifdef CONFIG_DEBUG_CREDENTIALS
BUG_ON(read_cred_subscribers(old) < 2);
validate_creds(old);
validate_creds(new);
#endif
BUG_ON(atomic_read(&new->usage) < 1);
get_cred(new); /* we will require a ref for the subj creds too */
/* dumpability changes */
if (!uid_eq(old->euid, new->euid) ||
!gid_eq(old->egid, new->egid) ||
!uid_eq(old->fsuid, new->fsuid) ||
!gid_eq(old->fsgid, new->fsgid) ||
!cred_cap_issubset(old, new)) {
if (task->mm)
set_dumpable(task->mm, suid_dumpable);
task->pdeath_signal = 0;
/*
* If a task drops privileges and becomes nondumpable,
* the dumpability change must become visible before
* the credential change; otherwise, a __ptrace_may_access()
* racing with this change may be able to attach to a task it
* shouldn't be able to attach to (as if the task had dropped
* privileges without becoming nondumpable).
* Pairs with a read barrier in __ptrace_may_access().
*/
smp_wmb();
}
/* alter the thread keyring */
if (!uid_eq(new->fsuid, old->fsuid))
key_fsuid_changed(new);
if (!gid_eq(new->fsgid, old->fsgid))
key_fsgid_changed(new);
/* do it
* RLIMIT_NPROC limits on user->processes have already been checked
* in set_user().
*/
alter_cred_subscribers(new, 2);
if (new->user != old->user)
atomic_inc(&new->user->processes);
rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);
if (new->user != old->user)
atomic_dec(&old->user->processes);
alter_cred_subscribers(old, -2);
/* send notifications */
if (!uid_eq(new->uid, old->uid) ||
!uid_eq(new->euid, old->euid) ||
!uid_eq(new->suid, old->suid) ||
!uid_eq(new->fsuid, old->fsuid))
proc_id_connector(task, PROC_EVENT_UID);
if (!gid_eq(new->gid, old->gid) ||
!gid_eq(new->egid, old->egid) ||
!gid_eq(new->sgid, old->sgid) ||
!gid_eq(new->fsgid, old->fsgid))
proc_id_connector(task, PROC_EVENT_GID);
/* release the old obj and subj refs both */
put_cred(old);
put_cred(old);
return 0;
}
EXPORT_SYMBOL(commit_creds);
int commit_creds(struct cred *new);
这将修改凭据和进程的各个方面,给LSM(Linux Security Modules)提供机会做同样的修改,然后使用rcu_assign_pointer()将新的凭据实际提交给current->cred,释放current->cred_replace_mutex以允许ptrace()进行操作,并通知调度程序和其他组件有关更改的情况。
该函数保证返回0,以便可以在诸如sys_setresuid()之类的函数的末尾进行尾调用。
请注意,该函数会消耗调用者对新凭据的引用。调用者在此之后不应调用put_cred()释放新凭据。
此外,一旦调用了该函数来处理新的凭据集,就不能进一步更改这些凭据。
如果在调用prepare_creds()之后安全检查失败或发生其他错误,则应调用以下函数:
void abort_creds(struct cred *new);
该函数释放prepare_creds()获取的current->cred_replace_mutex的锁,并释放新的凭据。
一个典型的凭据修改函数可能如下所示:
int alter_suid(uid_t suid)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
new->suid = suid;
ret = security_alter_suid(new);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
有一些函数用来辅助凭证管理:
(1)
/**
* put_cred - Release a reference to a set of credentials
* @cred: The credentials to release
*
* Release a reference to a set of credentials, deleting them when the last ref
* is released. If %NULL is passed, nothing is done.
*
* This takes a const pointer to a set of credentials because the credentials
* on task_struct are attached by const pointers to prevent accidental
* alteration of otherwise immutable credential sets.
*/
static inline void put_cred(const struct cred *_cred)
{
struct cred *cred = (struct cred *) _cred;
if (cred) {
validate_creds(cred);
if (atomic_dec_and_test(&(cred)->usage))
__put_cred(cred);
}
}
该函数释放对给定凭据集的引用。当引用计数达到零时,凭据集将由RCU(Read-Copy-Update)系统安排进行销毁。释放对凭据的引用非常重要,以避免内存泄漏。
(2)
/**
* get_cred - Get a reference on a set of credentials
* @cred: The credentials to reference
*
* Get a reference on the specified set of credentials. The caller must
* release the reference. If %NULL is passed, it is returned with no action.
*
* This is used to deal with a committed set of credentials. Although the
* pointer is const, this will temporarily discard the const and increment the
* usage count. The purpose of this is to attempt to catch at compile time the
* accidental alteration of a set of credentials that should be considered
* immutable.
*/
static inline const struct cred *get_cred(const struct cred *cred)
{
struct cred *nonconst_cred = (struct cred *) cred;
if (!cred)
return cred;
validate_creds(cred);
nonconst_cred->non_rcu = 0;
return get_new_cred(nonconst_cred);
}
该函数获取对活动凭据集的引用。它返回对凭据集的指针,允许您访问和使用凭据。通过获取引用,确保在使用期间凭据保持有效,并且不会在使用过程中被销毁。
(3)
/**
* get_new_cred - Get a reference on a new set of credentials
* @cred: The new credentials to reference
*
* Get a reference on the specified set of new credentials. The caller must
* release the reference.
*/
static inline struct cred *get_new_cred(struct cred *cred)
{
atomic_inc(&cred->usage);
return cred;
}
该函数获取对当前正在构建且可变的凭据集的引用。它返回对凭据集的指针,允许在构建阶段修改凭据。通常在创建或修改凭据并在其提交之前使用此函数。
当打开一个新文件时,会将打开文件的进程的凭据与文件结构相关联。这样做是为了跟踪执行文件操作的进程的身份和特权。不再直接在文件结构中存储用户ID(UID)和组ID(GID),而是使用f_cred字段来存储对进程凭据的引用。
要访问打开文件关联的UID和GID,现在应该访问file->f_cred->fsuid获取UID,访问file->f_cred->fsgid获取GID。这些字段表示打开文件的进程的有效UID和GID。
struct file {
......
const struct cred *f_cred;
.....
}
需要注意的是,访问f_cred不需要使用RCU(Read-Copy-Update)或锁定机制。这是因为指向凭据的指针以及指向的凭据结构的内容在文件结构的整个生命周期中保持不变。然而,在涉及任务凭据的某些操作中存在例外情况。
为了防止潜在的“被误导的代理人”攻击,对已打开文件进行后续访问控制检查时,应使用与文件关联的凭据(file->f_cred),而不是依赖于“当前”进程的凭据(current->cred)。这是必要的,因为文件可能已被传递给一个权限更高的进程,使用文件的凭据确保访问控制决策基于最初打开文件的进程的特权。
通过使用文件的凭据,在对已打开的文件执行操作时,可以维护适当的授权和安全性,考虑到原始打开进程的上下文,而不是当前执行后续操作的进程。
在某些情况下,希望覆盖VFS使用的凭据,可以通过调用如vfs_mkdir()等函数,并提供不同的凭据来实现。以下是一些进行此操作的位置:
(1)sys_faccessat(): 在sys_faccessat()系统调用中,可以通过提供不同的凭据来覆盖VFS使用的凭据。该系统调用用于检查文件的访问权限。
(2)do_coredump(): 在do_coredump()函数中,可以使用不同的凭据来覆盖VFS使用的凭据。该函数用于生成核心转储文件。
(3)nfs4recover.c: 在nfs4recover.c文件中,可以通过使用不同的凭据来覆盖VFS使用的凭据。该文件涉及NFSv4恢复相关的操作。
通过在这些位置调用相应的函数,并传递不同的凭据,可以重写VFS使用的凭据,以适应特定的需求或场景。这样可以实现对访问权限和核心转储等操作所使用的凭据进行定制。
https://static.lwn.net/kerneldoc/security/credentials.html