A-Dada-crab
### Kernel Version Android 15 / Linux 6.6. This report is based on a static review of the official `gki-android15-6.6` SUSFS patch. It is not specific to a single device kernel build. ### Kernel Source/Download Official repository and affected branch: https://gitlab.com/simonpunk/susfs4ksu/-/tree/gki-android15-6.6 Reviewed branch HEAD: ```text d4f50f6d7cfa21fef497c48cdeffa3b50952bf56 ``` ### SUSFS Version SUSFS kernel interface: `v2.2.0` Affected configuration: ```text CONFIG_KSU_SUSFS_OPEN_REDIRECT=y ``` ### SUSFS Module Version Not applicable. The issue is in the kernel-side OPEN_REDIRECT handling for `/proc/*/maps` output. ### SUSFS Settings The affected path is reachable when: - an OPEN_REDIRECT rule applies to the process; - the process has a file-backed VMA whose inode is marked for OPEN_REDIRECT; - `/proc/<pid>/maps` traverses that matching VMA. ### SUSFS Logs Not available. The pointer ownership error was confirmed through static source review. I have not measured the leak rate dynamically on a device or test kernel. ### What happened? The OPEN_REDIRECT helper used by `show_map_vma()` attempts to allocate and return a spoofed pathname: ```c int susfs_open_redirect_spoof_show_map_vma( struct inode *inode, unsigned long *out_ino, dev_t *out_dev, char *spoofed_name) ``` On a matching entry, the helper allocates a pathname buffer: ```c spoofed_name = kzalloc(SUSFS_MAX_LEN_PATHNAME, GFP_KERNEL); ``` The reviewed source defines: ```c #define SUSFS_MAX_LEN_PATHNAME 256 ``` The helper then copies the intended pathname into that buffer: ```c strscpy( spoofed_name, entry->info.redirected_pathname, SUSFS_MAX_LEN_PATHNAME - 1); ``` However, `spoofed_name` is passed by value. Assigning the result of `kzalloc()` only changes the helper's local pointer copy. The allocated address is not returned to the caller. The caller initializes: ```c char *spoofed_redirected_name = NULL; ``` and calls the helper as: ```c susfs_open_redirect_spoof_show_map_vma( inode, &ino, &dev, spoofed_redirected_name) ``` After the helper returns successfully, the caller's `spoofed_redirected_name` is still `NULL`. The caller contains the intended output and cleanup path: ```c if (spoofed_redirected_name) { seq_pad(m, ' '); seq_puts(m, spoofed_redirected_name); seq_putc(m, '\n'); kfree(spoofed_redirected_name); return; } ``` Because the pointer remains `NULL`, this block does not run. The allocation becomes unreachable and cannot be freed. ### Effective sequence ```text show_map_vma() initializes spoofed_redirected_name to NULL helper receives a copy of that NULL pointer helper allocates SUSFS_MAX_LEN_PATHNAME bytes helper stores the address only in its local pointer helper updates out_ino and out_dev helper returns success caller still sees spoofed_redirected_name == NULL allocated pathname is neither printed nor freed normal pathname output continues ``` ### Expected behavior For every successful OPEN_REDIRECT maps lookup: - the spoofed pathname should be returned to the caller; - the caller should print the spoofed pathname; - the allocation should be freed exactly once after output; - the displayed pathname should remain consistent with the spoofed inode and device values. ### Actual behavior For every successful matching lookup where `kzalloc()` succeeds: - one `SUSFS_MAX_LEN_PATHNAME` allocation becomes unreachable; - the reviewed configuration therefore leaks 256 bytes per matching VMA; - the intended pathname is not returned or printed; - `out_ino` and `out_dev` are still modified; - the pathname may remain inconsistent with the spoofed inode and device values. A process with a persistent matching file-backed VMA can cause the same allocation path to run whenever its maps output is traversed. Repeated reads of `/proc/self/maps` may therefore accumulate kernel heap allocations until reboot. The practical memory-pressure impact depends on the number of matching VMAs and the rate of maps traversal. This report does not claim that a particular device can be forced into OOM within a specific time. ### Additional SRCU cleanup issue The same helper acquires the OPEN_REDIRECT SRCU read lock before checking `spoofed_name`: ```c int srcu_idx = srcu_read_lock(&susfs_srcu_open_redirect); if (spoofed_name) { SUSFS_LOGE("spoofed_name must be NULL first!\n"); return -EINVAL; } ``` This early return does not call: ```c srcu_read_unlock(&susfs_srcu_open_redirect, srcu_idx); ``` With the current caller, `spoofed_name` is initialized to `NULL`, so this branch appears unreachable through the reviewed call site. However, changing the output parameter interface without also fixing this condition could make the missing unlock reachable. ### Potential impact The confirmed effects are: - a 256-byte kernel heap leak for every successful matching lookup; - OPEN_REDIRECT pathname spoofing failure in `/proc/*/maps`; - possible inconsistency between the displayed pathname and the spoofed inode/device values. Repeated maps traversal can increase the leak over time. This report does not claim privilege escalation or a confirmed device OOM. ### Suggested fix The helper should use an explicit output parameter or a caller-owned buffer. For example, conceptually: ```c int susfs_open_redirect_spoof_show_map_vma( struct inode *inode, unsigned long *out_ino, dev_t *out_dev, char **out_spoofed_name) ``` The caller would pass: ```c &spoofed_redirected_name ``` The implementation should then: - validate that `out_spoofed_name` is not `NULL`; - require `*out_spoofed_name` to be `NULL` on entry; - allocate into a local pointer; - assign it to `*out_spoofed_name` only on success; - free the local allocation on every failure after allocation; - define that the caller owns and frees the returned allocation; - release the SRCU read lock on every return path. A caller-provided fixed-size buffer would also avoid allocating kernel memory during every maps traversal. ### Suggested regression test On a disposable test kernel: 1. Register an OPEN_REDIRECT rule for a regular file that can be memory-mapped. 2. From an eligible process, open and map the redirected file. 3. Confirm that the process has at least one matching file-backed VMA. 4. Read `/proc/self/maps` repeatedly. 5. Use kernel leak instrumentation or allocation tracing to check allocation and free counts. 6. Verify that the fixed implementation does not accumulate pathname allocations. 7. Verify that the expected spoofed pathname is printed. 8. Verify that the pathname, inode and device values are mutually consistent. 9. Exercise validation and allocation-failure paths and confirm that no SRCU read lock remains held. ### Validation status Confirmed by static review of: ```text Repository: https://gitlab.com/simonpunk/susfs4ksu Branch: gki-android15-6.6 Commit: d4f50f6d7cfa21fef497c48cdeffa3b50952bf56 SUSFS kernel interface: v2.2.0 Allocation size: SUSFS_MAX_LEN_PATHNAME = 256 ``` No dynamic leak measurement was attempted because no disposable test device or VM was used.