标签归档:seccomp

Linux系统安全_sandbox_seccomp-bpf

        linux3.5以后linux支持了Will Drewry的Seccomp-BPF的特性。Seccomp-BPF是建立在能够发送小的BPF(BSD Packet Filter)程序,有内核来执行。这个特性起先源于tcpdump的设计,因为性能的原因,可以直接在内核中运行。BPF相对与内核是不可信的,因此, 限制了有限的数量。值得注意的是,他们不能循环,限定单一函数的执行时间,以及大小,并允许内核知道他们的永远终止。

BPF(BSD Packet Filter)是一种用于Unix内核网络数据包的过滤机制。Linux之前包过滤LPF。Linux随着版本的不同,所支持的捕获机制也有所不同。2.0之前的内核版本,对Socket使用类型SOCK_PACKET,调用形式是Socket(PF_INET,SOCK_PACKET,int proctol),但这种用法已经过时,2.2之后,提出的一种新的协议簇,PF_PACKET来实现捕获机制,调用形式为:socket(PF_PACKET, int socket_type, int protocol),其中socket类型可以是SOCK_RAW,和SOCK_DGRAM,SOCK_RAW 类型使得数据包从数据链路层取得后,不做任何修改,直接传递给用户数据,而SOCK_DGRAM,则要对数据包进行加工(cooked),把数据包的数据链路层头部去掉,而使得一个通用结构sockaddr_ll来保存链路信息。

使用2.0版本内核捕获数据包存在多个问题,首先,SOCK_PACKET方式使用结构sockaddr_pkt来保存数据链路层信息,该结构缺乏包类型信息;其次,如果参数MSG_TRUNC传递给读包函数recvmsg, recv, recvfrom 等,则函数返回的数据包长度是实际读到的包数据长度,而不是数据包的真正长度。

PF_PACKET方式规避上以上的问题,在实际应用中,用户程序显然希望直接得到“原始”数据包,因此使得SOCK_RAW类型最好。Libpcap使用的SOCK_DGRAM.从而也必须为数据包合成一个“伪”链路层头部(sockaddr_ll).1,某些类型的设备数据链路层头部不可用,例如linux 内核的ppp协议,实现代码对ppp数据包的支持的不可靠,2在捕获设备为any时,所有设备意味着libpcap对所有的接口进行捕获。需要要求对所有数据包有相同的数据链路头部。具体的详细描述:http://www.linuxjournal.com/article/4852

    BPF是一种过滤机制,有网络阀(Network tap)和过滤包组成,网络阀用于从网卡设备驱动处收集网络数据包拷贝,并负责向已注册监听的用户态应用程序递交数据,过滤器则是用于决定收集的网络数据是否满足过滤器要求,满足条件的则进行后继的拷贝工作,否则丢弃该数据。

    该图的过程为:当数据包到达网络接口时,链路层的设备驱动程序通常是将数据包直接传送给协议栈进行处理。而当BPF在该网络接口注册监听时,链路层驱动程序将数据包传送给协议栈之前,先会首先调用BPF。BPF负责将此数据包指针传递至每个监听进程指定的内核过滤器,这些用户自定义的过滤器决定数据包是否被接受,以及数据包中的那些内容将被接受,对于每一个决定接受数据包的过滤器,BPF此时才进行数据拷贝工作,将所需的数据拷贝至于过滤器相连的缓冲区中,以供用户监听进程读取。数据拷贝结束后,网络设备驱动程序重新获得系统控制权,系统进行正常的网络协议处理。我们注意到BPF是先对数据包过滤,在缓冲,避免了类似SUN的NIT过滤机制,先缓冲每个数据包直到用户读取数据时在过滤造成的效率问题。

    BPF 的设计思路和当时的计算机的硬件发展有大关系,相对较老的CSPF(CMU/Stanford Packet Filter)它有两大特点,1 :基于寄存器的过滤机制,而不是早期的内存堆栈过滤机制,2:直接使用独立的,非共享的内存缓冲区。同时,BPF在过滤算法上也有很大进步(有人也做了一些优化BPF+),它使用了无环控制流图(CFG,control flow graph).而不是老式的布尔表达式树(Boolean expression tree)。

