vArmor中的ptrace阻断功能实现分析

说明

最近发现vArmor-ebpf增加一个pstrace的功能,学习其实现原理。具体参见 ptrace-confine

内核实现

v_ptrace

1
2
3
4
5
6
7
8
9
10
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32);
__type(value, u64); // rule: permissions + flags
__uint(max_entries, OUTER_MAP_ENTRIES_MAX);
} v_ptrace SEC(".maps");

static __always_inline u64 *get_ptrace_rule(u32 mnt_ns) {
return bpf_map_lookup_elem(&v_ptrace, &mnt_ns);
}

定义了v_ptrace用来保存ptrace的规则。

函数get_ptrace_rule获取对应的命名空间的ptrace的规则。

ptrace_access_check

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
SEC("lsm/ptrace_access_check")
int BPF_PROG(varmor_ptrace_access_check, struct task_struct *child, unsigned int mode) {
// Retrieve the current task
struct task_struct *current = (struct task_struct *)bpf_get_current_task();
u32 current_mnt_ns = get_task_mnt_ns_id(current);
u32 child_mnt_ns = get_task_mnt_ns_id(child);

// Whether the current task has ptrace access control rule
u64 *rule = get_ptrace_rule(current_mnt_ns);
if (rule != 0) {
DEBUG_PRINT("================ lsm/ptrace_access_check ================");
if (!ptrace_permission_check(current_mnt_ns, child_mnt_ns, *rule, (mode & PTRACE_MODE_READ) ? AA_PTRACE_READ : AA_PTRACE_TRACE))
return -EPERM;
}

// Whether the child task has ptrace access control rule
// We allow tasks from the init mnt ns by default
rule = get_ptrace_rule(child_mnt_ns);
if (current_mnt_ns != init_mnt_ns && rule != 0) {
DEBUG_PRINT("================ lsm/ptrace_access_check ================");
if (!ptrace_permission_check(current_mnt_ns, child_mnt_ns, *rule, (mode & PTRACE_MODE_READ) ? AA_MAY_BE_READ : AA_MAY_BE_TRACED))
return -EPERM;
}

return 0;
}

有关ptrace_access_check原函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int apparmor_ptrace_access_check(struct task_struct *child,
unsigned int mode)
{
struct aa_label *tracer, *tracee;
int error;

tracer = __begin_current_label_crit_section();
tracee = aa_get_task_label(child);
error = aa_may_ptrace(tracer, tracee,
(mode & PTRACE_MODE_READ) ? AA_PTRACE_READ
: AA_PTRACE_TRACE);
aa_put_label(tracee);
__end_current_label_crit_section(tracer);

return error;
}

针对mode的值,以下是可能的选项及其含义:

  1. PTRACE_MODE_READ(0x01):表示读取另一个任务的状态。
  2. PTRACE_MODE_ATTACH(0x02):表示附加到另一个任务。

所以通过(mode & PTRACE_MODE_READ) ? AA_PTRACE_READ : AA_PTRACE_TRACE),就可以知道当前pstrace操作类型。

