分类目录归档:Kernel

systemtap 学习

SystemTap 提供了一个简单的命令行接口和强大的脚本语言,同时预定义了丰富的脚本库。基于内核中的 kprobes,SystemTap 允许用户自由地从运行中的内核收集调试信息和性能数据,来用于之后的分析和处理。用户可以随时开始或者停止这个收集过程,而无需漫长的修改代码、编译内核和重启系统这个循环
0×01 SystemTap Features

动态的性能分析能力,不打断分析目标的逻辑,不改写、侵入分析目标的代码。
多种多样的probe events
也可以调试分析功能性的问题
低开销,安全性高,适用于正式产品
持续长时间的性能分析
按需probe,可以用脚本编写,过滤信息的输出和采集
易用性较好

0×02 SystemTap 利用的技术Kprobes/Kretprobe/Uprobes/Relayfs
kernel层的一个接口,注册后可以非侵入的动态插入绝大多数(注1)函数并采集调试信息和性能数据。当插桩点被hit时,会指定一个自定义的处理函数(handler)来处理新的逻辑。
Kprobes 从 2.6.9 版本开始就添加到主流的 Linux 内核中,并且为探测内核提供一般性服务。它提供一些不同的服务,但最重要的两种服务是 Kprobe 和 Kretprobe。Kprobes插桩的技术很简单,同调试器和动态补丁技术的原理大致相同。首先会拷贝一份需要probe点的指令内容,并且把旧的代码段替换成一个断点指令(e.g., int3 on i386 and x86_64)。当断点指令被触发后,处理了新逻辑,然后会恢复原来的代码段内容,接着执行原始的指令(从断点开始)。
Kretprobes 有所不同,它操作调用函数的返回结果。注意,因为一个函数可能有多个返回点,所以听起来事情有些复杂。不过,它实际使用一种称为 trampoline 的简单技术。您将向函数条目添加一小段代码,而不是检查函数中的每个返回点。这段代码使用 trampoline 地址替换堆栈上的返回地址 —— Kretprobe 地址。当该函数存在时,它没有返回到调用方,而是调用 Kretprobe(执行它的功能),然后从 Kretprobe 返回到实际的调用方。
Relayfs 一种高速的数据转发文件系统。可以从kernel层高速,直接,可靠的把数据转发到用户空间读。
许多trace/debug工具都是利用了relayfs机制,eg. Systemtap/Lttng/Debugfs
0×03 SystemTap working
stap
语法分析阶段(parse)
主要是检查输入脚本是否存在语法错误,例如大括号是否匹配、变量定义是否规范等
细化(Elaborate)
细化是分析输入脚本并解析对内核的引用或用户符号与 tapsets 的处理阶段。Tapsets 是用来扩展脚本能力的脚本或 C 代码库。细化将脚本中的外部引用解析为符号信息并导入脚本子程序,为下一步转换为 C 程序做准备,这个过程就类似于将库文件链接到目标文件。对内核数据如函数参数、局部和全局变量、函数以及源地址都需要被解析为运行时实际的地址,这是通过对构建内核时编译器产生的 DWARF 调试信息进行处理来实现的。所有的调试数据都会在内核模块运行之前被处理。调试数据包含足够的信息来定位内联函数、局部变量、类型以及一些通常不被导出到内核模块的声明。
下面总结一下细化阶段所做的主要工作:

获取用户的探测脚本
预处理宏
包含所需要的脚本库
解析代码中对符号的引用
查找函数入口地址
查找行号信息
查找全局和局部变量的类型及地址

