// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ #include #include #include #include "bpf_misc.h" typedef unsigned __int128 u128; __naked u128 global_agg_good(void) { asm volatile ( "r0 = 0x1234;" /* low 64 bits */ "r2 = 0x5678;" /* high 64 bits */ "exit;" ); } __naked u128 global_agg_bad(void) { asm volatile ( "r0 = 0;" "exit;" ); } __naked u128 global_agg_bad_ptr(void) { asm volatile ( "r0 = 0;" "r2 = r10;" "exit;" ); } SEC("tc") __failure __msg("R2 !read_ok") __naked int aggregate_ret_global_fail(void) { asm volatile ( "call %[global_agg_bad];" "r0 = r2;" "exit;" : : __imm(global_agg_bad) : __clobber_all); } SEC("tc") __load_if_JITed() __failure __msg("At subprogram exit the register R2 is not a scalar value") __naked int aggregate_ret_global_ptr_fail(void) { asm volatile ( "call %[global_agg_bad_ptr];" "r0 = r2;" "exit;" : : __imm(global_agg_bad_ptr) : __clobber_all); } static __naked __noinline u128 static_agg_bad_ptr(void) { asm volatile ( "r0 = 0;" "r2 = r10;" /* stack pointer placed in the second return register */ "exit;" ); } /* * R2 is a return register once the subprogram returns a pair, so a stack * pointer left in it is rejected at the callee's exit exactly as one in R0 * is: the callee frame is gone by the time the caller could use it. */ SEC("tc") __load_if_JITed() __failure __msg("cannot return stack pointer to the caller") __naked int aggregate_ret_static_ptr_fail(void) { asm volatile ( "call %[static_agg_bad_ptr];" "r0 = 0;" "exit;" : : __imm(static_agg_bad_ptr) : __clobber_all); } static __naked __noinline u128 static_agg_no_r2(void) { asm volatile ( "r0 = 0;" "exit;" ); } SEC("tc") __failure __msg("R2 !read_ok") __naked int aggregate_ret_static_uninit_fail(void) { asm volatile ( "call %[static_agg_no_r2];" "r0 = r2;" "exit;" : : __imm(static_agg_no_r2) : __clobber_all); } static __naked __noinline u128 static_agg_precise(void) { asm volatile ( "r0 = 0;" "r2 = 4;" /* second half; its value is made precise below */ "exit;" ); } SEC("tc") __load_if_JITed() __success __retval(0) __log_level(2) __msg("mark_precise: frame0: last_idx 5 first_idx 0 subseq_idx -1") __msg("mark_precise: frame0: regs=r6 stack= before 4: (07) r1 += -8") __msg("mark_precise: frame0: regs=r6 stack= before 3: (bf) r1 = r10") __msg("mark_precise: frame0: regs=r6 stack= before 2: (57) r6 &= 7") __msg("mark_precise: frame0: regs=r6 stack= before 1: (bf) r6 = r2") __msg("mark_precise: frame0: regs=r2 stack= before 12: (95) exit") __msg("mark_precise: frame1: regs=r2 stack= before 11: (b7) r2 = 4") __naked int aggregate_ret_static_precise(void) { asm volatile ( "call %[static_agg_precise];" "r6 = r2;" /* derived from the aggregate's second half */ "r6 &= 7;" /* keep it in [0, 7] to index the stack */ "r1 = r10;" "r1 += -8;" "r1 += r6;" /* ptr += scalar marks r6 (hence R2) precise */ "r0 = 0;" "*(u8 *)(r1 + 0) = r0;" "r0 = 0;" "exit;" : : __imm(static_agg_precise) : __clobber_all); } SEC("tc") __load_if_JITed() __success __retval(0) __log_level(2) __msg("mark_precise: frame0: last_idx 5 first_idx 0 subseq_idx -1") __msg("mark_precise: frame0: regs=r6 stack= before 4: (07) r1 += -8") __msg("mark_precise: frame0: regs=r6 stack= before 3: (bf) r1 = r10") __msg("mark_precise: frame0: regs=r6 stack= before 2: (57) r6 &= 7") __msg("mark_precise: frame0: regs=r6 stack= before 1: (bf) r6 = r2") __msg("mark_precise: frame0: regs=r2 stack= before 0: (85) call pc+9") __naked int aggregate_ret_global_precise(void) { asm volatile ( "call %[global_agg_good];" "r6 = r2;" /* derived from the aggregate's second half */ "r6 &= 7;" /* keep it in [0, 7] to index the stack */ "r1 = r10;" "r1 += -8;" "r1 += r6;" /* ptr += scalar marks r6 (hence R2) precise */ "r0 = 0;" "*(u8 *)(r1 + 0) = r0;" "r0 = 0;" "exit;" : : __imm(global_agg_good) : __clobber_all); } #if defined(__clang_major__) && __clang_major__ >= 23 /* A by-value struct that smuggles a pointer, which must be rejected. */ struct with_ptr { void *p; __u64 x; }; /* A by-value union that smuggles a pointer, which must be rejected too. */ union upair_with_ptr { void *p; __u64 halves[2]; }; __naked struct with_ptr global_ret_struct_ptr(void) { asm volatile ( "r0 = 0;" "r2 = 0;" "exit;" ); } SEC("tc") __failure __msg("Global function global_ret_struct_ptr() has unsupported return type") __naked int aggregate_ret_global_struct_ptr_fail(void) { asm volatile ( "call %[global_ret_struct_ptr];" "r0 = 0;" "exit;" : : __imm(global_ret_struct_ptr) : __clobber_all); } __naked union upair_with_ptr global_ret_union_ptr(void) { asm volatile ( "r0 = 0;" "r2 = 0;" "exit;" ); } SEC("tc") __failure __msg("Global function global_ret_union_ptr() has unsupported return type") __naked int aggregate_ret_global_union_ptr_fail(void) { asm volatile ( "call %[global_ret_union_ptr];" "r0 = 0;" "exit;" : : __imm(global_ret_union_ptr) : __clobber_all); } #endif /* * gcc returns a by-value struct through a hidden pointer, and emits the * 'r0 = r1' returning it after the __naked body's exit, leaving the * subprogram falling through. Build these with clang only. */ #if defined(__clang__) struct arena_pair { void __arena *lo; void __arena *hi; }; struct arena_and_scalar { void __arena *p; __u64 x; }; struct arena_array { void __arena *p[2]; }; struct arena_single { void __arena *p; }; union arena_upair { void __arena *p; __u64 halves[2]; }; __naked struct arena_pair global_ret_arena_pair(void) { asm volatile ( "r0 = 0;" "r2 = 0;" "exit;" ); } SEC("tc") __load_if_JITed() __success __retval(0) __naked int aggregate_ret_global_arena_pair(void) { asm volatile ( "call %[global_ret_arena_pair];" "r0 = 0;" "exit;" : : __imm(global_ret_arena_pair) : __clobber_all); } __naked struct arena_and_scalar global_ret_arena_and_scalar(void) { asm volatile ( "r0 = 0;" "r2 = 0;" "exit;" ); } SEC("tc") __load_if_JITed() __success __retval(0) __naked int aggregate_ret_global_arena_and_scalar(void) { asm volatile ( "call %[global_ret_arena_and_scalar];" "r0 = 0;" "exit;" : : __imm(global_ret_arena_and_scalar) : __clobber_all); } __naked struct arena_array global_ret_arena_array(void) { asm volatile ( "r0 = 0;" "r2 = 0;" "exit;" ); } SEC("tc") __load_if_JITed() __success __retval(0) __naked int aggregate_ret_global_arena_array(void) { asm volatile ( "call %[global_ret_arena_array];" "r0 = 0;" "exit;" : : __imm(global_ret_arena_array) : __clobber_all); } __naked struct arena_single global_ret_arena_single(void) { asm volatile ( "r0 = 0;" "exit;" ); } SEC("tc") __success __retval(0) __naked int aggregate_ret_global_arena_single(void) { asm volatile ( "call %[global_ret_arena_single];" "r0 = 0;" "exit;" : : __imm(global_ret_arena_single) : __clobber_all); } __naked union arena_upair global_ret_arena_union(void) { asm volatile ( "r0 = 0;" "r2 = 0;" "exit;" ); } SEC("tc") __load_if_JITed() __success __retval(0) __naked int aggregate_ret_global_arena_union(void) { asm volatile ( "call %[global_ret_arena_union];" "r0 = 0;" "exit;" : : __imm(global_ret_arena_union) : __clobber_all); } #endif char _license[] SEC("license") = "GPL";