下面说一下BPF的包过滤模式:

    包过滤可以看作是针对数据包的布尔函数运算。如果返回值为真,那么内核为监听程序拷贝数据,反之则丢弃。形式上包过滤由一个或者多个谓词判断的AND操作和OR操作组成。

    包过滤在具体的实现上是通过将数据包简单的看作一个字节数组,谓词判断根据具体的协议映射到数组的特定位置进行判断过滤工作。例如,判断数据包是否是ARP协议,只需要盘点数组的第13,14个字节是否为0×0806。因此,包过滤的算法的中心任务,是使用最少的判断操作。

    布尔表达式树理解上比较直观,它的每一个叶子结点即一个谓词判断,而非叶子结点则为AND或OR操作。而CFG算法是一种特殊的状态机,图中每一个非叶节点代表的是对数据包进行谓词判断操作,而边则是判断的结果。在CFG中,只存在2个叶子结点,分别代表过滤通过True和未通过Flase。
CSPF主要有3个缺点,1:过滤操作使用的栈在内存中被模拟,维护栈指针需要使用若干的加/减等操作,而内存操作时现代计算机架构的主要瓶颈。2:布尔表达树造成了不需要的重复计算。3不能分析数据包的变长头部。BPF使用的CFG算法,实际上是一种特殊的状态机,每一个节点代表了一个谓词判断,而左右分别对应了判断失败和成功后的跳转,跳转后又是谓词判断,这样反复操作,直到到达成功或失败的终点。CFG算法的优点在于把对数据包的分析信息直接建立在图中。从而不需要重复计算。CFG是一种“快速的,一直向前”的算法。

BPF包过滤模式的实现,BPF采用了一种过滤器伪机的方式(filter Pseudo-machine)。BPF伪机器方式是一个轻量级,高效的状态机。对BPF过滤代码进行解释,BPF过滤机过滤代码形式为”opcode jt jf k”,分别代表了操作码和寻址方式,判断正确的跳转和失败的跳转,以及操作所使用的通用数据域。

    BPF过滤代码从逻辑上看,很类似汇编语言,但它实际上是机器语言,注意到上述的3个域的数据都是int和char型。显然,有用户来写过滤代码太过于复杂,但这种设计更适合用于操作硬件。特别用来编写需要写少量固定序列的硬件驱动程序。BPF实际上是一组基于状态机匹配的过滤序列,用于简单的数据包模式匹配。用指令集简单的将上图描述如下:

    每个匹配包至少包含4个元素,定义为一个结构体如下:

    其很显然应该是一个状态驱动的循环:

看看linux实现的代码,基本上是这样实现的。

    BPF用于很多的抓包程序,在linux中,一般内核自动编译进了af_packet这个驱动,因此只需要在用户态配置一个PACKET的socket,然后将filter配置进内核即可,使用setsockopt的SO_ATTACH_FILTER 命令,这个filter是在用户空间配制的,比如tcpdump应用程序,tcpdump和内核BPF过滤器的关系类似iptables与netfilter的关系,只是Netfilter实现了match/target的复合配合,而BPF的target则只是选择是否要与不要。

    回到主题上来,seccomp-bpf在使用BPF的时候,借鉴了其思想。
大量的系统调用暴露在用户空间,在程序的整个生命周期内并没有使用。而随着系统调用的改变和成熟,bug的发现和根除,一套可用的系统调用是满足用户程序基本的调用。将可以产生出一组尽量少的系统调用暴露在用户空间。系统调用的过滤就是在应用程序的使用中。

Seccomp过滤器提给了一种手段,为一个进程调用系统调用时指定了过滤器,而这个过滤器则是BPF。BPF作为socket过滤器不同之处则是操作当前的user_regs_struct数据结构,这需要预先定义好系统调用的ABI和定义过滤规则。但系统调用的参数保存在用户空间,但要验证该参数是否安全是非常困难的。

    Seccomp用户无法避免TOCTOU (time-of-check-time-of-use)系统调用对框架的注入式攻击。如一个恶意进程可能会在“参数被安全检查”之后,而在实际使用之前,将参数换掉,这边使节或系统调用时所作的参数检查变得没有意义。但要解决这个问题,也不能仅把目光盯住系统的调用入口。

针对系统调用的过滤,不是一种沙箱的模式,其更多的提供了减少内核暴露的表面,除此之外,需要和其他的规则和方案进行配合。
当seccomp模式被添加,但它们不是直接设置超时的过程,是一种新的模式, 仅当CONFIG_SECCOMP_FILTER设置,并用prctl启用PR_ATTACH_SECCOMP_FILTER参数。
与seccomp过滤器交互式通过prctl调用。
PR_ATTACH_SECCOMP_FILTER ,允许一个新的过滤器使用BPF程序的规范,BPF程序将被执行user_regs_struct结构的数据。用法为

