I have an eBPF program where I want to compare the end of 2 strings:
char buffer[MAX_FILE_NAME];
int length = bpf_probe_read_str(buffer, sizeof(buffer), file_name) - 1;
if (length < 0 || length >= (MAX_FILE_NAME - 1)) {
bpf_trace_printk("ERROR: Failed to read file name. \n");
return 1;
}
if (length == 0) {
return 0;
}
if (ends_with(file->file_name, file->length, buffer, length)) {
bpf_trace_printk("INFO: Accessed file: %s \n", file->file_name)
}
return 0;
I’ve generated this code to do the comparison for each possible permutation of comparisons up to 32 characters unrolled. Here’s an example of the generated code:
inline bool ends_with09(char needle[], const char haystack[], int haystack_length) {
int haystack_start = haystack_length - 9;
if (haystack_start < 0) {
return false;
}
return
needle [0] == haystack[(haystack_start + 0)] &&
needle [1] == haystack[(haystack_start + 1)] &&
needle [2] == haystack[(haystack_start + 2)] &&
needle [3] == haystack[(haystack_start + 3)] &&
needle [4] == haystack[(haystack_start + 4)] &&
needle [5] == haystack[(haystack_start + 5)] &&
needle [6] == haystack[(haystack_start + 6)] &&
needle [7] == haystack[(haystack_start + 7)] &&
needle [8] == haystack[(haystack_start + 8)];
};
inline bool ends_with(char needle[], u8 needle_length, char haystack[], int haystack_length) {
switch (needle_length) {
case 1: return ends_with01(needle, haystack, haystack_length);
case 2: return ends_with02(needle, haystack, haystack_length);
/* ... more generated code ... */
case 9: return ends_with09(needle, haystack, haystack_length);
/* ... more generated code ... */
case 31: return ends_with31(needle, haystack, haystack_length);
case 32: return ends_with32(needle, haystack, haystack_length);
default: return false;
}
};
However, the verifier constantly is giving me an error like the following:
invalid unbounded variable-offset read from stack R2
Or:
R2 invalid mem access 'scalar'
Even if I add a check to verify that haystack_start + 8 >= haystack_length
(which is always false), the program still fails to verify:
inline bool ends_with09(char needle[], const char haystack[], int haystack_length) {
int haystack_start = haystack_length - 9;
if (haystack_start < 0 || haystack_start + 8 >= haystack_length) {
return false;
}
return
needle [0] == haystack[(haystack_start + 0)] &&
needle [1] == haystack[(haystack_start + 1)] &&
needle [2] == haystack[(haystack_start + 2)] &&
needle [3] == haystack[(haystack_start + 3)] &&
needle [4] == haystack[(haystack_start + 4)] &&
needle [5] == haystack[(haystack_start + 5)] &&
needle [6] == haystack[(haystack_start + 6)] &&
needle [7] == haystack[(haystack_start + 7)] &&
needle [8] == haystack[(haystack_start + 8)];
};
The verifier doesn’t know that you are safely accessing the data because of the variable length
returned from bpf_probe_read_str
. Instead it sees that you are accessing data from a buffer and you might be reading outside the buffer’s boundaries (even though that’s not possible given your code). In The art of writing eBPF programs: a primer., Gianluca Borello describes how to convince the verifier that your code is safe.
Bitwise ANDing your access/index with the max possible buffer index:
needle [8] == haystack[(haystack_start + 8) & (MAX_FILE_NAME - 1)]
So the generated code looks like:
inline bool ends_with09(char needle[], const char haystack[], int haystack_length) {
int haystack_start = haystack_length - 9;
if (haystack_start < 0) {
return false;
}
return
needle [0] == haystack[(haystack_start + 0) & (MAX_FILE_NAME - 1)] &&
needle [1] == haystack[(haystack_start + 1) & (MAX_FILE_NAME - 1)] &&
needle [2] == haystack[(haystack_start + 2) & (MAX_FILE_NAME - 1)] &&
needle [3] == haystack[(haystack_start + 3) & (MAX_FILE_NAME - 1)] &&
needle [4] == haystack[(haystack_start + 4) & (MAX_FILE_NAME - 1)] &&
needle [5] == haystack[(haystack_start + 5) & (MAX_FILE_NAME - 1)] &&
needle [6] == haystack[(haystack_start + 6) & (MAX_FILE_NAME - 1)] &&
needle [7] == haystack[(haystack_start + 7) & (MAX_FILE_NAME - 1)] &&
needle [8] == haystack[(haystack_start + 8) & (MAX_FILE_NAME - 1)];
};
Then the verifier will know that you are not overrunning the bounds of the buffer.