diff --git a/zh_CN.GB2312/books/arch-handbook/jail/chapter.sgml b/zh_CN.GB2312/books/arch-handbook/jail/chapter.sgml index 5459227b82..3500df412b 100644 --- a/zh_CN.GB2312/books/arch-handbook/jail/chapter.sgml +++ b/zh_CN.GB2312/books/arch-handbook/jail/chapter.sgml @@ -1,643 +1,643 @@ Evan Sarmiento
evms@cs.bu.edu
2001 Evan Sarmiento &author.cn.intron; &cnproj.translated.by;
Jail子系统 security(安全) Jail(囚禁) root(根用户,管理员用户) 在大多数&unix;系统中,用户root是万能的。这也就增加了许多危险。 如果一个攻击者获得了一个系统中的root,就可以在他的指尖掌握系统中所有的功能。 在FreeBSD里,有一些sysctl项削弱了root的权限, 这样就可以将攻击者造成的损害减小到最低限度。这些安全功能中,有一种叫安全级别。 另一种在FreeBSD 4.0及以后版本中提供的安全功能,就是&man.jail.8;。 Jail将一个运行环境的文件树根切换到某一特定位置, 并且对这样环境中叉分生成的进程做出限制。例如, 一个被监禁的进程不能影响这个jail之外的进程、不能使用一些特定的系统调用, 也就不能对主计算机造成破坏。译者注 英文单词“jail”的中文意思是“囚禁、监禁”。 Jail已经成为一种新型的安全模型。 人们可以在jail中运行各种可能很脆弱的服务器程序,如ApacheBINDsendmail。 这样一来,即使有攻击者取得了jail中的root, 这最多让人们皱皱眉头,而不会使人们惊慌失措。 本文主要关注jail的内部原理(源代码)。 如果你正在寻找设置Jail的指南性文档, 我建议你阅读我的另一篇文章,发表在Sys Admin Magazine, May 2001, 《Securing FreeBSD using Jail》。 Jail的系统结构 Jail由两部分组成:用户级程序, 也就是&man.jail.8;;还有在内核中Jail的实现代码:&man.jail.2; 系统调用和相关的约束。我将讨论用户级程序和jail在内核中的实现原理。 用户级代码 Jail(囚禁) userland program(用户级程序) Jail的用户级源代码在/usr/src/usr.sbin/jail, 由一个文件jail.c组成。这个程序有这些参数:jail的路径, 主机名,IP地址,还有需要执行的命令。 数据结构 jail.c中,我将最先注解的是一个重要结构体 struct jail j;的声明,这个结构类型的声明包含在 /usr/include/sys/jail.h之中。 jail结构的定义是: /usr/include/sys/jail.h: struct jail { u_int32_t version; char *path; char *hostname; u_int32_t ip_number; }; 正如你所见,传送给命令&man.jail.8;的每个参数都在这里有对应的一项。 事实上,当命令&man.jail.8;被执行时,这些参数才由命令行真正传入: /usr/src/usr.sbin/jail.c char path[PATH_MAX]; ... if(realpath(argv[0], path) == NULL) err(1, "realpath: %s", argv[0]); if (chdir(path) != 0) err(1, "chdir: %s", path); memset(&j, 0, sizeof(j)); j.version = 0; j.path = path; j.hostname = argv[1]; 网络 传给&man.jail.8;的参数中有一个是IP地址。这是在网络上访问jail时的地址。 &man.jail.8;将IP地址翻译成网络字节顺序,并存入j(jail类型的结构体)。 /usr/src/usr.sbin/jail/jail.c: struct in_addr in; ... if (inet_aton(argv[2], &in) == 0) errx(1, "Could not make sense of ip-number: %s", argv[2]); j.ip_number = ntohl(in.s_addr); 函数&man.inet.aton.3;“将指定的字符串解释为一个Internet地址, 并将其转存到指定的结构体中”。&man.inet.aton.3;设定了结构体in, 之后in中的内容再用&man.ntohl.3;转换成主机字节顺序, 并置入jail结构体的ip_number成员。 囚禁进程 最后,用户级程序囚禁进程。现在Jail自身变成了一个被囚禁的进程, 并使用&man.execv.3;执行用户指定的命令。 /usr/src/usr.sbin/jail/jail.c i = jail(&j); ... if (execv(argv[3], argv + 3) != 0) err(1, "execv: %s", argv[3]); 正如你所见,函数jail()被调用,参数是结构体jail中被填入数据项, 而如前所述,这些数据项又来自&man.jail.8;的命令行参数。 最后,执行了用户指定的命令。下面我将开始讨论jail在内核中的实现。 相关的内核源代码 Jail(囚禁) kernel architecture(内核架构) 现在我们来看文件/usr/src/sys/kern/kern_jail.c。 在这里定义了&man.jail.2;的系统调用、相关的sysctl项,还有网络函数。 sysctl项 sysctl(系统控制项) kern_jail.c里定义了如下sysctl项: /usr/src/sys/kern/kern_jail.c: int jail_set_hostname_allowed = 1; SYSCTL_INT(_security_jail, OID_AUTO, set_hostname_allowed, CTLFLAG_RW, &jail_set_hostname_allowed, 0, "Processes in jail can set their hostnames"); /* Jail中的进程可设定自身的主机名 */ int jail_socket_unixiproute_only = 1; SYSCTL_INT(_security_jail, OID_AUTO, socket_unixiproute_only, CTLFLAG_RW, &jail_socket_unixiproute_only, 0, "Processes in jail are limited to creating UNIX/IPv4/route sockets only"); /* Jail中的进程被限制只能建立UNIX套接字、IPv4套接字、路由套接字 */ int jail_sysvipc_allowed = 0; SYSCTL_INT(_security_jail, OID_AUTO, sysvipc_allowed, CTLFLAG_RW, &jail_sysvipc_allowed, 0, "Processes in jail can use System V IPC primitives"); /* Jail中的进程可以使用System V进程间通讯原语 */ static int jail_enforce_statfs = 2; SYSCTL_INT(_security_jail, OID_AUTO, enforce_statfs, CTLFLAG_RW, &jail_enforce_statfs, 0, "Processes in jail cannot see all mounted file systems"); /* jail 中的进程查看系统中挂接的文件系统时受到何种限制 */ int jail_allow_raw_sockets = 0; SYSCTL_INT(_security_jail, OID_AUTO, allow_raw_sockets, CTLFLAG_RW, &jail_allow_raw_sockets, 0, "Prison root can create raw sockets"); /* jail 中的 root 用户是否可以创建 raw socket */ int jail_chflags_allowed = 0; SYSCTL_INT(_security_jail, OID_AUTO, chflags_allowed, CTLFLAG_RW, &jail_chflags_allowed, 0, "Processes in jail can alter system file flags"); /* jail 中的进程是否可以修改系统级文件标记 */ int jail_mount_allowed = 0; SYSCTL_INT(_security_jail, OID_AUTO, mount_allowed, CTLFLAG_RW, &jail_mount_allowed, 0, "Processes in jail can mount/unmount jail-friendly file systems"); /* jail 中的进程是否可以挂载或卸载对jail友好的文件系统 */ 这些sysctl项中的每一个都可以用命令&man.sysctl.8;访问。在整个内核中, 这些sysctl项按名称标识。例如,上述第一个sysctl项的名字是 security.jail.set_hostname_allowed &man.jail.2;系统调用 像所有的系统调用一样,系统调用&man.jail.2;带有两个参数, struct thread *tdstruct jail_args *uaptd是一个指向thread结构体的指针,该指针用于描述调用&man.jail.2;的线程。 在这个上下文中,uap指向一个结构体,这个结构体中包含了一个指向从用户级 jail.c传送过来的jail结构体的指针。 在前面我讲述用户级程序时,你已经看到过一个jail结构体被作为参数传送给系统调用 &man.jail.2;。 /usr/src/sys/kern/kern_jail.c: /* * struct jail_args { * struct jail *jail; * }; */ int jail(struct thread *td, struct jail_args *uap) 于是uap->jail可以用于访问被传递给&man.jail.2;的jail结构体。 然后,&man.jail.2;使用&man.copyin.9;将jail结构体复制到内核内存空间中。 &man.copyin.9;需要三个参数:要复制进内核内存空间的数据的地址 uap->jail,在内核内存空间存放数据的j, 以及数据的大小。uap->jail指向的Jail结构体被复制进内核内存空间, 并被存放在另一个jail结构体j里。 /usr/src/sys/kern/kern_jail.c: error = copyin(uap->jail, &j, sizeof(j)); 在jail.h中定义了另一个重要的结构体型prison。 结构体prison只被用在内核空间中。 下面是prison结构体的定义。 /usr/include/sys/jail.h: struct prison { LIST_ENTRY(prison) pr_list; /* (a) all prisons */ int pr_id; /* (c) prison id */ int pr_ref; /* (p) refcount */ char pr_path[MAXPATHLEN]; /* (c) chroot path */ struct vnode *pr_root; /* (c) vnode to rdir */ char pr_host[MAXHOSTNAMELEN]; /* (p) jail hostname */ u_int32_t pr_ip; /* (c) ip addr host */ void *pr_linux; /* (p) linux abi */ int pr_securelevel; /* (p) securelevel */ struct task pr_task; /* (d) destroy task */ struct mtx pr_mtx; void **pr_slots; /* (p) additional data */ }; 然后,系统调用&man.jail.2;为一个prison结构体分配一块内存, 并在jailprison结构体之间复制数据。 /usr/src/sys/kern/kern_jail.c: MALLOC(pr, struct prison *, sizeof(*pr), M_PRISON, M_WAITOK | M_ZERO); ... error = copyinstr(j.path, &pr->pr_path, sizeof(pr->pr_path), 0); if (error) goto e_killmtx; ... error = copyinstr(j.hostname, &pr->pr_host, sizeof(pr->pr_host), 0); if (error) goto e_dropvnref; pr->pr_ip = j.ip_number; 下面,我们将讨论另外一个重要的系统调用&man.jail.attach.2;,它实现了将进程监禁的功能。 /usr/src/sys/kern/kern_jail.c /* * struct jail_attach_args { * int jid; * }; */ int jail_attach(struct thread *td, struct jail_attach_args *uap) 这个系统调用做出一些可以用于区分被监禁和未被监禁的进程的改变。 要理解&man.jail.attach.2;为我们做了什么,我们首先要理解一些背景信息。 在FreeBSD中,每个对内核可见的线程是通过其thread结构体来识别的, 同时,进程都由它们自己的proc结构体描述。 你可以在/usr/include/sys/proc.h中找到threadproc结构体的定义。 例如,在任何系统调用中,参数td实际上是个指向调用线程的thread结构体的指针, 正如前面所说的那样。td所指向的thread结构体中的td_proc成员是一个指针, 这个指针指向td所表示的线程所属进程的proc结构体。 结构体proc包含的成员可以描述所有者的身份 (p_ucred),进程资源限制(p_limit), 等等。在由proc结构体的p_ucred成员所指向的ucred结构体的定义中, 还有一个指向prison结构体的指针(cr_prison)。 /usr/include/sys/proc.h: struct thread { ... struct proc *td_proc; ... }; struct proc { ... struct ucred *p_ucred; ... }; /usr/include/sys/ucred.h struct ucred { ... struct prison *cr_prison; ... }; kern_jail.c中,函数jail()以给定的jid 调用函数jail_attach()。随后jail_attach()调用函数change_root()以改变 调用进程的根目录。接下来,jail_attach()创建一个新的ucred结构体,并在 成功地将prison结构体连接到这个ucred结构体后,将这个ucred结构体连接 到调用进程上。从此时起,这个调用进程就会被识别为被监禁的。 当我们以新创建的这个ucred结构体为参数调用内核路径jailed()时, 它将返回1来说明这个用户身份是和一个jail相连的。 在jail中叉分出来的所有进程的的公共祖先进程就是这个执行了&man.jail.2;的进程, 因为正是它调用了&man.jail.2;系统调用。当一个程序通过&man.execve.2;而被执行时, 它将从其父进程的ucred结构体继承被监禁的属性, 因而它也会拥有一个被监禁的ucred结构体。 /usr/src/sys/kern/kern_jail.c int jail(struct thread *td, struct jail_args *uap) { ... struct jail_attach_args jaa; ... error = jail_attach(td, &jaa); if (error) goto e_dropprref; ... } int jail_attach(struct thread *td, struct jail_attach_args *uap) { struct proc *p; struct ucred *newcred, *oldcred; struct prison *pr; ... p = td->td_proc; ... pr = prison_find(uap->jid); ... change_root(pr->pr_root, td); ... newcred->cr_prison = pr; p->p_ucred = newcred; ... } 当一个进程被从其父进程叉分来的时候, 系统调用&man.fork.2;将用crhold()来维护其身份凭证。 这样,很自然的就保持了子进程的身份凭证于其父进程一致,所以子进程也是被监禁的。 /usr/src/sys/kern/kern_fork.c: p2->p_ucred = crhold(td->td_ucred); ... td2->td_ucred = crhold(p2->p_ucred); 系统对被囚禁程序的限制 在整个内核中,有一系列对被囚禁程序的约束措施。 通常,这些约束只对被囚禁的程序有效。如果这些程序试图突破这些约束, 相关的函数将出错返回。例如: if (jailed(td->td_ucred)) return EPERM; SysV进程间通信(IPC) System V IPC(系统V进程间通信) System V 进程间通信 (IPC) 是通过消息实现的。 每个进程都可以向其它进程发送消息, 告诉对方该做什么。 处理消息的函数是: &man.msgctl.3;、&man.msgget.3;、&man.msgsnd.3; 和 &man.msgrcv.3;。前面已经提到,一些 sysctl 开关可以影响 jail 的行为, 其中有一个是 security.jail.sysvipc_allowed。 在大多数系统上, 这个 sysctl 项会设成0。 如果将它设为1, 则会完全失去 jail 的意义: 因为那样在 jail 中特权进程就可以影响被监禁的环境外的进程了。 消息与信号的区别是:消息仅由一个信号编号组成。 /usr/src/sys/kern/sysv_msg.c: msgget(key, msgflg): msgget返回(也可能创建)一个消息描述符, 以指派一个在其它函数中使用的消息队列。 msgctl(msgid, cmd, buf): 通过这个函数, 一个进程可以查询一个消息描述符的状态。 msgsnd(msgid, msgp, msgsz, msgflg): msgsnd向一个进程发送一条消息。 msgrcv(msgid, msgp, msgsz, msgtyp, msgflg): 进程用这个函数接收消息。 在这些函数对应的系统调用的代码中,都有这样一个条件判断: /usr/src/sys/kern/sysv_msg.c: if (!jail_sysvipc_allowed && jailed(td->td_ucred)) return (ENOSYS); semaphores(信号量) 信号量系统调用使得进程可以通过一系列原子操作实现同步。 信号量为进程锁定资源提供了又一种途径。 然而,进程将为正在被使用的信号量进入等待状态,一直休眠到资源被释放。 在jail中如下的信号量系统调用将会失效: &man.semget.2;, &man.semctl.2; 和&man.semop.2;。 /usr/src/sys/kern/sysv_sem.c: semctl(semid, num, cmd, ...): semctl对在信号量队列中用semid标识的信号量执行cmd指定的命令。 semget(key, nsems, flag): semget建立一个对应于key的信号量数组。 参数key和flag与他们在msgget()的意义相同。 setop(semid, array, nops): semop对semid标识的信号量完成一组由array所指定的操作。 shared memory(共享内存) System V IPC使进程间可以共享内存。进程之间可以通过它们虚拟地址空间 的共享部分以及相关数据读写操作直接通讯。这些系统调用在被监禁的环境中将会失效: &man.shmdt.2;、&man.shmat.2;、&man.shmctl.2;和&man.shmget.2; /usr/src/sys/kern/sysv_shm.c: shmctl(shmid, cmd, buf): shmctlid标识的共享内存区域做各种各样的控制。 shmget(key, size, flag): shmget建立/打开size字节的共享内存区域。 shmat(shmid, addr, flag): shmatshmid标识的共享内存区域指派到进程的地址空间里。 shmdt(addr): shmdt取消共享内存区域的地址指派。 套接字 sockets(套接字) Jail以一种特殊的方式处理&man.socket.2;系统调用和相关的低级套接字函数。 为了决定一个套接字是否允许被创建,它先检查sysctl项 security.jail.socket_unixiproute_only是否被设置为1。 如果被设为1,套接字建立时将只能指定这些协议族: PF_LOCAL, PF_INET, PF_ROUTE。否则,&man.socket.2;将会返回出错。 /usr/src/sys/kern/uipc_socket.c: int socreate(int dom, struct socket **aso, int type, int proto, struct ucred *cred, struct thread *td) { struct protosw *prp; ... if (jailed(cred) && jail_socket_unixiproute_only && prp->pr_domain->dom_family != PF_LOCAL && prp->pr_domain->dom_family != PF_INET && prp->pr_domain->dom_family != PF_ROUTE) { return (EPROTONOSUPPORT); } ... } Berkeley包过滤器 Berkeley Packet Filter(伯克利包过滤器) data link layer(数据链路层) Berkeley包过滤器提供了一个与协议无关的,直接通向数据链路层的低级接口。 现在BPF是否可以在监禁的环境中被使用是通过&man.devfs.8;来控制的。 网络协议 protocols(协议) 网络协议TCP, UDP, IP和ICMP很常见。IP和ICMP处于同一协议层次:第二层, 网络层。当参数nam被设置时, 有一些限制措施会防止被囚禁的程序绑定到一些网络接口上。 nam是一个指向sockaddr结构体的指针, 描述可以绑定服务的地址。一个更确切的定义:sockaddr“是一个模板,包含了地址的标识符和地址的长度”。 在函数in_pcbbind_setup()sin是一个指向sockaddr_in结构体的指针, 这个结构体包含了套接字可以绑定的端口、地址、长度、协议族。 这就禁止了在jail中的进程指定不属于这个进程所存在于的jail的IP地址。 /usr/src/sys/kern/netinet/in_pcb.c: int in_pcbbind_setup(struct inpcb *inp, struct sockaddr *nam, in_addr_t *laddrp, u_short *lportp, struct ucred *cred) { ... struct sockaddr_in *sin; ... if (nam) { sin = (struct sockaddr_in *)nam; ... if (sin->sin_addr.s_addr != INADDR_ANY) if (prison_ip(cred, 0, &sin->sin_addr.s_addr)) return(EINVAL); ... if (lport) { ... if (prison && prison_ip(cred, 0, &sin->sin_addr.s_addr)) return (EADDRNOTAVAIL); ... } } if (lport == 0) { ... if (laddr.s_addr != INADDR_ANY) if (prison_ip(cred, 0, &laddr.s_addr)) return (EINVAL); ... } ... if (prison_ip(cred, 0, &laddr.s_addr)) return (EINVAL); ... } 你也许想知道函数prison_ip()做什么。 prison_ip()有三个参数,一个指向身份凭证的指针(用cred表示), 一些标志和一个IP地址。当这个IP地址不属于这个jail时,返回1; 否则返回0。正如你从代码中看见的,如果,那个IP地址确实不属于这个jail, 就不再允许向这个网络地址绑定协议。 /usr/src/sys/kern/kern_jail.c: int prison_ip(struct ucred *cred, int flag, u_int32_t *ip) { u_int32_t tmp; if (!jailed(cred)) return (0); if (flag) tmp = *ip; else tmp = ntohl(*ip); if (tmp == INADDR_ANY) { if (flag) *ip = cred->cr_prison->pr_ip; else *ip = htonl(cred->cr_prison->pr_ip); return (0); } if (tmp == INADDR_LOOPBACK) { if (flag) *ip = cred->cr_prison->pr_ip; else *ip = htonl(cred->cr_prison->pr_ip); return (0); } if (cred->cr_prison->pr_ip != tmp) return (1); return (0); } 文件系统 filesystem(文件系统) 如果完全级别大于0,即便是jail里面的root, 也不允许在Jail中取消或更改文件标志,如“不可修改”、“只可添加”、“不可删除”标志。 /usr/src/sys/ufs/ufs/ufs_vnops.c: static int ufs_setattr(ap) ... { ... if (!priv_check_cred(cred, PRIV_VFS_SYSFLAGS, 0)) { if (ip->i_flags & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) { error = securelevel_gt(cred, 0); if (error) return (error); } ... } } /usr/src/sys/kern/kern_priv.c int priv_check_cred(struct ucred *cred, int priv, int flags) { ... error = prison_priv_check(cred, priv); if (error) return (error); ... } /usr/src/sys/kern/kern_jail.c int prison_priv_check(struct ucred *cred, int priv) { ... switch (priv) { ... case PRIV_VFS_SYSFLAGS: if (jail_chflags_allowed) return (0); else return (EPERM); ... } ... }
diff --git a/zh_CN.GB2312/books/arch-handbook/mac/chapter.sgml b/zh_CN.GB2312/books/arch-handbook/mac/chapter.sgml index 82aecbadb9..fc8569f56c 100644 --- a/zh_CN.GB2312/books/arch-handbook/mac/chapter.sgml +++ b/zh_CN.GB2312/books/arch-handbook/mac/chapter.sgml @@ -1,7257 +1,7257 @@ Chris Costello TrustedBSD 项目
chris@FreeBSD.org
Robert Watson TrustedBSD 项目
rwatson@FreeBSD.org
&author.cn.coolcoold; &cnproj.translated.by;
TrustedBSD MAC 框架 MAC 文档版权声明 本文档是作为 DARPA CHATS 研究计划的一部分,由供职于 Security Research Division of Network Associates 公司Safeport Network Services and Network Associates Laboratories 的Chris Costello依据 DARPA/SPAWAR 合同 N66001-01-C-8035 (CBOSS),为 FreeBSD 项目编写的。 Redistribution and use in source (SGML DocBook) and 'compiled' forms (SGML, HTML, PDF, PostScript, RTF and so forth) with or without modification, are permitted provided that the following conditions are met: Redistributions of source code (SGML DocBook) must retain the above copyright notice, this list of conditions and the following disclaimer as the first lines of this file unmodified. Redistributions in compiled form (transformed to other DTDs, converted to PDF, PostScript, RTF and other formats) must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS DOCUMENTATION IS PROVIDED BY THE NETWORKS ASSOCIATES TECHNOLOGY, INC "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NETWORKS ASSOCIATES TECHNOLOGY, INC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 本文中许可证的非官方中文翻译仅供参考, 不作为判定任何责任的依据。如与英文原文有出入,则以英文原文为准。 在满足下列许可条件的前提下,允许再分发或以源代码 (SGML DocBook) 或 “编译” (SGML, HTML, PDF, PostScript, RTF 等) 的经过修改或未修改的形式: 再分发源代码 (SGML DocBook) 必须不加修改的保留上述版权告示、本条件清单和下述弃权书作为该文件的最先若干行。 再分发编译的形式 (转换为其它DTD、 PDF、 PostScript、 RTF 或其它形式),必须将上述版权告示、 本条件清单和下述弃权书复制到与分发品一同提供的文件,以及其它材料中。 本文档由 NETWORKS ASSOCIATES TECHNOLOGY, INC “按现状条件”提供,并在此明示不提供任何明示或暗示的保障, 包括但不限于对商业适销性、对特定目的的适用性的暗示保障。任何情况下, NETWORKS ASSOCIATES TECHNOLOGY, INC 均不对任何直接、 间接、 偶然、 特殊、 惩罚性的, 或必然的损失 (包括但不限于替代商品或服务的采购、 使用、 数据或利益的损失或营业中断) 负责, 无论是如何导致的并以任何有责任逻辑的, 无论是否是在本文档使用以外以任何方式产生的契约、严格责任或是民事侵权行为(包括疏忽或其它)中的, 即使已被告知发生该损失的可能性。 术语解析 FreeBSD 以一个内核安全扩展性框架(TrustedBSD MAC 框架)的方式,为若干强制访问控制策略(也称“集权式访问控制策略”) 提供试验性支持。MAC 框架是一个插入式的访问控制框架,允许新的安全策略更方便地融入内核:安全策略可以静态链入内核,也可以 在引导时加载,甚至在运行时动态加载。该框架所提供的标准化接口,使得运行在其上的安全策略模块能对系统对象的安全属性进行诸如标记等一系列操作。 MAC 框架的存在,简化了这些操作在策略模块中的实现,从而显著降低了新安全策略模块的开发难度。 本章将介绍 MAC 策略框架,为读者提供一个示例性的 MAC 策略模块文档。 概述 TrustedBSD MAC 框架提供的机制,允许在其上运行的内核模块在内核编译或者运行时,对内核的访问控制模型进行扩展。 新的系统安全策略作为一个内核模块实现,并被链接到内核中;如果系统中同时存在多个安全策略模块,则它们的决策结果将以某种确定的方式组合。 为了给简化新安全策略的开发,MAC 向上提供了大量用于访问控制的基础设施,特别是,对临时的或者持久的、策略无关的对象安全标记的支持。 该支持目前仍是试验性质的。 本章所提供的信息不仅将使在 MAC 使能环境下工作的潜在用户受益, 也可以为需要了解 MAC 框架是如何支持对内核访问控制进行扩展的策略模块开发人员所用。 安全策略背景知识 强制访问控制(简称 MAC),是指由操作系统强制实施的一组针对用户的访问控制策略。 在某些情况下,强制访问控制的策略可能会与自主访问控制(简称 DAC)所提供的保护措施发生冲突, 后者是用来向非管理员用户对数据采取保护措施提供支持的。在传统的 UNIX 系统中, DAC 保护措施包括文件访问模式和访问控制列表;而 MAC 则提供进程控制和防火墙等。 操作系统设计者和安全机制研究人员对许多经典的 MAC 安全策略作了形式化的表述,比如, 多级安全(MLS)机密性策略,Biba 完整性策略,基于角色的访问控制策略(RBAC),域和型裁决策略(DTE),以及型裁决策略(TE)。 安全策略的形式化表述被称为安全模型。每个模型根据一系列条件做出安全相关的决策,这些条件包括, 用户的身份、角色和安全信任状,以及对象的安全标记(用来代表该对象数据的机密性/完整性级别)。 TrustedBSD MAC 框架所提供的对策略模块的支持,不仅可以用来实现上述所有策略, 还能用于实现其他利用已有安全属性(如,用户和组ID、文件扩展属性等)决策的系统安全强化策略。 此外,因为具体策略模块在访问授权方面所拥有的高度灵活性和自主性,所以MAC 框架同样可以用来实现完全自主式的安全策略. MAC 框架的内核体系结构 TrustedBSD MAC 框架为大多数的访问控制模块提供基本设施,允许它们以内核模块的形式灵活地扩展系统中实施的安全策略。 如果系统中同时加载了多个策略,MAC 框架将负责将各个策略的授权结果以一种(某种程度上)有意义的方式组合,形成最后的决策。 内核元素 MAC 框架由下列内核元素组成: 框架管理接口 并发与同步原语 策略注册 内核对象的扩展性安全标记 策略入口函数的组合操作 标记管理原语 由内核服务调用的入口函数 API 策略模块的入口函数 API 入口函数的实现(包括策略生命周期管理、标记管理和访问控制检查三部分) 管理策略无关标记的系统调用 复用的mac_syscall() 系统调用 以 MAC 的策略加载模块形式实现的各种安全策略 框架管理接口 对 TrustedBSD MAC 框架进行直接管理的方式有三种:通过 sysctl 子系统、通过 loader 配置, 或者使用系统调用。 多数情况下,与同一个内核内部变量相关联的 sysctl 变量和 loader 参数的名字是相同的, 通过设置它们,可以控制保护措施的实施细节,比如,某个策略在各个内核子系统中的实施与否等等。 另外,如果在内核编译时选择支持 MAC 调试选项,内核将维护若干计数器以跟踪标记的分配使用情况。 通常不建议在实用环境下通过在不同子系统上设置不同的变量或参数来实施控制,因为这种方法将会作用于系统中所有的活跃策略。 如果希望对具体策略实施管理而不相影响其他活跃策略,则应当使用策略级别的控制,因为这种方法的控制粒度更细, 并能更好地保证策略模块的功能一致性。 与其他内核模块一样,系统管理员可以通过系统的模块管理系统调用和其他系统接口,包括 boot loader 变量,对策略模块执行加载与卸载操作; 策略模块可以在加载时,设置加载标志,来指示系统对其加载、卸载操作进行相应控制,比如阻止非期望的卸载操作。 策略链表的并发与同步 在运行时,系统中活跃的策略集合可能发生变化,然而对策略入口函数的使用操作并不是原子性的,因此,当某一个入口函数正被使用时, 系统需要提供额外的同步机制来阻止对该策略模块的加载与卸载,以确保当前活跃的策略集合不会在此过程中发生改变。 通过使用"框架忙”计数器,就可以做到这一点:一旦某个入口函数被调用,计数器的值被增加1;而每当一个入口函数调用结束时,计数器的值被减少1。 检查计数器的值,如果其值为正,框架将阻止对策略链表的修改操作,请求操作的线程将被迫进入睡眠,直到计数器的值重新减少到0为止。 计数器本身由一个互斥锁保护,同时结合一个条件变量(用于唤醒等待对策略链表进行修改操作的睡眠线程)。 采用这种同步模型的一个副作用是,在同一个策略模块内部,允许嵌套地调用框架,不过这种情况其实很少出现。 为了减少由于采用计数器引入的额外开销,设计者采用了各种优化措施。其中包括,当策略链表为空或者其中仅含有静态表项 (那些只能在系统运行之前加载而且不能动态卸载的策略)时,框架不对计数器进行操作,其值总是为0,从而将此时的同步开销减到0。 另一个极端的办法是,使用一个编译选项来禁止在运行时对加载的策略链表进行修改,此时不再需要对策略链表的使用进行同步保护。 因为 MAC 框架不允许在某些入口函数之内阻塞,所以不能使用普通的睡眠锁。 故而,加载或卸载操作可能会为等待框架空闲而被阻塞相当长的一段时间。 标记同步 MAC 框架必须对其负责维护的安全属性标记的存储访问提供同步保护。下列两种情形,可能导致对安全属性标记的不一致访问: 第一,作为安全属性标记的持有者,内核对象本身可能同时被多个线程访问;第二,MAC 框架代码是可重入的, 即允许多个线程同时在框架内执行。通常,MAC 框架使用内核对象数据上已有的内核同步机制来保护该其上附加的 MAC 安全标记。 例如,套接字上的 MAC 标记由已有的套接字互斥锁保护。类似的,对于安全标记的并发访问的过程与对其所在对象进行的并发访问在语义上是一样的, 例如,信任状安全标记,将保持与该数据结构中其他内容一致的"写时复制"的更新过程。 MAC 框架在引用一个内核对象时,将首先对访问该对象上的标记需要用到的锁进行断言。 策略模块的编写者必须了解这些同步语义, 因为它们可能会限制对安全标记所能进行的访问类型。 举个例子,如果通过入口函数传给策略模块的是对某个信任状的只读引用,那么在策略内部,只能读该结构对应的标记状态。 策略间的同步与并发 FreeBSD 内核是一个可抢占式的内核,因此,作为内核一部分的策略模块也必须是可重入的,也就是说, 在开发策略模块时必须假设多个内核线程可以同时通过不同的入口函数进入该模块。 如果策略模块使用可被修改的内核状态,那么还需要在策略内部使用恰当的同步原语,确保在策略内部的多个线程不会因此观察到不一致的内核状态, 从而避免由此产生的策略误操作。为此,策略可以使用 FreeBSD 现有的同步原语,包括互斥锁、睡眠锁、条件变量和计数信号量。 对这些同步原语的使用必须慎重,需要特别注意两点:第一,保持现有的内核上锁次序; 第二,在非睡眠的入口函数之内不要使用互斥锁和唤醒操作。 为避免违反内核上锁次序或造成递归上锁,策略模块在调用其他内核子系统之前,通常要释放所有在策略内部申请的锁。 这样做的结果是,在全局上锁次序形成的拓朴结构中,策略内部的锁总是作为叶子节点, 从而保证了这些锁的使用不会导致由于上锁次序混乱造成的死锁。 策略注册 为了记录当前使用的策略模块集合,MAC 框架维护两个链表:一个静态链表和一个动态链表。 两个链表的数据结构和操作基本相同,只是动态链表还额外使用了一个"引用计数"以同步对其的访问操作。 当包含 MAC 框架策略的内核模块被加载时,该策略模块会通过 SYSINIT 调用一个注册函数; 相对应的,每当一个策略模块被卸载,SYSINIT 也会调用一个注销函数。 只有当遇到下列情况之一时,注册过程才会失败: 一个策略模块被加载多次,或者系统资源不足不能满足注册过程的需要( 例如,策略模块需要对内核对象添加标记而可用资源不足),或者其他的策略加载前提条件不满足(有些策略要求只能在系统引导之前加载)。 类似的,如果一个策略被标记为不可卸载的,对其调用注销过程将会失败。 入口函数 内核服务与 MAC 框架之间进行交互有两种途径: 一是,内核服务调用一系列 API 通知 MAC 框架安全事件的发生; 二是,内核服务向 MAC 框架提供一个指向安全对象的策略无关安全标记数据结构的指针。 标记指针由 MAC 框架经由标记管理入口函数进行维护, 并且,只要对管理相关对象的内核子系统稍作修改,就可以允许 MAC 框架向策略模块提供标记服务。 例如,在进程、进程信任状、套接字、管道、Mbuf、网络接口、IP 重组队列和其他各种安全相关的数据结构中均增加了指向安全标记的指针。 另外,当需要做出重要的安全决策时,内核服务也会调用 MAC 框架,以便各个策略模块根据其自己的标准(可以使用存储在安全标记中的数据)完善这些决策。 绝大多数安全相关的关键决策是显式的访问控制检查; 也有少数涉及更加一般的决策函数,比如,套接字的数据包匹配和程序执行时刻的标记转换。 策略组合 如果内核中同时加载了多个策略模块,这些策略的决策结果将由框架使用一个合成运算子来进行组合汇总,得出最终的结果。 目前,该算子是硬编码的,并且只有当所有的活跃策略均对请求表示同意时才会返回成功。 由于各个策略返回的出错条件可能并不相同(成功、访问被拒绝、请求对象不存在等等), 需要使用一个选择子先从各个策略返回的错误条件集合中选择出一个作为最终返回结果。 一般情况下,与“访问被拒绝”相比,将更倾向于选择“请求对象不存在”。 尽管不能从理论上保证合成结果的有效性与安全性,但试验结果表明,对于许多实用的策略集合来说,事实的确如此。 例如,传统的可信系统常常采用类似的方法对多个安全策略进行组合。 标记支持 与许多需要给对象添加安全标记的访问控制扩展一样,MAC 框架为各种用户可见的对象提供了一组用于管理策略无关标记的系统调用。 常用的标记类型有,partition标识符、机密性标记、完整性标记、区间(非等级类别)、域、角色和型。 “策略无关”的意思是指,标记的语法与使用它的具体策略模块无关,而同时策略模块能够完全独立地定义和使用与对象相关联的元数据的语义。 用户应用程序提供统一格式的基于字符串的标记,由使用它的策略模块负责解析其内在含义并决定其外在表示。 如果需要,应用程序可以使用多重标记元素。 内存中的标记实例被存放在由 slab 分配的struct label数据结构中。 该结构是一个固定长度的数组,每个元素是由一个 void * 指针和一个 long组成的联合结构。 申请标记存储的策略模块在向 MAC 注册时,将被分配一个“slot”值,作为框架分配给其使用的策略标记元素在整个标记存储结构中的位置索引。 而所分配的存储空间的语义则完全由该策略模块来决定:MAC 框架向策略模块提供了一系列入口函数用于对内核对象生命周期的各种事件进行控制,包括, 对象的初始化、标记的关联/创建和对象的注销。使用这些接口,可以实现诸如访问计数等存储模型。 MAC 框架总是给入口函数传入一个指向相关对象的指针和一个指向该对象标记的指针,因此,策略模块能够直接访问标记而无需知悉该对象的内部结构。 唯一的例外是进程信任状结构,指向其标记的指针必须由策略模块手动解析计算。今后的 MAC 框架实现可能会对此进行改进。 初始化入口函数通常有一个睡眠标志位,用来表明一个初始化操作是否允许中途睡眠等待; 如果不允许,则可能会失败返回,并要求撤销此次标记分配操作(乃至对象分配操作)。 例如,如果在网络栈上处理中断时因为不允许睡眠或者调用者持有一个互斥锁,就可能出现这种情况。 考虑到在处理中的网络数据包(Mbufs)上维护标记的性能损失太大,策略必须就自己对 Mbuf 进行标记的要求向 MAC 框架做出特别声明。 动态加载到系统中而又使用标记的策略必须为处理未被其初始化函数处理过的对象作好准备, 这些对象在策略加载之前就已经存在,故而无法在初始化时调用策略的相关函数进行处理。 MAC 框架向策略保证,没有被初始化的标记 slot 的值必为0或者 NULL,策略可以借此检测到未初始化的标记。 需要注意的是,因为对 Mbuf 标记的存储分配是有条件的,因此需要使用其标记的动态加载策略还可能需要处理 Mbuf 中值为 NULL 的标记指针。 对于文件系统对象的标记,MAC 框架在文件的扩展属性中为其分配永久存储。 只要可能,扩展属性的原子化的事务操作就被用于保证对 vnode 上安全标记的复合更新操作的一致性--目前,该特性只被 UFS2 文件系统支持。 为了实现细粒度的文件系统对象标记(即每个文件系统对象一个标记),策略编写者可能选择使用一个(或者若干)扩展属性块。 为了提高性能, vnode 数据结构中有一个标记 (v_label)字段,用作磁盘标记的缓冲; vnode 结构实例化时,策略可以将标记值装入该缓冲,并在需要时对其进行更新。 如此,不必在每次进行访问控制检查时,均无条件地访问磁盘上的扩展属性。 目前,如果一个使用标记的策略允许被动态卸载,则卸载该模块之后,其状态 slot 尚无法被系统回收重用, 由此导致了 MAC 框架对标记策略卸载-重载操作数目上的严格限制。 相关系统调用 MAC 框架向应用程序提供了一组系统调用:其中大多数用于向进行查询和修改策略无关标记操作的应用 API提供支持。 这些标记管理系统调用,接受一个标记描述结构, struct mac,作为输入参数。 这个结构的主体是一个数组,其中每个元素包含了一个应用级的 MAC 标记形式。每个元素又由两部分组成:一个字符串名字,和其对应的值。 每个策略可以向系统声明一个特定的元素名字,这样一来,如果需要,就可以将若干个相互独立的元素作为一个整体进行处理。 策略模块经由入口函数,在内核标记和用户提供的标记之间作翻译转换的工作,这种实现提供了标记元素语义上的高度灵活性。 标记管理系统调用通常有对应的库函数包装,这些包装函数可以提供内存分配和错误处理功能,从而简化了用户应用程序的标记管理工作。 目前的FreeBSD 内核提供了下列 MAC 相关的系统调用: mac_get_proc() 用于查询当前进程的安全标记。 mac_set_proc() 用于请求改变当前进程的安全标记。 mac_get_fd() 用于查询由文件描述符所引用的对象( 文件、 套接字、 管道文件等等) 的安全标记。 mac_get_file() 用于查询由文件系统路径所描述的对象的安全标记。 mac_set_fd() 用于请求改变由文件描述符所引用的对象( 文件、套接字、 管道文件等等) 的安全标记。 mac_set_file() 用于请求改变由文件系统路径所描述的对象的安全标记。 mac_syscall() 通过复用该系统调用,策略模块能够在不修改系统调用表的前提下创建新的系统调用; 其调用参数包括:目标策略名字、 操作编号和将被该策略内部使用的参数。 mac_get_pid() 用于查询由进程号指定的另一个进程的安全标记。 mac_get_link()mac_get_file() 功能相同, 只是当路径参数的最后一项为符号链接时, 前者将返回该符号链接的安全标记, 而后者将返回其所指文件的安全标记。 mac_set_link()mac_set_file() 功能相同, 只是当路径参数的最后一项为符号链接时, 前者将设置该符号链接的安全标记, 而后者将设置其所指文件的安全标记。 mac_execve()execve() 功能类似, 只是前者还可以在开始执行一个新程序时,根据传入的请求参数,设置执行进程的安全标记。 由于执行一个新程序而导致的进程安全标记的改变,被称为“转换”。 mac_get_peer(), 通过一个套接字选项自动实现, 用于查询一个远程套接字对等实体的安全标记。 除了上述系统调用之外, 也可以通过 SIOCSIGMACSIOCSIFMAC 网络接口的 ioctl 类系统调用来查询和设置网络接口的安全标记。 MAC策略模块体系结构 安全策略可以直接编入内核,也可以编译成独立的内核模块,在系统引导时或者运行时使用模块加载命令加载。 策略模块通过一组预先定义好的入口函数与系统交互。通过它们,策略模块能够掌握某些系统事件的发生,并且在必要的时候影响系统的访问控制决策。 每个策略模块包含下列组成部分: 可选:策略配置参数 策略逻辑和参数的集中实现 可选:策略生命周期事件的实现,比如,策略的初始化和销毁 可选:对所选内核对象的安全标记进行初始化、维护和销毁的支持 可选:对所选对象的使用进程进行监控以及修改对象安全标记的支持 策略相关的访问控制入口函数的实现 对策略标志、模块入口函数和策略特性的声明 策略注销 策略模块可以使用 MAC_POLICY_SET() 宏来声明。 该宏完成以下工作:为该策略命名(向系统声明该策略提供的名字);提交策略定义的 MAC 入口函数向量的地址; 按照策略的要求设置该策略的加载标志位,保证 MAC 框架将以策略所期望的方式对其进行操作; 另外,还可能请求框架为策略分配标记状态 slot 值。 static struct mac_policy_ops mac_policy_ops = { .mpo_destroy = mac_policy_destroy, .mpo_init = mac_policy_init, .mpo_init_bpfdesc_label = mac_policy_init_bpfdesc_label, .mpo_init_cred_label = mac_policy_init_label, /* ... */ .mpo_check_vnode_setutimes = mac_policy_check_vnode_setutimes, .mpo_check_vnode_stat = mac_policy_check_vnode_stat, .mpo_check_vnode_write = mac_policy_check_vnode_write, }; 如上所示,MAC 策略入口函数向量,mac_policy_ops, 将策略模块中定义的功能函数挂接到特定的入口函数地址上。 在稍后的“入口函数参考”小节中,将提供可用入口函数功能描述和原型的完整列表。 与模块注册相关的入口函数有两个:.mpo_destroy.mpo_init。 当某个策略向模块框架注册操作成功时,.mpo_init将被调用,此后其他的入口函数才能被使用。 这种特殊的设计使得策略有机会根据自己的需要,进行特定的分配和初始化操作,比如对特殊数据或锁的初始化。 卸载一个策略模块时,将调用 .mpo_destroy 用来释放策略分配的内存空间或注销其申请的锁。 目前,为了防止其他入口函数被同时调用,调用上述两个入口函数的进程必须持有 MAC 策略链表的互斥锁:这种限制将被放开, 但与此同时,将要求策略必须谨慎使用内核原语,以避免由于上锁次序或睡眠造成死锁。 之所以向策略声明提供模块名字域,是为了能够唯一标识该模块,以便解析模块依赖关系。选择使用恰当的字符串作为名字。 在策略加载和卸载时,策略的完整字符串名字将经由内核日志显示给用户。另外,当向用户进程报告状态信息时也会包含该字符串。 策略标志 在声明时提供标志参数域的机制,允许策略模块在作为模块被加载时,就自身特性向 MAC 框架提供说明。 目前,已经定义的标志有三个: MPC_LOADTIME_FLAG_UNLOADOK 表示该策略模块可以被卸载。 如果未提供该标志,则表示该策略模块拒绝被卸载。 那些使用安全标记的状态,而又不能在运行时释放该状态的模块可能会设置该标志。 MPC_LOADTIME_FLAG_NOTLATE 表示该策略模块必须在系统引导过程时进行加载和初始化。 如果该标志被设置,那么在系统引导之后注册该模块的请求将被 MAC 框架所拒绝。 那些需要为大范围的系统对象进行安全标记初始化工作,而又不能处理含有未被正确初始化安全标记的对象的策略模块可能会设置该标志。 MPC_LOADTIME_FLAG_LABELMBUFS 表示该策略模块要求为 Mbuf 指定安全标记,并且为存储其标记所需的内存空间总是提前分配好的。 缺省情况下,MAC 框架并不会为 Mbuf 分配标记存储,除非系统中注册的策略模块中至少有一个设置了该标志。 这种做法在没有策略需要对 Mbuf 做标记时,显著地提升了系统网络性能。另外,在某些特殊环境下,可以通过设置内核选项, MAC_ALWAYS_LABEL_MBUF,强制 MAC 框架为 Mbuf 的安全标记分配存储,而不论上述标志如何设置。 那些使用了 MPC_LOADTIME_FLAG_LABELMBUFS 标志但没有设置 MPC_LOADTIME_FLAG_NOTLATE 标志的 策略模块必须能够正确地处理通过入口函数传入的值为 NULL 的 Mbuf 安全标记指针。 这是因为那些没有分配标记存储的处理中的 Mbuf 在一个需要 Mbuf 安全标记的策略模块加载之后, 其安全标记的指针将仍然为空。 如果策略在网络子系统活跃之前被加载(即,该策略不是被推迟加载的),那么所有的 Mbuf 的标记存储的分配就可以得到保证。 策略入口函数 MAC 框架为注册的策略提供四种类型的入口函数: 策略注册和管理入口函数; 用于处理内核对象声明周期事件,如初始化、 创建和销毁,的入口函数; 处理该策略模块感兴趣的访问控制决策事件的入口函数; 以及用于管理对象安全标记的调用入口函数。 此外, 还有一个 mac_syscall() 入口函数, 被策略模块用于在不注册新的系统调用的前提下, 扩展内核接口。 策略模块的编写人员除了必须清楚在进入特定入口函数之后, 哪些对象锁是可用的之外, 还应该熟知内核所采用的加锁策略。 编程人员在入口函数之内应该避免使用非叶节点锁, 并且遵循访问和修改对象时的加锁规程, 以降低导致死锁的可能性。 特别地, 程序员应该清楚, 虽然在通常情况下, 进入入口函数之后, 已经上了一些锁, 可以安全地访问对象及其安全标记, 但是这并不能保证对它们进行修改( 包括对象本身和其安全标记) 也是安全的。 相关的上锁信息,可以参考 MAC 框架入口函数的相关文档。 策略入口函数把两个分别指向对象本身和其安全标记的指针传递给策略模块。 这样一来,即使策略并不熟悉对象内部结构,也能基于标记作出正确决策。 只有进程信任状这个对象例外:MAC 框架总是假设所有的策略模块是理解其内部结构的。 MAC策略入口函数参考 通用的模块入口函数 <function>&mac.mpo;_init</function> void &mac.mpo;_init struct mac_policy_conf *conf &mac.thead; conf MAC 策略定义 策略加载事件。当前进程正持有策略链表上的互斥锁,因此是非睡眠的,对其他内核子系统的调用也须慎重。 如果需要在策略初始化阶段进行可能造成睡眠阻塞的存储分配操作,可以将它们放在一个单独的模块 SYSINIT() 过程中集中进行。 <function>&mac.mpo;_destroy</function> void &mac.mpo;_destroy struct mac_policy_conf *conf &mac.thead; conf MAC 策略定义 策略加载事件。必须持有策略链表互斥锁,因此需要慎重行事。 <function>&mac.mpo;_syscall</function> int &mac.mpo;_syscall struct thread *td int call void *arg &mac.thead; td 调用线程 call 策略特有的系统调用编号 arg 系统调用参数的指针 该入口函数提供策略复用的系统调用,这样策略模块不需要为其向用户进程提供的每一个额外服务而注册专用的系统调用。 由应用程序提供的策略注册名字来确定提供其所申请服务的特定策略,所有参数将通过该入口函数传递给被调用的策略。 当实现新服务时,安全模块必须在必要时通过 MAC 框架调用相应的访问控制检查机制。 比方说,假如一个策略实现了某种额外的信号功能,那么它应该调用相关的信号访问控制检查,以接受 MAC 框架中注册的其他策略的检查。 不同的模块需要并发地手动进行copyin()拷贝系统调用数据。 <function>&mac.mpo;_thread_userret</function> void &mac.mpo;_thread_userret struct thread *td &mac.thead; td 返回线程 使用该入口函数,策略模块能够在线程返回用户空间时(系统调用返回、异常返回等等)进行 MAC 相关的处理工作。 使用动态进程标记的策略需要使用该入口函数,因为在处理系统调用的过程中,并不是在任意时刻都能申请到进程锁的; 进程的标记可能表示传统的认证信息、进程历史记录或者其他数据。为使用该入口函数,对进程信任状所作的修改 可能被存放在 p_label ,该域受一个进程级自旋锁的保护;接下来,设置线程级的TDF_ASTPENDING 标志位和进程级的PS_MACPENDM标志位,表明将调度一个对 userret 入口函数的调用。通过该入口函数, 策略可以在相对简单的同步上下文中创建信任状的替代品。策略编程人员必须清楚,需要保证与调度一个 AST 相关的事件执行次序, 同时所执行的 AST 可能很复杂,而且在处理多线程应用程序时可能被重入。 操作标记 <function>&mac.mpo;_init_bpfdesc_label</function> void &mac.mpo;_init_bpfdesc_label struct label *label &mac.thead; label 将被应用的新标记 为一个新近实例化的 bpfdesc(BPF 描述子)初始化标记。可以睡眠。 <function>&mac.mpo;_init_cred_label</function> void &mac.mpo;_init_cred_label struct label *label &mac.thead; label 将被初始化的新标记 为一个新近实例化的用户信任状初始化标记。可以睡眠。 <function>&mac.mpo;_init_devfsdirent_label</function> void &mac.mpo;_init_devfsdirent_label struct label *label &mac.thead; label 将被应用的新标记 为一个新近实例化的 devfs 表项初始化标记。可以睡眠。 <function>&mac.mpo;_init_ifnet_label</function> void &mac.mpo;_init_ifnet_label struct label *label &mac.thead; label 将被应用的新标记 为一个新近实例化的网络接口初始化标记。可以睡眠。 <function>&mac.mpo;_init_ipq_label</function> void &mac.mpo;_init_ipq_label struct label *label int flag &mac.thead; label 将被应用的新标记 flag 睡眠/不睡眠 &man.malloc.9;; 参见下文 为一个新近实例化的 IP 分片重组队列初始化标记。其中的flag域可能取M_WAITOKM_NOWAIT之一,用来避免在该初始化调用中因为 &man.malloc.9; 而进入睡眠。IP 分片重组队列的分配操作通常是在 对性能有严格要求的环境下进行的,因此实现代码必须小心地避免睡眠和长时间的操作。IP 分片重组队列分配操作失败时上述入口函数将失败返回。 <function>&mac.mpo;_init_mbuf_label</function> void &mac.mpo;_init_mbuf_label int flag struct label *label &mac.thead; flag 睡眠/不睡眠 &man.malloc.9;; 参见下文 label 将被初始化的策略标记 为一个新近实例化的 mbuf 数据包头部(mbuf)初始化标记。 其中的flag的值可能取M_WAITOKM_NOWAIT之一, 用来避免在该初始化调用中因为 &man.malloc.9; 而进入睡眠。Mbuf 头部的分配操作常常在对性能有严格要求的环境下被频繁执行, 因此实现代码必须小心地避免睡眠和长时间的操作。上述入口函数在 Mbuf 头部分配操作失败时将失败返回。 <function>&mac.mpo;_init_mount_label</function> void &mac.mpo;_init_mount_label struct label *mntlabel struct label *fslabel &mac.thead; mntlabel 将被初始化的mount 结构策略标记 fslabel 将被初始化的文件系统策略标记 为一个新近实例化的 mount 点初始化标记。可以睡眠。 <function>&mac.mpo;_init_mount_fs_label</function> void &mac.mpo;_init_mount_fs_label struct label *label &mac.thead; label 将被初始化的标记 为一个新近加载的文件系统初始化标记。可以睡眠。 <function>&mac.mpo;_init_pipe_label</function> void &mac.mpo;_init_pipe_label struct label*label &mac.thead; label 将被填写的标记 为一个刚刚实例化的管道初始化安全标记。可以睡眠。 <function>&mac.mpo;_init_socket_label</function> void &mac.mpo;_init_socket_label struct label *label int flag &mac.thead; label 将被初始化的新标记 flag &man.malloc.9; flags 为一个刚刚实例化的套接字初始化安全标记。其中的 flag 域的值必须被指定为 M_WAITOKM_NOWAIT之一,以避免在该初始化程中使用可能睡眠的&man.malloc.9; 。 <function>&mac.mpo;_init_socket_peer_label</function> void &mac.mpo;_init_socket_peer_label struct label *label int flag &mac.thead; label 将被初始化的新标记 flag &man.malloc.9; flags 为刚刚实例化的套接字对等体进行标记的初始化。其中的 flag 域的值必须被指定为 M_WAITOKM_NOWAIT 之一,以避免在该初始化程中使用可能睡眠的 &man.malloc.9;。 <function>&mac.mpo;_init_proc_label</function> void &mac.mpo;_init_proc_label struct label *label &mac.thead; label 将被初始化的新标记 为一个刚刚实例化的进程初始化安全标记。可以睡眠。 <function>&mac.mpo;_init_vnode_label</function> void &mac.mpo;_init_vnode_label struct label *label &mac.thead; label 将被初始化的新标记 为一个刚刚实例化的 vnode 初始化安全标记。可以睡眠。 <function>&mac.mpo;_destroy_bpfdesc_label</function> void &mac.mpo;_destroy_bpfdesc_label struct label *label &mac.thead; label bpfdesc 标记 销毁一个 BPF 描述子上的标记。在该入口函数中,策略应当释放所有在内部分配与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_cred_label</function> void &mac.mpo;_destroy_cred_label struct label *label &mac.thead; label 将被销毁的标记 销毁一个信任状上的标记。在该入口函数中,策略应当释放所有在内部分配的与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_devfsdirent_label</function> void &mac.mpo;_destroy_devfsdirent_label struct label *label &mac.thead; label 将被销毁的标记 销毁一个 devfs 表项上的标记。在该入口函数中,策略应当释放所有在内部分配的与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_ifnet_label</function> void &mac.mpo;_destroy_ifnet_label struct label *label &mac.thead; label 将被销毁的标记 销毁与一个已删除接口相关联的标记。在该入口函数中,策略应当释放所有在内部分配的与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_ipq_label</function> void &mac.mpo;_destroy_ipq_label struct label *label &mac.thead; label 将被销毁的标记 销毁与一个 IP 分片队列相关联的标记。在该入口函数中,策略应当释放所有在内部分配的与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_mbuf_label</function> void &mac.mpo;_destroy_mbuf_label struct label *label &mac.thead; label 将被销毁的标记 销毁与一个 Mbuf 相关联的标记。在该入口函数中,策略应当释放所有在内部分配的与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_mount_label</function> void &mac.mpo;_destroy_mount_label struct label *label &mac.thead; label 将被销毁的 Mount 点标记 销毁与一个 mount 点相关联的标记。在该入口函数中,策略应当释放所有在内部分配的与 mntlabel 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_mount_label</function> void &mac.mpo;_destroy_mount_label struct label *mntlabel struct label *fslabel &mac.thead; mntlabel 将被销毁的 Mount 点标记 fslabel File system label being destroyed> 销毁与一个 mount 点相关联的标记。在该入口函数中,策略应当释放所有在内部分配的,与 mntlabelfslabel 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_socket_label</function> void &mac.mpo;_destroy_socket_label struct label *label &mac.thead; label 将被销毁的套接字标记 销毁与一个套接字相关联的标记。在该入口函数中,策略应当释放所有在内部分配的,与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_socket_peer_label</function> void &mac.mpo;_destroy_socket_peer_label struct label *peerlabel &mac.thead; peerlabel 将被销毁的套接字对等实体标记 销毁与一个套接字相关联的对等实体标记。在该入口函数中,策略应当释放所有在内部分配的,与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_pipe_label</function> void &mac.mpo;_destroy_pipe_label struct label *label &mac.thead; label 管道标记 销毁一个管道的标记。在该入口函数中,策略应当释放所有在内部分配的,与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_proc_label</function> void &mac.mpo;_destroy_proc_label struct label *label &mac.thead; label 进程标记 销毁一个进程的标记。在该入口函数中,策略应当释放所有在内部分配的,与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_destroy_vnode_label</function> void &mac.mpo;_destroy_vnode_label struct label *label &mac.thead; label 进程标记 销毁一个 vnode 的标记。在该入口函数中,策略应当释放所有在内部分配的,与 label 相关联的存储空间,以便销毁该标记。 <function>&mac.mpo;_copy_mbuf_label</function> void &mac.mpo;_copy_mbuf_label struct label *src struct label *dest &mac.thead; src 源标记 dest 目标标记 src 中的标记信息拷贝到 dest中。 <function>&mac.mpo;_copy_pipe_label</function> void &mac.mpo;_copy_pipe_label struct label *src struct label *dest &mac.thead; src 源标记 dest 目标标记 src 中的标记信息拷贝至 dest <function>&mac.mpo;_copy_vnode_label</function> void &mac.mpo;_copy_vnode_label struct label *src struct label *dest &mac.thead; src 源标记 dest 目标标记 src 中的标记信息拷贝至 dest <function>&mac.mpo;_externalize_cred_label</function> int &mac.mpo;_externalize_cred_label &mac.externalize.paramdefs; &mac.thead; &mac.externalize.tbody; &mac.externalize.para; <function>&mac.mpo;_externalize_ifnet_label</function> int &mac.mpo;_externalize_ifnet_label &mac.externalize.paramdefs; &mac.thead; &mac.externalize.tbody; &mac.externalize.para; <function>&mac.mpo;_externalize_pipe_label</function> int &mac.mpo;_externalize_pipe_label &mac.externalize.paramdefs; &mac.thead; &mac.externalize.tbody; &mac.externalize.para; <function>&mac.mpo;_externalize_socket_label</function> int &mac.mpo;_externalize_socket_label &mac.externalize.paramdefs; &mac.thead; &mac.externalize.tbody; &mac.externalize.para; <function>&mac.mpo;_externalize_socket_peer_label</function> int &mac.mpo;_externalize_socket_peer_label &mac.externalize.paramdefs; &mac.thead; &mac.externalize.tbody; &mac.externalize.para; <function>&mac.mpo;_externalize_vnode_label</function> int &mac.mpo;_externalize_vnode_label &mac.externalize.paramdefs; &mac.thead; &mac.externalize.tbody; &mac.externalize.para; <function>&mac.mpo;_internalize_cred_label</function> int &mac.mpo;_internalize_cred_label &mac.internalize.paramdefs; &mac.thead; &mac.internalize.tbody; &mac.internalize.para; <function>&mac.mpo;_internalize_ifnet_label</function> int &mac.mpo;_internalize_ifnet_label &mac.internalize.paramdefs; &mac.thead; &mac.internalize.tbody; &mac.internalize.para; <function>&mac.mpo;_internalize_pipe_label</function> int &mac.mpo;_internalize_pipe_label &mac.internalize.paramdefs; &mac.thead; &mac.internalize.tbody; &mac.internalize.para; <function>&mac.mpo;_internalize_socket_label</function> int &mac.mpo;_internalize_socket_label &mac.internalize.paramdefs; &mac.thead; &mac.internalize.tbody; &mac.internalize.para; <function>&mac.mpo;_internalize_vnode_label</function> int &mac.mpo;_internalize_vnode_label &mac.internalize.paramdefs; &mac.thead; &mac.internalize.tbody; &mac.internalize.para; 标记事件 策略模块使用MAC 框架提供的“标记事件”类入口函数,对内核对象的标记进行操作。策略模块将感兴趣的被标记内核对象的相关生命周期事件 注册在恰当的入口点上。对象的初始化、创建和销毁事件均提供了钩子点。在某些对象上还可以实现重新标记,即,允许用户进程改变对象上的标记值。 对某些对象可以实现其特定的对象事件,比如与 IP 重组相关的标记事件。一个典型的被标记对象在其生命周期中将拥有下列入口函数: 标记初始化 o (对象相关的等待) \ 标记创建 o \ 重新标记事件, o--<--. 各种对象相关的, | | 访问控制事件 ~-->--o \ 标记销毁 o 使用标记初始化入口函数,策略可以以一种统一的、与对象使用环境无关的方式设置标记的初始值。 分配给一个策略的缺省 slot 值为0,这样不使用标记的策略可能并不需要执行专门的初始化操作。 标记的创建事件发生在将一个内核数据结构同一个真实的内核对象相关联(内核对象实例化)的时刻。 例如,在真正被使用之前,在一个缓冲池内已分配的 mbuf 数据结构,将保持为“未使用”状态。 因此,mbuf 的分配操作将导致针对该 mbuf 的标记初始化操作,而 mbuf 的创建操作则被推迟到该 mbuf 真正与一个数据报相关联的时刻。 通常,调用者将会提供创建事件的上下文,包括创建环境、创建过程中涉及的其他对象的标记等。例如,当一个套接字创建一个 mbuf 时, 除了新创建的 mbuf 及其标记之外,作为创建者的套接字与其标记也被提交给策略检查。 不提倡在创建对象时就为其分配内存的原因有两个:创建操作可能发生在对性能有严格要求的内核接口上; 而且,因为创建调用不允许失败,所以无法报告内存分配失败。 对象特有的事件一般不会引发其他的标记事件,但是在对象上下文发生改变时,策略使用它们可以对相关标记进行修改或更新操作。 例如,在MAC_UPDATE_IPQ 入口函数之内,某个 IP 分片重组队列的标记可能会因为队列中接收了新的 mbuf 而被更新。 访问控制事件将在后续章节中详细讨论。 策略通过执行标记销毁操作,释放为其分配的存储空间或维护的状态,之后内核才可以重用或者释放对象的内核数据结构。 除了与特定内核对象绑定的普通标记之外,还有一种额外的标记类型:临时标记。这些标记用于存放由用户进程提交的更新信息。 它们的初始化和销毁操作与其他标记一样,只是创建事件,MAC_INTERNALIZE,略有不同: 该函数接受用户提交的标记,负责将其转化为内核表示形式。 文件系统对象标记事件操作 <function>&mac.mpo;_associate_vnode_devfs</function> void &mac.mpo;_associate_vnode_devfs struct mount *mp struct label *fslabel struct devfs_dirent *de struct label *delabel struct vnode *vp struct label *vlabel &mac.thead; mp Devfs 挂载点 fslabel Devfs 文件系统标记 (mp->mnt_fslabel) de Devfs 目录项 delabel de 相关联的策略标记 vp de 相关联的 vnode vlabel vp 相关联的策略标记 根据参数 de 传入的 devfs 目录项及其标记信息,为一个新近创建的 devfs vnode 填充标记(vlabel)。 <function>&mac.mpo;_associate_vnode_extattr</function> int &mac.mpo;_associate_vnode_extattr struct mount *mp struct label *fslabel struct vnode *vp struct label *vlabel &mac.thead; mp 文件系统挂载点 fslabel 文件系统标记 vp 将被标记的 vnode vlabel vp 相关联的策略标记 从文件系统扩展属性中读取 vp 的标记。成功,返回 0。 不成功,则在 errno 指定的相应的错误编码。 如果文件系统不支持扩展属性的读取操作,则可以考虑将 fslabel 拷贝至 vlabel <function>&mac.mpo;_associate_vnode_singlelabel</function> void &mac.mpo;_associate_vnode_singlelabel struct mount *mp struct label *fslabel struct vnode *vp struct label *vlabel &mac.thead; mp 文件系统挂载点 fslabel 文件系统标记 vp 将被标记的 vnode vlabel vp 相关联的策略标记 在非多重标记文件系统上,使用该入口函数,根据文件系统标记,fslabel, 为 vp 设置策略标记。 <function>&mac.mpo;_create_devfs_device</function> void &mac.mpo;_create_devfs_device dev_t dev struct devfs_dirent *devfs_dirent struct label *label &mac.thead; dev devfs_dirent 对应的设备 devfs_dirent 将被标记的 Devfs 目录项 label 将被填写的 devfs_dirent 标记 为传入设备新建的 devfs_dirent 填写标记。该函数将在设备文件系统加载、重构或添加新设备时被调用。 <function>&mac.mpo;_create_devfs_directory</function> void &mac.mpo;_create_devfs_directory char *dirname int dirnamelen struct devfs_dirent *devfs_dirent struct label *label &mac.thead; dirname 新建目录的名字 namelen 字符串 dirname 的长度 devfs_dirent 新建目录在 Devfs 中对应的目录项 为传入目录参数的新建 devfs_dirent 填写标记。该函数将在加载、重构设备文件系统,或者添加一个需要指定目录结构的新设备时被调用。 <function>&mac.mpo;_create_devfs_symlink</function> void &mac.mpo;_create_devfs_symlink struct ucred *cred struct mount *mp struct devfs_dirent *dd struct label *ddlabel struct devfs_dirent *de struct label *delabel &mac.thead; cred 主体信任状 mp devfs 挂载点 dd 链接目标 ddlabel dd 相关联的标记 de 符号链接项 delabel de 相关联的策略标记 为新近创建的 &man.devfs.5; 符号链接项填写标记(delabel)。 <function>&mac.mpo;_create_vnode_extattr</function> int &mac.mpo;_create_vnode_extattr struct ucred *cred struct mount *mp struct label *fslabel struct vnode *dvp struct label *dlabel struct vnode *vp struct label *vlabel struct componentname *cnp &mac.thead; cred 主体信任状 mount 文件系统挂载点 label 文件系统标记 dvp 父目录 vnode dlabel dvp 相关联的策略标记 vp 新创建的 vnode vlabel vp 相关联的策略标记 cnp vp中的子域名字 vp 的标记写入文件扩展属性。成功,将标记填入 vlabel, 并返回 0。否则,返回对应的错误编码。 <function>&mac.mpo;_create_mount</function> void &mac.mpo;_create_mount struct ucred *cred struct mount *mp struct label *mnt struct label *fslabel &mac.thead; cred 主体信任状 mp 客体;将被挂载的文件系统 mntlabel 将被填写的 mp 的策略标记 fslabel 将被挂载到 mp 的文件系统的策略标记。 为传入的主体信任状所创建的挂载点填写标记。该函数将在文件系统挂载时被调用。 <function>&mac.mpo;_create_root_mount</function> void &mac.mpo;_create_root_mount struct ucred *cred struct mount *mp struct label *mntlabel struct label *fslabel &mac.thead; . 为传入的主体信任状所创建的挂载点填写标记。该函数将在挂载根文件系统时,&mac.mpo;_create_mount; 之后被调用。 <function>&mac.mpo;_relabel_vnode</function> void &mac.mpo;_relabel_vnode struct ucred *cred struct vnode *vp struct label *vnodelabel struct label *newlabel &mac.thead; cred 主体信任状 vp 将被重新标记的 vnode vnodelabel vp 现有的策略标记 newlabel 将取代vnodelabel的新(可能只是部分)标记 根据传入的新标记和主体信任状,更新参数 vnode 的标记。 <function>&mac.mpo;_setlabel_vnode_extattr</function> int &mac.mpo;_setlabel_vnode_extattr struct ucred *cred struct vnode *vp struct label *vlabel struct label *intlabel &mac.thead; cred 主体信任状 vp 写出标记所对应的 vnode vlabel vp的策略标记 intlabel 将被写入磁盘的标记 将参数 intlabel 给出的标记信息写入指定 vnode 的扩展属性。 该函数被 vop_stdcreatevnode_ea 所调用。 <function>&mac.mpo;_update_devfsdirent</function> void &mac.mpo;_update_devfsdirent struct devfs_dirent *devfs_dirent struct label *direntlabel struct vnode *vp struct label *vnodelabel &mac.thead; devfs_dirent 客体;devfs 目录项 direntlabel 将被更新的devfs_dirent的策略标记 vp 父 vnode 已锁定 vnodelabel vp的策略标记 根据所传入的 devfs vnode 标记,对 devfs_dirent 的标记进行更新。 重新标记一个 devfs vnode 的操作成功之后,将调用该函数来确认标记的改变,如此,即使相应的 vnode 数据结构被内核回收重用, 也不会丢失标记的新状态。另外,在 devfs 中新建一个符号链接时,紧接着mac_vnode_create_from_vnode, 也将调用该函数,对 vnode 标记进行初始化操作。 IPC 对象标记事件操作 <function>&mac.mpo;_create_mbuf_from_socket</function> void &mac.mpo;_create_mbuf_from_socket struct socket *so struct label *socketlabel struct mbuf *m struct label *mbuflabel &mac.thead; socket 套接字 套接字锁定 WIP socketlabel socket 的策略标记 m 客体;mbuf mbuflabel 将被填写的 m 的策略标记 根据传入的套接字标记为新创建的mbuf头部设置标记。 每当套接字产生一个新的数据报或者消息,并将其存储在参数 mbuf 中时,将调用该函数。 <function>&mac.mpo;_create_pipe</function> void &mac.mpo;_create_pipe struct ucred *cred struct pipe *pipe struct label *pipelabel &mac.thead; cred 主体信任状 pipe 管道 pipelabel pipe 的策略标记 根据传入的主体信任状参数,设置新建管道的标记。每当一个新管道被创建,该函数将被调用。 <function>&mac.mpo;_create_socket</function> void &mac.mpo;_create_socket struct ucred *cred struct socket *so struct label *socketlabel &mac.thead; cred 主体信任状 不可改变 so 客体;将被标记的套接字 socketlabel 将被填写的 so 的标记 根据传入的主体信任状参数,设置新建套接字的标记。每当新建一个套接字,该函数将被调用。 <function>&mac.mpo;_create_socket_from_socket</function> void &mac.mpo;_create_socket_from_socket struct socket *oldsocket struct label *oldsocketlabel struct socket *newsocket struct label *newsocketlabel &mac.thead; oldsocket 监听套接字 oldsocketlabel oldsocket 的策略标记 newsocket 新建套接字 newsocketlabel newsocket 的策略标记 根据 &man.listen.2 套接字 oldsocket, 为新建 &man.accept.2 的套接字 newsocket,设置标记。 <function>&mac.mpo;_relabel_pipe</function> void &mac.mpo;_relabel_pipe struct ucred *cred struct pipe *pipe struct label *oldlabel struct label *newlabel &mac.thead; cred 主体信任状 pipe 管道 oldlabel pipe 的当前策略标记 newlabel 将为pipe 设置的新的策略标记 pipe设置新标记newlabel <function>&mac.mpo;_relabel_socket</function> void &mac.mpo;_relabel_socket struct ucred *cred struct socket *so struct label *oldlabel struct label *newlabel &mac.thead; cred 主体信任状 不可改变 so 客体;套接字 oldlabel so 的当前标记 newlabel 将为socket 设置的新标记 根据传入的标记参数,对套接字的当前标记进行更新。 <function>&mac.mpo;_set_socket_peer_from_mbuf</function> void &mac.mpo;_set_socket_peer_from_mbuf struct mbuf *mbuf struct label *mbuflabel struct label *oldlabel struct label *newlabel &mac.thead; mbuf 从套接字接收到的第一个数据报 mbuflabel mbuf 的标记 oldlabel 套接字的当前标记 newlabel 将为套接字填写的策略标记 根据传入的 mbuf 标记,设置某个 stream 套接字的对等标志。 除Unix域的套接字之外,每当一个 stream 套接字接收到第一个数据报时,该函数将被调用。 <function>&mac.mpo;_set_socket_peer_from_socket</function> void &mac.mpo;_set_socket_peer_from_socket struct socket *oldsocket struct label *oldsocketlabel struct socket *newsocket struct label *newsocketpeerlabel &mac.thead; oldsocket 本地套接字 oldsocketlabel oldsocket 的策略标记 newsocket 对等套接字 newsocketpeerlabel 将为newsocket填写的策略标记 根据传入的远程套接字端点,为一个 stream UNIX 与套接字设置对等标记。 每当相应的套接字对之间进行连接时,该函数将在两端分别被调用。 Network Object Labeling Event Operations <function>&mac.mpo;_create_bpfdesc</function> void &mac.mpo;_create_bpfdesc struct ucred *cred struct bpf_d *bpf_d struct label *bpflabel &mac.thead; cred 主体信任状 不可改变 bpf_d 客体;bpf 描述子 bpf 将为bpf_d填写的策略标记 根据传入的主体信任状参数,为新建的 BPF 描述子设置标记。 当进程打开 BPF 设备节点时,该函数将被调用。 <function>&mac.mpo;_create_ifnet</function> void &mac.mpo;_create_ifnet struct ifnet *ifnet struct label *ifnetlabel &mac.thead; ifnet 网络接口 ifnetlabel 将为ifnet填写的策略标记 为新建的网络接口设置标记。该函数在以下情况下被调用: 当一个新的物理接口变为可用时,或者当一个伪接口在引导时或由于某个用户操作而实例化时。 <function>&mac.mpo;_create_ipq</function> void &mac.mpo;_create_ipq struct mbuf *fragment struct label *fragmentlabel struct ipq *ipq struct label *ipqlabel &mac.thead; fragment 第一个被接收的 IP 分片 fragmentlabel fragment 的策略标记 ipq 将被标记的 IP 重组队列 ipqlabel 将为ipq填写的策略标记 根据第一个接收到的分片的 mbuf 头部信息,为新建的 IP 分片重组队列设置标记。 <function>&mac.mpo;_create_datagram_from_ipq</function> void &mac.mpo;_create_create_datagram_from_ipq struct ipq *ipq struct label *ipqlabel struct mbuf *datagram struct label *datagramlabel &mac.thead; ipq IP 重组队列 ipqlabel ipq 的策略标记 datagram 将被标记的数据报 datagramlabel 将为datagramlabel填写的策略标记 根据 IP 分片重组队列,为刚刚重组完毕的 IP 数据报设置标记。 <function>&mac.mpo;_create_fragment</function> void &mac.mpo;_create_fragment struct mbuf *datagram struct label *datagramlabel struct mbuf *fragment struct label *fragmentlabel &mac.thead; datagram 数据报 datagramlabel datagram 的策略标记 fragment 将被标记的分片 fragmentlabel 将为datagram填写的策略标记 根据数据报所对应的 mbuf 头部信息,为其新建的分片的 mbuf 头部设置标记。 <function>&mac.mpo;_create_mbuf_from_mbuf</function> void &mac.mpo;_create_mbuf_from_mbuf struct mbuf *oldmbuf struct label *oldmbuflabel struct mbuf *newmbuf struct label *newmbuflabel &mac.thead; oldmbuf 已有的(源)mbuf oldmbuflabel oldmbuf 的策略标记 newmbuf 将被标记的新建 mbuf newmbuflabel 将为newmbuf填写的策略标记 根据某个现有数据报的 mbuf 头部信息,为新建数据报的 mbuf 头部设置标记。在许多条件下将会调用该函数, 比如,由于对齐要求而重新分配某个 mbuf 时。 <function>&mac.mpo;_create_mbuf_linklayer</function> void &mac.mpo;_create_mbuf_linklayer struct ifnet *ifnet struct label *ifnetlabel struct mbuf *mbuf struct label *mbuflabel &mac.thead; ifnet 网络接口 ifnetlabel ifnet 的策略标记 mbuf 新建数据报的 mbuf 头部 mbuflabel 将为mbuf填写的策略标记 为在给定接口上由于某个链路层响应而新建的数据报的mbuf头部设置标记。 该函数将在若干条件下被调用,比如当IPv4和IPv6协议栈在响应ARP或者ND6时。 <function>&mac.mpo;_create_mbuf_from_bpfdesc</function> void &mac.mpo;_create_mbuf_from_bpfdesc struct bpf_d *bpf_d struct label *bpflabel struct mbuf *mbuf struct label *mbuflabel &mac.thead; bpf_d BPF 描述子 bpflabel bpflabel 的策略标记 mbuf 将被标记的新建 mbuf mbuflabel 将为mbuf填写的策略标记 为使用参数 BPF 描述子创建的新数据报的 mbuf 头部设置标记。 当对参数 BPF 描述子所关联的 BPF 设备进行写操作时,该函数将被调用。 <function>&mac.mpo;_create_mbuf_from_ifnet</function> void &mac.mpo;_create_mbuf_from_ifnet struct ifnet *ifnet struct label *ifnetlabel struct mbuf *mbuf struct label *mbuflabel &mac.thead; ifnet 网络接口 ifnetlabel ifnetlabel 的策略标记 mbuf 新建数据报的 mbuf 头部 mbuflabel 将为mbuf填写的策略标记 为从网络接口参数创建的数据报的 mbuf 头部设置标记。 <function>&mac.mpo;_create_mbuf_multicast_encap</function> void &mac.mpo;_create_mbuf_multicast_encap struct mbuf *oldmbuf struct label *oldmbuflabel struct ifnet *ifnet struct label *ifnetlabel struct mbuf *newmbuf struct label *newmbuflabel &mac.thead; oldmbuf 现有数据报的 mbuf 头部 oldmbuflabel oldmbuf 的策略标记 ifnet 网络接口 ifnetlabel ifnet 的策略标记 newmbuf 将被标记的新建数据报 mbuf 头部 newmbuflabel 将为newmbuf填写的策略标记 当传入的已有数据报被给定多播封装接口(multicast encapsulation interface)处理时被调用, 为新创建的数据报所在 mbuf 头部设置标记。 每当使用该虚拟接口传递一个mbuf时,将调用该函数。 <function>&mac.mpo;_create_mbuf_netlayer</function> void &mac.mpo;_create_mbuf_netlayer struct mbuf *oldmbuf struct label *oldmbuflabel struct mbuf *newmbuf struct label *newmbuflabel &mac.thead; oldmbuf 接收的数据报 oldmbuflabel oldmbuf 的策略标记 newmbuf 新建数据报 newmbuflabel newmbuf 的策略标记 为由 IP 堆栈因为响应接收数据报(oldmbuf)而新建的数据报设置其 mbuf 头部的标记。 许多情况下需要调用该函数,比如,响应 ICMP 请求数据报时。 <function>&mac.mpo;_fragment_match</function> int &mac.mpo;_fragment_match struct mbuf *fragment struct label *fragmentlabel struct ipq *ipq struct label *ipqlabel &mac.thead; fragment IP 数据报分片 fragmentlabel fragment 的策略标记 ipq IP 分片重组队列 ipqlabel ipq 的策略标记 根据所传入的 IP 分片重组队列(ipq)的标记, 检查包含一个 IP 数据报(fragment)的 mbuf 的头部是否符合其要求。 符合,则返回1。否则,返回0。 每当 IP 堆栈尝试将一个刚刚接收到的分片放入某个已有的分片重组队列中时,将调用该函数进行安全检查; 如果失败,将为分片重新实例化一个新的分片重组队列。 策略可以利用该入口函数,根据标记或者其他信息阻止不期望的 IP 分片重组。 <function>&mac.mpo;_relabel_ifnet</function> void &mac.mpo;_relabel_ifnet struct ucred *cred struct ifnet *ifnet struct label *ifnetlabel struct label *newlabel &mac.thead; cred 主体信任状 ifnet 客体;网络接口 ifnetlabel ifnet 的策略标记 newlabel 将为ifnet设置的新标记 根据所传入的新标记,newlabel,以及主体信任状, cred,对网络接口的标记进行更新。 <function>&mac.mpo;_update_ipq</function> void &mac.mpo;_update_ipq struct mbuf *fragment struct label *fragmentlabel struct ipq *ipq struct label *ipqlabel &mac.thead; mbuf IP 分片 mbuflabel mbuf 的策略标记 ipq IP 分片重组队列 ipqlabel 将被更新的ipq的当前策略标记 根据所传入的 IP 分片 mbuf 头部(mbuf)为接收 它的 IP 分片重组队列(ipq)的标记进行更新。 进程标记事件操作 <function>&mac.mpo;_create_cred</function> void &mac.mpo;_create_cred struct ucred *parent_cred struct ucred *child_cred &mac.thead; parent_cred 父主体信任状 child_cred 子主体信任状 根据所传入的主体信任状,为新建的主体信任状设置标记。 每当为一个新建的 struct ucred调用 &man.crcopy.9; 时,将调用此函数。 该函数不应与进程复制(forking)或者创建事件混为一谈。 <function>&mac.mpo;_execve_transition</function> void &mac.mpo;_execve_transition struct ucred *old struct ucred *new struct vnode *vp struct label *vnodelabel &mac.thead; old 已有的主体信任状 不可改变 new 将被标记的新主体信任状 vp 将被执行的文件 已被锁定 vnodelabel vp 的策略标记 一个拥有信任状old的主体由于执行(vp文件而导致标记转换时, 该函数根据vnode标记为该主体重新标记为new。 每当一个进程请求执行vnode文件,而通过 入口函数mpo_execve_will_transition 有成功返回的策略时,将调用该函数。 策略模块可以通过传入两个主体信任状和简单地调用 mpo_create_cred 来实现该入口函数, so as not to implement a transitioning event. 一旦策略实现了mpo_create_cred函数,即使没有实现 mpo_execve_will_transition,也应该实现该函数。 <function>&mac.mpo;_execve_will_transition</function> int &mac.mpo;_execve_will_transition struct ucred *old struct vnode *vp struct label *vnodelabel &mac.thead; old 在执行&man.execve.2;之前的主体信任状 不可改变 vp 将被执行的文件 vnodelabel vp 的策略标记 由策略决定,当参数主体信任状执行参数 vnode 时,是否需要进行一个标记转换操作。如果需要,返回1; 否则,返回0。即使一个策略返回0,它也必须为自己不期望的对 mpo_execve_transition的调用作好准备,因为只要有其他任何一个策略要求转换,就将执行此函数。 <function>&mac.mpo;_create_proc0</function> void &mac.mpo;_create_proc0 struct ucred *cred &mac.thead; cred 将被填写的主体信任状 为进程0,所有内核进程的祖先,创建主体信任状。 <function>&mac.mpo;_create_proc1</function> void &mac.mpo;_create_proc1 struct ucred *cred &mac.thead; cred 将被填写的主体信任状 为进程1,所有用户进程的祖先,创建主体信任状。 <function>&mac.mpo;_relabel_cred</function> void &mac.mpo;_relabel_cred struct ucred *cred struct label *newlabel &mac.thead; cred 主体信任状 newlabel 将被应用到 cred 上的新标记 根据传入的新标记,对主体信任状上的标记进行更新。 访问控制检查 通过访问控制入口函数,策略模块能影响内核的访问控制决策。 通常情况下,不是绝对,一个访问控制入口函数的参数有,一个或者若干个授权信任状,和相关操作涉及的其他任何对象的信息(其中可能包含标记)。 访问控制入口函数返回0,表示允许该操作;否则,返回一个 &man.errno.2; 错误编码。调用该入口函数,将遍历所有系统注册的策略模块,逐一进行 策略相关的检查和决策,之后按照下述方法组合不同策略的返回结果:只有当所有的模块均允许该操作时,才成功返回。 否则,如果有一个或者若干模块失败返回,则整个检查不通过。如果有多个模块的检查出错返回,将由定义在kern_mac.c 中的 error_select() 函数从它们返回的错误编码中,选择一个合适的,返回给用户。 最高优先级 EDEADLK EINVAL ESRCH EACCES 最低优先级 EPERM 如果所有策略模块返回的错误编码均没有出现在上述优先级序列表中,则任意选择一个返回。 选择错误编码的一般次序为:内核错误,无效的参数,对象不存在,访问被拒绝,和其他错误。 <function>&mac.mpo;_check_bpfdesc_receive</function> int &mac.mpo;_check_bpfdesc_receive struct bpf_d *bpf_d struct label *bpflabel struct ifnet *ifnet struct label *ifnetlabel &mac.thead; bpf_d 主体;BPF 描述子 bpflabel bpf_d 的策略标记 ifnet 客体;网络接口 ifnetlabel ifnet 的策略标记 决定 MAC 框架是否应该允许将由参数接口接收到的数据报传递给由 BPF 描述子所对应的缓冲区。成功,则返回0; 否则,返回错误编码信息errno。建议使用的错误编码有:EACCES,用于标记不符的情况; EPERM,用于缺少特权的情况。 <function>&mac.mpo;_check_kenv_dump</function> int &mac.mpo;_check_kenv_dump struct ucred *cred &mac.thead; cred 主体信任状 决定相关主体是否应该被允许查询内核环境状态(参考 &man.kenv.2;)。 <function>&mac.mpo;_check_kenv_get</function> int &mac.mpo;_check_kenv_get struct ucred *cred char *name &mac.thead; cred 主体信任状 name 内核的环境变量名字 决定相关主体是否可以查询内核中给定环境变量的状态。 <function>&mac.mpo;_check_kenv_set</function> int &mac.mpo;_check_kenv_set struct ucred *cred char *name &mac.thead; cred 主体信任状 name 内核的环境变量名字 决定相关主体是否有权设置给定内核环境变量的值。 <function>&mac.mpo;_check_kenv_unset</function> int &mac.mpo;_check_kenv_unset struct ucred *cred char *name &mac.thead; cred 主体信任状 name 内核的环境变量名字Kernel environment variable name 决定相关主体是否有权清除给定的内核环境变量的设置。 <function>&mac.mpo;_check_kld_load</function> int &mac.mpo;_check_kld_load struct ucred *cred struct vnode *vp struct label *vlabel &mac.thead; cred 主体信任状 vp 内核模块的 vnode vlabel vp的策略标记 决定相关主体是否有权加载给定的模块文件。 <function>&mac.mpo;_check_kld_stat</function> int &mac.mpo;_check_kld_stat struct ucred *cred &mac.thead; cred 主体信任状 决定相关主体是否有权访问内核的加载模块文件链表以及相关的统计数据。 <function>&mac.mpo;_check_kld_unload</function> int &mac.mpo;_check_kld_unload struct ucred *cred &mac.thead; cred 主体信任状 决定相关主体是否有权卸载一个内核模块。 <function>&mac.mpo;_check_pipe_ioctl</function> int &mac.mpo;_check_pipe_ioctl struct ucred *cred struct pipe *pipe struct label *pipelabel unsigned long cmd void *data &mac.thead; cred 主体信任状 pipe 管道 pipelabel pipe的策略标记 cmd &man.ioctl.2; 命令 data &man.ioctl.2; 数据 决定相关主体是否有权调用指定的 &man.ioctl.2; 系统调用。 <function>&mac.mpo;_check_pipe_poll</function> int &mac.mpo;_check_pipe_poll struct ucred *cred struct pipe *pipe struct label *pipelabel &mac.thead; cred 主体信任状 pipe 管道 pipelabel pipe的策略标记 决定相关主体是否有权对管道pipe执行poll操作。 <function>&mac.mpo;_check_pipe_read</function> int &mac.mpo;_check_pipe_read struct ucred *cred struct pipe *pipe struct label *pipelabel &mac.thead; cred 主体信任状 pipe 管道 pipelabel pipe的策略标记 决定该主体是否有权读取pipe <function>&mac.mpo;_check_pipe_relabel</function> int &mac.mpo;_check_pipe_relabel struct ucred *cred struct pipe *pipe struct label *pipelabel struct label *newlabel &mac.thead; cred 主体信任状 pipe 管道 pipelabel pipe的当前策略标记 newlabel 将为pipelabel设置的新标记 决定该主体是否有权为pipe重新设置标记。 <function>&mac.mpo;_check_pipe_stat</function> int &mac.mpo;_check_pipe_stat struct ucred *cred struct pipe *pipe struct label *pipelabel &mac.thead; cred 主体信任状 pipe 管道 pipelabel pipe的策略标记 决定该主体是否有权查询与pipe相关的统计信息。 <function>&mac.mpo;_check_pipe_write</function> int &mac.mpo;_check_pipe_write struct ucred *cred struct pipe *pipe struct label *pipelabel &mac.thead; cred 主体信任状 pipe 管道 pipelabel pipe的策略标记 决定该主体是否有权写pipe <function>&mac.mpo;_check_socket_bind</function> int &mac.mpo;_check_socket_bind struct ucred *cred struct socket *socket struct label *socketlabel struct sockaddr *sockaddr &mac.thead; cred 主体信任状 socket 将被绑定的套接字 socketlabel socket的策略标记 sockaddr socket的地址 <function>&mac.mpo;_check_socket_connect</function> int &mac.mpo;_check_socket_connect struct ucred *cred struct socket *socket struct label *socketlabel struct sockaddr *sockaddr &mac.thead; cred 主体信任状 socket 将被连接的套接字 socketlabel socket的策略标记 sockaddr socket的地址 决定该主体(cred)是否有权将套接字(socket)绑定到地址 sockaddr。成功,返回0,否则返回一个错误编码errno。 建议采用的错误编码有:EACCES,用于标记不符的情况;EPERM,用于特权不足的情况。 <function>&mac.mpo;_check_socket_receive</function> int &mac.mpo;_check_socket_receive struct ucred *cred struct socket *so struct label *socketlabel &mac.thead; cred 主体信任状 so 套接字 socketlabel so的策略标记 决定该主体是否有权查询套接字so的相关信息。 <function>&mac.mpo;_check_socket_send</function> int &mac.mpo;_check_socket_send struct ucred *cred struct socket *so struct label *socketlabel &mac.thead; cred 主体信任状 so 套接字 socketlabel so的策略标记 决定该主体是否有权通过套接字so发送信息。 <function>&mac.mpo;_check_cred_visible</function> int &mac.mpo;_check_cred_visible struct ucred *u1 struct ucred *u2 &mac.thead; u1 主体信任状 u2 对象信任状 确定该主体信任状u1是否有权 see 具有信任状u2 的其他主体。 成功,返回0;否则,返回错误编码errno。建议采用的错误编码有: EACCES,用于标记不符的情况;EPERM,用于特权不足的情况;ESRCH, 用来提供不可见性。该函数可在许多环境下使用,包括命令ps所使用的进程间的状态 sysctl,以及通过procfs 的状态查询操作。 <function>&mac.mpo;_check_socket_visible</function> int &mac.mpo;_check_socket_visible struct ucred *cred struct socket *socket struct label *socketlabel &mac.thead; cred 主体信任状 socket 客体;套接字 socketlabel socket的策略标记 <function>&mac.mpo;_check_ifnet_relabel</function> int &mac.mpo;_check_ifnet_relabel struct ucred *cred struct ifnet *ifnet struct label *ifnetlabel struct label *newlabel &mac.thead; cred 主体信任状 ifnet 客体;网络接口 ifnetlabel ifnet现有的策略标记 newlabel 将被应用到ifnet上的新的策略标记 决定该主体信任状是否有权使用传入的标记更新参数对给定的网络接口的标记进行重新设置。 <function>&mac.mpo;_check_socket_relabel</function> int &mac.mpo;_check_socket_relabel struct ucred *cred struct socket *socket struct label *socketlabel struct label *newlabel &mac.thead; cred 主体信任状 socket 客体;套接字 socketlabel socket现有的策略标记 newlabel 将被应用到socketlabel上的更新标记 决定该主体信任状是否有权采用传入的标记对套接字参数的标记进行重新设置。 <function>&mac.mpo;_check_cred_relabel</function> int &mac.mpo;_check_cred_relabel struct ucred *cred struct label *newlabel &mac.thead; cred 主体信任状 newlabel 将被应用到cred上的更新标记 决定该主体信任状是否有权将自己的标记重新设置为给定的更新标记。 <function>&mac.mpo;_check_vnode_relabel</function> int &mac.mpo;_check_vnode_relabel struct ucred *cred struct vnode *vp struct label *vnodelabel struct label *newlabel &mac.thead; cred 主体信任状 不可改变 vp 客体;vnode 已被锁定 vnodelabel vp现有的策略标记 newlabel 将被应用到vp上的策略标记 决定该主体信任状是否有权将参数 vnode 的标记重新设置为指定标记。 <function>&mac.mpo;_check_mount_stat</function> int &mac.mpo;_check_mount_stat struct ucred *cred struct mount *mp struct label *mountlabel &mac.thead; cred 主体信任状 mp 客体;文件系统挂载 mountlabel mp的策略标记 确定相关主体信任状是否有权查看在给定文件系统上执行 statfs 的结果。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 该函数可能在下列情况下被调用: 在 &man.statfs.2; 和其他相关调用期间,或者当需要从文件系统列表中选择排除哪个文件系统时,比如, 调用 &man.getfsstat.2;时。 <function>&mac.mpo;_check_proc_debug</function> int &mac.mpo;_check_proc_debug struct ucred *cred struct proc *proc &mac.thead; cred 主体信任状 不可改变 proc 客体;进程 确定相关主体信任状是否有权 debug 给定进程。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配;EPERM,用于权限不够; ESRCH,用于隐瞒目标的存在。 &man.ptrace.2; 和 &man.ktrace.2; API,以及某些 procfs 操作将调用该函数。 <function>&mac.mpo;_check_vnode_access</function> int &mac.mpo;_check_vnode_access struct ucred *cred struct vnode *vp struct label *label int flags &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 flags &man.access.2; 标志 根据相关主体信任状决定其对给定 vnode 以给定访问标志执行的 &man.access.2; 和其他相关调用的返回值。一般,应采用与&mac.mpo;_check_vnode_open 相同的语义来实现该函数。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_chdir</function> int &mac.mpo;_check_vnode_chdir struct ucred *cred struct vnode *dvp struct label *dlabel &mac.thead; cred 主体信任状 dvp 客体;&man.chdir.2; 的目的 vnode dlabel dvp的策略标记 确定相关主体信任状是否有权将进程工作目录切换到给定 vnode。成功,则返回 0; 否则,返回一个 errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_chroot</function> int &mac.mpo;_check_vnode_chroot struct ucred *cred struct vnode *dvp struct label *dlabel &mac.thead; cred 主体信任状 dvp 目录 vnode dlabel dvp相关联的策略标记 确定相关主体是否有权 &man.chroot.2; 到由 (dvp)给定的目录。 <function>&mac.mpo;_check_vnode_create</function> int &mac.mpo;_check_vnode_create struct ucred *cred struct vnode *dvp struct label *dlabel struct componentname *cnp struct vattr *vap &mac.thead; cred 主体信任状 dvp 客体;vnode dlabel dvp的策略标记 cnp dvp中的成员名 vap vap的 vnode 属性 确定相关主体信任状是否有权在给定父目录,以给定的名字和属性, 常见一个 vnode。成功,则返回 0;否则, 返回一个errno值。 建议使用的错误编码:EACCES 来表示用于标记不匹配, 而用 EPERM,用于权限不足。 以O_CREAT为参数调用 &man.open.2;,或对 &man.mknod.2;,&man.mkfifo.2; 等的调用将导致该函数被调用。 <function>&mac.mpo;_check_vnode_delete</function> int &mac.mpo;_check_vnode_delete struct ucred *cred struct vnode *dvp struct label *dlabel struct vnode *vp void *label struct componentname *cnp &mac.thead; cred 主体信任状 dvp 父目录 vnode dlabel dvp的策略标记 vp 客体;将被删除的 vnode label vp的策略标记 cnp vp中的成员名 确定相关主体信任状是否有权从给定的父目录中,删除给定名字的 vnode。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 使用 &man.unlink.2; 和 &man.rmdir.2;,将导致该函数被调用。 提供该入口函数的策略还必须实现一个 mpo_check_rename_to, 用来授权由于重命名操作导致的目标文件的删除。 <function>&mac.mpo;_check_vnode_deleteacl</function> int &mac.mpo;_check_vnode_deleteacl struct ucred *cred struct vnode *vp struct label *label acl_type_t type &mac.thead; cred 主体信任状 不可改变 vp 客体;vnode 被锁定 label vp的策略标记 type ACL 类型 确定相关主体信任状是否有权删除给定 vnode 的给定类型的 ACL。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_exec</function> int &mac.mpo;_check_vnode_exec struct ucred *cred struct vnode *vp struct label *label &mac.thead; cred 主体信任状 vp 客体;将被执行的 vnode label vp的策略标记 确定相关主体信任状是否有权执行给定 vnode。 对于执行特权的决策与任何瞬时事件的决策是严格分开的。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_getacl</function> int &mac.mpo;_check_vnode_getacl struct ucred *cred struct vnode *vp struct label *label acl_type_t type &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 type ACL 类型 确定相关主体信任状是否有权查询给定 vnode 上的给定类型的 ACL。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_getextattr</function> int &mac.mpo;_check_vnode_getextattr struct ucred *cred struct vnode *vp struct label *label int attrnamespace const char *name struct uio *uio &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 attrnamespace 扩展属性名字空间 name 扩展属性名 uio I/O 结构指针;参见 &man.uio.9; 确定相关主体信任状是否有权查询给定 vnode 上给定名字空间和名字的扩展属性。 使用扩展属性实现标记存储的策略模块可能会需要对这些扩展属性的操作进行特殊处理。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_link</function> int &mac.mpo;_check_vnode_link struct ucred *cred struct vnode *dvp struct label *dlabel struct vnode *vp struct label *label struct componentname *cnp &mac.thead; cred 主体信任状 dvp 目录 vnode dlabel dvp相关联的策略标记 vp 链接目的 vnode label vp相关联的策略标记 cnp 将被创建的链接对应的成员名 确定相关主体是否有权为参数vp给定的 vnode 创建一个由参数cnp给定名字的链接。 <function>&mac.mpo;_check_vnode_mmap</function> int &mac.mpo;_check_vnode_mmap struct ucred *cred struct vnode *vp struct label *label int prot &mac.thead; cred 主体信任状 vp 将被映射的 vnode label vp相关联的策略标记 prot mmap 保护 (参见 &man.mmap.2;) 确定相关主体是否有权将给定 vnode vpprot指定的保护方式进行映射. <function>&mac.mpo;_check_vnode_mmap_downgrade</function> void &mac.mpo;_check_vnode_mmap_downgrade struct ucred *cred struct vnode *vp struct label *label int *prot &mac.thead; cred See . vp label prot 将被降级的 mmap protections 根据主体和客体标记,降低 mmap protections。 <function>&mac.mpo;_check_vnode_mprotect</function> int &mac.mpo;_check_vnode_mprotect struct ucred *cred struct vnode *vp struct label *label int prot &mac.thead; cred 主体信任状 vp 映射的 vnode prot 存储保护 确定相关主体是否有权将给定 vnodevp 映射内存空间的存储保护参数设置为指定值。 <function>&mac.mpo;_check_vnode_poll</function> int &mac.mpo;_check_vnode_poll struct ucred *active_cred struct ucred *file_cred struct vnode *vp struct label *label &mac.thead; active_cred 主体信任状 file_cred struct file相关联的信任状 vp 将被执行 poll 操作的 vnode label vp相关联的策略标记 确定相关主体是否有权对给定 vnode vp执行 poll 操作。 <function>&mac.mpo;_check_vnode_rename_from</function> int &mac.mpo;_vnode_rename_from struct ucred *cred struct vnode *dvp struct label *dlabel struct vnode *vp struct label *label struct componentname *cnp &mac.thead; cred 主体信任状 dvp 目录 vnode dlabel dvp相关联的策略标记 vp 将被重命名的 vnode label vp相关联的策略标记 cnp vp中的成员名 确定相关主体是否有权重命名给定vnode,vp <function>&mac.mpo;_check_vnode_rename_to</function> int &mac.mpo;_check_vnode_rename_to struct ucred *cred struct vnode *dvp struct label *dlabel struct vnode *vp struct label *label int samedir struct componentname *cnp &mac.thead; cred 主体信任状 dvp 目录 vnode dlabel dvp相关联的策略标记 vp 被覆盖的 vnode label vp相关联的策略标记 samedir 布尔型变量;如果源和目的目录是相同的,则被置为1 cnp 目标component名 确定相关主体是否有权重命名给定 vnode vp,至指定目录 dvp,或更名为cnp。如果无需覆盖已有文件, 则vplabel 的值将为 NULL. <function>&mac.mpo;_check_socket_listen</function> int &mac.mpo;_check_socket_listen struct ucred *cred struct socket *socket struct label *socketlabel &mac.thead; cred 主体信任状 socket 客体;套接字 socketlabel socket的策略标记 确定相关主体是否有权监听给定套接字。 成功,则返回0;否则,返回错误编码值errno。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_lookup</function> int &mac.mpo;_check_vnode_lookup struct ucred *cred struct vnode *dvp struct label *dlabel struct componentname *cnp &mac.thead; cred 主体信任状 dvp 客体;vnode dlabel dvp的策略标记 cnp 被检查的成员名 确定相关主体信任状是否有权在给定的目录 vnode 中为查找给定名字执行lookup操作。 成功,则返回 0;否则,返回一个 errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_open</function> int &mac.mpo;_check_vnode_open struct ucred *cred struct vnode *vp struct label *label int acc_mode &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 acc_mode &man.open.2; 访问模式 确定相关主体信任状是否有权在给定 vnode 上以给定的访问模式执行 open 操作。 如果成功,则返回 0;否则,返回一个错误编码。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_readdir</function> int &mac.mpo;_check_vnode_readdir struct ucred *cred struct vnode *dvp struct label *dlabel &mac.thead; cred 主体信任状 dvp 客体;目录 vnode dlabel dvp的策略标记 确定相关主体信任状是否有权在给定的目录 vnode 上执行 readdir 操作。 成功,则返回 0;否则,返回一个错误编码 errno。 建议使用的错误编码:EACCES,用于标记不匹配;EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_readlink</function> int &mac.mpo;_check_vnode_readlink struct ucred *cred struct vnode *vp struct label *label &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 确定相关主体信任状是否有权在给定符号链接 vnode 上执行 readlink 操作。成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配;EPERM,用于权限不够。 该函数可能在若干环境下被调用,包括由用户进程显式执行的 readlink 调用, 或者是在进程执行名字查询时隐式执行的 readlink <function>&mac.mpo;_check_vnode_revoke</function> int &mac.mpo;_check_vnode_revoke struct ucred *cred struct vnode *vp struct label *label &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 确定相关主体信任状是否有权撤销对给定 vnode 的访问。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配;EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_setacl</function> int &mac.mpo;_check_vnode_setacl struct ucred *cred struct vnode *vp struct label *label acl_type_t type struct acl *acl &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 type ACL 类型 acl ACL 确定相关主体信任状是否有权设置给定 vnode 的给定类型的 ACL。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配;EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_setextattr</function> int &mac.mpo;_check_vnode_setextattr struct ucred *cred struct vnode *vp struct label *label int attrnamespace const char *name struct uio *uio &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 attrnamespace 扩展属性名字空间 name 扩展属性名 uio I/O 结构指针;参见 &man.uio.9; 确定相关主体信任状是否有权设置给定 vnode 上给定名字空间中给定名字的扩展属性的值。 使用扩展属性备份安全标记的策略模块可能需要对其使用的属性实施额外的保护。另外, 由于在检查和实际操作时间可能存在的竞争, 策略模块应该避免根据来自uio中的数据做出决策。 如果正在执行一个删除操作,则参数 uio 的值也可能为 NULL。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配;EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_setflags</function> int &mac.mpo;_check_vnode_setflags struct ucred *cred struct vnode *vp struct label *label u_long flags &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 flags 文件标志;参见 &man.chflags.2; 确定相关主体信任状是否有权为给定的 vnode 设置给定的标志。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_setmode</function> int &mac.mpo;_check_vnode_setmode struct ucred *cred struct vnode *vp struct label *label mode_t mode &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 mode 文件模式;参见 &man.chmod.2; 确定相关主体信任状是否有权将给定 vnode 的模式设置为给定值。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_setowner</function> int &mac.mpo;_check_vnode_setowner struct ucred *cred struct vnode *vp struct label *label uid_t uid gid_t gid &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 uid 用户ID gid 组ID 确定相关主体信任状是否有权将给定 vnode 的文件 uid 和文件 gid 设置为给定值。如果无需更新, 相关参数值可能被设置为(-1)。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_vnode_setutimes</function> int &mac.mpo;_check_vnode_setutimes struct ucred *cred struct vnode *vp struct label *label struct timespec atime struct timespec mtime &mac.thead; cred 主体信任状 vp 客体;vp label vp的策略标记 atime 访问时间;参见 &man.utimes.2; mtime 修改时间;参见 &man.utimes.2; 确定相关主体信任状是否有权将给定 vnode 的访问时间标签设置为给定值。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_proc_sched</function> int &mac.mpo;_check_proc_sched struct ucred *ucred struct proc *proc &mac.thead; cred 主体信任状 proc 客体;进程 确定相关主体信任状是否有权改变给定进程的调度参数。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够; ESRCH,用于提供不可见性质。 See &man.setpriority.2; for more information. <function>&mac.mpo;_check_proc_signal</function> int &mac.mpo;_check_proc_signal struct ucred *cred struct proc *proc int signal &mac.thead; cred 主体信任状 proc 客体;进程 signal 信号;参见 &man.kill.2; 确定相关主体信任状是否有权向给定进程发送给定信号。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES, 用于标记不匹配;EPERM,用于权限不够; ESRCH,用于提供不可见性质。 <function>&mac.mpo;_check_vnode_stat</function> int &mac.mpo;_check_vnode_stat struct ucred *cred struct vnode *vp struct label *label &mac.thead; cred 主体信任状 vp 客体;vnode label vp的策略标记 确定相关主体信任状是否有权在给定 vnode 上执行 stat 操作。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 See &man.stat.2; for more information. <function>&mac.mpo;_check_ifnet_transmit</function> int &mac.mpo;_check_ifnet_transmit struct ucred *cred struct ifnet *ifnet struct label *ifnetlabel struct mbuf *mbuf struct label *mbuflabel &mac.thead; cred 主体信任状 ifnet 网络接口 ifnetlabel ifnet的策略标记 mbuf 客体;将被发送的 mbuf mbuflabel mbuf的策略标记 确定相关网络接口是否有权传送给定的 mbuf。成功,则返回 0; 否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_socket_deliver</function> int &mac.mpo;_check_socket_deliver struct ucred *cred struct ifnet *ifnet struct label *ifnetlabel struct mbuf *mbuf struct label *mbuflabel &mac.thead; cred 主体信任状 ifnet 网络接口 ifnetlabel ifnet的策略标记 mbuf 客体;将被传送的 mbuf mbuflabel mbuf的策略标记 确定相关套接字是否有权从给定的 mbuf 中接收数据报。 成功,则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够。 <function>&mac.mpo;_check_socket_visible</function> int &mac.mpo;_check_socket_visible struct ucred *cred struct socket *so struct label *socketlabel &mac.thead; cred 主体信任状 不可改变 so 客体;套接字 socketlabel so的策略标记 确定相关主体信任状cred 是否有权使用系统监控函数,比如, 由&man.netstat.8; 和 &man.sockstat.1;使用的程序来观察 给定的套接字(socket)。成功, 则返回 0;否则,返回一个errno值。 建议使用的错误编码:EACCES,用于标记不匹配; EPERM,用于权限不够; ESRCH,用于提供不可见性质。 <function>&mac.mpo;_check_system_acct</function> int &mac.mpo;_check_system_acct struct ucred *ucred struct vnode *vp struct label *vlabel &mac.thead; ucred 主体信任状 vp 审计文件;&man.acct.5; vlabel vp相关联的标记 根据主体标记和审计日志文件的标记,确定该主体是否有权启动审计。 <function>&mac.mpo;_check_system_nfsd</function> int &mac.mpo;_check_system_nfsd struct ucred *cred &mac.thead; cred 主体信任状 确定相关主体是否有权调用 &man.nfssvc.2;。 <function>&mac.mpo;_check_system_reboot</function> int &mac.mpo;_check_system_reboot struct ucred *cred int howto &mac.thead; cred 主体信任状 howto 来自 &man.reboot.2;的howto 参数 确定相关主体是否有权以指定方式重启系统。 <function>&mac.mpo;_check_system_settime</function> int &mac.mpo;_check_system_settime struct ucred *cred &mac.thead; cred 主体信任状 确定相关用户是否有权设置系统时钟。 <function>&mac.mpo;_check_system_swapon</function> int &mac.mpo;_check_system_swapon struct ucred *cred struct vnode *vp struct label *vlabel &mac.thead; cred 主体信任状 vp swap设备 vlabel vp相关联的标记 确定相关主体是否有权增加一个作为swap设备的 vp <function>&mac.mpo;_check_system_sysctl</function> int &mac.mpo;_check_system_sysctl struct ucred *cred int *name u_int *namelen void *old size_t *oldlenp int inkernel void *new size_t newlen &mac.thead; cred 主体信任状 name 参见 &man.sysctl.3; namelen old oldlenp inkernel 布尔型变量;如果从内核被调用,其值被置为1 new 参见 &man.sysctl.3; newlen 确定相关主体是否应该被允许执行指定的 &man.sysctl.3; 事务。 标记管理调用 当用户进程请求对某个对象的标记进行修改时,将引发重新标记事件。对应的更新操作分两步进行: 首先,进行访问控制检查,确认此次更新操作是有效且被允许的;然后,调用另一个独立的入口函数对标记进行修改。 重新标记入口函数通常接收由请求进程提交的对象、对象标记指针和请求新标记,作为输入参数。 对象重新标记操作的失败将由先期的标记检查报告,所以,不允许在接下来的标记修改过程中报告失败,故而不提倡在此过程中新分配内存。 应用层体系结构 TrustedBSD MAC 框架包含了一组策略无关的组成元素,包括管理抽象标记的 MAC 接口库, 对系统信任状管理体系的修改, 为用户分配 MAC 标记提供支持的 login 库函数, 以及若干负责维护和更新内核对象(进程、文件和网络接口等)安全标记的工具。 不久,将有更多关于应用层体系结构的详细信息被包含进来。 策略无关的标记管理 API TrustedBSD MAC 提供的大量库函数和系统调用,允许应用程序使用一种统一的、策略无关的接口来处理对象的 MAC 标记。 如此,应用程序可以轻松管理各种策略的标记,无需为增加对某个特定策略的支持而重新编码。许多通用工具,比如 &man.ifconfig.8;,&man.ls.1; 和 &man.ps.1;,使用这些策略无关的接口查询网络结构、文件和进程的标记信息。 这些 API 也被用于支持 MAC 管理工具,比如,&man.getfmac.8;,&man.getpmac.8;, &man.setfmac.8;, &man.setfsmac.8;,和 &man.setpmac.8;。 MAC API的设计细节可参考 &man.mac.3;. 应用程序处理的 MAC 标记有两种存在形式:内部形式,用来返回和设置进程和对象的标记(mac_t); 基于 C 字符串的外部形式,作为标记在配置文件中的存放形式,用于向用户显示或者由用户输入。 每一个 MAC 标记由一组标记元素组成,其中每个元素是一个形如(名字,值)的二元组。 内核中的每个策略模块分别被指定一个特定的名字,由它们对标记中与该名字对应的值采用其策略特有的方式进行解析。 采用外部形式表示的标记,其标记元素表示为名字 / 值,元素之间以逗号分隔。 应用程序可以使用 MAC 框架提供的 API 将一个安全标记在内部形式和文本形式之间进行转换。 每当向内核查询某个对象的安全标记时,内部形式的标记必须针对所需的元素集合作好内部标记存储准备。 为此,通常采用下面两种方式之一:使用 &man.mac.prepare.3; 和一个包含所需标记元素的任意列表;或者, 使用从&man.mac.conf.5; 配置文件中加载缺省元素集合的某个系统调用。在对象级别设置缺省标记,将允许应用程序在不确定 系统是否采用相关策略的情况下,也能向用户返回与对象相关联的有意义的安全标记。 目前的 MAC 库不支持直接修改内部形式的标记元素,所有的修改必须按照下列的步骤进行: 将内部形式的标记转换成文本字符串,对字符串进行编辑,最后将其转换成内部形式标记。如果应用程序的作者证明确实有需要, 可以在将来的版本中加入对内部形式标记进行直接修改的接口。 为用户指定标记 用户上下文管理的标记接口, &man.setusercontext.3; ,的行为已经被修改为,从 &man.login.conf.5; 中查询与某个用户登录类别相关联的 MAC 安全标记。 当 LOGIN_SETALL 被设置,或者当 LOGIN_SETMAC 被明确指定时,这些安全标记将和其他用户上下文参数一起被设置。 可以预期,在今后的某个版本中,FreeBSD 将把 MAC 标记从 login.conf 的用户类别数据库中抽出,为其维护一个独立的数据库。 不过在此前后,&man.setusercontext.3; API应该保持不变。 小结 TrustedBSD MAC 框架使得内核模块能以一种集中的方式,完善系统的安全策略。 它们既可利用现有的内核对象属性,又能使用由 MAC 框架协助维护的安全标记数据,来实施访问控制。 框架提供的灵活性使得开发人员可以在其上实现各种策略,如利用 BSD 现有的信任状(credential) 与文件保护机制的策略,以及信息流安全策略(如 MLS 和 Biba)。 实现新安全服务的策略编程人员,可以参考本文档,以了解现有安全模块的信息。
diff --git a/zh_CN.GB2312/books/arch-handbook/usb/chapter.sgml b/zh_CN.GB2312/books/arch-handbook/usb/chapter.sgml index 99b05e2b9b..04e0e6ede5 100644 --- a/zh_CN.GB2312/books/arch-handbook/usb/chapter.sgml +++ b/zh_CN.GB2312/books/arch-handbook/usb/chapter.sgml @@ -1,462 +1,462 @@ Nick Hibma &cnproj.written.by; Murray Stokely &cnproj.modified.for.handbook.by; &author.cn.spellar; &cnproj.translated.by; USB设备 简介 Universal Serial Bus (USB, 通用串行总线) NetBSD 通用串行总线(USB)是将设备连接到个人计算机的一种新方法。总线 结构突出了双向通信的特色,并且其开发充分考虑到了设备正逐渐智能化 和需要与host进行更多交互的现实。对USB的支持包含在当前所有芯片中, 因此在新近制造的PC中都可用。苹果(Apple)引入仅带USB的iMac对硬件 制造商生产他们USB版本的设备是一个很大的激励。未来的PC规范指定 PC上的所有老连接器应当由一个或多个USB连接器取代,提供通用的 即插即用能力。对USB硬件的支持在NetBSD的相当早期就有了,它是由 Lennart Augustsson为NetBSD项目开发的。代码已经被移植到FreeBSD上, 我们目前维护着一个底层共享代码。对USB子系统的实现来说,许多USB的 特性很重要。 Lennart Augustsson已经完成了NetBSD项目中USB支持的 大部分实现。十分感谢这项工作量惊人的工作。也十分感谢Ardy和Dirk 对本文稿的评论和校对。 设备直接连接到计算机上的端口,或者连接到称为 集中器的设备,形成树型设备结构。 设备可在运行时连接或断开。 设备可以挂起自身并触发host系统的重新投入运行。 由于设备可由总线供电,因此host软件必须跟踪每个 集中器的电源预算。 不同设备类型需要不同的服务质量,并且同一总线 可以连接最多126个设备,这就需要恰当地调度总线上的传输以充分 利用12Mbps的可用带宽。(USB 2.0超过400Mbps) 设备智能化并包含很容易访问到的关于自身的信息。 为USB子系统以及连接到它的设备开发驱动程序受已开发或将要开发的 规范的支持。这些规范可以从USB主页公开获得。苹果(Apple)通过使得 通用类驱动程序可从其操作系统MacOS中获得,而且不鼓励为每种新设备 使用单独的驱动程序来强烈推行基于标准的驱动程序。本章试图整理基本 信息以便对FreeBSD/NetBSD中USB栈的当前实现有个基本的了解。然而, 建议将下面参考中提及的相关规范与本章同时阅读。 USB栈的结构 FreeBSD中的USB支持可被分为三层。最底层包含主控器,向硬件 及其调度设施提供一个通用接口。它支持硬件初始化,对传输进行调度, 处理已完成/失败的传输。每个主控器驱动程序实现一个虚拟hub, 以硬件无关方式提供对控制机器背面根端口的寄存器的访问。 中间层处理设备连接和断开,设备的基本初始化,驱动程序的选择, 通信通道(管道)和资源管理。这个服务层也控制默认管道和其上传输的 设备请求。 顶层包含支持特定(类)设备的各个驱动程序。这些驱动程序实现 除默认管道外的其他管道上使用的协议。他们也实现额外功能,使得设备 对内核或用户空间是可见的。他们使用服务层暴露出的USB驱动程序接口 (USBDI)。 主控器 USB(通用串行总线)host controllers(主控制器) 主控器(HC)控制总线上包的传输。使用1毫秒的帧。在每帧开始 时,主控器产生一个帧开始(SOF, Start of Frame)包。 SOF包用于同步帧的开始和跟踪帧的数目。包在帧中被传输,或由host 到设备(out),或由设备到host(in)。传输总是由host发起(轮询传输)。 因此每条USB总线只能有一个host。每个包的传输都有一个状态阶段, 数据接收者可以在其中返回ACK(应答接收),NAK(重试),STALL(错误 条件)或什么也没有(混乱数据阶段,设备不可用或已断开)。USB规范 USB specification的第8.5节更详细地解释了包的细节。USB总线 上可以出现四中不同类型的传输:控制(control), 大块(bulk), 中断 (interrupt)和同步(isochronous)。传输的类型和他们的特性在下面 描述(`管道'子节中)。 USB总线上的设备和设备驱动程序间的大型传输被主控器或HC 驱动程序分割为多个包。 到默认端点的设备请求(控制传输)有些特殊。它们由两或三个阶段 组成:启动(SETUP),数据(DATA,可选)和状态(STATUS)。设置(set-up) 包被发送到设备。如果存在数据阶段,数据包的方向在设置包中给出。 状态阶段中的方向与数据阶段期间的方向相反,或者当没有数据阶段时 为IN。主控器硬件也提供寄存器,用于保存根端口的当前状态和自从 状态改变寄存器最后一次复位以来所发生的改变。USB规范[2]建议使用一个 虚拟hub来提供对这些寄存器的访问。虚拟hub必须符合规范第11章中给出的 hub设备类。它必须提供一个默认管道使得设备请求可以发送给它。它返回 标准和hub类特定的一组描述符。它也应当提供一个中断管道用来报告其 端口发生的变化。当前可用的主控器规范有两个: 通用主控器接口(UHCI;英特尔)和 开放主控器接口(OHCI;康柏,微软,国家半导体)。 UHCI规范的设计通过要求主控器驱动程序为每帧的传输提供完整的调度, 从而减少了硬件复杂性。OHCI类型的控制器自身提供一个更抽象的接口来 完成很多工作,从而更加独立。 UHCI USB(通用串行总线)UHCI(通用主控制器接口) UHCI主控器维护着带有1024个指向每帧数据结构的帧列表。 它理解两种不同的数据类型:传输描述符(TD)和队列头(QH)。每个 TD表示表示与设备端点进行通信的一个包。QH是将一些TD(和QH)划分 成组的一种方法。 每个传输由一个或多个包组成。UHCI驱动程序将大的传输分割成 多个包。除同步传输外,每个传输都会分配一个QH。对于每种类型的 传输,都有一个与此类型对应的QH,所有这些QH都会被集中到这个QH上。 由于有固定的时延需求,同步传输必须首先执行,它是通过帧列表中的 指针直接引用的。最后的同步TD传输引用那一帧的中断传输的QH。中断 传输的所有QH指向控制传输的QH,控制传输的QH又指向大块传输的QH。 下面的图表给出了一个图形概览: 这导致下面的调度会在每帧中运行。控制器从帧列表中取得当前帧 的指针后,首先为那一帧中的所有的同步(isochronous)包执行TD。 这些TD的最后一个 引用那一帧的中断传输的QH。然后主控器将从那个QH下行到各个 中断传输的QH。完成那一队列后,中断传输的QH会将控制器指向到所有 控制传输的QH。它将执行在那儿等待调度的所有子队列,然后是在大块QH中 排队的所有传输。为了方便处理已完成或失败的传输,硬件会在每帧末尾 产生不同类型的中断。在传输的最后一个TD中,HC驱动程序设置 Interrupt-On-Completion位来标记传输完成时的一个中断。如果TD达到了 其最大错误数,就标记错误中断。如果在TD中设置短包侦测位,且传输了 小于所设置的包长度(的包),就会标记此中断以通知控制器驱动程序传输 已完成。找出哪个传输已完成或产生错误是主控器驱动程序的任务。 当中断服务例程被调用时,它将定位所有已完成的传输并调用它们的回调。 更详尽的描述请看 UHCI specification。 OHCI USB(通用串行总线)OHCI(开放主控制器接口) 对OHCI主控器进行编程要容易得多。控制器假设有一组端点(endpoint)可用, 并知道帧中不同传输类型的调度优先级和排序。主控器使用的主要 数据结构是端点描述符(ED),它上面连接着一个传输描述符(TD)的队列。 ED包含端点所允许的最大的包大小,控制器硬件完成包的分割。每次传输 后都会更新指向数据缓冲区的指针,当起始和终止指针相等时,TD就退归 到完成队列(done-queue)。四种类型的端点各有其自己的队列。控制和 大块(bulk)端点分别在它们自己的队列排队。中断ED在树中排队,在树中的深度 定义了它们运行的频度。 帧列表 中断 同步(isochronous) 控制 大块(bulk) 主控器在每帧中运行的调度看起来如下。控制器首先运行非 周期性控制和大块队列,最长可到HC驱动程序设置的一个时间限制。 然后以帧编号低5位作为中断ED树上深度为0的那一层中的索引,运行 那个帧编号的中断传输。在这个树的末尾,同步ED被连接,并随后被 遍历。同步TD包含了传输应当运行其中的第一个帧的帧编号。所有周期 性的传输运行过以后,控制和大块队列再次被遍历。中断服务例程会被 周期性地调用,来处理完成的队列,为每个传输调用回调,并重新调度 中断和同步端点。 更详尽的描述请看 OHCI specification。服务层,即中间层,提供了以可控的方式 对设备进行访问,并维护着由不同驱动程序和服务层所使用的资源。 此层处理下面几方面: 设备配置信息 与设备进行通信的管道 探测和连接设备,以及从设备分离(detach)。 USB设备信息 设备配置信息 每个设备提供了不同级别的配置信息。每个设备具有一个或多个 配置,探测/连接期间从其中选定一个。配置提供功率和带宽要求。 每个配置中可以有多个接口。设备接口是端点的汇集(collection)。 例如,USB扬声器可以有一个音频接口(音频类),和对旋钮(knob)、 拨号盘(dial)和按钮的接口(HID类)。 一个配置中的所有接口可以同时有效,并可被不同的 驱动程序连接。每个接口可以有备用接口,以提供不同质量的服务参数。 例如,在照相机中,这用来提供不同的帧大小以及每秒帧数。 每个接口中可以指定0或多个端点。端点是与设备进行通信的单向 访问点。它们提供缓冲区来临时存储从设备而来的,或外出到设备的数据。 每个端点在配置中有唯一地址,即端点号加上其方向。默认端点,即 端点0,不是任何接口的一部分,并且在所有配置中可用。它由服务层 管理,并且设备驱动程序不能直接使用。 Level 0 Level 1 Level 2 Slot 0 Slot 3 Slot 2 Slot 1 (只显示了32个槽中的4个) 这种层次化配置信息在设备中通过标准的一组描述符来描述(参看 USB规范[2]第9.6节)。它们可以通过Get Descriptor Request来请求。 服务层缓存这些描述符以避免在USB总线上进行不必要的传输。对这些 描述符的访问是通过函数调用来提供的。 设备描述符:关于设备的通用信息,如供应商,产品 和修订ID,支持的设备类、子类和适用的协议,默认端点的最大包大小 等。 配置描述符:此配置中的接口数,支持的挂起和 恢复能力,以及功率要求。 接口描述符:接口类、子类和适用的协议,接口备用 配置的数目和端点数目。 端点描述符:端点地址、方向和类型,支持的最大包 大小,如果是中断类型的端点则还包括轮询频率。默认端点(端点0) 没有描述符,而且从不被计入接口描述符中。 字符串描述符:在其他描述符中会为某些字段提供 字符串索引。它们可被用来检取描述性字符串,可能以多种语言 的形式提供。 类说明(specification)可以添加它们自己的描述符类型,这些描述符 也可以通过GetDescriptor Request来获得。 管道与设备上端点的通信,流经所谓的管道。驱动程序将到端点的 传输提交到管道,并提供传输(异步传输)失败或完成时调用的回调, 或等待完成(同步传输)。到端点的传输在管道中被串行化。传输或者完成, 或者失败,或者超时(如果设置了超时)。对于传输有两种类型的超时。 超时的发生可能由于USB总线上的超时(毫秒)。这些超时被视为失败, 可能是由于设备断开连接引起的。另一种超时在软件中实现,当传输没有 在指定的时间(秒)内完成时触发。这是由于设备对传输的包否定应答引起的。 其原因是由于设备还没有准备好接收数据,缓冲区欠载或超载,或协议错误。 如果管道上的传输大于关联的端点描述符中指定的最大包大小,主 控器(OHCI)或HC驱动程序(UHCI)将按最大包大小分割传输,并且最后 一个包可能小于最大包的大小。 有时候对设备来说返回少于所请求的数据并不是个问题。例如, 到调制解调器的大块in传输可能请求200字节的数据,但调制解调器 那时只有5个字节可用。驱动程序可以设置短包(SPD)标志。它允许主 控器即使在传输的数据量少于所请求的数据量的情况下也接受包。 这个标志只在in传输中有效,因为将要被发送到设备的数据量总是事先 知道的。如果传输过程中设备出现不可恢复的错误,管道会被停顿。 接受或发送更多数据以前,驱动程序需要确定停顿的原因,并通过在 默认管道上发送清除端点挂起设备请求(clear endpoint halt device request)来清除端点停顿条件。 有四种不同类型的端点和对应的管道: - 控制管道/默认管道: 每个设备有一个控制管道,连接到默认端点(端点0)。此管道运载设备 请求和关联的数据。默认管道和其他管道上的传输的区别在于传输所 使用的协议,协议在USB规范[2]中描述。这些请求用于复位和配置设备。 每个设备必须支持USB规范[2]的第9章中提供的一组基本命令。管道上 支持的命令可以通过设备类规范扩展,以支持额外的功能。 大块(bulk)管道:这是USB与原始传输媒体对应的等价物。 中断管道:host向设备发送数据请求,如果设备没有 东西发送,则将NAK(否定应答)数据包。中断传输按创建管道时指定的 频率被调度。 同步管道:这些管道用于具有固定时延的同步数据, 例如视频或音频流,但不保证一定传输。当前实现中已经有对这种类型 管道的某些支持。当传输期间出现错误,或者由于,例如缺乏缓冲区空间 来存储进入的数据而引起的设备否定应答包(NAK)时,控制、大块和中断 管道中的包会被重试。而同步包在传递失败或对包NAK时不会重试,因为 那样可能违反同步约束。 所需带宽的可用性在管道的创建期间被计算。传输在1毫秒的帧内 进行调度。帧中的带宽分配由USB规范的第5.6节规定。同步和中断传输被 允许消耗帧中多达90%的带宽。控制和大块传输的包在所有同步和中断包 之后进行调度,并将消耗所有剩余带宽。 关于传输调度和带宽回收的更多信息可以在USB规范[2]的第5章, UHCI规范[3]的的第1.3节,OHCI规范[4]的3.4.2节中找到。 设备的探测和连接 USB(通用串行总线)probe(探测) 集中器(hub)通知新设备已连接后,服务层给端口加电(switch on), 为设备提供100mA的电流。 此时设备处于其默认状态,并监听设备地址0。服务层会通过默认 管道继续检取各种描述符。此后它将向设备发送Set Address请求,将设备 从默认设备地址(地址0)移开。可能有多个设备驱动程序支持此设备。例如, 一个调制解调器可能通过AT兼容接口支持ISDN TA。然而,特定型号的ISDN 适配器的驱动程序可能提供对此设备的更好支持。为了支持这样的灵活性, 探测会返回优先级,指示他们的支持级别。支持产品的特定版本会具有最高 优先级,通用驱动程序具有最低优先级。如果一个配置内有多个接口,也可能 多个驱动程序会连接到一个设备。每个驱动程序只需支持所有接口的一个子集。 为新连接的设备探测驱动程序时,首先探测设备特定的驱动程序。 如果没有发现,则探测代码在所有支持的配置上重复探测过程,直到 在一个配置中连接到一个驱动程序。为了支持不同接口上使用多个驱动 程序的设备,探测会在一个配置中的所有尚未被驱动程序声明(claim)的 接口上重复进行。超出集中器功率预算的配置会被忽略。连接期间,驱动 程序应当把设备初始化到适当状态,但不能复位,因为那样会使得设备将 它自己从总线上断开,并重新启动探测过程。为了避免消耗不必要的带宽, 不应当在连接时声明中断管道,而应当延迟分配管道,直到打开文件并真的 使用数据。当关闭文件时,管道也应当被再次关闭,尽管设备可能仍然 连接着。 设备断开连接(disconnect)和分离(detach) USB(通用串行总线)disconnect(断开) 设备驱动程序与设备进行任何事务期间,应当预期会接收到错误。 USB的设计支持并鼓励设备在任何点及时断开连接。驱动程序应当确保 当设备不在时做正确的事情。 此外,断开连接(disconnect)后又重新连接(reconnect)的设备不会 被重新连接(reattach)为相同的设备实例。 将来当更多的设备支持序列号(参看设备描述符), 或开发出其他定义设备标识的方法的时候,这种情况可能会改变。 设备断开连接是由集中器在传递到集中器驱动程序的中断包中发 信号通知(signal)的。状态改变信息指示哪个端口发现了连接改变。 连接到那个端口上的设备的所有设备驱动程序共用的设备分离方法被调用, 结构被彻底清理。如果端口状态指示同时一个设备已经连接(connect)到那个 端口,则探测和连接设备的过程将被启动。设备复位将在集中器上产生 一个断开-连接序列,并将按上面所述进行处理。 USB驱动程序的协议信息 USB规范没有定义除默认管道外其他管道上使用的协议。这方面的信息 可以从各种来源获得。最准确的来源是USB主页[1]上的开发者部分。从这些 页面上可以得到数目不断增长的设备类的规范。这些规范指定从驱动程序 角度看起来兼容设备应当怎样,它需要提供的基本功能和通信通道上使用的 协议。USB规范[2]包括了集中器类的描述。人机界面设备(HID)的类规范已经 创建出来,以迎合对键盘、数字输入板、条形码阅读器、按钮、旋钮(手柄knob)、 开关等的要求。另一个例子是用于大容量存储设备的类规范。设备类的完整列表 参看USB主页[1]的开发者部分。 然而, 许多设备的协议信息还没有被公布。关于所用协议的信息 可能可以从制造设备的公司获得。一些公司会在给你规范之前要求你签署 保密协议(Non-Disclosure Agreement, NDA)。大多数情况下,这会阻止 将驱动程序开放源代码。 另一个信息的很好来源是Linux驱动程序源代码,因为很多公司已经 开始为他们的设备提供Linux下的驱动程序。联系那些驱动程序作者询问 他们的信息来源总是一个好主意。 例子:人机界面设备。人机界面设备,如键盘、鼠标、数字输入板、 按钮、拨号盘等的规范被其他设备类规范引用,并在很多设备中使用。 例如,音频扬声器提供到数模转换器的端点,可能还提供额外管道 用于麦克风。它们也为设备前面的按钮和拨号盘在单独的接口中提供HID 端点。监视器控制类也是如此。通过可用的内核和用户空间的库,与HID 类驱动程序或通用驱动程序一起可以简单直接地创建对这些接口的支持。 另一个设备可以作为在一个配置中的多个接口由不同的设备驱动程序驱动 的例子,这个设备是一种便宜的键盘,带有老的鼠标接口。为了避免在 设备中为USB集中器包括一个硬件而导致的成本上升,制造商将从键盘背面的 PS/2端口接收到的鼠标数据与来自键盘的按键组合成在同一个配置中的 两个单独的接口。鼠标和键盘驱动程序各自连接到适当的接口,并分配到 两个独立端点的管道. USB(通用串行总线)firmware(固件) 例子:固件下载。已经开发出来的许多设备是基于通用目的处理器, 并将额外的USB核心加入其中。由于驱动程序的开发和USB设备的固件仍然 非常新,许多设备需要在连接(connect)之后下载固件。 下面的步骤非常简明直接。设备通过供应商和产品ID标识自身。第一 个驱动程序探测并连接到它,并将固件下载到其中。此后设备自己软复位, 驱动程序分离。短暂的暂停之后设备宣布它在总线上的存在。设备将改变 其供应商/产品/版本的ID以反映其提供有固件的事实,因此另一个驱动程序 将探测它并连接(attach)到它。 这些类型的设备的一个例子是基于EZ-USB的ActiveWire I/O板。这个 芯片有一个通用固件下载器。下载到ActiveWire板子上的固件改变版本ID。 然后它将执行EZ-USB芯片的USB部分的软复位,从USB总线上断开,并再次 重新连接。 例子:大容量存储设备。对大容量存储设备的支持主要围绕现有的 协议构建。Iomega USB Zip驱动器是基于SCSI版本的驱动器。SCSI命令和 状态信息被包装到块中,在大块(bulk)管道上传输到/来自设备,在USB线 上模拟SCSI控制器。ATAPI和UFI命令以相似的方式被支持。 ATAPI(AT Attachment Packet Interface, (IBM PC)AT附属包接口) 大容量存储规范支持两种不同类型的对命令块的包装。最初的尝试 基于通过默认管道发送命令和状态信息,使用大块传输在host和设备之间 移动数据。在经验基础上设计出另一种方法,这种方法基于包装命令和 状态块,并在大块out和in端点上发送它们。规范精确地指定了何时必须 发生什么,以及在碰到错误条件的情况下应该做什么。为这些设备编写 驱动程序的最大挑战是协调基于USB的协议,让它适合已有的对大容量存储设备 的支持。CAM提供了钩子,以相当直接了当的方式来完成这个。ATAPI就 没有这么简单了,因为历史上IDE接口从未有过多种不同的表现方式。 来自Y-E Data的对USB软盘的支持也不是那么直观,因为设计了一套 新的命令集。