Solaris 开发者安全性指南

第 2 章 开发特权应用程序

本章介绍如何开发特权应用程序。本章包含以下主题:

特权应用程序

特权应用程序是可以覆盖系统控制并检查特定用户 ID (user ID, UID)、组 ID (group ID, GID)、授权或权限的应用程序。这些访问控制元素由系统管理员指定。有关管理员如何使用这些访问控制元素的一般讨论,请参见《系统管理指南:安全性服务》中的第 8  章 “使用角色和权限(概述)”

Solaris 操作系统为开发者提供了两个用于更为精细地授予权限的元素:

授权与权限之间的差异与定义谁可以执行哪种强制操作的策略的级别有关。权限是在内核级别上强制实施的。没有正确的权限,进程就无法在特权应用程序中执行特定的操作。授权是在用户应用程序级别强制执行策略的。访问特权应用程序或执行特权应用程序中的特定操作时可能需要授权。

关于权限

权限是授予进程执行 Solaris 操作系统禁止执行的操作的独立权利。大多数程序不使用权限,因为程序通常在系统安全策略界限内运行。

权限是由管理员指定的。权限是根据程序的设计来启用的。登录或进入配置文件 shell 时,管理员的权限指定将应用于任何在 shell 中执行的命令。运行应用程序时,将通过编程方式启用或禁用权限。如果使用 exec(1) 命令启动新程序,则该程序可能会使用父进程的所有可继承权限。但是,该程序不能添加任何新权限。

管理员如何指定权限

系统管理员负责为命令指定权限。有关权限指定的更多信息,请参见《系统管理指南:安全性服务》中的“权限(概述)”

如何实现权限

每个进程都有四个权限集,用于确定进程是否可以使用特定权限:

允许权限集

允许权限集中必须包含进程可能使用的所有权限。相反,应该从相应程序的允许权限集中排除任何永远不会使用的权限。

启动进程时,该进程将从父进程继承允许权限集。通常,在登录时或进入新的配置文件 shell 时,在初始的允许权限集中包含所有权限。此集中的权限由管理员指定。每个子进程都可以从允许集中删除权限,但不能向允许集中添加其他权限。作为安全预防措施,应该从允许集中删除程序从不使用的那些权限。这样,便可避免程序使用错误分配或继承的权限。

系统将从有效集中自动删除从允许集中删除的权限。

可继承权限集

在登录时或在新的配置文件 shell 中,可继承集包含管理员指定的权限。调用 exec(1) 后,可以将这些可继承权限传递给子进程。进程应该删除任何不必要的权限以防止这些权限传递给子进程。通常,允许集和可继承集是相同的。但是,可以从可继承集删除某个权限,而允许集中仍保留该权限。

限制权限集

使用限制集,开发者可以控制进程可以使用或传递给子进程的权限。子进程和后续进程只能获取限制集中的权限。执行 setuid(0) 函数时,限制集将确定允许应用程序使用的权限。限制集是在执行 exec(1) 时强制实施的。执行 exec(1) 之前,从限制集中删除权限不会影响任何其他集。

有效权限集

进程可实际使用的权限包含在该进程的有效集中。启动程序时,有效集等于允许集。之后,有效集将成为允许集的子集,或等于允许集。

建议将有效集缩小至基本权限集。权限类别中介绍了基本权限集(包含核心权限)。请将程序中不需要的所有权限全部删除。禁用任何基本权限,直到需要该权限为止。例如,使用 file_dac_read 权限可以读取所有文件。程序可以具有多个读取文件的例程。对于适当的读取例程,程序最初会禁用所有权限,然后启用 file_dac_read。这样,开发者可以确保程序不会针对错误的读取例程使用 file_dac_read 权限。此做法称为权限包括权限编码示例中说明了权限包括。

超级用户模型与权限模型之间的兼容性

