CVE-2017-16995复现与分析

CVE-2017-16995复现与分析

前言

CVE-2017-16995是一个内核提权漏洞,最近PWN2OWN爆出了一个ebpf模块相关的提权漏洞,因此打算系统地学习一下ebpf这个内核模块,并复现和分析与之相关的内核漏洞,之前先知已经有rebeyond前辈发了一篇分析深入分析Ubuntu本地提权漏洞—【CVE-2017-16995】,本篇文章将补充一部分ebpf的基础知识为之后的其他漏洞复现做准备。

漏洞概述

漏洞存在于内核版本小于4.13.9的系统中,漏洞成因为kernel/bpf/verifier.c文件中的check_alu_op函数的检查问题,这个漏洞可以允许一个普通用户向系统发起拒绝服务攻击(内存破坏)或者提升到特权用户。

漏洞复现

本次复现使用的是4.4.110版本的内核,下载地址为Download

下载源码之后编译内核得到二进制的内核文件,结合之前漏洞复现的文件系统启动qemu,使用exploit-db上的exp编译gcc ./poc.c -static -pthread -o poc,打包进文件系统再重新启动qemu,运行编译后的文件即可提权成功。

前置知识

eBPF模块

ebpf是bpf模块的功能增强版本,我们先来了解一下bpf模块。BPF的全称为Berkeley Packet Filter,顾名思义,这是一个用来做包过滤的架构。它也是tcpdump和wireshark实现的基础。BPF的两大核心功能是过滤和复制,以tcpdump为例,BPF一方面接受tcpdump经过libpcap转码后的滤包条件,根据这些规则过滤报文;另一方面也将符合条件的报文复制到用户空间,最终由libpcap发送给tcpdump。

BPF设计的架构如下,基本原理是网卡驱动在收到数据报文之后多出一条路径转发给内核的BPF模块,供其和用户态的程序交互使用。

我们这里使用tcpdump的-d参数查看我们过滤数据报文的实际规则,可以看到BPF有一套自己的指令集来过滤数据包。

╭─wz@wz-virtual-machine ~/Desktop/CTF/wangdingbei2020/zhuque/supersafe_vm ‹hexo*› 
╰─$ sudo tcpdump -d -i ens33 tcp port 23                                                                                         1(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 8
#判断是否是ipv6,false则jmp到L8
(002) ldb      [20]
(003) jeq      #0x6             jt 4    jf 19
(004) ldh      [54]
(005) jeq      #0x17            jt 18   jf 6
(006) ldh      [56]
(007) jeq      #0x17            jt 18   jf 19
(008) jeq      #0x800           jt 9    jf 19
#判断是否是ipv4
(009) ldb      [23]
(010) jeq      #0x6             jt 11   jf 19
#判断是否是tcp
(011) ldh      [20]
(012) jset     #0x1fff          jt 19   jf 13
#检测是否是ip分片报文
(013) ldxb     4*([14]&0xf)
(014) ldh      [x + 14]
#tcp报文中的src port位置
(015) jeq      #0x17            jt 18   jf 16
(016) ldh      [x + 16]
#tcp报文中的dest port位置
(017) jeq      #0x17            jt 18   jf 19
(018) ret      #262144
#符合要求
(019) ret      #0
#不符合要求

BPF采用的报文过滤设计全称是CFG((Computation Flow Graph)),过滤器基于if-else的控制流图,具体的实现不再展开。

内核filter文档有关于BPF开发的sample,每条指令的格式类似,都是一个32字节大小的结构体类型,code表明指令类型,k用来做一些跳转的value或其他用处。

struct sock_filter {    /* Filter block */
    __u16   code;   /* Actual filter code */
    __u8    jt; /* Jump true */
    __u8    jf; /* Jump false */
    __u32   k;      /* Generic multiuse field */
};

BPF的交互过程也是类似的,可以总结为以下几个步骤:

  1. 使用sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))创建一个raw socket
  2. 将Socket绑定指定网卡
  3. 使用setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)))里的SO_ATTACH_FILTER参数将bpf结构体传入内核
  4. bytes = recv(s, buf, sizeof(buf), 0);recv函数来获取过滤的数据报文

