Topics

Verifier error: variable stack access var_off

Brendan Gregg
 

I'm hoping someone knows a workaround here.

I have a char buf[128] and I'd like to write to arbitrary offsets, but
keep hitting this error. Any workaround? I've included a sample bcc
program below, which has a block comment as to what I'm trying to do
(join an argv[]). Thanks,

Brendan

---execsnoop2.py---
#!/usr/bin/python
# From execsnoop (bcc/eBPF).

from __future__ import print_function
from bcc import BPF
from bcc.utils import ArgString, printb
import bcc.utils as utils
import argparse
import ctypes as ct
import re
import time

# arguments
examples = """examples:
./execsnoop # trace all exec() syscalls
"""
parser = argparse.ArgumentParser(
description="Trace exec() syscalls",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=examples)
parser.add_argument("--ebpf", action="store_true",
help=argparse.SUPPRESS)
args = parser.parse_args()

# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>

#define ARGSIZE 128

struct data_t {
char argv[ARGSIZE];
};

BPF_PERF_OUTPUT(events);

//
// Here's what I'm trying to do. Let's say this has:
// __argv[0] = "ls"
// __argv[1] = "-l"
// I'm trying to create a buffer with "ls -l", by doing bpf_probe_read_str() for
// each element into the buffer, while keeping track of the length of
// the previous read so I can insert a space delimiter at that offset,
// and begin the next read after the delimiter.
//
int syscall__execve(struct pt_regs *ctx,
const char __user *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct data_t data = {};
uint64_t max = sizeof(data.argv);
const char *argp = NULL;
bpf_probe_read(&argp, sizeof(argp), (void *)&__argv[0]);
uint64_t len = bpf_probe_read_str(&data.argv, max, argp);
len &= 0xffffffff; // to avoid: "math between fp pointer and register errs"
bpf_trace_printk("len: %d\\n", len); // sanity check: len is indeed valid

data.argv[len] = ' ';
// XXX this fails with:
// "variable stack access var_off=(0x0; 0xffffffff) off=-128 size=1"
// how do I fix this?

// events.perf_submit(ctx, &data, len);
// XXX this fails with:
// "R5 min value is negative, either use unsigned or 'var &= const'"
// how do I fix this? In the meantime, I'm passing the whole buffer out:
events.perf_submit(ctx, &data, sizeof(data.argv));
out:
return 0;
}
"""

if args.ebpf:
print(bpf_text)
exit()

# initialize BPF
b = BPF(text=bpf_text)
execve_fnname = b.get_syscall_fnname("execve")
b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")

ARGSIZE = 128 # should match #define in C above

class Data(ct.Structure):
_fields_ = [
("argv", ct.c_char * ARGSIZE),
]

print("running");

# process event
def print_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(Data)).contents
printb(b"%s" % event.argv)

# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
b.perf_buffer_poll()
---execsnoop2.py---

Teng Qin
 

Firstly, note that bpf_probe_read_str adds an extra \0 to the read string. So your max should be sizeof(data.argv) - 1 instead in order for data.argv[len] = ' ' to work (from Verifier's perspective, logically you don't need that extra delimiter~)

Then, Yonghong had a patch a few month ago addressing very similar issue. See the example in patch series
bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics
Does your Kernel have those patches?

However, even with all those the data.argv[len] = ' ' part still fails with something about stack offset not being fixed. I will try debug more to see how to fix that. For now, you can use a per-CPU array of size 1 for the data instead of allocating it on the stack. The following works for me:
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>
 
#define ARGSIZE  128
 
struct data_t {
    char argv[ARGSIZE];
};
 
BPF_PERF_OUTPUT(events);
BPF_PERCPU_ARRAY(mem, struct data_t, 1);
//
// Here's what I'm trying to do. Let's say this has:
//   __argv[0] = "ls"
//   __argv[1] = "-l"
// I'm trying to create a buffer with "ls -l", by doing bpf_probe_read_str() for
// each element into the buffer, while keeping track of the length of
// the previous read so I can insert a space delimiter at that offset,
// and begin the next read after the delimiter.
//
int on_event(struct pt_regs *ctx,
    const char __user *filename,
    const char __user *const __user *__argv,
    const char __user *const __user *__envp)
{
    int zero = 0;
    struct data_t* data = mem.lookup(&zero);
    if (!data)
      return 0;
 
    uint64_t max = sizeof(data->argv) - 1;
    const char *argp = NULL;
    bpf_probe_read(&argp, sizeof(argp), (void *)&__argv[0]);
    uint64_t len = bpf_probe_read_str(&(data->argv), max, argp);
    len &= 0xffffffff; // to avoid: "math between fp pointer and register errs"
    bpf_trace_printk("len: %d\\n", len); // sanity check: len is indeed valid
 
    data->argv[len] = ' ';
 
    events.perf_submit(ctx, data, len);
out:
    return 0;
}

Yonghong Song
 

The kernel needs to a constant offset from the stack for write. The
corresponding kernel verifier code below:

/* stack accesses must be at a fixed offset, so that we can
* determine what type of data were returned.
* See check_stack_read().
*/
if (!tnum_is_const(reg->var_off)) {
char tn_buf[48];

tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
verbose(env, "variable stack access var_off=%s
off=%d size=%d",
tn_buf, off, size);
return -EACCES;
}

You can initialize the array with ' ' to workaround the issue:
struct data_t data;
uint64_t max = sizeof(data.argv);
const char *argp = NULL;
memset(&data, ' ', sizeof(data));
bpf_probe_read(&argp, sizeof(argp), (void *)&__argv[0]);
uint64_t len = bpf_probe_read_str(&data.argv, max, argp);
len &= 0xffffffff; // to avoid: "math between fp pointer and register errs"
bpf_trace_printk("len: %d\\n", len); // sanity check: len is indeed valid

// data.argv[len] = ' ';

On Sun, Jul 1, 2018 at 6:20 PM, Teng Qin <palmtenor@...> wrote:
Firstly, note that bpf_probe_read_str adds an extra \0 to the read string.
So your max should be sizeof(data.argv) - 1 instead in order for
data.argv[len] = ' ' to work (from Verifier's perspective, logically you
don't need that extra delimiter~)

Then, Yonghong had a patch a few month ago addressing very similar issue.
See the example in patch series
bpf: improve verifier ARG_CONST_SIZE_OR_ZERO semantics
Does your Kernel have those patches?

However, even with all those the data.argv[len] = ' ' part still fails with
something about stack offset not being fixed. I will try debug more to see
how to fix that. For now, you can use a per-CPU array of size 1 for the data
instead of allocating it on the stack. The following works for me:
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>

#define ARGSIZE 128

struct data_t {
char argv[ARGSIZE];
};

BPF_PERF_OUTPUT(events);
BPF_PERCPU_ARRAY(mem, struct data_t, 1);
//
// Here's what I'm trying to do. Let's say this has:
// __argv[0] = "ls"
// __argv[1] = "-l"
// I'm trying to create a buffer with "ls -l", by doing bpf_probe_read_str()
for
// each element into the buffer, while keeping track of the length of
// the previous read so I can insert a space delimiter at that offset,
// and begin the next read after the delimiter.
//
int on_event(struct pt_regs *ctx,
const char __user *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
int zero = 0;
struct data_t* data = mem.lookup(&zero);
if (!data)
return 0;

uint64_t max = sizeof(data->argv) - 1;
const char *argp = NULL;
bpf_probe_read(&argp, sizeof(argp), (void *)&__argv[0]);
uint64_t len = bpf_probe_read_str(&(data->argv), max, argp);
len &= 0xffffffff; // to avoid: "math between fp pointer and register
errs"
bpf_trace_printk("len: %d\\n", len); // sanity check: len is indeed
valid

data->argv[len] = ' ';

events.perf_submit(ctx, data, len);
out:
return 0;
}