Summary
The ksock_lsm selftest fails when compiled with the GCC BPF backend because the verifier's retval range constraint ([-4095, 0]) is overwritten by coerce_reg_to_size_sx() during post-processing of sign-extending sub-word context loads.
Failure Details
- Test / Component: ksock_lsm (test_progs GCC BPF variant)
- Frequency: every GCC BPF run since test was added (Aug 13), across all independent PRs
- Failure mode: verifier rejection — wrong return value range
- Affected architectures: x86_64 (GCC BPF backend, all arches where test_progs-bpf_gcc runs)
- CI runs observed:
Root Cause Analysis
GCC BPF generates a sign-extending 32-bit load (BPF_LDX | BPF_MEMSX | BPF_W, opcode 0x81) to read the int ret parameter from the LSM trampoline context:
0: (81) r6 = *(s32 *)(r1 +24) ; R6=scalar(smin=0xffffffff80000000,smax=0x7fffffff)
The verifier correctly identifies this as the LSM retval (via btf_ctx_access setting info->is_retval = true) and applies the return value range [-4095, 0] using __mark_reg_s32_range() in check_mem_access() at kernel/bpf/verifier.c:6441.
However, later in the same function (line 6557), because size=4 < BPF_REG_SIZE=8 and is_ldsx=true, the code calls coerce_reg_to_size_sx(). This function attempts to determine the sign-extension bounds from the register's current s64 range. The range [-4095, 0] has smin=-4095 (top 32 bits = 0xFFFFFFFF) and smax=0 (top 32 bits = 0x00000000). Since these differ, coerce_reg_to_size_sx() conservatively widens the register to [S32_MIN, S32_MAX], destroying the retval range.
The verifier then rejects the program at exit because R0 has range [-2147483648, 2147483647] instead of [-4095, 0].
LLVM/clang avoids this by generating a 64-bit load (BPF_LDX | BPF_MEM | BPF_DW) for the same access, so size == BPF_REG_SIZE and the coercion is skipped.
Relevant commits:
- c7838e3dc61a ("selftests/bpf: Add ksock kfunc test") — added Aug 13
- 5e0b273e0a62 ("bpf: Reset register bounds before narrowing retval range in check_mem_access()") — added
mark_reg_unknown() before range, but didn't address the post-coercion issue
- Denylisted in vmtest commit beebabb (Aug 15)
Proposed Fix
Skip coerce_reg_to_size_sx() when the retval range has already been correctly applied. The range set by __mark_reg_s32_range() already represents the valid post-sign-extension bounds (a 32-bit value in [-4095, 0] sign-extends to the same 64-bit range), so the coercion is a no-op at best and destructive at worst.
See: output/0001-bpf-Fix-retval-range-for-sign-extending-ctx-loads.patch
Impact
Without this fix, any LSM BPF program that reads its ret parameter using a sign-extending 32-bit load (as GCC BPF generates) will be rejected by the verifier. This blocks all LSM programs with return value semantics from working under GCC BPF compilation.
The test is currently denylisted in CI (DENYLIST.test_progs-bpf_gcc), masking the issue.
References
- CI failure log:
R6=scalar(smin=0xffffffff80000000,smax=0x7fffffff) → At program exit the register R0 has smin=-2147483648 smax=2147483647 should have been in [-4095, 0]
kernel/bpf/verifier.c:6439-6444 — retval range application
kernel/bpf/verifier.c:6557-6576 — post-load coercion
kernel/bpf/verifier.c:5647-5700 — coerce_reg_to_size_sx() implementation
kernel/bpf/btf.c:6895 — info->is_retval = true for LSM retval
Summary
The
ksock_lsmselftest fails when compiled with the GCC BPF backend because the verifier's retval range constraint ([-4095, 0]) is overwritten bycoerce_reg_to_size_sx()during post-processing of sign-extending sub-word context loads.Failure Details
Root Cause Analysis
GCC BPF generates a sign-extending 32-bit load (
BPF_LDX | BPF_MEMSX | BPF_W, opcode 0x81) to read theint retparameter from the LSM trampoline context:The verifier correctly identifies this as the LSM retval (via
btf_ctx_accesssettinginfo->is_retval = true) and applies the return value range [-4095, 0] using__mark_reg_s32_range()incheck_mem_access()atkernel/bpf/verifier.c:6441.However, later in the same function (line 6557), because
size=4 < BPF_REG_SIZE=8andis_ldsx=true, the code callscoerce_reg_to_size_sx(). This function attempts to determine the sign-extension bounds from the register's current s64 range. The range [-4095, 0] hassmin=-4095(top 32 bits = 0xFFFFFFFF) andsmax=0(top 32 bits = 0x00000000). Since these differ,coerce_reg_to_size_sx()conservatively widens the register to [S32_MIN, S32_MAX], destroying the retval range.The verifier then rejects the program at exit because R0 has range [-2147483648, 2147483647] instead of [-4095, 0].
LLVM/clang avoids this by generating a 64-bit load (
BPF_LDX | BPF_MEM | BPF_DW) for the same access, sosize == BPF_REG_SIZEand the coercion is skipped.Relevant commits:
mark_reg_unknown()before range, but didn't address the post-coercion issueProposed Fix
Skip
coerce_reg_to_size_sx()when the retval range has already been correctly applied. The range set by__mark_reg_s32_range()already represents the valid post-sign-extension bounds (a 32-bit value in [-4095, 0] sign-extends to the same 64-bit range), so the coercion is a no-op at best and destructive at worst.See:
output/0001-bpf-Fix-retval-range-for-sign-extending-ctx-loads.patchImpact
Without this fix, any LSM BPF program that reads its
retparameter using a sign-extending 32-bit load (as GCC BPF generates) will be rejected by the verifier. This blocks all LSM programs with return value semantics from working under GCC BPF compilation.The test is currently denylisted in CI (
DENYLIST.test_progs-bpf_gcc), masking the issue.References
R6=scalar(smin=0xffffffff80000000,smax=0x7fffffff)→At program exit the register R0 has smin=-2147483648 smax=2147483647 should have been in [-4095, 0]kernel/bpf/verifier.c:6439-6444— retval range applicationkernel/bpf/verifier.c:6557-6576— post-load coercionkernel/bpf/verifier.c:5647-5700—coerce_reg_to_size_sx()implementationkernel/bpf/btf.c:6895—info->is_retval = truefor LSM retval