CVE Candidate: Permanent DoS via UB Bitmask in afs_do_probe_vlserver
Summary
An undefined-behavior bitmask computation in fs/afs/vl_probe.c causes a permanent kernel-thread hang (Denial of Service) when an AFS volume-location server advertises exactly AFS_MAX_ADDRESSES (64) addresses. This is the maximum the allocator allows, making the condition reachable both from a malicious server and from any legitimate large-scale AFS deployment.
Affected File
fs/afs/vl_probe.c, function afs_do_probe_vlserver, line 171.
Root Cause
/* internal.h */
#define AFS_MAX_ADDRESSES ((unsigned int)(sizeof(unsigned long) * 8)) // == 64
/* addr_list.c – allocator hard-caps nr_addrs at this value */
if (nr > AFS_MAX_ADDRESSES)
nr = AFS_MAX_ADDRESSES;
/* vl_probe.c:171 – bug */
unsigned long unprobed;
...
atomic_set(&server->probe_outstanding, alist->nr_addrs); // set to 64
...
unprobed = (1UL << alist->nr_addrs) - 1; // UB when nr_addrs == 64
When nr_addrs == 64:
1UL << 64is undefined behavior (C11 §6.5.7 ¶3: shift count must be < width of the type).- On x86-64 the
SHLinstruction masks the shift count to 6 bits, so the hardware executesSHL RAX, 0, leaving RAX = 1. - Therefore
unprobed = 1 - 1 = 0.
Because unprobed == 0, the while (unprobed) loop at line 172 never executes: no probe RPCs are dispatched and afs_done_one_vl_probe is never called.
Impact Chain
| Step | Effect |
|---|---|
test_and_set_bit_lock(AFS_VLSERVER_FL_PROBING, &server->flags) |
Flag set before afs_do_probe_vlserver is called |
afs_do_probe_vlserver returns false |
No RPCs sent; probe_outstanding stays at 64 |
afs_done_one_vl_probe never called |
atomic_dec_and_test never reaches 0 |
afs_finished_vl_probe never called |
AFS_VLSERVER_FL_PROBING never cleared |
afs_wait_for_vl_probes sees AFS_VLSERVER_FL_PROBING set |
Calls schedule() in a for(;;) loop — permanent sleep |
Every kernel thread that subsequently tries to use this VL server (e.g. during cell lookup or mount) blocks forever in afs_wait_for_vl_probes, consuming a kernel thread indefinitely. The condition is permanent: no in-flight RPC will ever complete to clear the flag.
Severity
High — remote, unauthenticated Denial of Service.
- Attack vector: Network (AFS VL server response, or DNS/
/proc/net/afs/cellspointing to an attacker-controlled server). - Privileges required: None — any client can be directed to probe an adversarial server.
- Trigger reliability: 100 % deterministic once
nr_addrs == 64. The allocator guarantees it cannot exceed 64, so this value is trivially reachable. - Recovery: None without rebooting or unloading the AFS module; the hung threads cannot be killed (they check
signal_pendingonly inside the loop, which they never re-enter).
Reproduction Scenario
- Attacker runs a VL server that, in its
VL.GetCapabilitiesresponse, returns aBulkAddressesrecord with exactly 64 IPv4/IPv6 entries. - Client kernel calls
afs_send_vl_probes→afs_do_probe_vlserver. unprobed = 0; no probe calls dispatched.- Any thread calling
afs_wait_for_vl_probeshangs permanently.
Fix
Replace the unsafe shift with an expression that handles the boundary correctly:
/* Before (UB when nr_addrs == BITS_PER_LONG): */
unprobed = (1UL << alist->nr_addrs) - 1;
/* After: */
unprobed = (alist->nr_addrs < BITS_PER_LONG)
? (1UL << alist->nr_addrs) - 1
: ULONG_MAX;
Alternatively, lower AFS_MAX_ADDRESSES to BITS_PER_LONG - 1 (63), but that changes the ABI/protocol limit and would silently drop a valid address.
References
fs/afs/vl_probe.c—afs_do_probe_vlserver(line 146–199)fs/afs/internal.h—AFS_MAX_ADDRESSESdefinition (line 123)fs/afs/addr_list.c—afs_alloc_addrlistcap (line 66–67)- C11 §6.5.7 ¶3 — undefined behavior for shift count ≥ width of promoted left operand