转换(Translate)
脚本被细化后它将会被转换为 C 语言代码,每个脚本子程序都被扩展成C语言代码块并进行安全检查,例如对循环结构进行递增检查了防止无限循环。由多个探测点共享的变量会被映射成适当的静态声明,并以块为单位进行访问保护。在内核中使用kprobes中的注册APIs对探测点处理器(probe handlers)进行注册。对于在内核中的探测点,会被插入到内核的内存空间中;对于用户级别探测点,它将被插入到可执行代码中,当处理器在内核中运行时将其载入到用户的内存空间。
构造(Build)
转换完成后,产生的 C 语言代码会被编译并与运行时进行链接成一个stand-alone 内核模块。该模块可被加密标记以用于安全归档或远程使用。
运行(Execution)
生成模块后 SystemTap 驱动程序会使用 insmod 将内核模块载入。该模块会进行初始化、插入探测点,然后等待探测点被触发。探测点被触发后会调用相关联的处理器程序并挂起执行线程,当该探测点所有的处理器都运行后,线程重新运行。当用户发送中断或脚本调用 exit 时,SystemTap 脚本将中止运行。然后卸载模块并移除探测点。
数据采集
SystemTap 需要将从内核中获取的数据传输到用户空间,并且需要很高的吞吐量、低延迟以及最小化对被监测系统性能的影响。缺省情况下,SystemTap 的输出会在脚本出口以批处理的方式写入 stdout,同时该输出会被自动保存到文件中。在用户空间,SystemTap 可以以简单的文本方式显示数据,或使用图形类的应用程序生成计算机可读的表格数据等。

0×04 语言基础
编写函数体和探测处理器的语法大部分借鉴于 awk 和 C 语言。SystemTap 还允许 C、C++和 awk 风格的注释。除了$也是合法的字符外,SystemTap 定义标识符的语法与 C 语言完全一样。标识符用来命名变量和函数,以$开头的标识符被认为是对目标程序(即被监测程序)中变量的引用而不是 SystemTap 脚本中的变量。SystemTap 的语言中包括少量的数据类型,但是没有类型声明:变量的类型是从它的使用中推断出来的。类似的,字符串和数字之间也没有隐式的类型转换。
语言中包含传统的if-then-else 语句以及 C语言和 awk 中的表达式,同时还支持结构化控制语句如 for和 while 循环,但不支持非结构化操作如标号和 goto 语句。为支持关联数组,SystemTap 语言包含了迭代器和 delete 语句。迭代器语句允许程序员指定执行数组中所有成员的操作,delete 操作可以移除数组中一个或全部成员。C 语言中典型的算术、位、赋值及一元操作在 SystemTap中都可以使用,但都是操作在 64 位数字上的,赋值和比较运算符可以重载为字符串使用。
Events
探测点指定了在内核的什么位置进行探测,探测点还可以定义在与目标程序上某个点没有联系的抽象事件上,但这时转换器就不能获得太多关于探测点的符号信息。

Handlers
探测点名称后紧跟的一组大括号内定义了每次内核运行到该探测点时需要进行的操作,比如可以指定输出数据过滤的条件,或者更为复杂的控制逻辑。这些操作完成后再返回探测点,继续下面的指令。
Helper functions

Tapset
当我们诊断系统级的问题时,通常需要跟踪操作系统和应用程序的不同子系统。为使我们更方便的进行诊断,SystemTap 包含了一个为多个子系统编写好的监测模块库,即 tapsets,用户可以在脚本中使用已经发布的tapsets。Tapset 的主要思想是:一个给定子系统的专家知道哪些方面对于深入理解该子系统是重要的;该专家会将重要的数据以 tapset 的形式,通过在探测点中调用一个或多个函数进行输出。这样,我们不需要成为内核所有领域中的专家,但仍然可以从系统中获得重要的数据。Tapsets已经覆盖包含的子系统: systemcalls, process, scheduler, filesystem, networking etc.

0×05 Systemtap安全特性
使用的toolchain经过了很好的测试,没有用新的compiler or interpreter
使用了经过完善测试的内核特性
另外systemtap语言本身的安全特性有:

No dynamic memory allocation
Types and type conversions limited
Limited pointer operations

内嵌的安全检查:

Infinite loops and recursion
Invalid variable access
Division by zero
Restricted access to kernel memory
Array bound checks
Version compatibility checks

Limitations

Off kernel. Generating kernel modules
Running kernel modules. Requires privilege to load, mistakes are fatal, mitigated by protected stap language
No early boot tracing
Review kernel codes detailedly, or have to understand tapsets clearly
Single step break

0×06 备注
1.实际上,systemtap也不是所有的kernel函数都可以插桩,有个blacklist屏蔽了systemstap插桩可能会引起问题的函数列表
2.尽管该语言允许开发复杂的脚本,但每个探针只能执行 1000 条语句(这个数量是可配置的)
3 systemtap tutorial
4 l-systemtap
5 systemtap-intro

打开ceph内核dout日志输出

首先对ceph的内核模块打开debug信息。在Kconfig文件中添加DDEBUG选项。

