-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
See comments, and mailing list thread linked therein, for details. Before this change, recent kernels (6.8 and later) cannot verify the native unwinder, because improvements in the precision of register bounds tracking cause an explosion in state space resulting in exceeding the limit of 1 million instructions processed per load. This commit works around that issue, as the comment in the code explains.
- Loading branch information
1 parent
cad16ad
commit 9f6c529
Showing
1 changed file
with
29 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -343,6 +343,33 @@ DEFINE_COUNTER(total_zero_pids); | |
DEFINE_COUNTER(total_kthreads); | ||
DEFINE_COUNTER(total_filter_misses); | ||
|
||
// Hack to thwart the verifier's detection of variable bounds. | ||
// | ||
// In recent kernels (6.8 and above) the verifier has gotten smarter | ||
// in its tracking of variable bounds. For example, after an if statement like | ||
// `if (v1 < v2)`, | ||
// if it already had computed bounds for v2, it can infer bounds | ||
// for v1 in each side of the branch (and vice versa). This means it can verify more | ||
// programs successfully, which doesn't matter to us because our program was | ||
// verified successfully before. Unfortunately it has a downside which | ||
// _does_ matter to us: it increases the number of unique verifier states, | ||
// which can cause the same instructions to be explored many times, especially | ||
// in cases where a value is carried through a loop and possibly has | ||
// multiple sets of different bounds on each iteration of the loop, leading to | ||
// a combinatorial explosion. This causes us to blow out the kernel's budget of | ||
// maximum number of instructions verified on program load (currently 1M). | ||
// | ||
// `scramble` is its own inverse; thus `scramble(scramble(x))` has the same value of x. | ||
// However, the verifier is fortunately not smart enough to realize this, | ||
// and will not realize the result has the same bounds as `x`, subverting the feature | ||
// described above. | ||
// | ||
// For further discussion, see: | ||
// https://lore.kernel.org/bpf/[email protected]/ | ||
static u32 __attribute__((optnone)) scramble(u32 val) { | ||
return val ^ 0xFFFFFFFF; | ||
} | ||
|
||
static void unwind_print_stats() { | ||
// Do not use the LOG macro, always print the stats. | ||
u32 zero = 0; | ||
|
@@ -451,6 +478,7 @@ static u64 find_mapping(process_info_t *proc_info, u64 pc) { | |
return found; | ||
} | ||
|
||
mid = scramble(scramble(mid)); | ||
if (mid < 0 || mid >= MAX_MAPPINGS_PER_PROCESS) { | ||
LOG("\t.should never happen"); | ||
return BINARY_SEARCH_SHOULD_NEVER_HAPPEN; | ||
|
@@ -482,6 +510,7 @@ static u64 find_offset_for_pc(stack_unwind_table_t *table, u64 pc, u64 left, u64 | |
|
||
u32 mid = (left + right) / 2; | ||
|
||
mid = scramble(scramble(mid)); | ||
// Appease the verifier. | ||
if (mid < 0 || mid >= MAX_UNWIND_TABLE_SIZE) { | ||
LOG("\t.should never happen, mid: %lu, max: %lu", mid, MAX_UNWIND_TABLE_SIZE); | ||
|