<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ks on Homelab</title>
    <link>https://psn.af/k/</link>
    <description>Recent content in Ks on Homelab</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language><atom:link href="https://psn.af/k/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title></title>
      <link>https://psn.af/k/1/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/1/</guid>
      <description>Vulnerability Report: Silent Misconfiguration via Ambiguous Return Value in get_coeff() File: sound/soc/codecs/wm8731.c
Severity: High
Type: Logic Error / Incorrect Error Handling (CWE-393: Return of Wrong Status Code)
Summary get_coeff() returns 0 when no matching clock/rate coefficient is found, but 0 is also the valid index of the first entry in coeff_div[]. The caller wm8731_hw_params() cannot distinguish between &amp;ldquo;found at index 0&amp;rdquo; and &amp;ldquo;not found&amp;rdquo;, causing silent hardware misconfiguration and a false success return to userspace.
Vulnerable Code get_coeff() — lines 302–311 static inline int get_coeff(int mclk, int rate) { int i; for (i = 0; i &amp;lt; ARRAY_SIZE(coeff_div); i++) { if (coeff_div[i].rate == rate &amp;amp;&amp;amp; coeff_div[i].mclk == mclk) return i; } return 0; // BUG: &amp;#34;not found&amp;#34; is indistinguishable from index 0 } wm8731_hw_params() — lines 320–326 int i = get_coeff(wm8731-&amp;gt;sysclk, params_rate(params)); u16 srate = (coeff_div[i].sr &amp;lt;&amp;lt; 2) | (coeff_div[i].bosr &amp;lt;&amp;lt; 1) | coeff_div[i].usb; wm8731-&amp;gt;playback_fs = params_rate(params); snd_soc_component_write(component, WM8731_SRATE, srate); Root Cause get_coeff() uses return 0 as its error sentinel. Index 0 in coeff_div[] corresponds to the valid entry {12288000, 48000, 256, 0x0, 0x0, 0x0}. When an unsupported (mclk, rate) pair is requested — for example when wm8731-&amp;gt;sysclk is 0 (uninitialized, since wm8731_priv is zero-allocated) or set to a value not present in the table — the function returns 0, which the caller treats as a successful lookup of the first table entry.
Impact Silent hardware misconfiguration: wm8731_hw_params() programs the WM8731_SRATE register with the coefficients for 48000 Hz / 12288000 Hz regardless of what was actually requested.
No error returned to userspace: wm8731_hw_params() returns 0 (success) even when the requested sample rate is unsupported by the current clock configuration. The PCM open/prepare path completes without error.
Exploitable via uninitialized state: If a userspace process opens a PCM stream and calls hw_params before set_sysclk has been called (leaving wm8731-&amp;gt;sysclk == 0), the driver silently accepts the request with wrong hardware register values. An attacker with access to the ALSA PCM device can trigger this path to force the codec into a misconfigured state.
Interplay with deemphasis: wm8731_hw_params() also calls wm8731_set_deemph() which reads wm8731-&amp;gt;playback_fs — the value that was just set to the wrong rate — further compounding the misconfiguration.
Proof-of-Concept Trigger // 1. Open PCM device without calling snd_soc_dai_set_sysclk first // =&amp;gt; wm8731-&amp;gt;sysclk remains 0 // 2. Call hw_params with any valid rate (e.g. 44100) // =&amp;gt; get_coeff(0, 44100) finds no match, returns 0 // =&amp;gt; coeff_div[0] (48000 Hz coefficients) is written to hardware // =&amp;gt; hw_params returns 0 (success) // 3. Audio plays at wrong sample rate; no error is signalled Fix get_coeff() must return a negative error code when no match is found, and wm8731_hw_params() must check for it:
static inline int get_coeff(int mclk, int rate) { int i; for (i = 0; i &amp;lt; ARRAY_SIZE(coeff_div); i++) { if (coeff_div[i].rate == rate &amp;amp;&amp;amp; coeff_div[i].mclk == mclk) return i; } return -EINVAL; } // In wm8731_hw_params(): int i = get_coeff(wm8731-&amp;gt;sysclk, params_rate(params)); if (i &amp;lt; 0) { dev_err(component-&amp;gt;dev, &amp;#34;No coefficient for mclk %u rate %u\n&amp;#34;, wm8731-&amp;gt;sysclk, params_rate(params)); return i; } References coeff_div[] table: lines 238–270 get_coeff(): lines 302–311 wm8731_hw_params(): lines 313–347 wm8731_priv.sysclk initialized to 0 (zero-allocation): wm8731.h line 51 </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/10/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/10/</guid>
      <description>Vulnerability Report: Double module_put / Refcount Underflow in sound/aoa/core/core.c Summary A double module_put() in the Apple Onboard Audio (AOA) driver core causes a module reference-count underflow. When the reference count reaches zero the kernel may unload the owning module; any subsequent access to that module&amp;rsquo;s code or data is a use-after-free. This is exploitable for local privilege escalation on PowerPC Mac hardware running an affected kernel.
Affected File sound/aoa/core/core.c
Vulnerability Class CWE-911 – Improper Update of Reference Count
(leads to CWE-416 – Use After Free)
Root Cause Each time a codec is successfully attached to a fabric, attach_codec_to_fabric() increments the module reference count exactly once:
/* core.c:27 */ if (!try_module_get(c-&amp;gt;owner)) // +1 ref return -EBUSY; There are two independent code paths that each unconditionally call module_put() on that same owner, but neither checks whether the other has already done so.
Path 1 – aoa_fabric_unlink_codec() (line 146) void aoa_fabric_unlink_codec(struct aoa_codec *codec) { ... codec-&amp;gt;fabric = NULL; module_put(codec-&amp;gt;owner); // &amp;lt;-- first put } Called from aoa_fabric_unregister() for every codec still linked to the fabric being removed.
Path 2 – aoa_codec_unregister() (line 80) void aoa_codec_unregister(struct aoa_codec *codec) { list_del(&amp;amp;codec-&amp;gt;list); if (codec-&amp;gt;fabric &amp;amp;&amp;amp; codec-&amp;gt;exit) // fabric is already NULL here codec-&amp;gt;exit(codec); if (fabric &amp;amp;&amp;amp; fabric-&amp;gt;remove_codec) // fabric global also NULL fabric-&amp;gt;remove_codec(codec); codec-&amp;gt;fabric = NULL; module_put(codec-&amp;gt;owner); // &amp;lt;-- second put } After aoa_fabric_unregister() sets codec-&amp;gt;fabric = NULL and clears the global fabric pointer, all the guard conditions in aoa_codec_unregister() evaluate to false — so neither early-exit path fires — and module_put() is called a second time on the same owner.
Exploit Scenario Setup: A codec driver module (codec-&amp;gt;owner = THIS_MODULE) is registered while a fabric is present. try_module_get increments the refcount to N+1.
Trigger fabric removal: aoa_fabric_unregister() iterates codec_list, calls aoa_fabric_unlink_codec() for each attached codec → module_put() → refcount drops to N. When N was 1, refcount is now 0 and the kernel considers the module eligible for unloading.
Trigger codec unregister: aoa_codec_unregister() is called for the same codec (normal driver teardown order). module_put() is called again → refcount underflows (wraps or goes negative depending on the atomic type).
Use-after-free: If the module was freed between steps 2 and 3 (refcount hit 0), the module_put in step 3 writes to freed struct module memory. An attacker who can reallocate that memory (e.g. via usercopy gadgets, SLUB heap spray) controls what the write lands on, enabling kernel code execution.
Even without a race, a negative/wrapped refcount permanently prevents the module from being unloaded in future iterations, leaking kernel memory indefinitely and potentially enabling denial-of-service.
Proof-of-Concept (pseudo-code) // 1. Load codec module → aoa_codec_register() [try_module_get, ref=1] // 2. Load fabric module → aoa_fabric_register() [attaches codec] // 3. Unload fabric → aoa_fabric_unregister() [module_put, ref=0] // (module may be freed here by the kernel) // 4. Unload codec → aoa_codec_unregister() [module_put on freed struct, ref=-1] // ^ UAF / refcount underflow Impact Property Value Attack vector Local (requires ability to load/unload kernel modules, or trigger via device hotplug) Privileges required CAP_SYS_MODULE or physical device access Impact Kernel use-after-free → potential local privilege escalation / kernel crash CVSS v3 (estimated) 7.8 (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) Fix Track whether module_put has already been called (e.g. via a bool owner_put flag on struct aoa_codec), or centralise the put exclusively in aoa_codec_unregister() and remove it from aoa_fabric_unlink_codec():
/* aoa_fabric_unlink_codec: remove the unconditional module_put */ - module_put(codec-&amp;gt;owner); /* aoa_codec_unregister: keep the single authoritative put */ module_put(codec-&amp;gt;owner); Additionally, the entire file lacks any locking on the global fabric pointer and codec_list, which introduces TOCTOU races between register/unregister paths — a secondary issue that should be addressed with a mutex.</description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/11/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/11/</guid>
      <description>CVE-Class: Integer Overflow in BPF Map Index Computation — xdp_sample.bpf.c Summary A silent integer overflow in the BPF map array index formula causes all XDP redirect error statistics to be corrupted by default (when nr_cpus = 0). Additionally, with crafted nr_cpus values an attacker controlling program load parameters can force arbitrary error-type aliasing, permanently masking or fabricating security-relevant packet drop events.
Affected File samples/bpf/xdp_sample.bpf.c
Vulnerability Details Primary: Default-Zero Collapse (Always Triggered) Location: xdp_redirect_collect_stat(), line 78
// xdp_sample.bpf.c:68-86 static __always_inline int xdp_redirect_collect_stat(int from, int err) { u32 cpu = bpf_get_smp_processor_id(); u32 key = XDP_REDIRECT_ERROR; struct datarec *rec; u32 idx; if (!IN_SET(from_match, from)) return 0; key = xdp_get_err_key(err); // key ∈ {0,1,2,3,4,5,6} idx = key * nr_cpus + cpu; // ← VULNERABLE rec = bpf_map_lookup_elem(&amp;amp;redir_err_cnt, &amp;amp;idx); Root cause:
nr_cpus is declared as const volatile int nr_cpus = 0 (line 23). Its default value is zero. Because it is a const volatile global meant to be patched before load, the BPF verifier cannot constant-fold it away once loaded with a non-zero value, but it also does not validate it is sensible.
When nr_cpus == 0 (the default, and the state when the program is loaded without explicit configuration):
idx = key * 0 + cpu = cpu (for every value of key) All seven distinct error buckets (key = 0–6 representing SUCCESS, generic error, EINVAL, ENETDOWN, EMSGSIZE, EOPNOTSUPP, ENOSPC) collapse to the same per-CPU datarec entry. Every call to xdp_redirect_collect_stat increments either rec-&amp;gt;dropped or rec-&amp;gt;processed in slot [cpu], regardless of error type.
Consequence: success packets and every error class overwrite each other&amp;rsquo;s counters. Monitoring tools that rely on these statistics — including the companion xdp_redirect sample — silently receive completely wrong data.
Secondary: Integer Overflow for Bucket Aliasing When a user loads the program with a crafted nr_cpus, the 32-bit multiplication can be made to overflow, causing two different error key values to map to the same idx:
key=6 (ENOSPC), nr_cpus = 715827883: 6 × 715827883 = 4,294,967,298 → wraps to 2 (mod 2³²) idx = 2 + cpu key=0 (SUCCESS), nr_cpus = 715827883: 0 × 715827883 = 0 idx = 0 + cpu With a suitable nr_cpus, ENOSPC errors alias to the same bucket as another error class (or as SUCCESS), so dropped packets are counted as successfully processed — a security monitoring bypass.
Tertiary: Same Pattern in tp_xdp_cpumap_enqueue (line 138) idx = to_cpu * nr_cpus + cpu; rec = bpf_map_lookup_elem(&amp;amp;cpumap_enqueue_cnt, &amp;amp;idx); to_cpu is a signed int parameter received from the tracepoint. If a crafted tracepoint fires with a negative to_cpu, the expression to_cpu * nr_cpus is negative, and when stored into u32 idx it wraps to a large value or, after modular arithmetic with nr_cpus, aliases another valid slot.
Impact Scenario Effect Default load (nr_cpus=0) All redirect stats silently mixed; monitoring useless Crafted nr_cpus at load time Error types alias each other; dropped packets appear as successes Negative to_cpu + crafted nr_cpus cpumap enqueue stats corrupted; overload detection bypassed In a production XDP deployment, these corrupted statistics feed into userspace tools that may alert on packet drops, redirect errors, and overload conditions. Silent corruption means real denial-of-service events (e.g., ENOSPC on a cpumap queue) are invisible to operators.
Root Cause (CWE) CWE-190 — Integer Overflow or Wraparound CWE-682 — Incorrect Calculation CWE-369 — Divide By Zero (degenerate case: multiply by zero) Proof of Concept With nr_cpus left at the default (0), load the BPF program and trigger XDP redirects. Read redir_err_cnt map entries 0–6 × ncpus: all entries for keys 1–6 will be zero while entries at key=0 accumulate all events — drops and successes merged into a single bucket per CPU.
Fix Validate nr_cpus is positive and non-zero before use, and add a bounds check on the computed index:
if (nr_cpus &amp;lt;= 0) return 0; // reject invalid configuration idx = key * (u32)nr_cpus + cpu; /* Ensure index stays within allocated map bounds */ if (idx &amp;gt;= (u32)(XDP_REDIRECT_ERROR + 1) * (u32)nr_cpus) return 0; rec = bpf_map_lookup_elem(&amp;amp;redir_err_cnt, &amp;amp;idx); Apply the same fix to tp_xdp_cpumap_enqueue (line 138) and reject negative to_cpu values before the multiply.</description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/12/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/12/</guid>
      <description>CVE-Candidate: FIONREAD loff_t-to-int Silent Truncation — Userspace Heap Buffer Overflow File: fs/ioctl.c
Line: 553
Severity: High
Privileges required: None (unprivileged)
Vulnerable Code // fs/ioctl.c:549-554 case FIONREAD: if (!S_ISREG(inode-&amp;gt;i_mode) || IS_ANON_FILE(inode)) return vfs_ioctl(filp, cmd, arg); return put_user(i_size_read(inode) - filp-&amp;gt;f_pos, (int __user *)argp); Root Cause i_size_read(inode) returns loff_t (signed 64-bit). filp-&amp;gt;f_pos is also loff_t. The subtraction result is therefore loff_t (64-bit).
put_user writes to int __user * — only 32 bits. The upper 32 bits of the 64-bit result are silently discarded with no range validation or error before writing to userspace.
Attack Scenarios Scenario 1 — File ≥ 2 GiB (no special seek needed) A regular file of size 3 GiB = 0xC000_0000 bytes, f_pos = 0:
i_size - f_pos = 0x0000_0000_C000_0000 (loff_t, positive) put_user to int* → truncated to 0xC000_0000 as signed int = -1,073,741,824 FIONREAD returns −1 073 741 824 to the caller. Userspace programs that cast this to size_t (unsigned) get 0xFFFFFFFFC0000000 — a near-SIZE_MAX allocation request, leading to OOM or a near-zero-length heap allocation used as a read buffer.
Scenario 2 — Seek past EOF Any unprivileged user can lseek(fd, large_offset, SEEK_SET) on any readable regular file without restriction. With f_pos &amp;gt; i_size:
i_size - f_pos &amp;lt; 0 (loff_t, negative) The negative 64-bit value is truncated to a 32-bit int. Depending on the magnitude:
f_pos − i_size FIONREAD result (int) 1 −1 (mimics EAGAIN/error) 0x1_0000_0001 −1 (lower 32 bits all-ones) 0x1_0000_0000 0 (false &amp;ldquo;no data&amp;rdquo; signal) Programs that treat FIONREAD == −1 as &amp;ldquo;retry later&amp;rdquo; can be manipulated into an infinite busy-loop or skip a read entirely.
Scenario 3 — Heap buffer overflow via read-size confusion A typical pattern in userspace:
int avail; ioctl(fd, FIONREAD, &amp;amp;avail); if (avail &amp;gt; 0) { char *buf = malloc(avail); // avail is truncated/negative read(fd, buf, avail); // reads actual data into undersized buffer } When avail is negative, malloc((size_t)avail) allocates SIZE_MAX - |avail| + 1 bytes (wraps unsigned), which fails or returns a tiny chunk. The subsequent read() with (size_t)avail as a count then writes gigabytes into that chunk — heap buffer overflow.
Secondary Vulnerability — ioctl_preallocate Signed Integer Overflow Lines: 280, 283
case SEEK_CUR: sr.l_start += filp-&amp;gt;f_pos; // signed 64-bit overflow, UB in C break; case SEEK_END: sr.l_start += i_size_read(inode); // same break; sr.l_start is fully user-controlled (__s64 from struct space_resv). There is no overflow check before adding filp-&amp;gt;f_pos or i_size_read(). Setting sr.l_start = LLONG_MAX and using SEEK_CUR/SEEK_END causes signed 64-bit wraparound — undefined behavior in C, practically wrapping to a large negative on x86-64. While vfs_fallocate rejects negative offsets, this UB can be miscompiled or exploited via compiler optimizations.
Fix FIONREAD Add a bounds check before writing to userspace:
case FIONREAD: { loff_t avail = i_size_read(inode) - filp-&amp;gt;f_pos; if (avail &amp;lt; 0) avail = 0; if (avail &amp;gt; INT_MAX) avail = INT_MAX; return put_user((int)avail, (int __user *)argp); } ioctl_preallocate Use checked arithmetic:
case SEEK_CUR: if (check_add_overflow(sr.l_start, filp-&amp;gt;f_pos, &amp;amp;sr.l_start)) return -EOVERFLOW; break; case SEEK_END: if (check_add_overflow(sr.l_start, i_size_read(inode), &amp;amp;sr.l_start)) return -EOVERFLOW; break; Summary Property Value Location fs/ioctl.c:553 Type CWE-197 Numeric Truncation / CWE-190 Integer Overflow Trigger FIONREAD ioctl on any regular file ≥ 2 GiB, or after lseek past EOF Privileges None — any process that can open a file Impact Wrong byte-count returned to userspace → downstream heap buffer overflow </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/13/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/13/</guid>
      <description>Type Confusion via Stale Function Pointers in ceph_auth_none_create_authorizer Summary net/ceph/auth_none.c — ceph_auth_none_create_authorizer() — fails to clear the sign_message and check_message_signature function pointers in the ceph_auth_handshake struct when creating an auth_none authorizer. If a Ceph connection previously used CephX authentication and then downgrades to auth_none (e.g., on reconnection with force_new=true), stale CephX function pointers persist in the handshake. When any subsequent message is signed, the kernel calls ceph_x_sign_message() while auth-&amp;gt;authorizer actually points to a ceph_none_authorizer — causing type confusion, out-of-bounds memory access, and potential kernel memory corruption or privilege escalation.
Vulnerability Details Root Cause In ceph_auth_none_create_authorizer() (net/ceph/auth_none.c:93):
auth-&amp;gt;authorizer = (struct ceph_authorizer *) au; auth-&amp;gt;authorizer_buf = au-&amp;gt;buf; auth-&amp;gt;authorizer_buf_len = au-&amp;gt;buf_len; auth-&amp;gt;authorizer_reply_buf = NULL; auth-&amp;gt;authorizer_reply_buf_len = 0; // BUG: auth-&amp;gt;sign_message and auth-&amp;gt;check_message_signature are NOT cleared Compare to ceph_x_create_authorizer() (net/ceph/auth_x.c:765), which explicitly sets them:
auth-&amp;gt;sign_message = ac-&amp;gt;ops-&amp;gt;sign_message; auth-&amp;gt;check_message_signature = ac-&amp;gt;ops-&amp;gt;check_message_signature; Exploitation Path Initial connection: CephX auth is used. ceph_x_create_authorizer() sets:
auth-&amp;gt;authorizer → struct ceph_x_authorizer * auth-&amp;gt;sign_message → ceph_x_sign_message auth-&amp;gt;check_message_signature → ceph_x_check_message_signature Reconnection with force_new=true and auth downgrade to auth_none (e.g., server no longer requires CephX):
ceph_auth_destroy_authorizer() frees the old ceph_x_authorizer ceph_auth_none_create_authorizer() creates a new ceph_none_authorizer and sets auth-&amp;gt;authorizer to it Stale pointers remain: auth-&amp;gt;sign_message still points to ceph_x_sign_message Message send: ceph_auth_sign_message() checks auth-&amp;gt;sign_message != NULL and calls it:
// net/ceph/auth_x.c:1038 ret = calc_signature((struct ceph_x_authorizer *)auth-&amp;gt;authorizer, msg, &amp;amp;sig); auth-&amp;gt;authorizer is now a ceph_none_authorizer, but is cast to ceph_x_authorizer.
Type Confusion in calc_signature calc_signature() (net/ceph/auth_x.c:964) accesses fields by offset assuming ceph_x_authorizer layout:
struct ceph_x_authorizer layout (64-bit): offset 0: base (destroy fn ptr, 8 bytes) offset 8: session_key (struct ceph_crypto_key, ~32 bytes) - type (int, 4B) - created (ceph_timespec, 8B) - len (int, 4B) - key (void*, 8B) ← kernel pointer read from auth_none buf - tfm (skcipher*, 8B) offset ~40: buf (pointer, 8B) offset ~48: service (u32, 4B) offset ~52: nonce (u64, 8B) offset ~60: secret_id (u64, 8B) offset ~72: enc_buf[128] ← written to by calc_signature struct ceph_none_authorizer layout (64-bit): offset 0: base (destroy fn ptr, 8 bytes) offset 8: buf[128] ← read as session_key (crypto key!) offset 136: buf_len (int) Total: ~140 bytes When calc_signature reads au-&amp;gt;session_key (offset 8), it reads 32 bytes of ceph_none_authorizer::buf — attacker-influenced authorizer data — and treats it as a ceph_crypto_key. The key field within (a void pointer at offset 8+16=24, so buf[24..31]) is read as a kernel pointer and passed to ceph_x_encrypt().
When calc_signature writes to au-&amp;gt;enc_buf (offset ~72), it writes encrypted data into the middle of ceph_none_authorizer::buf[64..191] — overflowing the 128-byte buf array by roughly 64 bytes, corrupting adjacent heap memory.
Impact Effect Severity Kernel heap buffer overflow (~64B past end of buf[128]) Critical Arbitrary kernel pointer dereference via attacker-influenced session_key.key Critical Kernel crash (null deref / invalid pointer from buf contents) High Kernel memory disclosure via crafted HMAC signature leaking heap data High An attacker who controls the contents of the auth_none authorizer buffer (by controlling the entity name or global_id encoded into it) can place a crafted pointer at buf[24..31], which is then dereferenced as a ceph_crypto_key::key pointer inside ceph_x_encrypt(). Combined with the heap overflow from the enc_buf write, this is a strong primitive for local privilege escalation from a malicious Ceph client context.
Affected Code File Line Issue net/ceph/auth_none.c 93–119 create_authorizer does not clear sign_message/check_message_signature net/ceph/auth_x.c 1029–1045 ceph_x_sign_message unsafely casts auth-&amp;gt;authorizer to ceph_x_authorizer net/ceph/auth_x.c 964–1028 calc_signature writes to enc_buf at a ceph_x offset, overflowing auth_none&amp;rsquo;s buf[128] Fix In ceph_auth_none_create_authorizer(), explicitly clear the stale CephX function pointers:
auth-&amp;gt;sign_message = NULL; auth-&amp;gt;check_message_signature = NULL; </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/14/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/14/</guid>
      <description>Out-of-Bounds Array Access via Wrong Index in hci_drv_process_cmd Summary net/bluetooth/hci_drv.c, function hci_drv_process_cmd (line 88–89), uses the full 16-bit HCI opcode (which encodes both the 6-bit OGF and 10-bit OCF) as the array index into common_handlers, instead of the 10-bit ocf. The driver-specific path on line 91–92 correctly uses ocf. This inconsistency creates an out-of-bounds read primitive that can escalate to arbitrary kernel code execution.
Vulnerable Code // net/bluetooth/hci_drv.c:87-103 if (ogf != HCI_DRV_OGF_DRIVER_SPECIFIC) { if (opcode &amp;lt; hdev-&amp;gt;hci_drv-&amp;gt;common_handler_count) // bounds-checks full opcode handler = &amp;amp;hdev-&amp;gt;hci_drv-&amp;gt;common_handlers[opcode]; // indexes by full opcode } else { if (ocf &amp;lt; hdev-&amp;gt;hci_drv-&amp;gt;specific_handler_count) // bounds-checks ocf (correct) handler = &amp;amp;hdev-&amp;gt;hci_drv-&amp;gt;specific_handlers[ocf]; // indexes by ocf (correct) } ... if (!handler || !handler-&amp;gt;func) return hci_drv_cmd_status(...); if (len != handler-&amp;gt;data_len) return hci_drv_cmd_status(...); return handler-&amp;gt;func(hdev, skb-&amp;gt;data, len); // ← function pointer call Root Cause All defined common commands have OGF=0 (e.g., HCI_DRV_OP_READ_INFO = 0x0000). For OGF=0, opcode == ocf, so the bug is latent in the current codebase. However, the bounds check opcode &amp;lt; common_handler_count and the index common_handlers[opcode] use the full 16-bit opcode, not the 10-bit OCF. This creates a mismatch between the semantic intent (index by command number within OGF=0) and the actual index computation (index by raw opcode word including OGF bits).
The dangerous pattern: a driver author who sizes common_handlers for N OCF values and sets common_handler_count = N creates a discrepancy because:
For OGF=0, OCF=k: opcode = k. Index k &amp;lt; N is within bounds. Correct. For OGF=2, OCF=0: opcode = 0x0800 = 2048. If N ≤ 2048, the check fails safely. But if an author sets common_handler_count = 1024 (a natural &amp;ldquo;max OCF&amp;rdquo; value) while allocating common_handlers[5] (only 5 real entries), an attacker sending OGF=0, OCF=5..1023 passes the check and reads common_handlers[5] through common_handlers[1023] — all out of bounds. Attack Surface Packets of type HCI_DRV_PKT (0xF1) are accepted from HCI_CHANNEL_USER sockets (hci_sock.c:1869–1876) and routed directly to hci_drv_process_cmd (hci_core.c:3064–3070). A process with CAP_NET_ADMIN (required to bind HCI_CHANNEL_USER) can send arbitrary crafted payloads.
Exploit Scenario Attacker opens an AF_BLUETOOTH / BTPROTO_HCI socket and binds to HCI_CHANNEL_USER (requires CAP_NET_ADMIN — achievable via a user namespace if unprivileged user namespaces are enabled). Attacker sends an HCI_DRV_PKT with: opcode crafted so that opcode &amp;gt;= ARRAY_SIZE(common_handlers) but opcode &amp;lt; common_handler_count (requires a driver with an oversized common_handler_count). len matching handler-&amp;gt;data_len read from OOB memory so the data_len check passes. The code calls handler-&amp;gt;func(hdev, skb-&amp;gt;data, len) where func is a pointer read from out-of-bounds kernel heap memory. If the attacker controls the heap layout (e.g., heap spray), they can place a crafted hci_drv_handler struct adjacent to the common_handlers array, directing execution to arbitrary kernel code. Impact Severity: Critical (kernel arbitrary code execution)
Out-of-bounds read: Reading hci_drv_handler entries beyond the allocated common_handlers array leaks kernel heap pointers, bypassing KASLR. Arbitrary function call: The handler-&amp;gt;func pointer from OOB memory is called with attacker-controlled data (skb-&amp;gt;data) as the second argument, enabling kernel code execution. Full kernel privilege escalation from CAP_NET_ADMIN (or from a user namespace with unprivileged namespaces enabled). Fix Replace the opcode index with ocf in the common handler path, mirroring the driver-specific path:
// Before (buggy): if (opcode &amp;lt; hdev-&amp;gt;hci_drv-&amp;gt;common_handler_count) handler = &amp;amp;hdev-&amp;gt;hci_drv-&amp;gt;common_handlers[opcode]; // After (fixed): if (ocf &amp;lt; hdev-&amp;gt;hci_drv-&amp;gt;common_handler_count) handler = &amp;amp;hdev-&amp;gt;hci_drv-&amp;gt;common_handlers[ocf]; Additionally, add a check that ogf == 0 before entering the common handler path, to reject commands with unexpected OGF values early.
References Vulnerable file: net/bluetooth/hci_drv.c, lines 87–89 Driver-specific path (correct reference): net/bluetooth/hci_drv.c, lines 91–92 Entry point from userspace: net/bluetooth/hci_sock.c, lines 1863–1879 Dispatch to vulnerable function: net/bluetooth/hci_core.c, lines 3064–3070 </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/15/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/15/</guid>
      <description>CVE Candidate: GFS2 Journal Recovery CRC Bypass Leads to Statfs Corruption via Signed Integer Overflow File: fs/gfs2/recovery.c
Function: __get_log_header() → update_statfs_inode()
Severity: High
Type: Integrity Check Bypass + Signed Integer Overflow (CWE-354, CWE-190)
Vulnerability Description 1. CRC Integrity Check Bypass (__get_log_header, line 138) The GFS2 log header is split into two regions:
V1 region (bytes 0 to LH_V1_SIZE): covered by a CRC-32 hash stored in lh_hash. V2 extension region (bytes LH_V1_SIZE+4 to end of block): covered by a CRC-32C stored in lh_crc. The V2 extension contains lh_local_total, lh_local_free, and lh_local_dinodes — the statfs delta fields applied to the master statfs inode during journal recovery.
The CRC validation for the V2 region is:
// recovery.c:135-139 crc = crc32c(~0, (void *)lh + LH_V1_SIZE + 4, sdp-&amp;gt;sd_sb.sb_bsize - LH_V1_SIZE - 4); if ((lh-&amp;gt;lh_crc != 0 &amp;amp;&amp;amp; be32_to_cpu(lh-&amp;gt;lh_crc) != crc)) return 1; The condition short-circuits when lh-&amp;gt;lh_crc == 0: the check is entirely skipped. An attacker who can write a crafted GFS2 filesystem image simply sets lh_crc = 0 to bypass the integrity check on all V2 extension fields, including the three statfs delta counters.
The lh_hash check (V1 region, line 132) still applies, but it does not cover lh_local_total/free/dinodes. Those fields are completely unprotected when lh_crc = 0.
2. Signed Integer Overflow in Statfs Update (update_statfs_inode, lines 325–327) After the CRC bypass, the attacker-controlled values flow into:
// recovery.c:324-328 gfs2_statfs_change_in(&amp;amp;sc, bh-&amp;gt;b_data + sizeof(struct gfs2_dinode)); sc.sc_total += head-&amp;gt;lh_local_total; sc.sc_free += head-&amp;gt;lh_local_free; sc.sc_dinodes += head-&amp;gt;lh_local_dinodes; gfs2_statfs_change_out(&amp;amp;sc, bh-&amp;gt;b_data + sizeof(struct gfs2_dinode)); sc.sc_total, sc.sc_free, and sc.sc_dinodes are all s64 (signed 64-bit). Adding attacker-controlled lh_local_* values (which are decoded as s64 via be64_to_cpu) with no bounds checking causes signed integer overflow, which is undefined behavior in C and can produce arbitrary corrupted values.
The corrupted values are then written directly back to the master statfs inode buffer and synced to disk via gfs2_inode_metasync.
Attack Scenario An attacker who can present a crafted GFS2 block device (e.g., via a malicious disk image, USB device, network block device, or VM disk) to a victim kernel mounts it normally. During mount, gfs2_recover_func is called. The crafted journal contains a log header with:
Valid lh_header.mh_magic, mh_type, lh_blkno — passes the metadata check. Valid lh_hash — passes the V1 CRC-32 check. lh_crc = 0 — bypasses the V2 CRC-32C integrity check. lh_local_total = INT64_MAX, lh_local_free = INT64_MAX, lh_local_dinodes = INT64_MAX in the (now unchecked) V2 extension. The kernel applies these values to the master statfs inode (shared across all cluster nodes in GFS2), corrupting global filesystem accounting. The corrupted values are then flushed to disk.
Impact Impact Detail Filesystem corruption Master statfs inode sc_total/sc_free/sc_dinodes written with overflowed garbage values Cluster-wide effect GFS2 is a cluster filesystem; the master statfs inode is shared — corruption affects all nodes Persistent Damage is flushed to disk; persists after unmount Privilege required Attacker needs ability to present a crafted block device (local user with USB, or VM escape / container escape scenarios) DoS df, quota accounting, and block allocator all rely on accurate statfs data Root Cause The lh_crc = 0 bypass was likely introduced as a compatibility shim to allow V1-format log headers (which lack the CRC field) to pass validation. However, it creates an exploitable path: an attacker can write a V2-format header that claims to be V1 by zeroing lh_crc, stripping all integrity protection from the extension fields.
Proof-of-Concept (Sketch) import struct, binascii # Patch an existing valid GFS2 log header block on-disk: # 1. Set lh_crc = 0x00000000 (at offset LH_V1_SIZE) # 2. Set lh_local_total = 0x7FFFFFFFFFFFFFFF (at its struct offset) # 3. Set lh_local_free = 0x7FFFFFFFFFFFFFFF # The lh_hash covers only the V1 region and remains valid. # Result: CRC check skipped; overflowed values written to master statfs inode. Fix Replace the bypass condition with an explicit version check based on lh_flags, or require a non-zero CRC for any header that contains non-zero V2 extension fields:
// Proposed fix: treat lh_crc==0 as invalid if V2 fields are present if (be32_to_cpu(lh-&amp;gt;lh_crc) != crc) return 1; Additionally, add bounds validation on the statfs delta values before applying them:
if (head-&amp;gt;lh_local_total &amp;lt; -max_blocks || head-&amp;gt;lh_local_total &amp;gt; max_blocks) return -EIO; </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/2/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/2/</guid>
      <description>CVE-Candidate: Kernel Divide-by-Zero via Malicious USB HID Sensor (hid-sensor-attributes) Summary A kernel divide-by-zero can be triggered by a malicious USB HID sensor device that crafts specific unit_expo values in its HID report descriptor. The root cause is an implicit negative-to-unsigned integer conversion when calling int_pow(), causing it to return 0, which is then used as a divisor — crashing the kernel.
Severity: High (Kernel Panic / Denial of Service) Attack Vector: Physical (USB) / Guest-to-Host in virtualized environments Affected component: drivers/iio/common/hid-sensors/hid-sensor-attributes.c Root cause: lib/math/int_pow.c — no guard against implicit signed→unsigned conversion of negative exponents
Vulnerable Code lib/math/int_pow.c — the primitive u64 int_pow(u64 base, unsigned int exp) // exp is unsigned { u64 result = 1; while (exp) { if (exp &amp;amp; 1) result *= base; exp &amp;gt;&amp;gt;= 1; base *= base; } return result; } The function silently accepts any unsigned int. Callers that pass negative int values trigger implicit conversion to huge unsigned numbers.
include/linux/hid-sensor-hub.h — exponent decoding static inline int hid_sensor_convert_exponent(int unit_expo) { if (unit_expo &amp;lt; 0x08) return unit_expo; // 0..7 else if (unit_expo &amp;lt;= 0x0f) return -(0x0f - unit_expo + 1); // -8..-1 else return 0; } A HID device controls unit_expo (4-bit nibble in the report descriptor). Values 0x08 and 0x09 yield exponents -8 and -7 respectively.
drivers/iio/common/hid-sensors/hid-sensor-attributes.c:135-156 — the crash site static u32 convert_to_vtf_format(int size, int exp, int val1, int val2) { int divisor; u32 value; ... exp = hid_sensor_convert_exponent(exp); if (exp &amp;lt; 0) { divisor = int_pow(10, 6 + exp); // ← line 145: BUG value = abs(val1) * int_pow(10, -exp); value += abs(val2) / divisor; // ← line 147: DIVIDE BY ZERO } ... } When exp = -7: 6 + (-7) = -1. The -1 (type int) is implicitly cast to unsigned int → UINT_MAX (0xFFFFFFFF) before being passed to int_pow.
int_pow(10, UINT_MAX): The binary exponentiation runs 32 iterations (since UINT_MAX has 32 bits). At each step base *= base squares the value mod 2⁶⁴. Because 10 = 2 × 5, every squaring adds factors of 2. After ~20 squarings, base mod 2⁶⁴ = 0. All subsequent result *= base multiply by 0, so the function returns 0.
Result: divisor = 0, then abs(val2) / divisor → kernel division by zero → oops/panic.
The same overflow path applies to exp = -8 (6 + (-8) = -2 → UINT_MAX - 1 → same result of 0).
Trigger Conditions The vulnerable path is reached in two ways:
Path 1 — Writing sensitivity (user-triggered, physical attacker) /sys/bus/iio/devices/iio:deviceN/in_accel_x_thresh_rising_value (write) Attacker plugs in a USB HID sensor with unit_expo = 0x09 in its HID report descriptor. Kernel enumerates the device; st-&amp;gt;sensitivity.unit_expo = 0x09. Any user writes a sensitivity value to the IIO sysfs file. Kernel calls hid_sensor_write_raw_hyst_value() → convert_to_vtf_format(..., st-&amp;gt;sensitivity.unit_expo, val1, val2). exp = hid_sensor_convert_exponent(0x09) = -7 → divisor = int_pow(10, UINT_MAX) = 0 → divide-by-zero. Path 2 — Reading sensitivity (automatic on device open) hid_sensor_read_raw_hyst_value() calls convert_from_vtf_format() which calls split_micro_fraction(value, 8, ...) when exp = -8, computing int_pow(10, 6 - 8) = int_pow(10, UINT_MAX) = 0, and then no % 0 or no / 0 — also undefined behavior / divide-by-zero.
Proof-of-Concept (HID Descriptor Snippet) A malicious HID report descriptor advertising a sensor with a crafted unit exponent:
... 0x55, 0x09, // Unit Exponent (0x09 → decoded as -7) ... Such a descriptor can be emitted by a cheap USB microcontroller (e.g., ATmega32U4, RP2040 in TinyUSB) acting as a HID sensor hub.
Impact Property Detail Effect Kernel oops / panic (divide by zero in interrupt-safe kernel context) Availability Complete — system requires reboot Confidentiality None Integrity None Privileges required Physical USB access (or malicious VM guest) User interaction None after device plug-in; triggered on IIO sysfs read/write With oops=panic (common in production kernels), this is a full system crash from a plugged-in USB device.
Fix Option A — Guard int_pow callers In convert_to_vtf_format and split_micro_fraction, clamp the exponent before passing to int_pow:
// Before: divisor = int_pow(10, 6 + exp); // After: int e = 6 + exp; if (e &amp;lt; 0) { /* exponent out of range — return safe fallback */ return 0; } divisor = int_pow(10, (unsigned int)e); if (divisor == 0) return 0; value += abs(val2) / divisor; Option B — Validate unit_expo at device enumeration time Reject or clamp unit_expo values that would produce exponents outside a safe range (e.g., [-6, 6]) when the HID device is first registered, before they reach the conversion path.
Affected Files lib/math/int_pow.c — no input validation on exp drivers/iio/common/hid-sensors/hid-sensor-attributes.c — lines 145, 99, 102 — negative exponents passed to int_pow without bounds check include/linux/hid-sensor-hub.h:241 — hid_sensor_convert_exponent can return -7 or -8 </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/3/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/3/</guid>
      <description>CVE-XXXX: Kernel Heap OOB Read in ext2 Extended Attribute Entry Validation Summary A heap out-of-bounds read vulnerability exists in fs/ext2/xattr.c in the ext2_xattr_entry_valid() function. The bounds check on the computed next entry pointer is off by up to 3 bytes, allowing IS_LAST_ENTRY() to read past the end of the xattr block buffer. A local attacker who can mount (or supply) a crafted ext2 filesystem can exploit this to leak kernel heap memory, potentially bypassing KASLR and aiding a full privilege escalation chain.
Affected File fs/ext2/xattr.c — ext2_xattr_entry_valid() (line 156) Triggered via fs/ext2/xattr_security.c — security xattr read/write paths Root Cause Macro definitions (xattr.h) #define EXT2_XATTR_LEN(name_len) \ (((name_len) + EXT2_XATTR_ROUND + \ sizeof(struct ext2_xattr_entry)) &amp;amp; ~EXT2_XATTR_ROUND) #define EXT2_XATTR_NEXT(entry) \ ( (struct ext2_xattr_entry *)( \ (char *)(entry) + EXT2_XATTR_LEN((entry)-&amp;gt;e_name_len)) ) #define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0) IS_LAST_ENTRY dereferences the pointer as a 4-byte __u32. This requires at least 4 bytes of valid, readable memory at entry.
The defective bounds check (xattr.c:155-157) next = EXT2_XATTR_NEXT(entry); if ((char *)next &amp;gt;= end) // ← off-by-up-to-3 return false; The check rejects next only when it is at or past end (bh-&amp;gt;b_data + bh-&amp;gt;b_size). It accepts next values of end-1, end-2, and end-3.
After validation succeeds the caller loop advances and immediately calls IS_LAST_ENTRY on the new pointer without further bounds checking:
// xattr.c:237-250 entry = FIRST_ENTRY(bh); while (!IS_LAST_ENTRY(entry)) { // ← 4-byte dereference, no recheck if (!ext2_xattr_entry_valid(entry, end, inode-&amp;gt;i_sb-&amp;gt;s_blocksize)) goto bad_block; ... entry = EXT2_XATTR_NEXT(entry); // entry may now be end-1/2/3 } The same pattern appears in ext2_xattr_list() (line 324-328) and ext2_xattr_set() (line 468-483).
Out-of-bounds window next value bytes read OOB by IS_LAST_ENTRY end - 1 3 bytes (end, end+1, end+2) end - 2 2 bytes (end, end+1) end - 3 1 byte (end) Trigger Conditions Mount (or loop-mount) a crafted ext2 filesystem image. The malicious xattr block must have h_magic = EXT2_XATTR_MAGIC and h_blocks = 1 (passes ext2_xattr_header_valid). The last entry is positioned so that EXT2_XATTR_NEXT(last_entry) = block_end - k for k ∈ {1,2,3}. Trigger any xattr read (getxattr, listxattr) or write (setxattr) on a file in the mounted filesystem, which calls the security handler through ext2_xattr_security_get / ext2_xattr_security_set. No special privileges are needed on systems where unprivileged users can mount filesystems (user namespaces with CLONE_NEWUSER+CLONE_NEWNS, or fuse/udisks2 setups).
Exploitation Impact Immediate: kernel heap disclosure The 1–3 bytes read past the block buffer come from whatever the slab/page allocator placed adjacent to bh-&amp;gt;b_data. Depending on allocation layout this can contain:
Kernel text/data pointers (defeating KASLR / defeating pointer hardening) struct buffer_head metadata of adjacent blocks Contents of other processes&amp;rsquo; cached filesystem data Chaining potential: local privilege escalation A KASLR bypass is often the first step in chaining with a separate write primitive to achieve ring-0 code execution. The ext2 xattr code is reachable from unprivileged contexts, making this a reliable info-leak primitive.
Proof-of-Concept (Sketch) import struct BLOCK_SIZE = 4096 XATTR_MAGIC = 0xEA020000 HDR_SIZE = 16 # sizeof(ext2_xattr_header) ENTRY_FIXED = 16 # sizeof(ext2_xattr_entry) without flexible name array # Place entries whose cumulative EXT2_XATTR_NEXT() lands at end-1. # EXT2_XATTR_LEN(name_len) = (name_len + 3 + 16) &amp;amp; ~3 # Adjust name_len of the last entry so that: # HDR_SIZE + sum(EXT2_XATTR_LEN(nlen_i)) == BLOCK_SIZE - 1 # Since EXT2_XATTR_LEN is always 4-aligned, the closest achievable # offset is BLOCK_SIZE - 4 + 3 = BLOCK_SIZE - 1 is impossible; # the real closest is BLOCK_SIZE - 4 (k=4 byte read exactly at end - # adjusted example): # set name_len such that next == end - 1, end - 2, or end - 3. # Use name_len values that produce non-4-aligned next pointers. block = bytearray(BLOCK_SIZE) # Write header: magic, refcount=1, blocks=1, hash=0, reserved=0 struct.pack_into(&amp;#39;&amp;lt;IIII&amp;#39;, block, 0, XATTR_MAGIC, 1, 1, 0) # Craft one entry: name_index=6 (SECURITY), name_len chosen to make # EXT2_XATTR_NEXT land at block_end - 1. # EXT2_XATTR_LEN(N) = (N + 19) &amp;amp; ~3 # HDR_SIZE + EXT2_XATTR_LEN(N) = block_end - 1 # 16 + (N+19)&amp;amp;~3 = 4095 =&amp;gt; (N+19)&amp;amp;~3 = 4079 # 4079 is not 4-aligned; use N=4060 =&amp;gt; EXT2_XATTR_LEN(4060)=4080, # next=16+4080=4096=end (rejected by &amp;gt;=end check). # Use N=4057 =&amp;gt; EXT2_XATTR_LEN(4057)=(4057+19)&amp;amp;~3=4076&amp;amp;~3=4076, # next=16+4076=4092, then need additional entries to reach 4095. # (Full image construction omitted for responsible disclosure.) # --- snip --- Fix Change the bounds check in ext2_xattr_entry_valid from:
/* fs/ext2/xattr.c:156 — VULNERABLE */ if ((char *)next &amp;gt;= end) return false; to:
/* Ensure IS_LAST_ENTRY (4-byte read) at &amp;#39;next&amp;#39; cannot go out of bounds */ if ((char *)next + sizeof(__u32) &amp;gt; end) return false; This ensures at least 4 readable bytes exist at next before any IS_LAST_ENTRY dereference, closing the 1–3 byte OOB window entirely.
References fs/ext2/xattr.c — ext2_xattr_entry_valid() lines 148–167 fs/ext2/xattr.c — entry traversal loops lines 237–251, 323–329, 467–484 fs/ext2/xattr_security.c — ext2_xattr_security_get/set, ext2_initxattrs fs/ext2/xattr.h — IS_LAST_ENTRY, EXT2_XATTR_NEXT macros </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/4/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/4/</guid>
      <description>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 &amp;gt; AFS_MAX_ADDRESSES) nr = AFS_MAX_ADDRESSES; /* vl_probe.c:171 – bug */ unsigned long unprobed; ... atomic_set(&amp;amp;server-&amp;gt;probe_outstanding, alist-&amp;gt;nr_addrs); // set to 64 ... unprobed = (1UL &amp;lt;&amp;lt; alist-&amp;gt;nr_addrs) - 1; // UB when nr_addrs == 64 When nr_addrs == 64:
1UL &amp;lt;&amp;lt; 64 is undefined behavior (C11 §6.5.7 ¶3: shift count must be &amp;lt; 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, &amp;amp;server-&amp;gt;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 Attacker runs a VL server that, in its VL.GetCapabilities response, returns a BulkAddresses record 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_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 &amp;lt;&amp;lt; alist-&amp;gt;nr_addrs) - 1; /* After: */ unprobed = (alist-&amp;gt;nr_addrs &amp;lt; BITS_PER_LONG) ? (1UL &amp;lt;&amp;lt; alist-&amp;gt;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_ADDRESSES definition (line 123) fs/afs/addr_list.c — afs_alloc_addrlist cap (line 66–67) C11 §6.5.7 ¶3 — undefined behavior for shift count ≥ width of promoted left operand </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/5/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/5/</guid>
      <description>CVE Candidate: Missing max_entries Validation in BPF Map-in-Map Allows Security Policy Bypass and Spectre Mitigation Undermining Summary bpf_map_meta_equal() in kernel/bpf/map_in_map.c validates a candidate inner map against its prototype when a user calls bpf_map_update_elem on an ARRAY_OF_MAPS or HASH_OF_MAPS outer map. The function checks map_type, key_size, value_size, and map_flags — but omits max_entries. An unprivileged user (with CAP_BPF or a privileged fd to the outer map) can therefore insert an inner map whose max_entries differs arbitrarily from the prototype, violating the invariants the BPF verifier relied on when it analysed the program at load time.
The sample samples/bpf/test_map_in_map.bpf.c is the canonical exerciser of this path and makes the impact concrete.
Affected Files File Location Role kernel/bpf/map_in_map.c bpf_map_meta_equal(), line 83 Missing check kernel/bpf/map_in_map.c bpf_map_fd_get_ptr(), line 94 Calls the flawed comparator samples/bpf/test_map_in_map.bpf.c all BPF program whose verifier assumptions are violated Root Cause /* kernel/bpf/map_in_map.c – prototype is built correctly … */ inner_map_meta-&amp;gt;max_entries = inner_map-&amp;gt;max_entries; // line 40 – saved inner_array_meta-&amp;gt;index_mask = inner_array-&amp;gt;index_mask; // line 69 – saved /* … but equality check ignores max_entries entirely: */ bool bpf_map_meta_equal(const struct bpf_map *meta0, const struct bpf_map *meta1) { return meta0-&amp;gt;map_type == meta1-&amp;gt;map_type &amp;amp;&amp;amp; meta0-&amp;gt;key_size == meta1-&amp;gt;key_size &amp;amp;&amp;amp; meta0-&amp;gt;value_size== meta1-&amp;gt;value_size &amp;amp;&amp;amp; meta0-&amp;gt;map_flags == meta1-&amp;gt;map_flags &amp;amp;&amp;amp; btf_record_equal(meta0-&amp;gt;record, meta1-&amp;gt;record); /* ← max_entries NEVER compared */ } bpf_map_fd_get_ptr() calls inner_map_meta-&amp;gt;ops-&amp;gt;map_meta_equal(inner_map_meta, inner_map) and accepts the candidate if it returns true. Because max_entries is not part of that test, any array/hash map with matching type/key/value can be swapped in.
Impact 1. BPF Verifier Invariant Violation → Spectre v1 Mitigation Bypass The verifier records the prototype&amp;rsquo;s max_entries and index_mask in inner_map_meta (stored in outer_map-&amp;gt;inner_map_meta). For the JIT-compiled inline path (array_map_gen_lookup, arraymap.c:220) the bounds-check instruction and the masking instruction are emitted once at JIT time using the prototype&amp;rsquo;s values:
*insn++ = BPF_JMP_IMM(BPF_JGE, ret, map-&amp;gt;max_entries, 4); // prototype max *insn++ = BPF_ALU32_IMM(BPF_AND, ret, array-&amp;gt;index_mask); // prototype mask If a substituted inner map has a larger max_entries (e.g. 2 × MAX_NR_PORTS = 131072) its index_mask is 0x1FFFF rather than the prototype&amp;rsquo;s 0xFFFF. After substitution the runtime pointer arithmetic uses the actual (larger) index_mask of the swapped-in map (arraymap.c:175):
return array-&amp;gt;value + (u64)array-&amp;gt;elem_size * (index &amp;amp; array-&amp;gt;index_mask); Spectre v1 protection relies on the AND mask matching the conditional branch. With mismatched masks the speculative window is wider than the verifier intended, re-opening the Spectre gadget that bypass_spec_v1 was supposed to close.
2. Silent Security-Policy Bypass test_map_in_map.bpf.c maps TCP ports to policy values. The outer map a_of_port_a (type ARRAY_OF_MAPS) has max_entries = MAX_NR_PORTS = 65536; its prototype inner map inner_a also has max_entries = 65536.
Attack:
Open the fd for a_of_port_a. Create a new BPF_MAP_TYPE_ARRAY map with matching key_size=4, value_size=4, but max_entries=1. Call bpf(BPF_MAP_UPDATE_ELEM) to insert it at any port index. bpf_map_meta_equal accepts it. The running BPF program calls bpf_map_lookup_elem(inner_map, &amp;amp;port_key). The actual inner map&amp;rsquo;s array_map_lookup_elem checks index &amp;gt;= array-&amp;gt;map.max_entries (i.e. port_key &amp;gt;= 1): every port except 0 returns NULL. do_reg_lookup returns -ENOENT; the reg_result_h entry is written as &amp;ldquo;not found&amp;rdquo;. Any firewall or auditing decision downstream of reg_result_h now sees a false negative for every port except 0 — an attacker-controlled silent bypass.
3. Out-of-Bounds Read (larger inner map) Conversely, a substituted inner map with max_entries much larger than the prototype causes the BPF program to successfully look up indices the prototype never covered. The returned pointer points into the swapped-in map&amp;rsquo;s value array beyond the bounds assumed by the verifier, leaking uninitialised or attacker-controlled kernel heap data through the result maps (reg_result_h, inline_result_h).
Severity Critical — local privilege escalation / information disclosure / security-policy bypass.
Attribute Value Attack vector Local Privileges required CAP_BPF (or open fd to the outer map) User interaction None Confidentiality High (kernel heap leak) Integrity High (verifier invariant broken, Spectre mitigation undermined) Availability None Reproduction Sketch // 1. Load test_map_in_map BPF program — outer map fd is exposed via bpffs int outer_fd = bpf_obj_get(&amp;#34;/sys/fs/bpf/a_of_port_a&amp;#34;); // 2. Create undersized inner map (same type/key/value, wrong max_entries) int inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, NULL, sizeof(u32), sizeof(int), 1, // ← max_entries = 1, prototype expects 65536 NULL); // 3. Insert at port 80 — bpf_map_meta_equal() accepts it u32 key = 80; bpf_map_update_elem(outer_fd, &amp;amp;key, &amp;amp;inner_fd, BPF_ANY); // 4. Connect to dead:beef::80 — BPF probe fires, inner lookup returns ENOENT // for port 80, silently bypassing any policy recorded there. Fix Add max_entries to the equality check:
bool bpf_map_meta_equal(const struct bpf_map *meta0, const struct bpf_map *meta1) { return meta0-&amp;gt;map_type == meta1-&amp;gt;map_type &amp;amp;&amp;amp; meta0-&amp;gt;key_size == meta1-&amp;gt;key_size &amp;amp;&amp;amp; meta0-&amp;gt;value_size == meta1-&amp;gt;value_size &amp;amp;&amp;amp; meta0-&amp;gt;map_flags == meta1-&amp;gt;map_flags &amp;amp;&amp;amp; meta0-&amp;gt;max_entries== meta1-&amp;gt;max_entries &amp;amp;&amp;amp; /* ← add this */ btf_record_equal(meta0-&amp;gt;record, meta1-&amp;gt;record); } References kernel/bpf/map_in_map.c — bpf_map_meta_equal() (line 83), bpf_map_fd_get_ptr() (line 94), bpf_map_meta_alloc() (line 10) kernel/bpf/arraymap.c — array_map_gen_lookup() (line 220), array_map_lookup_elem() (line 170) samples/bpf/test_map_in_map.bpf.c — affected BPF program Related prior art: CVE-2021-20268 (BPF map reference count UAF), CVE-2022-2905 (BPF array OOB read) </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/6/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/6/</guid>
      <description>Vulnerability Report: fs/9p/vfs_super.c Most Serious: Kernel Heap Memory Leak in v9fs_init_fs_context Error Path File: fs/9p/vfs_super.c
Lines: 307–355
Severity: High (Kernel memory exhaustion / DoS)
Description v9fs_init_fs_context sets fc-&amp;gt;fs_private = ctx after both kstrdup calls succeed. If either allocation fails, the function jumps to the error: label — but fc-&amp;gt;fs_private has never been written. The cleanup callback v9fs_free_fc is registered in fc-&amp;gt;ops-&amp;gt;free, but fc-&amp;gt;ops is also set after the kstrdups:
// fs/9p/vfs_super.c:319-354 ctx-&amp;gt;session_opts.uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL); if (!ctx-&amp;gt;session_opts.uname) goto error; // &amp;lt;-- jumps here ctx-&amp;gt;session_opts.aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL); if (!ctx-&amp;gt;session_opts.aname) goto error; // &amp;lt;-- or here ... fc-&amp;gt;ops = &amp;amp;v9fs_context_ops; // never reached on error fc-&amp;gt;fs_private = ctx; // never reached on error return 0; error: fc-&amp;gt;need_free = 1; return -ENOMEM; v9fs_free_fc (line 283) starts with:
struct v9fs_context *ctx = fc-&amp;gt;fs_private; if (!ctx) return; // always taken — ctx is leaked The kernel&amp;rsquo;s put_fs_context path only invokes fc-&amp;gt;ops-&amp;gt;free when fc-&amp;gt;ops != NULL. Since fc-&amp;gt;ops is not set on the error path, even the guard in put_fs_context (fc-&amp;gt;need_free &amp;amp;&amp;amp; fc-&amp;gt;ops &amp;amp;&amp;amp; fc-&amp;gt;ops-&amp;gt;free) evaluates to false. The result:
The v9fs_context allocation (ctx) is never freed. If the uname kstrdup succeeded before aname failed, ctx-&amp;gt;session_opts.uname is also never freed. Impact An unprivileged local user with CAP_SYS_ADMIN (or in a user namespace that allows mounting) can trigger repeated 9p mount attempts under artificial memory pressure (e.g., using userfaultfd or resource limits to cause kstrdup to fail). Each failed attempt leaks at least sizeof(v9fs_context) bytes of kernel slab memory. Sufficient repetition exhausts kernel heap, causing a system-wide denial of service.
Root Cause fc-&amp;gt;fs_private and fc-&amp;gt;ops are assigned too late — only after all infallible allocations have succeeded. The error label does not have access to ctx for cleanup, and no explicit kfree(ctx) is present.
Fix Move fc-&amp;gt;fs_private = ctx immediately after kzalloc succeeds, so the existing v9fs_free_fc callback can handle partial-initialization cleanup:
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; fc-&amp;gt;ops = &amp;amp;v9fs_context_ops; // register early fc-&amp;gt;fs_private = ctx; // allow free callback to find ctx ctx-&amp;gt;session_opts.uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL); if (!ctx-&amp;gt;session_opts.uname) return -ENOMEM; // v9fs_free_fc will clean up ctx ... Secondary: Undefined Behavior in v9fs_fill_super Block Size Calculation File: fs/9p/vfs_super.c
Lines: 40–41
Severity: Medium (implementation-defined / UB, unlikely to be exploitable directly)
Description // line 40-41 sb-&amp;gt;s_blocksize_bits = fls(v9ses-&amp;gt;maxdata - 1); sb-&amp;gt;s_blocksize = 1 &amp;lt;&amp;lt; sb-&amp;gt;s_blocksize_bits; maxdata is unsigned int. The validated upper bound for the 9p negotiated msize is INT_MAX (line 346 of v9fs.c), giving a maximum maxdata of INT_MAX - P9_IOHDRSZ = 2147483623. Then:
fls(2147483622) = 31 s_blocksize_bits = 31 1 &amp;lt;&amp;lt; 31 — shifting a signed int value 1 left by 31 bits sets the sign bit, which is undefined behavior under the C standard (C11 §6.5.7). On x86-64 GCC this evaluates to 0x80000000 in practice, but UB allows a compiler to produce any value (including zero, which would cascade into divide-by-zero in block I/O paths).
Fix Use an unsigned shift: sb-&amp;gt;s_blocksize = 1UL &amp;lt;&amp;lt; sb-&amp;gt;s_blocksize_bits;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/7/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/7/</guid>
      <description>UAF: Global Static Jack GPIO data Pointer in tegra_asoc_machine.c Summary A use-after-free vulnerability exists in the Tegra ASoC machine driver due to storing a per-device machine pointer in a module-level static snd_soc_jack_gpio structure. The pointer can be dereferenced via a GPIO interrupt handler after the device-managed memory has been freed.
Vulnerability Details File: sound/soc/tegra/tegra_asoc_machine.c
Root Cause Three jack GPIO structures are declared as module-level (file-scope) statics:
// lines 31–35 static struct snd_soc_jack_gpio tegra_machine_hp_jack_gpio = { … }; // lines 46–50 static struct snd_soc_jack_gpio tegra_machine_headset_jack_gpio = { … }; // lines 72–76 static struct snd_soc_jack_gpio tegra_machine_mic_jack_gpio = { … }; During card initialization (tegra_asoc_machine_init, line 139), the per-device machine pointer — allocated with devm_kzalloc — is written into the global structure:
// line 202 tegra_machine_mic_jack_gpio.data = machine; // line 203 tegra_machine_mic_jack_gpio.desc = machine-&amp;gt;gpiod_mic_det; The .data field is then consumed by the GPIO interrupt callback:
// lines 53–63 static int coupled_mic_hp_check(void *data) { struct tegra_machine *machine = (struct tegra_machine *)data; if (gpiod_get_value_cansleep(machine-&amp;gt;gpiod_hp_det) &amp;amp;&amp;amp; // UAF dereference gpiod_get_value_cansleep(machine-&amp;gt;gpiod_mic_det)) return SND_JACK_MICROPHONE; return 0; } Use-After-Free Path Device probes: devm_kzalloc allocates machine; tegra_machine_mic_jack_gpio.data = machine. snd_soc_jack_add_gpios (line 211) registers a GPIO IRQ that calls coupled_mic_hp_check. Device is unbound (e.g., driver module removed, or hotplug event). devm cleanup runs in LIFO order: devm_snd_soc_register_card fires first, beginning card teardown and jack GPIO IRQ removal. However, the IRQ is not guaranteed to be fully quiesced before the devm_kzalloc-managed machine block is freed. A concurrent or late-arriving GPIO interrupt invokes coupled_mic_hp_check(data) where data points to the now-freed machine struct → use-after-free. Second UAF Path: Multiple Device Instances Because all global statics are shared across all driver instances:
Device A probes: tegra_machine_mic_jack_gpio.data = machine_A. Device B probes: tegra_machine_mic_jack_gpio.data = machine_B — silently overwrites. Device B is unbound: machine_B freed by devm. Device A&amp;rsquo;s GPIO interrupt fires → coupled_mic_hp_check uses freed machine_B → UAF. Neither instance is aware of the other; the shared global creates an implicit cross-device lifetime dependency with no synchronization.
Impact Type: Use-After-Free (CWE-416) in kernel interrupt context Severity: High — kernel memory corruption from interrupt context; exploitable for privilege escalation (ring 3 → ring 0) by a local unprivileged user who can trigger GPIO events (e.g., via a malicious peripheral or by scripting headphone insertion/removal while racing device unbind) Affected subsystem: ALSA/ASoC Tegra machine driver; impacts NVIDIA Tegra-based boards (Chromebooks, Jetson, Shield) running affected kernels Relevant Lines Line Code 24–76 Module-level static jack and jack-GPIO structures 53–63 coupled_mic_hp_check — dereferences .data as freed machine * 202–203 tegra_machine_mic_jack_gpio.data = machine — stores per-device ptr in global 163, 183 Similar pattern for hp_jack_gpio and headset_jack_gpio 436 machine = devm_kzalloc(...) — lifetime tied to device, not module Fix Replace the module-level static snd_soc_jack_gpio instances with per-device allocations (e.g., embed them in struct tegra_machine), so their lifetime matches the machine struct they reference. Ensure IRQ teardown is fully completed (e.g., via synchronize_irq) before the machine allocation is freed.</description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/8/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/8/</guid>
      <description>CVE Candidate: Kernel Divide-by-Zero in dac33_calculate_times via UTHR_FROM_PERIOD_SIZE File: sound/soc/codecs/tlv320dac33.c
Severity: High (Kernel Panic / Local DoS)
Type: Divide-by-Zero (CWE-369)
Vulnerability Description The macro UTHR_FROM_PERIOD_SIZE defined at line 47 contains an unconditional division whose denominator can reach zero under reachable conditions:
#define UTHR_FROM_PERIOD_SIZE(samples, playrate, burstrate) \ (((samples)*5000) / (((burstrate)*5000) / ((burstrate) - (playrate)))) The inner sub-expression (burstrate) - (playrate) is unsigned integer subtraction. When burstrate == playrate, this evaluates to zero, causing a kernel divide-by-zero exception (#DE fault on x86) — a hard kernel oops / panic.
Trigger Path The macro is invoked at dac33_calculate_times(), line 1099:
dac33-&amp;gt;uthr = UTHR_FROM_PERIOD_SIZE(period_size, rate, dac33-&amp;gt;burst_rate) + 9; dac33-&amp;gt;burst_rate is computed in dac33_hw_params() via:
#define CALC_BURST_RATE(bclkdiv, bclk_per_sample) \ (BURST_BASEFREQ_HZ / bclkdiv / bclk_per_sample) // BURST_BASEFREQ_HZ = 49152000 Concrete crash scenario Parameter Value Format SNDRV_PCM_FORMAT_S32_LE bclk_per_sample 64 burst_bclkdiv 16 (valid u8, set via platform data) Computed burst_rate 49152000 / 16 / 64 = 48000 Playback rate 48000 Hz With these values: burst_rate - playrate = 48000 - 48000 = 0.
The expression (burstrate)*5000 / 0 executes in kernel context, producing a divide-by-zero trap.
For 16-bit format (bclk_per_sample = 32), the crash triggers at burst_bclkdiv = 32 with a 48000 Hz stream.
Control Flow to Reach the Bug User opens PCM device (ALSA) → sets FIFO Mode to Mode7 via kcontrol &amp;#34;FIFO Mode&amp;#34; (dac33_set_fifo_mode) → calls hw_params with rate=48000, format=S32_LE (dac33_hw_params) → starts stream (SNDRV_PCM_TRIGGER_START) (dac33_pcm_trigger) → schedules dac33_work → dac33_prefill_handler → calls dac33_playback_event (SND_SOC_DAPM_PRE_PMU) → dac33_calculate_times() → UTHR_FROM_PERIOD_SIZE(period_size, 48000, 48000) → (burst_rate - playrate) == 0 → DIVIDE BY ZERO ← CRASH Any local user with access to the audio device (/dev/snd/pcmC0D0p or similar) can trigger this.
Secondary Issue: Integer Overflow in Same Macro Even when burstrate != playrate, the expression (samples) * 5000 operates on unsigned int period_size (line 1064 stores snd_pcm_uframes_t — an unsigned long — into an unsigned int, truncating on 64-bit). When a userspace process sets period_size &amp;gt; 858,993 samples, period_size * 5000 silently overflows 32-bit unsigned, producing a wrapped-around (smaller) uthr value and corrupting FIFO threshold configuration. The subsequent clamping at lines 1101–1104 partially mitigates memory safety impact but does not prevent the wrong FIFO threshold being programmed into hardware.
Root Cause No guard against burst_rate == playrate before the division:
// Missing check: if (dac33-&amp;gt;burst_rate &amp;lt;= rate) { dev_err(...); return -EINVAL; } Impact Kernel panic (oops): exploitable by any local user with access to the ALSA audio device, achieving an unprivileged local denial-of-service. On systems where the panic handler reboots the machine, this is a reliable reboot primitive. If ALSA device nodes are accessible to an untrusted container or VM guest, this may cross privilege boundaries. Suggested Fix Add a pre-check in dac33_calculate_times() before invoking the macro:
case DAC33_FIFO_MODE7: if (dac33-&amp;gt;burst_rate &amp;lt;= rate) { dev_err(component-&amp;gt;dev, &amp;#34;burst_rate (%u) must exceed playback rate (%u)\n&amp;#34;, dac33-&amp;gt;burst_rate, rate); return; } dac33-&amp;gt;uthr = UTHR_FROM_PERIOD_SIZE(period_size, rate, dac33-&amp;gt;burst_rate) + 9; Additionally, fix the snd_pcm_uframes_t → unsigned int truncation:
- unsigned int period_size = substream-&amp;gt;runtime-&amp;gt;period_size; + snd_pcm_uframes_t period_size = substream-&amp;gt;runtime-&amp;gt;period_size; </description>
    </item>
    
    <item>
      <title></title>
      <link>https://psn.af/k/9/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://psn.af/k/9/</guid>
      <description>CVE Candidate: Integer Underflow in crypto_gcm_decrypt Leading to Heap OOB Read/Write File: crypto/gcm.c
Severity: Critical
Type: CWE-191 (Integer Underflow) → CWE-125/787 (Out-of-Bounds Read/Write)
Vulnerability Description In crypto_gcm_decrypt() (line 495–513), the ciphertext length is computed by subtracting authsize from req-&amp;gt;cryptlen using unsigned integer arithmetic with no prior bounds check:
// crypto/gcm.c:495 static int crypto_gcm_decrypt(struct aead_request *req) { struct crypto_aead *aead = crypto_aead_reqtfm(req); ... unsigned int authsize = crypto_aead_authsize(aead); unsigned int cryptlen = req-&amp;gt;cryptlen; // line 501 ... cryptlen -= authsize; // line 504 — UNDERFLOW HERE ... gctx-&amp;gt;cryptlen = cryptlen; // line 509 — stored for later use ... return gcm_hash(req, flags); } If a caller supplies req-&amp;gt;cryptlen &amp;lt; authsize (e.g., req-&amp;gt;cryptlen = 0, authsize = 16), the subtraction wraps around to 0xFFFFFFF0 (~4 GiB). This underflowed value is then propagated through the entire decrypt pipeline.
Exploit Chain Step 1 — Underflow stored as gctx-&amp;gt;cryptlen gctx-&amp;gt;cryptlen = cryptlen; // now ~0xFFFFFFF0 Step 2 — Out-of-bounds scatter-gather walk during GHASH gcm_hash() → gcm_hash_init_continue() → gcm_hash_assoc_remain_continue() →
gcm_hash_update(req, gcm_hash_crypt_done, gctx-&amp;gt;src, gctx-&amp;gt;cryptlen, flags)
// gcm_hash_update (line 198): ahash_request_set_crypt(ahreq, src, NULL, len); // len ≈ 4 GiB return crypto_ahash_update(ahreq); The GHASH update walks ~4 GiB through scatter-gather lists that hold only a few bytes of actual ciphertext. The scatterwalk iterates into unallocated or unrelated kernel memory, yielding a kernel heap out-of-bounds read. On kernels without SMAP/KASAN catching the walk, this can leak cryptographic material, kernel pointers, or other sensitive data from adjacent slab objects.
Step 3 — Integer wraparound neutralizes (but doesn&amp;rsquo;t fully save) the skcipher length After hashing, gcm_dec_hash_continue() calls:
crypto_gcm_init_crypt(req, gctx-&amp;gt;cryptlen); Inside crypto_gcm_init_crypt (line 185):
skcipher_request_set_crypt(skreq, pctx-&amp;gt;src, dst, cryptlen + sizeof(pctx-&amp;gt;auth_tag), // 0xFFFFFFF0 + 16 = 0x00000000 pctx-&amp;gt;iv); The skcipher receives length 0, so decryption encrypts nothing — but the OOB scatterwalk in Step 2 has already occurred.
Step 4 — Second underflow in crypto_gcm_verify // line 466 unsigned int cryptlen = req-&amp;gt;cryptlen - authsize; // same underflow scatterwalk_map_and_copy is then called with req-&amp;gt;assoclen + cryptlen as an offset — a huge offset into the destination scatter list — potentially enabling an out-of-bounds write of the computed auth tag to an arbitrary kernel memory address, depending on the scatter-gather layout.
Impact Impact Detail Kernel heap OOB read ~4 GiB scatter-walk leaks adjacent slab contents (keys, pointers, credentials) Kernel heap OOB write scatterwalk_map_and_copy in crypto_gcm_verify writes 16-byte auth tag at attacker-controlled offset Privilege escalation OOB write to controlled offset may overwrite kernel structures (e.g., cred, function pointers) Information disclosure Auth tag computed over attacker-readable OOB memory; observable via timing or error codes Affected Call Sites Any subsystem that calls crypto_aead_decrypt() on a GCM/RFC 4106/RFC 4543 transform with an unsanitized cryptlen is affected:
IPsec (net/xfrm/, net/ipv4/esp.c, net/ipv6/esp6.c) — passes skb data lengths that can be attacker-influenced via crafted ESP packets TLS (net/tls/) — tls_do_decryption() sets aead_request lengths from network data AF_ALG socket interface — userspace can directly supply arbitrary cryptlen via sendmsg/recvmsg The AF_ALG path is the most directly reachable from unprivileged userspace (CAP_NET_ADMIN is not required to open an AF_ALG socket for existing algorithms).
Proof-of-Concept (Trigger, No Exploit) // Trigger via AF_ALG from userspace (no privileges needed) int sock = socket(AF_ALG, SOCK_SEQPACKET, 0); struct sockaddr_alg sa = { .salg_family = AF_ALG, .salg_type = &amp;#34;aead&amp;#34;, .salg_name = &amp;#34;gcm(aes)&amp;#34;, }; bind(sock, (struct sockaddr *)&amp;amp;sa, sizeof(sa)); setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, 16); setsockopt(sock, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 16); int fd = accept(sock, NULL, 0); // Send decrypt request with cryptlen=0 (&amp;lt; authsize=16) // assoclen=0, cryptlen=0 → triggers underflow in crypto_gcm_decrypt struct msghdr msg = { ... }; // cryptlen field = 0 recvmsg(fd, &amp;amp;msg, 0); // kernel underflows, OOB walk begins Fix Add an explicit length validation at the top of crypto_gcm_decrypt before the subtraction:
static int crypto_gcm_decrypt(struct aead_request *req) { struct crypto_aead *aead = crypto_aead_reqtfm(req); unsigned int authsize = crypto_aead_authsize(aead); unsigned int cryptlen = req-&amp;gt;cryptlen; + if (cryptlen &amp;lt; authsize) + return -EINVAL; cryptlen -= authsize; ... } The same guard is needed in crypto_gcm_verify() (line 466) and crypto_rfc4543_crypt() (line 931) where analogous unsigned subtractions occur without prior validation.
References crypto/gcm.c lines 495–513 (crypto_gcm_decrypt) crypto/gcm.c lines 459–472 (crypto_gcm_verify) crypto/gcm.c lines 920–948 (crypto_rfc4543_crypt) CWE-191: Integer Underflow CWE-125: Out-of-bounds Read CWE-787: Out-of-bounds Write </description>
    </item>
    
  </channel>
</rss>