为了适应传统应用程序,权限的实现使用超级用户模型和权限模型。此适应通过使用 PRIV_AWARE 标志(指示程序使用权限)得以实现。PRIV_AWARE 标志由操作系统自动处理。

请考虑不能识别权限的子进程。此类进程的 PRIV_AWARE 标志为 false。从父进程继承的所有权限均可在允许集和有效集中找到。如果子进程将 UID 设置为 0,则进程的有效集和允许集将仅限于限制集中的那些权限。子进程不会获取全部的超级用户权限。这样,可识别权限进程的限制集会限制不能识别权限的子进程的超级用户权限。如果子进程可以修改任何权限集,则可以将 PRIV_AWARE 标志设置为 true。

权限类别

可以按照以下方式根据权限范围对权限进行逻辑分组:

有关 Solaris 权限的完整列表以及说明,请参见 privileges(5) 手册页。


注 –

Solaris 提供区域功能,通过此功能,管理员可以为运行的应用程序设置隔离环境。请参见 zones(5)。由于一个区域中的进程无法监视或干扰该区域外系统中的其他活动,因此该进程的所有权限也限于该区域。但是,如果需要,可以将 PRIV_PROC_ZONE 权限应用于全局区域中需要权限才能在非全局区域中操作的进程。


使用权限进行编程

本节讨论使用权限的接口。要使用权限编程接口,需要以下头文件。

#include <priv.h>

本节还提供了说明如何在特权应用程序中使用权限接口的示例。

权限数据类型

以下是权限接口使用的主要数据类型:

权限接口

下表列出了使用权限的接口。表后面提供了一些主要权限接口的说明。

表 2–1 使用权限的接口

目的 

函数 

其他注释 

获取和设置权限集 

setppriv(2)getppriv(2)priv_set(3C)priv_ineffect(3C)

setppriv()getppriv() 是系统调用。priv_ineffect()priv_set() 是为方便而使用的包装函数。

识别和转换权限 

priv_str_to_set(3C)priv_set_to_str(3C)priv_getbyname(3C)priv_getbynum(3C)priv_getsetbyname(3C)priv_getsetbynum(3C)

这些函数将指定的权限或权限集映射到名称或编号。  

处理权限集 

priv_allocset(3C)priv_freeset(3C)priv_emptyset(3C)priv_fillset(3C)priv_isemptyset(3C)priv_isfullset(3C)priv_isequalset(3C)priv_issubset(3C)priv_intersect(3C)priv_union(3C)priv_inverse(3C)priv_addset(3C)priv_copyset(3C)priv_delset(3C)priv_ismember(3C)

这些函数与权限内存分配、测试和各种设置操作有关。 

获取和设置进程标志 

getpflags(2)setpflags(2)

PRIV_AWARE 进程标志指示进程是否了解权限或是否在超级用户模型下运行。PRIV_DEBUG 用于权限调试。 

低级凭证处理 

ucred_get(3C)

这些例程用于调试、底层系统调用和内核调用。 

setppriv():用于设置权限

用于设置权限的主要函数为 setppriv(),该函数具有以下语法:

int setppriv(priv_op_t op, priv_ptype_t which, \

const priv_set_t *set);

op 表示要执行的权限操作。op 参数具有以下三个可能值之一:

which 用于指定要更改的权限集类型:

set 指定要在更改操作中使用的权限。

此外,还提供了便利函数:priv_set()

用于映射权限的 priv_str_to_set()

这些函数便于使用其数值映射权限名称。priv_str_to_set() 是此系列中的典型函数。priv_str_to_set() 具有以下语法:

priv_set_t *priv_str_to_set(const char *buf, const char *set, \

const char **endptr);

priv_str_to_set() 采用 buf 中指定的权限名字符串。priv_str_to_set() 返回可以与四个权限集之一组合的一组权限值。**endptr 可用于调试解析错误。请注意,可以在 buf 中包括以下关键字:

权限编码示例

本节对使用超级用户模型和最低权限模型包括权限的方式进行比较。

