vArmor代码学习下
前言
上篇文章中,简单介绍了vArmo如何通过behavior
和bpfenforcer
两种模式实现观察和阻断两种模式。在 vArmor 的实现中,规则是核心组件之一,它们用于定义和控制对网络流量和系统调用的访问权限。所以本文将详细分析 vArmor 中规则的定义、存储、匹配及更新机制。
规则
规则数据结构
在 vArmor 中,规则主要通过以下结构体来定义:
struct net_rule { u32 flags; // 规则标志位 unsigned char address[16]; // IP 地址 unsigned char mask[16]; // 子网掩码 u32 port; // 端口号 };
- flags:用于标识规则的特性,例如是否启用、策略类型等。
- address:表示允许或拒绝的 IP 地址,支持 IPv4 和 IPv6。
- mask:定义 IP 地址的匹配范围,通常用于 CIDR 表达式。
- port:指定规则适用的端口号。
在用户态,规则的定义类似于内核态:
type bpfNetworkRule struct { Flags uint32 Address [16]byte Mask [16]byte Port uint32 }
该结构体与内核态的 net_rule 结构体保持一致,以确保数据在内核态和用户态之间的一致性。
规则存储
规则的存储主要依赖于 eBPF 的映射结构。在 vArmor 中,使用了两种类型的映射:外部映射和内部映射。
外部映射
struct { __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); __uint(max_entries, OUTER_MAP_ENTRIES_MAX); __type(key, u32); __type(value, u32); } v_net_outer SEC(".maps");
- v_net_outer:这是一个哈希映射,类型为
BPF_MAP_TYPE_HASH_OF_MAPS
,用于存储与命名空间相关的内部映射的引用。其键是一个无符号整型(u32
),值则是指向内部映射的指针。
内部映射
内部映射用于存储具体的规则信息,它的定义如下:
struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, NET_INNER_MAP_ENTRIES_MAX); __type(key, u32); __type(value, struct net_rule); } v_net_inner SEC(".maps");
- v_net_inner:这是一个哈希映射,类型为
BPF_MAP_TYPE_HASH
,用于存储具体的网络规则。其键为规则 ID,值为net_rule
结构体。
规则匹配逻辑
规则的匹配逻辑在内核态通过 varmor_socket_connect
函数实现,主要步骤如下:
static u32 *get_net_inner_map(u32 mnt_ns) { return bpf_map_lookup_elem(&v_net_outer, &mnt_ns); }
- 使用当前进程的挂载命名空间 ID(
mnt_ns
)作为键,从v_net_outer
映射中查找对应的内部映射。
规则匹配过程
在 varmor_socket_connect
函数中,执行以下逻辑:
SEC("lsm/socket_connect") int BPF_PROG(varmor_socket_connect, struct socket *sock, struct sockaddr *address, int addrlen) { u32 mnt_ns = get_task_mnt_ns_id(current); u32 *vnet_inner = get_net_inner_map(mnt_ns); for(inner_id=0; inner_id<NET_INNER_MAP_ENTRIES_MAX; inner_id++) { struct net_rule *rule = get_net_rule(vnet_inner, inner_id); if (rule == NULL) { DEBUG_PRINT("access allowed"); return 0; // 允许访问 } // 进行规则匹配逻辑... } }
- 获取当前进程的挂载命名空间 ID。
- 根据命名空间 ID 从
v_net_outer
中获取对应的内部映射vnet_inner
。 - 遍历内部映射中的规则,进行匹配。如果没有找到匹配规则,则返回允许访问。
规则的更新
在用户态代码中,首先创建内部映射:
mapName := fmt.Sprintf("v_net_inner_%d", nsID) innerMapSpec := ebpf.MapSpec{ Name: mapName, Type: ebpf.Hash, KeySize: 4, ValueSize: 4*2 + 16*2, MaxEntries: uint32(varmortypes.MaxBpfNetworkRuleCount), } innerMap, err := ebpf.NewMap(&innerMapSpec)
- 这里使用
nsID
作为命名空间 ID,创建了一个新的内部映射。
接下来,解析规则并将其添加到内部映射中:
for i, network := range bpfContent.Networks { var rule bpfNetworkRule rule.Flags = network.Flags rule.Port = network.Port ip := net.ParseIP(network.Address) if ip.To4() != nil { copy(rule.Address[:], ip.To4()) } else { copy(rule.Address[:], ip.To16()) } if network.CIDR != "" { _, ipNet, err := net.ParseCIDR(network.CIDR) if err != nil { return err } copy(rule.Mask[:], ipNet.Mask) } var index uint32 = uint32(i) err = innerMap.Put(&index, &rule) if err != nil { return err } }
- 这里将解析的网络规则存储到内部映射中,使用规则的索引作为键。
最后,将内部映射的引用放入外部映射中:
err = enforcer.objs.V_netOuter.Put(&nsID, innerMap) if err != nil { return err }
- 通过
nsID
将innerMap
存入v_net_outer
,完成规则的更新。
总结
vArmor 的规则部分通过 eBPF 映射机制实现了灵活而高效的网络访问控制。规则的定义、存储、匹配及更新机制都经过精心设计,以确保在内核态和用户态之间的数据一致性。通过这种方式,vArmor 能够在运行时动态地管理和应用安全策略,为云原生环境提供了强大的安全防护能力。
参考
- https://github.com/bytedance/vArmor
- 从0到1打造云原生容器沙箱vArmor
- https://blog.spoock.com/2023/08/26/eBPF-vArmor-client/
本文作者:pamela@涂鸦智能安全实验室
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
文章目录