之后BPF引入了JIT编译代码优化性能,引入了seccomp功能添加沙箱,在kernel 3.15版本后引入了eBPF。

eBPF sample

eBPF(extended BPF)引进之后之前的BPF被称为cBPF(classical BPF),相比于cBPF,eBPF做了很多大刀阔斧的改变,比如拿C写的BPF代码,基于map的全新交互方式,新的指令集,Verifier的引进等。

我们首先来看下eBPF的sample来了解基本的交互方式。

第一个示例只有一个.c文件,BPF代码需要自己构造,可以类比成C里嵌了汇编。

//./linux-4.4.110/samples/bpf/sock_example.c

/* eBPF example program:
 * - creates arraymap in kernel with key 4 bytes and value 8 bytes
 *
 * - loads eBPF program:
 *   r0 = skb->data[ETH_HLEN + offsetof(struct iphdr, protocol)];
 *   *(u32*)(fp - 4) = r0;
 *   // assuming packet is IPv4, lookup ip->proto in a map
 *   value = bpf_map_lookup_elem(map_fd, fp - 4);
 *   if (value)
 *        (*(u64*)value) += 1;
 *
 * - attaches this program to eth0 raw socket
 *
 * - every second user space reads map[tcp], map[udp], map[icmp] to see
 *   how many packets of given protocol were seen on eth0
 */
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <linux/bpf.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <stddef.h>
#include "libbpf.h"

static int test_sock(void)
{
    int sock = -1, map_fd, prog_fd, i, key;
    long long value = 0, tcp_cnt, udp_cnt, icmp_cnt;
    //[1]使用bpf_create_map函数新建一个map,其中map类型为BPF_MAP_TYPE_ARRAY,模拟一个数组
    map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value),
                256);
    if (map_fd < 0) {
        printf("failed to create map '%s'\n", strerror(errno));
        goto cleanup;
    }
    //[2]编写eBPF代码,虽然指令集不同,但是跟x86的汇编代码非常类似
    struct bpf_insn prog[] = {
        BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
        BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol) /* R0 = ip->proto */),
        BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */
        BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */
        BPF_LD_MAP_FD(BPF_REG_1, map_fd),
        BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
        BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
        BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */
        BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* xadd r0 += r1 */
        BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */
        BPF_EXIT_INSN(),
    };
    //[3]使用bpf_prog_load函数加载eBPF代码到内核
    prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog, sizeof(prog),"GPL", 0);
    if (prog_fd < 0) {
        printf("failed to load prog '%s'\n", strerror(errno));
        goto cleanup;
    }
    //[4]sock绑定lo网卡
    sock = open_raw_sock("lo");
    //[5]eBPF代码绑定socket
    if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd,sizeof(prog_fd)) < 0) {
        printf("setsockopt %s\n", strerror(errno));
        goto cleanup;
    }
    //[5]执行eBPF代码,根据给定的key过滤数据包,输出value从而打印出每1s内不同类型的数据包个数
    for (i = 0; i < 10; i++) {
        key = IPPROTO_TCP;
        assert(bpf_lookup_elem(map_fd, &key, &tcp_cnt) == 0);

        key = IPPROTO_UDP;
        assert(bpf_lookup_elem(map_fd, &key, &udp_cnt) == 0);

        key = IPPROTO_ICMP;
        assert(bpf_lookup_elem(map_fd, &key, &icmp_cnt) == 0);

        printf("TCP %lld UDP %lld ICMP %lld packets\n",
               tcp_cnt, udp_cnt, icmp_cnt);
        sleep(1);
    }

