0

我写了一个 bpf 代码并用 clang 编译,在尝试加载时,我遇到了一个错误。我无法理解为什么以及如何解决它,需要专家的建议。

我在 VM 操作系统中运行此代码:Ubuntu 18.04.2 内核:Linux 4.18.0-15-generic x86_64

我尝试了简单的程序,我能够加载但不能使用这个程序。

static __inline int clone_netflow_record (struct __sk_buff *skb, unsigned long dstIpAddr)
{
    return XDP_PASS;
}

static __inline int process_netflow_records( struct __sk_buff *skb)
{
    int i = 0;

    #pragma clang loop unroll(full)
    for (i = 0; i < MAX_REPLICATIONS; i++) {
        clone_netflow_record (skb, ipAddr[i]);
    }

    return XDP_DROP;
}

__section("action")
static int probe_packets(struct __sk_buff *skb)
{
    /* We will access all data through pointers to structs */
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

    if (data > data_end)
        return XDP_DROP;

    /* for easy access we re-use the Kernel's struct definitions */
    struct ethhdr  *eth  = data;
    struct iphdr   *ip   = (data + sizeof(struct ethhdr));

    /* Only actual IP packets are allowed */
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_DROP;
    /* If Netflow packets process it */
    if (ip->protocol != IPPROTO_ICMP)
    {
        process_netflow_records (skb);
    }

    return XDP_PASS;
}

看到的错误:

$ sudo ip link set dev enp0s8 xdp object clone.o sec action

Prog section 'action' rejected: Permission denied (13)!
 - Type:         6
 - Instructions: 41 (0 over limit)
 - License:      GPL

Verifier analysis:

0: (bf) r2 = r1
1: (7b) *(u64 *)(r10 -16) = r1
2: (79) r1 = *(u64 *)(r10 -16)
3: (61) r1 = *(u32 *)(r1 +76)
invalid bpf_context access off=76 size=4

Error fetching program/map!
4

1 回答 1

1

在 Linux 内核中强制检查您的程序的内核验证程序确保不会尝试越界访问。您的程序被拒绝,因为它可能会触发这种越界访问。

如果我们仔细查看您的代码段:

    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

所以在这里我们得到指向data(数据包开始)和data_end.

    if (data > data_end)
        return XDP_DROP;

上述检查是不必要的(data不会高于data_end)。但是您应该在此处进行另一项检查。让我们看看下面:

    /* for easy access we re-use the Kernel's struct definitions */
    struct ethhdr  *eth  = data;
    struct iphdr   *ip   = (data + sizeof(struct ethhdr));

    /* Only actual IP packets are allowed */
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_DROP;

您在这里所做的是,首先,制作ethip指向数据包的开头和(假设)IP 标头的开头。这一步很好。但是,您尝试取消引用eth以访问其h_proto字段。

现在,如果数据包不是以太网,并且它的长度不足以在其中包含一个h_proto字段,会发生什么?您会尝试读取数据包边界之外的一些数据,这就是我之前提到的越界访问。请注意,这并不意味着您的程序实际上尝试读取此数据(事实上,我看不出您如何获得短于 14 字节的数据包)。但是从验证者的角度来看,从技术上讲,这种被禁止的访问可能会发生,因此它会拒绝您的程序。这就是它的含义invalid bpf_context access:您的代码尝试以无效的方式访问上下文(对于 XDP:数据包数据)。

那么我们该如何解决呢?在尝试取消引用指针之前应该进行的检查不应该是 on data > data_end,而应该是:

    if (data + sizeof(struct ethhdr) > data_end)
        return XDP_DROP;

因此,如果我们通过检查而不返回XDP_DROP,我们可以确定数据包足够长,可以包含一个完整的struct ethhdr(因此也是一个h_proto字段)。

请注意,出于同样的原因,data + sizeof(struct ethhdr) + sizeof(struct iphdr)在尝试取消引用之前需要进行类似的检查。ip每次尝试从数据包(上下文)访问数据时,都应确保数据包足够长以安全地取消引用指针。

于 2019-06-06T09:58:15.740 回答