打开选项:
[] Support BLK_DEV_RBD_DEBUG

其次,打开内核printk 日志等级为8的信息,并打开ceph的日志信息;

最后修改日志文件:/etc/rsyslog.conf

LXC,Linux containers

1 LXC安装

2 LXC的介绍

Lxc 全称,Linux containers,是一种基于容器的操作系统层级的虚拟化技术。LXC可以在操作系统层级上提供虚拟的执行环境,一个虚拟机的执行环境就是一个容器,可以为该容器绑定特定的CPU、Memory节点,并可以分配特定比例的CPU时间,IO时间,限制可以使用的内存大小(包括内存和swap空间),提供device访问控制,提供独立的namespace(网络,pid,ipc,mnt,uts);

LXC依靠Linux内核相关特性,基于容器的虚拟化技术起源于所谓的资源容器和安全容器,LXC在资源管理方面依赖与Linux内核的Cgroups子系统,Cgroups子系统是Linux内核提供的一个基于进程组的资源管理框架,可以为特定的进程组限定可以使用资源,LXC在资源隔离控制方面依赖与Linux内核的namespace特性,具体而言就是在clone时加入相应的flag(NEWNS NEWPID)等。

LXC和传统的HAL(硬件抽象层)层次的虚拟化技术相比,具有以下优势:

1 更小的虚拟化开销(LXC的诸多特性有Kernel提供,Kernel实现这些特性只有极少的消耗)

2       快速部署,不需要创建虚拟机,只需要安装LXC,不需要单独为内核打补丁。

1 Cgroups

Cgroups子系统是(control groups的缩写),是Linux内核提供的一种可以限制、记录、隔离进程组(process)所使用的物理资源(cpu,memory,io.etc)机制。

Cgroups最初的目标为资源管理器提供统一的框架,既整合现有的cpuset等子系统,也为未来开发新的子系统提供接口,现在的cgroups适用于多种应有场景,从单独的进程资源控制,到实现操作系统层次的虚拟化。

1 限制进程组可以使用资源的数量,Resource Limiting,如,memory子系统可以为进程设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会触发OOM(Out of memory).

2 进程组的优先级控制,比如,可以使用CPU子系统为某个进程组分配特定的cpu share.

3 记录进程组使用的资源数量,比如可以使用cpuacct子系统记录某个进程使用cpu的时间。

4 进程组隔离,比如使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间。

5 进程组控制,比如使用freezer子系统将进程组挂起和恢复。

Cgroups的相关概念:

1 任务:(task),在cgroups中,任务就是系统的一个进程

2 控制族群,(control groups)。控制族群就是一组按照某种标准划分的进程,Cgroups

中的资源控制是一控制族群为单位实现,一个进程可以加入到某个控制族群,也从一个进程组迁移到另一个进程组群。一个进程组的进程可以使用cgroups以控制族群为单位分配资源,同时也受到cgroups以控制族群为单位设定的限制。

3 层级,(hierarchy).控制族群可以组织成hierarchical的形式,既一颗控制族群树,控制族群树上的子节点控制族群是父节点控制族群的孩子。继承父控制族群的特定的属性。

4 子系统,(subsystem)。一个子系统就是一个资源控制器, 比如cpu子系统就是控制cpu时间分配器的一个控制器,子系统必须附加attach到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。

Cgroups的系统相互关系

1 每次在系统中创建新层级时, 该系统中的所有任务都是那个层次的默认cgroup.(root cgroup,此 cgroup在创建层级时,自动创建,后面在该层级中创建的cgroup都是此cgroup的后代)的初始成员。

2 一个子系统最多只能附加到一个层级。

3 一个层级可以附加多个子系统

4 一个任务可以是多个cgroup的成员,但这些cgroup必须在不同的层级。

5 系统中的进程(任务)创建子进程(任务)时,该子任务自动成为其父进程所在的cgroup的成员。 然后可以根据需要将该子任务移动到不同的cgroup中,但开始时,总是继承其父任务的cgroups

Cgroups子系统介绍

blkio–这个子系统为块设备设定输入/输出限制,比如物理设备(硬盘,固态硬盘,usb等)

cpu–这个子系统使用调度程序提供对CPU的cgroup 任务访问

cpuacct– 这个子系统自动生成cgroup中任务所使用的cpu报告