cleanup:
    /* maps, programs, raw sockets will auto cleanup on process exit */
    return 0;
}

int main(void)
{
    FILE *f;

    f = popen("ping -c5 localhost", "r");
    (void)f;

    return test_sock();
}

第二个示例的功能和第一个一致,因此我们不再增加注释,只是给大家展示另一种eBPF开发的方式,也就是用C的形式写eBPF代码,这种写法大大解放了开发人员的工作。

首先编译sockex1_kern.csockex1_kern.o,在这个代码文件中定义了eBPF规则。

//./linux-4.4.110/samples/bpf/sock_example.c/sockex1_kern.c
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/if_packet.h>
#include <uapi/linux/ip.h>
#include "bpf_helpers.h"

struct bpf_map_def SEC("maps") my_map = {
    .type = BPF_MAP_TYPE_ARRAY,
    .key_size = sizeof(u32),
    .value_size = sizeof(long),
    .max_entries = 256,
};

SEC("socket1")
int bpf_prog1(struct __sk_buff *skb)
{
    int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol));
    long *value;

    if (skb->pkt_type != PACKET_OUTGOING)
        return 0;

    value = bpf_map_lookup_elem(&my_map, &index);
    if (value)
        __sync_fetch_and_add(value, skb->len);

    return 0;
}
char _license[] SEC("license") = "GPL";

之后使用sockex1_user.c加载eBPF代码到内核进而执行代码,过滤数据包得到vaule并输出。

#include <stdio.h>
#include <assert.h>
#include <linux/bpf.h>
#include "libbpf.h"
#include "bpf_load.h"
#include <unistd.h>
#include <arpa/inet.h>

int main(int ac, char **argv)
{
    char filename[256];
    FILE *f;
    int i, sock;

    snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

    if (load_bpf_file(filename)) {
        printf("%s", bpf_log_buf);
        return 1;
    }

    sock = open_raw_sock("lo");

    assert(setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, prog_fd,
              sizeof(prog_fd[0])) == 0);

    f = popen("ping -c5 localhost", "r");
    (void) f;

    for (i = 0; i < 5; i++) {
        long long tcp_cnt, udp_cnt, icmp_cnt;
        int key;

        key = IPPROTO_TCP;
        assert(bpf_lookup_elem(map_fd[0], &key, &tcp_cnt) == 0);

        key = IPPROTO_UDP;
        assert(bpf_lookup_elem(map_fd[0], &key, &udp_cnt) == 0);

        key = IPPROTO_ICMP;
        assert(bpf_lookup_elem(map_fd[0], &key, &icmp_cnt) == 0);

        printf("TCP %lld UDP %lld ICMP %lld bytes\n",
               tcp_cnt, udp_cnt, icmp_cnt);
        sleep(1);
    }

    return 0;
}

以上就是两种eBPF开发的基本方式,可以看到C语言编写eBPF代码更加简洁,这也是eBPF相比于cBPF的一个优势。

eBPF代码执行过程

首先我们调用bpf_create_map来新建一个attr变量,这个变量为联合类型,其成员变量随系统调用的类型不同而变化,之后对变量成员进行初始化赋值,包括map类型,key大小,value大小,以及最打容量。

之后调用sys_bpf进而使用系统调用syscall(__NR_bpf, BPF_MAP_CREATE, attr, size);创建一个map数据结构,最终返回map的文件描述符。这个文件是用户态和内核态共享的,因此后续内核态和用户态都可以对这块共享内存进行读写(可以参见下面的bpf_cmd)。

