Bcc: Verifier failure on reading ctx

Created on 26 Apr 2017  路  2Comments  路  Source: iovisor/bcc

One of my trace.py call failed with an USDT tracepoint because of verifier issues.
And the verifier failed on:

bpf: Permission denied
0: (bf) r6 = r1
1: (85) call bpf_get_current_pid_tgid#14
2: (bf) r7 = r0
3: (bf) r8 = r7
4: (77) r8 >>= 32
5: (15) if r8 == 0xdb365 goto pc+46
 R0=inv R6=ctx R7=inv R8=inv32 R10=fp
6: (b7) r1 = 0
7: (7b) *(u64 *)(r10 -8) = r1
8: (7b) *(u64 *)(r10 -16) = r1
9: (7b) *(u64 *)(r10 -24) = r1
10: (7b) *(u64 *)(r10 -32) = r1
11: (7b) *(u64 *)(r10 -40) = r1
12: (7b) *(u64 *)(r10 -48) = r1
13: (7b) *(u64 *)(r10 -56) = r1
14: (7b) *(u64 *)(r10 -64) = r1
15: (7b) *(u64 *)(r10 -72) = r1
16: (7b) *(u64 *)(r10 -80) = r1
17: (7b) *(u64 *)(r10 -88) = r1
18: (7b) *(u64 *)(r10 -96) = r1
19: (85) call bpf_ktime_get_ns#5
20: (7b) *(u64 *)(r10 -112) = r0
21: (63) *(u32 *)(r10 -104) = r8
22: (63) *(u32 *)(r10 -100) = r7
23: (bf) r1 = r10
24: (07) r1 += -96
25: (b7) r2 = 16
26: (85) call bpf_get_current_comm#16
27: (79) r1 = *(u64 *)(r6 +128)
28: (15) if r1 == 0x111be2b goto pc+5
 R0=inv R1=inv R6=ctx R7=inv R8=inv32 R10=fp
29: (15) if r1 == 0x111bd1a goto pc+1
 R0=inv R1=inv R6=ctx R7=inv R8=inv32 R10=fp
30: (55) if r1 != 0x111bbfd goto pc+11
 R0=inv R1=inv,min_value=17939453,max_value=17939453 R6=ctx R7=inv R8=inv32 R10=fp
31: (bf) r1 = r6
32: (07) r1 += 96
33: (05) goto pc+2
36: (79) r3 = *(u64 *)(r1 +0)
R1 invalid mem access 'inv'

Looking with the full program (below), it seems we are just trying to access *(ctx+96), but doing it by

31: (bf) r1 = r6
32: (07) r1 += 96

confuses the verifier. I think this could happen to any BPF program that uses ctx value. Do we have a workaround or fix toward the issue?

The full BPF program:

#include <linux/ptrace.h>
#include <linux/sched.h>        /* For TASK_COMM_LEN */


struct probe_strobelight_data_destruct_1_data_t
{
        u64 timestamp_ns;
        u32 tgid;
        u32 pid;
        char comm[TASK_COMM_LEN];
        char v0[80];



};

BPF_PERF_OUTPUT(probe_strobelight_data_destruct_1_events);


int probe_strobelight_data_destruct_1(struct pt_regs *ctx)
{
        u64 __pid_tgid = bpf_get_current_pid_tgid();
        u32 __tgid = __pid_tgid >> 32;
        u32 __pid = __pid_tgid; // implicit cast to u32 for bottom half

        if (__tgid == 897893) { return 0; }



        if (!(1)) return 0;

        struct probe_strobelight_data_destruct_1_data_t __data = {0};
        __data.timestamp_ns = bpf_ktime_get_ns();
        __data.tgid = __tgid;
        __data.pid = __pid;
        bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
        int64_t arg1 = 0;
        bpf_usdt_readarg(1, ctx, &arg1);

        if (arg1 != 0) {
                bpf_probe_read(&__data.v0, sizeof(__data.v0), (void *)arg1);
        }


        probe_strobelight_data_destruct_1_events.perf_submit(ctx, &__data, sizeof(__data));
        return 0;
}