Linux系统安全_Sandbox

Sandbox沙箱在计算机领域中是一种程序隔离的机制,其目的则是限制不可信进程的权限。沙箱技术则常用于执行未经测试的或不可信的客户程序,(比如沙箱杀毒一类的),为了避免不可信程序可能破坏其他的程序运行,沙箱技术可以为不可信的程序提供虚拟化的磁盘,内存以及网络资源,而这又是对客户是透明的。

常规的安全机制则主要以降权来解决问题,但降权并不能带来真正的安全,sandbox也不能带来全方位的安全,沙箱机制结合多种系统安全技术来实现安全,从设计的理念上,打破传统的单一防御到纵深防御。

常规的纵深防御有六种常见的手法:

1 安全的编码(Secure Code),减少编写的错误。

2 应用技漏洞缓解技术(Application-level exploitation)(SSP,relro)

3 系统级漏洞缓解技术(System-level exploit mitigation)(ASLR,NX),

4 降权处理(Privilege Dropping)(Sandboxing)

5 强制访问控制(Mandatory access control)(MAC,SELinux)

6 更新策略(Update strategy)

 

进程和权限

         1 每个进程拥有独自的地址空间

         2 MMU硬件强制分离地址空间

         3 内核在系统中有强制性的接口层(kernel的安全策略)

         4 每个进程拥有进程权限边界

         5 root用户拥有至高的权限

         6 其他用户拥有自主访问控制DAC访问权限

一般情况下的权限控制为:

         进程A拥有的权能比进程B拥有的多,则A可以获得B进程可以访问的资源

         1 root用户运行任何程序都是特权用户

         2 2个进程如果拥有相同的uid,和gid。则可以拥有相同的权限

         3 一般不能比较不同uids用户的进程权限

进程和权限的分离

         1 线程

                   将内部特权进行分离(一般情况下)如NaCI,SECCOMP Sandbox

         2 Debugging

                 如果A进程可以Ptrace()B,那么A的权限要大于B的权限

 

 Linux标准的权限资源有

         1 Users,和 groups

                   1 uid,euid,suid,fsuid

                   2 gid,egid,sgid,fsgid,超级组

         2 POSIX.1e 权能

         3 Quota control

         4 Linux Container

         5 LSM(Linux Security Module)

 

Linux的权能划分了不同的权限单位

         如:CAP_NET_RAW: 允许使用RAW和PACKET Socket

                   CAP_SYS_ADMIN: 管理员操作权能(mount,sethostname等)

                   CAP_NET_BIND_SERVICE:bind绑定端口权限<1024

 

 权能的限制

         1 常见的误区:

                   1 忘记切换uid 为0的超级用户。很多功能需要root用户的权能

         2 root 操作

                   1 只有用到root权能的时候,才将权限提升为root权限

                   2 很多操作只需普通用户的权能。

使用切换root

1         需要对文件系统进行权限控制的时候,需要root用户

2         只有root可以操作的活动

3         需要一些陷入或escape的权限时,需要流行的chroot技术。

4         模块注入 非chroot进程的ptrace()

 

新的命名空间(Namespaces:CLONE_NEW*)

         CLONE_NEWPID: New pid namespace(2.6.24)

         CLONE_NEWNET:New network namsespace(2.6.26)

         CLONE_NEWIPC,CLONE_NEWUTS,CLONE_NEWNS(2.6.19)

 

资源限制

         为了安全的资源限制机制

         1 RLIMIT_NOFILE:不能获得新的句柄,但可以rename或者unlink

         2 RLIMIT_NPROC: 不能创建新的进程

         如果为了安全,RLIMIT_NPRC需要设置为(0,0),因为一些攻击者可以替代已经存在的句柄去创建新的socket或者文件句柄。

 

Dumpable(Debuggable)进程

         Linux支持对每个进程进行dumpable标志。

          通过PR_SET_DUMPABLE标记设置prctl,跟踪执行你没有权限的程序,或者切换uid。

          一个进程没有CAP_SYS_PTRACE权能,是不能ptrace非dumpable的进程

         需要一个进程权限提升或者可以降低其他进程的权限来满足dumpable。

 