cpuset –这个子系统为cgroup中的任务分配独立CPU和内存节点

devices–这个子系统允许或者拒绝cgroup中的任务访问设备

freezer– 这个子系统刮起或者恢复cgroup中的任务

memory — 这个子系统设定cgroup中任务内存的限制,并自动生成由那些任务使用的内存资源报告。

Net_cls–这个子系统使用等级识别符(classid)标记网络数据包,可以允许linux流量控制程序tc,识别从具体cgroup中生成的数据包

Ns — 命名空间子系统。

 

2 Namespaces机制

Linux Namespaces机制提供了一种资源隔离方案,PID、IPC、NetWork等系统资源不再是全局性的,而是属于特定的Namespace.每个Namespaces里面的资源对其他的Namespace都是透明的。要创建新的Namespace,只需要调用clone 时指定相应的flag。当调用clone时,设定为CLONE_NEWPID.就会创建一个新的pid Namespaces。clone出来的新进程将成为NameSpace里的第一个进程,一个PID Namespace 为进程提供了一个独立的PID环境,PID namespace内的PID将从1开始,在Namespace内调用fork,vfork,或clone都将产生一个在该Namespace内部独立的PID。新创建的Namespace里的第一个进程在该Namespace内的PID为1.PID namespace 是层次性的,新建的Namespace将会创建该Namespace的进程属于Namespace的子namespace。子namespace中的进程对于父namespace是可见的,一个进程将拥有不止一个PID。而是在所在的Namespace中的进程对所有直系祖先Namespace中都将有有一个PID。

当调用Clone,设定了Clone_NEWIPC.将会创建一个新的IPCNamespace,clone出来的进程将成为Namespace里的第一个进程,一个IPC Namespace 有一组System V IPC objects标识符构成,这个标识符有IPC相关的系统调用创建, 在一个IPC Namespace里面创建IPC object 对该Namespace内的所有进程可见,但对其他的Namespace不可见,这样就使的不同的Namespace 之间的进程不能直接通信。PID namespace和IPCnamespace可以组合起来一起使用,只需要在调用clone时,同时指定CLONE_NEWPID|CLONE_NEWIPC.

当调用Clone时,设定了CLONE_NEWNS,就会创建一个新的mount Namespace,每个进程都存在于一个mount namespace里面,mount namespace为进程提供一个文件层次视图,如果不设定这个flag,子进程和父进程共享一个mount namespace,其后子进程调用mount或umount将会影响到所有该Namespace内的进程,如果子进程在一个独立的mount namespace里面,就可以调用mount或umount建立一份新的文件层次视图。该flag配合pivot_root系统调用,可以为进程创建一个独立的目录空间。

当调用clone,设定CLONE_NEWNET,就会创建一个新的NetWork Namespace.一个network namespace为进程提供了一个完全独力的网络协议栈的视图,包括网络设备接口,IPV4/IPV6协议栈,IP路由表,防火墙规则,sockets等。一个Network namespace提供了一个独立的网络环境,就跟一个独立的系统一样。一个物理设备只能存在一个Network namespace中,可以从一个namespace 移动到另一个Namespace中,虚拟网络设备提供了一种类似管道的抽象,可以在不同的Namespace之间建立隧道,利用虚拟化网络设备,可以建立到其他的namespace中的物理设备桥接,当一个Namespace被销毁时,物理设备会自动移回init network namespace,即系统最开始的namespace.

当调用clone,设定CLONE_NEWUTS,就会创建一个新的UTS Namespace,一个UTSnamespcae 就是一组被uname返回的标识符,新的UTS namespace 中的标识符通过复制调用进程所属的namespace的标识符来初始化,clone出来的进程可以通过相关系统调用改变这些标识符,比如sethostname来改变namespace的hostname。这一改变,对namespace内所有进程可见。CLONE_NEWUTS和CLONE_NEWNET可以一起使用。

以上所有clone flag 都可以一起使用,为进程提供了一个独立的运行环境,LXC正是通过在clone时设定flag,为进程创建对立PID,IPC,FS,NETWORK,UTS的空间容器,一个容器就是一个虚拟的运行环境,对容器的进程是透明的,每个进程都以为自己直接在系统上运行。

chromium在sandbox里将设置namespace的代码:

 

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机制,我们将接下来进行分析。