包括在超级用户模型中的权限

以下示例说明如何在超级用户模型中包括特权操作。


示例 2–1 超级用户权限包括示例

/* Program start */

uid = getuid();

seteuid(uid);



/* Privilege bracketing */

seteuid(0);

/* Code requiring superuser capability */

...

/* End of code requiring superuser capability */

seteuid(uid);

...

/* Give up superuser ability permanently */

setreuid(uid,uid);

包括在最低权限模型中的权限

此示例说明如何在最低权限模型中包括特权操作。此示例使用以下假定:

代码后面是该示例的说明。


注 –

此示例的源代码也可以通过 Sun 下载中心获得。请访问 http://www.sun.com/download/products.xml?id=41912db5



示例 2–2 最低权限包括示例

1  #include <priv.h>

2  /* Always use the basic set. The Basic set might grow in future

3   * releases and potentially retrict actions that are currently

4   * unrestricted */

5  priv_set_t *temp = priv_str_to_set("basic", ",", NULL);



6  /* PRIV_FILE_DAC_READ is needed in this example */

7  (void) priv_addset(temp, PRIV_FILE_DAC_READ);



8  /* PRIV_PROC_EXEC is no longer needed after program starts */

9  (void) priv_delset(temp, PRIV_PROC_EXEC);



10 /* Compute the set of privileges that are never needed */

11  priv_inverse(temp);



12  /* Remove the set of unneeded privs from Permitted (and by

13   * implication from Effective) */

14  (void) setppriv(PRIV_OFF, PRIV_PERMITTED, temp);



15  /* Remove unneeded priv set from Limit to be safe */

16  (void) setppriv(PRIV_OFF, PRIV_LIMIT, temp);



17  /* Done with temp */

18  priv_freeset(temp);



19  /* Now get rid of the euid that brought us extra privs */

20  (void) seteuid(getuid());



21  /* Toggle PRIV_FILE_DAC_READ off while it is unneeded */

22  priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL);



23  /* Toggle PRIV_FILE_DAC_READ on when special privilege is needed*/

24  priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL);



25  fd = open("/some/retricted/file", O_RDONLY);



26  /* Toggle PRIV_FILE_DAC_READ off after it has been used */

27  priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL);



28  /* Remove PRIV_FILE_DAC_READ when it is no longer needed */

29  priv_set(PRIV_OFF, PRIV_ALLSETS, PRIV_FILE_DAC_READ, NULL);

该程序定义了名为 temp 的变量。temp 变量确定此程序不需要的权限集。最初在第 5 行,将 temp 定义为包含基本权限集。在第 7 行,将 file_dac_read 权限添加到 temp 中。proc_exec 权限对 exec(1) 新进程(在此程序中不允许)是必需的。因此,在第 9 行中从 temp 中删除了 proc_exec,从而使 exec(1) 命令无法执行新进程。

此时,temp 仅包含该程序所需的那些权限,即基本集加上 file_dac_read,再删除 proc_exec。在第 11 行中,priv_inverse() 函数将计算 temp 的逆向值,并将 temp 的值重置为补值。逆向值是从所有可能权限集中删除指定集(在本例中为 temp)所得的结果。作为第 11 行的结果,temp 现在包含该程序永不使用的那些权限。在第 14 行中,从允许集中删除了 temp 定义的不需要的权限。此删除操作还从有效集中有效地删除了这些权限。在第 16 行中,从限制集中删除了不需要的权限。在第 18 行中,因为不再需要 temp,因而释放了 temp 变量。

该程序可以识别权限。因此,该程序不使用 setuid,但可以将有效的 UID 重置为第 20 行中的用户的实际 UID。