varmor_ptrace_access_check函数根据传入的ptrace访问规则和请求的权限,对当前任务和child任务的挂载命名空间进行比较,并根据规则中的标志位决定是否允许访问。这个函数用于在ptrace_access_check函数中进行权限检查,以确定是否允许使用ptrace`系统调用来追踪或读取另一个任务的状态。

所以关键实现是看ptrace_permission_check函数具体实现。

ptrace_permission_check

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define PRECISE_MATCH 0x00000001
#define GREEDY_MATCH 0x00000002

static __always_inline bool ptrace_permission_check(u32 current_mnt_ns, u32 child_mnt_ns, u64 rule, u32 request_permission) {
DEBUG_PRINT("current task(mnt ns: %u) request the vArmor ptrace permission(0x%x) of child task(mnt ns: %u)",
current_mnt_ns, request_permission, child_mnt_ns);

u32 permissions = rule >> 32;
u32 flags = (u32)(rule & 0xffffffff);

if (permissions & request_permission) {
// deny all tasks
if (flags & GREEDY_MATCH) {
DEBUG_PRINT("access denied");
return false;
}

// only deny tasks outside the container
if (flags & PRECISE_MATCH && current_mnt_ns != child_mnt_ns) {
DEBUG_PRINT("access denied");
return false;
}
}

DEBUG_PRINT("access allowed");
return true;
}
  1. 通过rule >> 32(u32)(rule & 0xffffffff),分别得到permissionsflags

  2. permissions & request_permission 如果在permissions中包含了request_permission的权限,则进一步判断。

  3. 如果标志位(flags)中包含了GREEDY_MATCH,表示拒绝所有任务,函数返回false表示访问被拒绝。
  4. 如果标志位中包含了PRECISE_MATCH,表示只拒绝容器外的任务,那么如果当前任务的挂载命名空间ID与child任务的挂载命名空间ID不相等,函数返回false表示访问被拒绝。
  5. 如果以上判断都通过,表示访问被允许,函数返回true

这个函数根据传入的ptrace访问规则和请求的权限,对当前任务和child任务的挂载命名空间进行比较,并根据规则中的标志位决定是否允许访问。这个函数用于在ptrace_access_check函数中进行权限检查,以确定是否允许使用ptrace系统调用来追踪或读取另一个任务的状态。

用户态实现

PtraceContent

apis/varmor/v1beta1/armorprofile_types.go中添加了相关的规则。

定义了PtraceContent结构体,包含了PermissionsFlags,对应内核态判断标准得两个参数。

BpfContent结构体是所有规则类型的合集,在其中增加了PtraceContent结构体,后面就可以通过BpfContent设置pstrace的规则。

rule

pkg/lsm/bpfenforcer/profile.go

将pstrace相关的规则添加到V_ptrace规则列表中。

在添加规则时通过rule := uint64(bpfContent.Ptrace.)<<32 + uint64(bpfContent.Ptrace.Flags)PermissionsFlags合并成为rule,这种方式和内核态中代码是对应的。

1
2
u32 permissions = rule >> 32;
u32 flags = (u32)(rule & 0xffffffff);

generateRawPtraceRule

internal/profile/bpf/bpf.go中增加了generateRawPtraceRule()函数方法。

这个函数是将varmor.PtraceRule转换为varmor.BpfContent格式。两者对应的格式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type PtraceRule struct {
// StrictMode is used to indicate whether to restrict ptrace permissions for all source and destination processes.
// If set to false, it restricts ptrace permissions only for processes in other containers.
// If set to true, it restricts ptrace permissions for all processes (except those within the init mnt namespace)
StrictMode bool `json:"strictMode,omitempty"`
// Permissions are used to indicate which ptrace-related permissions of the target container should be restricted.
// Available values: trace, traceby, read, readby.
//
// trace, traceby
// For "write" operations, or other operations that are more dangerous, such as: ptrace attaching (PTRACE_ATTACH) to
// another process or calling process_vm_writev(2).
// read, readby
// For "read" operations or other operations that are less dangerous, such as: get_robust_list(2); kcmp(2); reading
// /proc/pid/auxv, /proc/pid/environ, or /proc/pid/stat; or readlink(2) of a /proc/pid/ns/* file.
Permissions []string `json:"permissions,omitempty"`
}

type PtraceContent struct {
Permissions uint32 `json:"permissions"`
Flags uint32 `json:"flags"`
}

permission类型(trace,read,traceby,readby)转换为AaPtraceTrace等类型的数字。

rule类型(StrictMode和其他模式)转换为GreedyMatch(0x00000002)和PreciseMatch(0x00000001)

所以,当规则设定使用字符串表示,函数generateRawPtraceRule()转换为对应的数字类型,方便后面转换为rule

org_varmorpolicies

crd.varmor.org_varmorpolicies.yaml中也增加了相关的规则格式和说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ptrace:
properties:
permissions:
description: "Permissions are used to indicate which
ptrace-related permissions of the target container
should be restricted. Available values: trace, traceby,
read, readby. \n trace, traceby For \"write\" operations,
or other operations that are more dangerous, such
as: ptrace attaching (PTRACE_ATTACH) to another
process or calling process_vm_writev(2). read, readby
For \"read\" operations or other operations that
are less dangerous, such as: get_robust_list(2);
kcmp(2); reading /proc/pid/auxv, /proc/pid/environ,
or /proc/pid/stat; or readlink(2) of a /proc/pid/ns/*
file."
items:
type: string
type: array
strictMode:
description: StrictMode is used to indicate whether
to restrict ptrace permissions for all source and
destination processes. If set to false, it restricts
ptrace permissions only for processes in other containers.
If set to true, it restricts ptrace permissions
for all processes (except those within the init
mnt namespace)
type: boolean
type: object

crd.varmor.org_varmorpolicies.yaml定义了一个 Kubernetes 自定义资源定义(Custom Resource Definition,CRD),用于扩展 Kubernetes API 并引入名为 varmorpolicies.crd.varmor.org 的新资源类型。

  • properties定义了两个属性,分别是permissionsstrictMode
  • permissions是一个字符串数组类型,可选值包括trace, traceby,read, readby
  • strictMode是布尔类型

这个配置的设置和前面在generateRawPtraceRule()函数中解析PtraceRule就对应上了。

总结

通过 ptrace-confine中添加了ptrace功能的相关实现,可以很好地了解在用户态对于规则的解析和运用。

参考

https://github.com/bytedance/vArmor-ebpf/commit/b539731a641283dbb48ff1e7e569fe521b717d41