0

I am writing a bpf program in which i need to match prefix of filename in openat syscall.

Since we cannot link libc, and there is no such builtin function, i wrote one myself.

#define MAX_FILE_NAME_LENGTH 128
#define LOG_DIR "/my/prefix"
#define LEN_LOG_DIR sizeof(LOG_DIR)

int matchPrefix(char str[MAX_FILE_NAME_LENGTH]) {
  for (int i = 0; i < LEN_LOG_DIR; i++) {
    char ch1 = LOG_DIR[i];
    if (ch1 == '\0') {
      return 0;
    }
    char ch2 = str[i];
    if (ch2 == '\0') {
      return -1;
    }
    if (ch1 != ch2) {
      return -2;
    }
  }
  return (-3);
}

i am getting invalid mem access 'mem_or_null' error when i try to load this program.

libbpf: load bpf program failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
libbpf: 
Validating matchPrefix() func#1...
38: R1=mem_or_null(id=2,off=0,imm=0) R10=fp0
; int matchPrefix(char str[MAX_FILE_NAME_LENGTH]) {
38: (18) r0 = 0xffffffff              ; R0_w=P4294967295
; char ch2 = str[i];
40: (71) r2 = *(u8 *)(r1 +0)
R1 invalid mem access 'mem_or_null'
processed 2 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

libbpf: -- END LOG --
libbpf: failed to load program 'syscall_enter_open'

R1 is the register for first argument. which is a char array on stack. Do i need to pass length of array separately?

the function is called this way

  char filename[MAX_FILE_NAME_LENGTH];    
  bpf_probe_read_user(filename, sizeof(filename), args->filename);    
  if (matchPrefix(filename) != 0) {    
    return 0;    
  }

Even if i change the function signature to accept a char * , there is some other error R1 invalid mem access 'scalar'.

Can someone help in understanding why am i getting this error in function verification?

weima
  • 4,653
  • 6
  • 34
  • 55
  • Did you try making the function a static inlined one? – pchaigno Jan 09 '23 at 23:43
  • @pchaigno making the function static inline fixed the problem. Thanks a LOT !! – weima Jan 13 '23 at 06:57
  • @pchaigno to call this function i need to copy a `const chat *` into a char array in stack. Can i avoid it? when i change signature of this function to `const char *`, i again get `invalid mem access 'scalar'` error, the function is `static inline` – weima Jan 13 '23 at 06:59
  • Where do you initially get the string from? – pchaigno Jan 13 '23 at 11:12
  • i am adding a tracepoint to `openat` syscall. so i need to check if the `args->filename` is in the path `/var/log` or not. So i needed a function `int matchPrefix(const char *)` which returns `0` if `args->filename` begins with `var/log` – weima Jan 13 '23 at 12:26
  • Could you show the copy statement you want to avoid? It's not the `bpf_probe_read_user` call, right? – pchaigno Jan 13 '23 at 14:59
  • it is `char filename[MAX_FILE_NAME_LENGTH]; bpf_probe_read_user_str(filename, sizeof(filename), args->filename);` this copy i want to avoid – weima Jan 13 '23 at 15:18
  • Unless you use BPF CO-RE maybe, I don't think you can avoid that copy. – pchaigno Jan 13 '23 at 16:02
  • Thanks @pchaigno, i want to accept your response as an answer. making it `static inline` is all that was needed – weima Jan 13 '23 at 18:05
  • 1
    Nice! I've written an answer with an explanation below. – pchaigno Jan 13 '23 at 18:12
  • another idea i wanted to explore was to use `BPF_MAP_TYPE_LPM_TRIE`. Since the prefix is fixed, from user space i would add the prefix to the trie, and then in the syscall tracepoint i would do a lookup and check the length of the match. – weima Jan 13 '23 at 18:14

2 Answers2

3

TL;DR. Making your matchPrefix function a static inline one should work around the verifier issue.


I believe this is happening because the BPF verifier recognizes your function as a global one (vs. inlined) and therefore verifies it independently. That means it won't assume anything for the arguments. Thus, the str argument is recognized as mem_or_null and verification fails because you didn't check that pointer isn't null.

Inlining the function will work around this issue because the verifier won't see a function anymore. It will be able to preserve the inferred type of filename when verifying the code that corresponds to the body of matchPrefix.

pchaigno
  • 11,313
  • 2
  • 29
  • 54
-1

there is easier solution using strcmp. find in xdp-project/bpf-next

code from the same is

    int strcmp(const char *cs, const char *ct)
    {
        unsigned char c1, c2;
    
        while (1) {
            c1 = *cs++;
            c2 = *ct++;
            if (c1 != c2)
                return c1 < c2 ? -1 : 1;
            if (!c1)
                break;
        }
        return 0;
    }

Do let me know if you still have issue.

NOTE: you cannot use #define to define string. do reverify line


   char ch1 = LOG_DIR[i];

Devidas
  • 2,479
  • 9
  • 24