blob: 0608e983d7fc426b85f519d9fe798426794ca00c [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
/*
* kernel/bpf/loops.c:compute_loops_in_subprog() distinguishes between
* the following cases:
* - B: backedge -> simple loop
* - C: cross edge to non-loop node -> no-op
* - D: edge to node whose header is in DFS path -> nested loop
* - E: edge to node whose header is NOT in DFS path -> irreducible
*
* Below test cases cover the above branches in various combinations.
*/
/* Case B: single bounded loop. */
SEC("socket")
__success
__log_level(2)
__msg("loop at 1{{$}}")
__msg("Program dump")
__msg(" -1 0: {{.*}} (b7) r0 = 0")
__msg(" 1 0 1: {{.*}} (07) r0 += 1")
__msg(" 1 1 1 2: {{.*}} (a5) if r0 < 0xa goto pc-2")
__msg(" 2 3: {{.*}} (95) exit")
__naked void loop_single(void)
{
asm volatile (" \
r0 = 0; \
1: r0 += 1; \
if r0 < 10 goto 1b; \
exit; \
" ::: __clobber_all);
}
/* Case B: two independent loops at the same nesting level. */
SEC("socket")
__success
__log_level(2)
__msg("loop at 1{{$}}")
__msg("loop at 4{{$}}")
__msg("Program dump")
__msg(" -1 0: {{.*}} (b7) r0 = 0")
__msg(" 2 0 1: {{.*}} (07) r0 += 1")
__msg(" 2 1 1 2: {{.*}} (a5) if r0 < 0xa goto pc-2")
__msg(" 2 3: {{.*}} (b7) r1 = 0")
__msg(" 1 3 4: {{.*}} (07) r1 += 1")
__msg(" 1 4 4 5: {{.*}} (a5) if r1 < 0xa goto pc-2")
__msg(" 5 6: {{.*}} (95) exit")
__naked void loop_two_independent(void)
{
asm volatile (" \
r0 = 0; \
1: r0 += 1; \
if r0 < 10 goto 1b; \
r1 = 0; \
2: r1 += 1; \
if r1 < 10 goto 2b; \
exit; \
" ::: __clobber_all);
}
/* Case B + D: nested loops. */
SEC("socket")
__success
__log_level(2)
__msg("loop at 1{{$}}")
__msg("loop at 3, nested in 1")
__msg("Program dump")
__msg(" -1 0: {{.*}} (b7) r0 = 0")
__msg(" 1 0 1: {{.*}} (07) r0 += 1") /* outer loop header */
__msg(" 1 1 1 2: {{.*}} (b7) r1 = 0") /* outer loop insn */
__msg(" 1 1 2 3: {{.*}} (07) r1 += 1") /* inner loop header */
__msg(" 1 3 3 4: {{.*}} (a5) if r1 < 0x5 goto pc-2") /* inner loop insn */
__msg(" 1 1 4 5: {{.*}} (a5) if r0 < 0xa goto pc-5") /* outer loop insn */
__msg(" 5 6: {{.*}} (95) exit")
__naked void loop_nested(void)
{
asm volatile (" \
r0 = 0; \
1: r0 += 1; \
r1 = 0; \
2: r1 += 1; \
if r1 < 5 goto 2b; \
if r0 < 10 goto 1b; \
exit; \
" ::: __clobber_all);
}
/* Case C: forward edges, no loops. */
SEC("socket")
__success
__log_level(2)
__msg("Program dump")
__msg(" -1 0: {{.*}} (85) call bpf_get_prandom_u32")
__msg(" 0 1: {{.*}} (25) if r0 > 0x0 goto pc+2")
__msg(" 1 2: {{.*}} (b7) r0 = 2")
__msg(" 2 3: {{.*}} (05) goto pc+1")
__msg(" 1 4: {{.*}} (b7) r0 = 3")
__msg(" 1 5: {{.*}} (95) exit")
__naked void fwd_edges_no_loop(void)
{
asm volatile (" \
call %[bpf_get_prandom_u32]; \
if r0 > 0 goto 1f; \
r0 = 2; \
goto 2f; \
1: r0 = 3; \
2: exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
/* Case B + D: two sibling inner loops within one outer loop. */
SEC("socket")
__success
__log_level(2)
__msg("loop at 1{{$}}")
__msg("loop at 3, nested in 1")
__msg("loop at 6, nested in 1")
__msg("Program dump")
__msg(" -1 0: {{.*}} (b7) r0 = 0")
__msg(" 1 0 1: {{.*}} (07) r0 += 1")
__msg(" 1 1 1 2: {{.*}} (b7) r1 = 0")
__msg(" 1 1 2 3: {{.*}} (07) r1 += 1")
__msg(" 1 3 3 4: {{.*}} (a5) if r1 < 0x5 goto pc-2")
__msg(" 1 1 4 5: {{.*}} (b7) r2 = 0")
__msg(" 1 1 5 6: {{.*}} (07) r2 += 1")
__msg(" 1 6 6 7: {{.*}} (a5) if r2 < 0x5 goto pc-2")
__msg(" 1 1 7 8: {{.*}} (a5) if r0 < 0xa goto pc-8")
__msg(" 8 9: {{.*}} (95) exit")
__naked void loop_nested_siblings(void)
{
asm volatile (" \
r0 = 0; \
1: r0 += 1; \
r1 = 0; \
2: r1 += 1; \
if r1 < 5 goto 2b; \
r2 = 0; \
3: r2 += 1; \
if r2 < 5 goto 3b; \
if r0 < 10 goto 1b; \
exit; \
" ::: __clobber_all);
}
/* Three levels of nesting. */
SEC("socket")
__success
__log_level(2)
__msg("loop at 1{{$}}")
__msg("loop at 3, nested in 1")
__msg("loop at 5, nested in 3")
__msg("Program dump")
__msg(" -1 0: {{.*}} (b7) r0 = 0")
__msg(" 1 0 1: {{.*}} (07) r0 += 1")
__msg(" 1 1 1 2: {{.*}} (b7) r1 = 0")
__msg(" 1 1 2 3: {{.*}} (07) r1 += 1")
__msg(" 1 3 3 4: {{.*}} (b7) r2 = 0")
__msg(" 1 3 4 5: {{.*}} (07) r2 += 1")
__msg(" 1 5 5 6: {{.*}} (a5) if r2 < 0x3 goto pc-2")
__msg(" 1 3 6 7: {{.*}} (a5) if r1 < 0x5 goto pc-5")
__msg(" 1 1 7 8: {{.*}} (a5) if r0 < 0xa goto pc-8")
__msg(" 8 9: {{.*}} (95) exit")
__naked void loop_three_levels(void)
{
asm volatile (" \
r0 = 0; \
1: r0 += 1; \
r1 = 0; \
2: r1 += 1; \
r2 = 0; \
3: r2 += 1; \
if r2 < 3 goto 3b; \
if r1 < 5 goto 2b; \
if r0 < 10 goto 1b; \
exit; \
" ::: __clobber_all);
}
/* Loop with an if-else body (forward branch inside loop, Case D). */
SEC("socket")
__success
__log_level(2)
__msg("loop at 1")
__msg("Program dump")
__msg(" -1 0: {{.*}} (b7) r0 = 0")
__msg(" 1 0 1: {{.*}} (07) r0 += 1")
__msg(" 1 1 1 2: {{.*}} (bf) r1 = r0")
__msg(" 1 1 2 3: {{.*}} (25) if r1 > 0x5 goto pc+1")
__msg(" 1 1 3 4: {{.*}} (b7) r1 = 1")
__msg(" 1 1 3 5: {{.*}} (0f) r0 += r1")
__msg(" 1 1 5 6: {{.*}} (a5) if r0 < 0x64 goto pc-6")
__msg(" 6 7: {{.*}} (95) exit")
__naked void loop_with_if_else(void)
{
asm volatile (" \
r0 = 0; \
1: r0 += 1; \
r1 = r0; \
if r1 > 5 goto 2f; \
r1 = 1; \
2: r0 += r1; \
if r0 < 100 goto 1b; \
exit; \
" ::: __clobber_all);
}
/* Case E: irreducible loop. */
SEC("socket")
__success
__log_level(2)
__msg("loop at 3, irreducible")
__msg("Program dump")
__msg(" -1 0: {{.*}} (85) call bpf_get_prandom_u32#7")
__msg(" 0 1: {{.*}} (b7) r1 = 0")
__msg(" 1 2: {{.*}} (25) if r0 > 0x5 goto pc+2")
__msg(" 1 2 3: {{.*}} (b7) r1 = 1")
__msg(" 1 3 3 4: {{.*}} (05) goto pc+1")
__msg(" 2 5: {{.*}} (b7) r1 = 2")
__msg(" 1 3 2 6: {{.*}} (0f) r0 += r1")
__msg(" 1 3 6 7: {{.*}} (a5) if r0 < 0x10 goto pc-5")
__msg(" 7 8: {{.*}} (95) exit")
__naked void loop_irreducible(void)
{
asm volatile (" \
call %[bpf_get_prandom_u32]; \
r1 = 0; \
if r0 > 5 goto 2f; \
1: r1 = 1; \
goto 3f; \
2: r1 = 2; \
3: r0 += r1; \
if r0 < 16 goto 1b; \
exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
SEC("socket")
__failure
__log_level(2)
__msg("loop at 1")
__msg("infinite loop detected at insn 1")
__naked void loop_self(void)
{
asm volatile (" \
call %[bpf_get_prandom_u32]; \
1: if r0 < 10 goto 1b; \
exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
/*
* Loops at 5 and 7 are nested inside loops at 3 and 1.
* The edge 6 -> 7 exits loop at 5 and enters loop at 7.
* Check that it is logged only as an exit from loop at 5.
*
* 0: r0 = 0;
* do {
* 1: r0++;
* 2: r1 = 0;
* do {
* 3: r1++;
* 4: r2 = 0;
* do {
* 5: r2++;
* 6: } while (r2 < 3);
* do {
* 7: r2--;
* 8: } while (r2 != 0);
* 9: } while (r1 < 4);
* 10: } while (r0 < 5);
* 11: return r0;
*/
SEC("socket")
__success
__log_level(2)
__msg("loop at 1{{$}}")
__msg_next(" backedge from 10, latch at 10")
__msg_next(" exit from 10 to 11")
__msg_next("loop at 3, nested in 1")
__msg_next(" backedge from 9, latch at 9")
__msg_next(" exit from 9 to 10")
__msg_next("loop at 5, nested in 3")
__msg_next(" backedge from 6, latch at 6")
__msg_next(" exit from 6 to 7")
__msg_next("loop at 7, nested in 3")
__msg_next(" backedge from 8, latch at 8")
__msg_next(" exit from 8 to 9")
__naked void loop_nested_siblings_common_ancestors(void)
{
asm volatile (" \
r0 = 0; \
1: r0 += 1; \
r1 = 0; \
2: r1 += 1; \
r2 = 0; \
3: r2 += 1; \
if r2 < 3 goto 3b; \
4: r2 += -1; \
if r2 != 0 goto 4b; \
if r1 < 4 goto 2b; \
if r0 < 5 goto 1b; \
exit; \
" ::: __clobber_all);
}
/*
* The outer loop backedge (4 -> 2) originates in an inner loop.
* For now, latch should not be identified in such cases.
*/
SEC("socket")
__success
__log_level(2)
__msg("loop at 2{{$}}")
__msg_next(" backedge from 4{{$}}")
__msg_next(" exit from 5 to 7")
__msg_next("loop at 3, nested in 2")
__msg_next(" backedge from 6, latch at 5")
__msg_next(" exit from 4 to 2")
__msg_next(" exit from 5 to 7")
__naked void loop_outer_backedge_from_inner(void)
{
asm volatile (" \
r6 = 99; \
r2 = 2; \
2: r6 += 1; /* loop at 2 */ \
3: r2 += 1; /* loop at 3 */ \
4: if r2 == 3 goto 2b; \
5: if r6 > 100 goto 7f; \
goto 3b; \
7: r0 = 0; \
exit; \
" ::: __clobber_all);
}
char _license[] SEC("license") = "GPL";