#include <uapi/linux/ptrace.h>
static inline int _bpf_readarg_probe_strobelight_data_destruct_1_1(struct pt_regs *ctx, void *dest, size_t len) {
  if (len != sizeof(int64_t)) return -1;
  switch(ctx->ip) {
  case 0x111bbfdULL: *((int64_t *)dest) = (int64_t)ctx->dx; return 0;
  case 0x111bd1aULL: *((int64_t *)dest) = (int64_t)ctx->dx; return 0;
  case 0x111be2bULL: *((int64_t *)dest) = (int64_t)ctx->cx; return 0;
  }
  return -1;
}
static inline int _bpf_readarg_probe_strobelight_data_destruct_1_2(struct pt_regs *ctx, void *dest, size_t len) {
  if (len != sizeof(int64_t)) return -1;
  switch(ctx->ip) {
  case 0x111bbfdULL: *((int64_t *)dest) = (int64_t)ctx->cx; return 0;
  case 0x111bd1aULL: *((int64_t *)dest) = (int64_t)ctx->cx; return 0;
  case 0x111be2bULL: *((int64_t *)dest) = (int64_t)ctx->dx; return 0;
  }
  return -1;
}
static inline int _bpf_readarg_probe_strobelight_data_destruct_1_3(struct pt_regs *ctx, void *dest, size_t len) {
  if (len != sizeof(int64_t)) return -1;
  switch(ctx->ip) {
  case 0x111bbfdULL: { u64 __addr = ctx->bx + (56);int64_t __res = 0x0; bpf_probe_read(&__res, sizeof(__res), (void *)__addr); *((int64_t *)dest) = __res; } return 0;
  case 0x111bd1aULL: { u64 __addr = ctx->bx + (56);int64_t __res = 0x0; bpf_probe_read(&__res, sizeof(__res), (void *)__addr); *((int64_t *)dest) = __res; } return 0;
  case 0x111be2bULL: { u64 __addr = ctx->bx + (48);int64_t __res = 0x0; bpf_probe_read(&__res, sizeof(__res), (void *)__addr); *((int64_t *)dest) = __res; } return 0;
  }
  return -1;
}

Most helpful comment

Seems like the same issue as here: #751. I think it's time to think of what we can do on the LLVM side to avoid this bad code gen.
@4ast @drzaeus77

All 2 comments

Seems like the same issue as here: #751. I think it's time to think of what we can do on the LLVM side to avoid this bad code gen.
@4ast @drzaeus77

Some root cause analysis:

static inline int _bpf_readarg_probe_strobelight_data_destruct_1_1(struct pt_regs *ctx, void *dest, size_t len) {
  if (len != sizeof(int64_t)) return -1;
  switch(ctx->ip) {
  case 0x111bbfdULL: *((int64_t *)dest) = (int64_t)ctx->dx; return 0;
  case 0x111bd1aULL: *((int64_t *)dest) = (int64_t)ctx->dx; return 0;
  case 0x111be2bULL: *((int64_t *)dest) = (int64_t)ctx->cx; return 0;
  }
  return -1;
}

after inlining, it is something like below

    a = (switch(ctx->ip) { case #1: ctx->dx; case #2: ctx->dx; case #3: ctx->cx })

the LLVM does some transformation likes below:

    p = (switch(ctx->ip) { case #1: &(ctx->dx); case #2: &(ctx->dx); case #3: &(ctx->cx); })
    a = *p;

This is a reasonable compiler optimization. it reduces potential code size.

I found a workaround for this:

static __always_inline int _bpf_readarg_probe_strobelight_data_destruct_1_1(struct pt_regs *ctx, void *dest, size_t len) {
  if (len != sizeof(int64_t)) return -1;
  switch(ctx->ip) {
  case 0x111bbfdULL: *((volatile int64_t *)dest) = (int64_t)ctx->dx; return 0;
  case 0x111bd1aULL: *((volatile int64_t *)dest) = (int64_t)ctx->dx; return 0;
  case 0x111be2bULL: *((volatile int64_t *)dest) = (int64_t)ctx->cx; return 0;
  }
  return -1;
}

Basically, a store to memory by dest will force read ctx->dx and ctx->cx,
hence prevent compiler optimization. I also changed inline to "__always_inline"
to guarantee inlining.

The diff for a bcc change:

[yhs@localhost bcc]$ git diff src/cc/usdt.cc
diff --git a/src/cc/usdt.cc b/src/cc/usdt.cc
index 7d1dd57..ef929d7 100644
--- a/src/cc/usdt.cc
+++ b/src/cc/usdt.cc
@@ -152,10 +152,10 @@ bool Probe::usdt_getarg(std::ostream &stream) {

   for (size_t arg_n = 0; arg_n < arg_count; ++arg_n) {
     std::string ctype = largest_arg_type(arg_n);
-    std::string cptr = tfm::format("*((%s *)dest)", ctype);
+    std::string cptr = tfm::format("*((volatile %s *)dest)", ctype);

     tfm::format(stream,
-                "static inline int _bpf_readarg_%s_%d("
+                "static __always_inline int _bpf_readarg_%s_%d("
                 "struct pt_regs *ctx, void *dest, size_t len) {\n"
                 "  if (len != sizeof(%s)) return -1;\n",
                 attached_to_.value(), arg_n + 1, ctype);
[yhs@localhost bcc]$ 

The proper way to address this issue is probably in verifier. In verifier, we have
frame pointer and stack pointer (frame pointer + offset), we also track packet
pointer with an offset. If also keep track of context + offset properly, we should
be fine. I am starting to working on verifier fix now.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mithunk18 picture mithunk18  路  7Comments

brendangregg picture brendangregg  路  4Comments

iaguis picture iaguis  路  4Comments

alvenwong picture alvenwong  路  7Comments

computer5student picture computer5student  路  7Comments