MAC强制的访问控制

         在内核机制中,基于LSM钩子的有,SELInux,SMACK,TOMOYO

         内核机制之外的有:GRSecuirty, RSBAC, AppArmor

 

Sandbox 采取权限限制的策略

Sandbox与MAC的区别

         MAC需要管理员去分发布商。每个程序需要一套单独的规则库,不需要对代码的控制权

         Sandbox则是面向开发者,每个程序可以运行在多个设备上,不要要设备管理员的权限

 

常见的sandbox的设计:

         1 ptrace() sandbox (vsftpd)

         2       setuid sandbox(chromium)

         3      SECCOMP sandbox(chromium)

1 ptrace 架构:

setuid sandbox

 setuid sandbox基于Linux Kernel 所提供的安全机制如DAC实现,利用了uid/gid +  chroot()+capability的组合来达到目标,实现起来相对较为简单。

         传统的root 很难避免以下问题:1 需要对文件系统删除的访问2 RLIMIT_NOFILE 不够时的处理3  防止其他进程ptrace 4阻止信号发送给其他进程。

(一) Setuid :

利用uid的切换工作:

1我们需要获取uid/gid的定义池,但不需要资源/etc/passwd

2调用时,搜索到未用的uid/gid.然后切换到这个uid/gid

3执行sandbox。

Linux uid/gid 机制主要是用于进程的权限隔离,当你要执行不可信的程序时,那么在启动该程序的时,为其分配一个random uid。然后设置setuid。工作流程如下

Fork() ->setuid()->{设置相关的进程资源限制,如RLMIT_NPROC(0,)}->execve();

Setuid() 需要root的权限(或者具有CAP_SETUID 权能)才能调用成功。

 

(二) Chroot:

chroot 是 linux kernel 的另一个安全功能,他主要用于修改进程的根目录,比如执行chroot(“/tmp/sandbox/1”)则可以将当前进程的根目录切换为”/tmp/sandbox/1”。那么该进程的文件操作权限则被限制在当前目录下。

Chroot需要root权限(或者具有CAP_SYS_CHROOT权能)才能调用成功。

如果按照前面执行的顺序,具有root权限的进程A去执行chroot,然后再调用setuid,->..->execve(),这样是不可以的,因为execve所执行的路径已经发生改变,可执行文件已经不存在了。

Chris Evans的文章给了一个解决方法:

 

对A程序来说,由于它是以Root身份运行,那么就可能作为攻击点,比如confuesed deputy问题。下面针对这个问题加以说明。

 

(三) Linux Capability

 主要解决confused deputy问题,这个问题典型的代表是跨站点请求伪造(CSRF)。

Linux支持Capability的主要目的则是细化root特权,避免confused deputy问题。比如ping这个命令,需要raw_sockets,所以需要root的权限才可以运行,如果有了Capablity机制,该程序只要一个CAP_NET_RAW的权能,便可以运行,根据最小权限原则,该程序可以去掉所有不必要的权能。

当前kernel支持的权能多大30多种。在kernel2.6.24以后,普通用户也具有了capability.

 

Setuid sandbox简单易行,在google chromuim  早期的沙箱机制中使用了它,但现在已经默认为seccomp机制了。利用ipc机制创建socket pair

Seccomp

Seccomp 是 Andrea Arcangeli在2005年设计的,其目的则是解决 grid computer中的安全问题,比如你打算租借cpu资源,但又担心不可信的代码会破坏你的系统,那么seccomp

则为你提供了一个“安全(SAFE,not SECURE)”的环境。

         内核从2.6.10开始引入seccomp机制,该机制限制系统调用为read(),write(),exit(),sigreturn(). 只有4个系统调用被允许,相对较为简单,其限制了不能分配内存,不能共享内存, 不能创建新的文件描述符。成为了一个纯计算型的代码。

  运行在seccomp机制下的线程成为可信线程,当不可信的线程请求可信线程进行系统调用时,可信线程验证后方可进行调用。可信线程也只能使用只读的存储器,不能读取可变的区域。

有人提出了对seccomp机制的改进,利用bitmap增加一个新的mode,来限制使用那些权能。

通过截获系统调用来实现sandbox是一贯的做法,在现在的操作系统中,用户空间和内核空间是隔离的,一个进程只能通过系统调用才能从用户空间进入内核空间,如果一个进程不执行系统调用,那么被攻击的风险便会降低。当然,不调用系统调用是不切合实际的。

针对chromium使用的sandbox机制,我们将接下来进行分析。