//lib/bpf.c
int bpf_create_map(enum bpf_map_type map_type, 
int key_size,int value_size, int max_entries)
{
    union bpf_attr attr;

    memset(&attr, '\0', sizeof(attr));

    attr.map_type = map_type;
    attr.key_size = key_size;
    attr.value_size = value_size;
    attr.max_entries = max_entries;

    return sys_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
}
//lib/bpf.c
static int sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr,
           unsigned int size)
{
    return syscall(__NR_bpf, cmd, attr, size);
}
//bpf.h
union bpf_attr {
    struct { /* anonymous struct used by BPF_MAP_CREATE command */
        __u32   map_type;   /* one of enum bpf_map_type */
        __u32   key_size;   /* size of key in bytes */
        __u32   value_size; /* size of value in bytes */
        __u32   max_entries;    /* max number of entries in a map */
    };

    struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
        __u32       map_fd;
        __aligned_u64   key;
        union {
            __aligned_u64 value;
            __aligned_u64 next_key;
        };
        __u64       flags;
    };

    struct { /* anonymous struct used by BPF_PROG_LOAD command */
        __u32       prog_type;  /* one of enum bpf_prog_type */
        __u32       insn_cnt;
        __aligned_u64   insns;
        __aligned_u64   license;
        __u32       log_level;  /* verbosity level of verifier */
        __u32       log_size;   /* size of user buffer */
        __aligned_u64   log_buf;    /* user supplied buffer */
        __u32       kern_version;   /* checked when prog_type=kprobe */
    };

    struct { /* anonymous struct used by BPF_OBJ_* commands */
        __aligned_u64   pathname;
        __u32       bpf_fd;
    };
} __attribute__((aligned(8)));
//bpf.h
/* BPF syscall commands, see bpf(2) man-page for details. */
enum bpf_cmd {
    BPF_MAP_CREATE,
    BPF_MAP_LOOKUP_ELEM,
    BPF_MAP_UPDATE_ELEM,
    BPF_MAP_DELETE_ELEM,
    BPF_MAP_GET_NEXT_KEY,
    BPF_PROG_LOAD,
    BPF_OBJ_PIN,
    BPF_OBJ_GET,
};

之后调用bpf_prog_load函数来将用户的eBPF代码加载到内核,我在内核文件中看到了两个同名不同参的函数定义,根据eBPF的调用情况应当是先调用libbpf的这个函数,之后通过系统调用调用后面的函数。

可以看到在使用BPF_PROG_LOAD这个命令时,变量的数据类型又会发生改变,其成员包括指令类型,指令起始地址,指令条数,使用的license(要求是GPL),日志地址,日志大小以及日志等级等。随后使用系统调用syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));去运行真正的函数。

在第二个函数中添加了部分注释帮助理解,其核心是在运行eBPF代码前使用verifier进行代码检查并对指令进行修正防止产生恶意的代码跳转。

//libbpf.c
int bpf_prog_load(enum bpf_prog_type prog_type,
          const struct bpf_insn *insns, int prog_len,
          const char *license, int kern_version)
{
    union bpf_attr attr = {
        .prog_type = prog_type,
        .insns = ptr_to_u64((void *) insns),
        .insn_cnt = prog_len / sizeof(struct bpf_insn),
        .license = ptr_to_u64((void *) license),
        .log_buf = ptr_to_u64(bpf_log_buf),
        .log_size = LOG_BUF_SIZE,
        .log_level = 1,
    };

    /* assign one field outside of struct init to make sure any
     * padding is zero initialized
     */
    attr.kern_version = kern_version;

    bpf_log_buf[0] = 0;

    return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}
//syscall.c
/* last field in 'union bpf_attr' used by this command */
#define BPF_PROG_LOAD_LAST_FIELD kern_version

