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 << 64 is undefined behavior (C11 §6.5.7 ¶3: shift count must be < width of the type).
  • On x86-64 the SHL instruction masks the shift count to 6 bits, so the hardware executes SHL 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/cells pointing 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_pending only inside the loop, which they never re-enter).

Reproduction Scenario

  1. Attacker runs a VL server that, in its VL.GetCapabilities response, returns a BulkAddresses record with exactly 64 IPv4/IPv6 entries.
  2. Client kernel calls afs_send_vl_probesafs_do_probe_vlserver.
  3. unprobed = 0; no probe calls dispatched.
  4. Any thread calling afs_wait_for_vl_probes hangs 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.cafs_do_probe_vlserver (line 146–199)
  • fs/afs/internal.hAFS_MAX_ADDRESSES definition (line 123)
  • fs/afs/addr_list.cafs_alloc_addrlist cap (line 66–67)
  • C11 §6.5.7 ¶3 — undefined behavior for shift count ≥ width of promoted left operand