在第 22 行中,通过从有效集中删除 file_dac_read 权限禁用了该权限。在实际的程序中,需要 file_dac_read 之前,还将发生其他活动。在该样例程序中,读取第 25 行中的文件需要 file_dac_read。因此,在第 24 行中,启用了 file_dac_read。读取文件后,将再次从有效集中立即删除 file_dac_read。读取所有文件后,通过在所有权限集中禁用 file_dac_read,可永久地删除 file_dac_read

下表说明了随着程序的运行如何转换权限集。已指出了行号。

表 2–2 权限集转换

步骤 

temp

允许权限集 

有效权限集 

限制权限集 

最初 

— 

所有 

所有 

所有 

第 5 行-将 temp 设置为基本权限

基本 

所有 

所有 

所有 

第 7 行-将 file_dac_read 添加到 temp 中。

基本 + file_dac_read

所有 

所有 

所有  

第 9 行-从 temp 中删除了 proc_exec

基本 + file_dac_read - proc_exec

所有 

所有 

所有  

第 11 行-将 temp 重置为逆向值。

所有 -(基本 + file_dac_read - proc_exec

所有 

所有 

所有  

第 14 行-在允许集中禁用不需要的权限。 

所有 -(基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

所有  

第 16 行-在限制集中禁用不需要的权限。 

所有 -(基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

第 18 行-释放了 temp 文件。

— 

基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

第 22 行-禁用 file_dac_read 直到需要时再启用。

— 

基本 - proc_exec

基本 - proc_exec

基本 + file_dac_read - proc_exec

第 24 行-需要时启用 file_dac_read

— 

基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

基本 + file_dac_read - proc_exec

第 27 行-执行 read() 操作后禁用 file_dac_read

— 

基本 - proc_exec

基本 - proc_exec

基本 + file_dac_read - proc_exec

第 29 行-不再需要 file_dac_read 时,从所有集中删除该权限。

— 

基本 - proc_exec

基本 - proc_exec

基本 - proc_exec

特权应用程序开发指南

本节为开发特权应用程序提供了以下建议:

关于授权

授权存储在 /etc/security/auth_attr 文件中。要创建使用授权的应用程序,请执行以下步骤:

  1. 扫描 /etc/security/auth_attr 以查找一个或多个应用程序授权。

  2. 请在程序开始时使用 chkauthattr(3SECDB) 函数检查所需的授权。chkauthattr() 函数将在以下位置按顺序搜索授权:

    • policy.conf(4) 数据库中的 AUTHS_GRANTED 键-AUTHS_GRANTED 指示缺省情况下指定的授权。

    • policy.conf(4) 数据库中的 PROFS_GRANTED 键-PROFS_GRANTED 指示缺省情况下指定的权限配置文件。chkauthattr() 将针对指定授权检查这些权限配置文件。

    • user_attr(4) 数据库-此数据库存储为用户指定的安全属性。

    • prof_attr(4) 数据库-此数据库存储为用户指定的权限配置文件。

    如果 chkauthattr() 在上述任何位置中都找不到权限授权,则将拒绝用户访问该程序。

  3. 使管理员了解此应用程序需要哪些授权。您可以通过手册页或其他文档通知管理员。


示例 2–3 检查授权

以下代码段说明如何使用 chkauthattr() 函数检查用户的授权。在本例中,该程序将检查 solaris.job.admin 授权。如果用户具有此授权,则该用户可以读取或写入其他用户的文件。如果没有此授权,则用户只能对其拥有的文件执行操作。


/* Define override privileges */

priv_set_t *override_privs = priv_allocset();



/* Clear privilege set before adding privileges. */

priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ,

			priv_FILE_DAC_WRITE, NULL);



priv_addset(override_privs, PRIV_FILE_DAC_READ);

priv_addset(override_privs, PRIV_FILE_DAC_WRITE);



if (!chkauthattr("solaris.jobs.admin", username)) {

    /* turn off privileges */

    setppriv(PRIV_OFF, PRIV_EFFECTIVE, override_privs);

}

/* Authorized users continue to run with privileges */

/* Other users can read or write to their own files only */