static int bpf_prog_load(union bpf_attr *attr)
{
    enum bpf_prog_type type = attr->prog_type;
    struct bpf_prog *prog;
    int err;
    char license[128];
    bool is_gpl;
    //检查成员
    if (CHECK_ATTR(BPF_PROG_LOAD))
        return -EINVAL;

    /* copy eBPF program license from user space */
    if (strncpy_from_user(license, u64_to_ptr(attr->license),
                  sizeof(license) - 1) < 0)
        return -EFAULT;
    license[sizeof(license) - 1] = 0;

    /* eBPF programs must be GPL compatible to use GPL-ed functions */
    is_gpl = license_is_gpl_compatible(license);
    //指令最大条数为4096
    if (attr->insn_cnt >= BPF_MAXINSNS)
        return -EINVAL;
    //检查指令类型和内核版本
    if (type == BPF_PROG_TYPE_KPROBE &&
        attr->kern_version != LINUX_VERSION_CODE)
        return -EINVAL;

    if (type != BPF_PROG_TYPE_SOCKET_FILTER && !capable(CAP_SYS_ADMIN))
        return -EPERM;
    //创建prog结构体,用来存储eBPF指令和其他参数
    /* plain bpf_prog allocation */
    prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER);
    if (!prog)
        return -ENOMEM;
    //获取锁
    err = bpf_prog_charge_memlock(prog);
    if (err)
        goto free_prog_nouncharge;

    prog->len = attr->insn_cnt;
    //将用户的指令拷贝到prog结构体中
    err = -EFAULT;
    if (copy_from_user(prog->insns, u64_to_ptr(attr->insns),
               prog->len * sizeof(struct bpf_insn)) != 0)
        goto free_prog;
    //默认的jitd为0
    prog->orig_prog = NULL;
    prog->jited = 0;
    //设置引用计数为1
    atomic_set(&prog->aux->refcnt, 1);
    prog->gpl_compatible = is_gpl ? 1 : 0;

    /* find program type: socket_filter vs tracing_filter */
    err = find_prog_type(type, prog);
    if (err < 0)
        goto free_prog;
    //eBPF verifier会对我们的eBPF指令进行检查,一些恶意的代码或者死循环的将不会得到执行
    /* run eBPF verifier */
    err = bpf_check(&prog, attr);
    if (err < 0)
        goto free_used_maps;

    /* fixup BPF_CALL->imm field */
    //修正指令中call和jmp的范围
    fixup_bpf_calls(prog);

    /* eBPF program is ready to be JITed */
    //JIT加载代码
    err = bpf_prog_select_runtime(prog);
    if (err < 0)
        goto free_used_maps;

    err = bpf_prog_new_fd(prog);
    if (err < 0)
        /* failed to allocate fd */
        goto free_used_maps;

    return err;

free_used_maps:
    free_used_maps(prog->aux);
free_prog:
    bpf_prog_uncharge_memlock(prog);
free_prog_nouncharge:
    bpf_prog_free(prog);
    return err;
}

之后调用setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd,sizeof(prog_fd))来执行eBPF代码,每一个socket数据包都会执行这个检查,从而实现包过滤。

eBPF指令集

eBPF也有一套自己的指令集,可以想象成实现了一个VM,其中有11个虚拟寄存器,根据调用规则可以对应到我们x86的寄存器中。

R0 -- RAX
R1 -- RDI
R2 -- RSI
R3 -- RDX
R4 -- RCX
R5 -- R8
R6 -- RBX
R7 -- R13
R8 -- R14
R9 -- R15
R10 -- RBP

每条指令的格式如下,成员包括操作码,目标寄存器,源寄存器,偏移和立即数。

struct bpf_insn {
    __u8    code;       /* opcode */
    __u8    dst_reg:4;  /* dest register */
    __u8    src_reg:4;  /* source register */
    __s16   off;        /* signed offset */
    __s32   imm;        /* signed immediate constant */
};

操作码共有8种大类,以低3bit区分不同操作码,BPF_ALU为计算指令,BPF_MISC为其他指令,其他指令根据名字就可以猜到其含义。

/* Instruction classes */
#define BPF_CLASS(code) ((code) & 0x07)
#define     BPF_LD      0x00
#define     BPF_LDX     0x01
#define     BPF_ST      0x02
#define     BPF_STX     0x03
#define     BPF_ALU     0x04
#define     BPF_JMP     0x05
#define     BPF_RET     0x06
#define     BPF_MISC        0x07

eBPF指令的编码如下,低三个bits被用来做指令大类的标志。这部分我参考了官方的手册,这里可以看到0x6和0x7两个指令名在源码中命名实际上是用BPF,这里只介绍eBPF。

+----------------+--------+--------------------+
  |   4 bits       |  1 bit |   3 bits           |
  | operation code | source | instruction class  |
  +----------------+--------+--------------------+
  (MSB)                                      (LSB)
  Classic BPF classes:    eBPF classes:

  BPF_LD    0x00          BPF_LD    0x00
  BPF_LDX   0x01          BPF_LDX   0x01
  BPF_ST    0x02          BPF_ST    0x02
  BPF_STX   0x03          BPF_STX   0x03
  BPF_ALU   0x04          BPF_ALU   0x04
  BPF_JMP   0x05          BPF_JMP   0x05
  BPF_RET   0x06          BPF_JMP32 0x06
  BPF_MISC  0x07          BPF_ALU64 0x07

当指令类型为BPF_ALU or BPF_JMP,第4bit进行编码,BPF_K表示使用32位的立即数作为源操作数,BPF_X表示使用寄存器X作为源操作数。MSB的4bit表示操作数。

BPF_K     0x00
BPF_X     0x08

当指令类型为BPF_ALU or BPF_ALU64,实际指令类型为以下之一,也就是常见的运算指令。

BPF_ADD   0x00
  BPF_SUB   0x10
  BPF_MUL   0x20
  BPF_DIV   0x30
  BPF_OR    0x40
  BPF_AND   0x50
  BPF_LSH   0x60
  BPF_RSH   0x70
  BPF_NEG   0x80
  BPF_MOD   0x90
  BPF_XOR   0xa0
  BPF_MOV   0xb0  /* eBPF only: mov reg to reg */
  BPF_ARSH  0xc0  /* eBPF only: sign extending shift right */
  BPF_END   0xd0  /* eBPF only: endianness conversion */

当指令类型为BPF_JMP or BPF_JMP32,指令实际类型为以下之一,包括条件跳转和非条件跳转。

BPF_JA    0x00  /* BPF_JMP only */
  BPF_JEQ   0x10
  BPF_JGT   0x20
  BPF_JGE   0x30
  BPF_JSET  0x40
  BPF_JNE   0x50  /* eBPF only: jump != */
  BPF_JSGT  0x60  /* eBPF only: signed '>' */
  BPF_JSGE  0x70  /* eBPF only: signed '>=' */
  BPF_CALL  0x80  /* eBPF BPF_JMP only: function call */
  BPF_EXIT  0x90  /* eBPF BPF_JMP only: function return */
  BPF_JLT   0xa0  /* eBPF only: unsigned '<' */
  BPF_JLE   0xb0  /* eBPF only: unsigned '<=' */
  BPF_JSLT  0xc0  /* eBPF only: signed '<' */
  BPF_JSLE  0xd0  /* eBPF only: signed '<=' */

举个小例子,如BPF_ADD | BPF_X | BPF_ALU表示的含义是dst_reg = (u32) dst_reg + (u32) src_regBPF_XOR | BPF_K | BPF_ALU表示src_reg = (u32) src_reg ^ (u32) imm32

eBPF的Verifier机制

代码检测是eBPF的核心机制,总的检测可以分成两次,第一次使用DAG检查来避免循环,主要是对代码进行有向无环图检测。

第二次的检测则是模拟代码的执行,观测寄存器的栈的变化情况。

主要的检测函数为bpf_check,注释部分补充了一些实现逻辑。

int bpf_check(struct bpf_prog **prog, union bpf_attr *attr)
{
    char __user *log_ubuf = NULL;
    struct verifier_env *env;
    int ret = -EINVAL;
    //指令条数判断
    if ((*prog)->len <= 0 || (*prog)->len > BPF_MAXINSNS)
        return -E2BIG;

    /* 'struct verifier_env' can be global, but since it's not small,
     * allocate/free it every time bpf_check() is called
     */
    env = kzalloc(sizeof(struct verifier_env), GFP_KERNEL);
    if (!env)
        return -ENOMEM;

    env->

本文来源于: https://xz.aliyun.com/t/7782

相关推荐

WordPress Ninja Forms插件 CSRF to XSS漏洞(CVE-2020-12462)分析

前言 Ninja Forms是一款WordPress插件。使用Ninja Forms插件无需编码即可以创建美观、用户友好的WordPress表单。据统计,全球超过 2,000,000 个网站正在使用 Ninja Forms。 Ninja F

CVE-2019-18679 Squid 敏感信息泄漏

0x1 前言 Squid是一个开源的高性能的代理缓存服务器。它接受来自客户端的请求并适当地处理这些请求。例如,如果一个人想下载一web页面,他请求Squid为他取得这个页面。Squid随之连接到远程服务器并向这个页面发出请求。然后,Squi

某cms代码审计引发的思考

0x01、前言 在CNVD闲逛的时候看到这款CMS,发现常见的用于getshell的漏洞都有人提交过,顿时来了兴趣,下载下来经过审计发现漏洞的利用方式和常规方法稍有不同,尤其是对于文件上传的漏洞来说,在以前的测试中主要集中在图片附件之类的地

Potato家族本地提权细节

本文结合POC源码,研究Potato家族本地提权细节 Feature or vulnerability 该提权手法的前提是拥有SeImpersonatePrivilege或SeAssignPrimaryTokenPrivilege权限,以下

一次对参数编码混淆越权的尝试

一、前言 当你进行越权测试遇到参数被混淆编码时是简单的尝试然后放弃,还是和他杠上?,本文将介绍一次对某系统某参数有混淆编码的越权尝试。 二、越权尝试 1、登录某系统,点击保存 2、使用burp抓包,发现key参数: RuYW1lPVRfW

Bug Bounty:$20000 Facebook DOM XSS

window.postMessage()方法保证窗口对象之间的安全跨域通信;例如,在页面和它产生的弹出窗口之间,或者在页面和嵌入其中的iFrame之间。 更多关于window.postMessage()方法的知识可以查阅Mozilla Po

mailoney蜜罐学习记录

蜜罐介绍 Mailoney是T-pot蜜罐系统中针对SMTP协议的一个蜜罐,该蜜罐中有三种工作模式,分别为open_relay,postfix_creds,schizo_open_relay。各种模式功能如下: open_relay-只是一

CVE-2020-11108: How I Stumbled into a Pi-hole RCE+LPE

原文地址:https://frichetten.com/blog/cve-2020-11108-pihole-rce/ 以下是CVE-2020-11108的writeup,Pi-hole Web应用程序的认证用户可以通过CVE-2020-1

VMPwn之温故知新

VMPwn之温故知新 前言 VMPwn泛指实现一些运算指令来模拟程序运行的Pwn题。去年十二月的时候跟着0xC4m3l师傅的文章系统学习了一下VMPwn,到今天发现VMPwn已经成了一个主流的出题方向,在去年的上海大学生网络安全大赛和红帽杯

浅析域渗透中的组策略利用

浅析域渗透中的组策略利用 0x0 前言 最近在实战过程和比赛过程都遇到了这个点,发现攻击面其实挺宽广的,这里记录下自己的分析和学习过程。 0x1 多域环境 test.local 域 DC: 10.211.55.38 win2012 Admi

Thinkphp5代码执行学习

缓存类RCE 版本 5.0.0<=ThinkPHP5<=5.0.10 Tp框架搭建 环境搭建 测试payload ?username=syst1m%0d%0a@eval($_GET[_]);// 可以看到已经写入了缓存 漏洞分析 think