Audit the following candidate code to determine whether it presents a real vulnerability.
This sample may come from a noisy retrieval or transformation stage, so reason from the supplied code and context rather than assuming the label.

Repository: git.kernel.org/pub/scm/linux/kernel/git/stable/linux
Language: C
File: fs/notify/mark.c
Function: fsnotify_put_inode_ref

Call path: fsnotify_put_mark (fs/notify/mark.c) → fsnotify_drop_object (fs/notify/mark.c) → fsnotify_put_inode_ref (fs/notify/mark.c) → fsnotify_put_sb_watched_objects (fs/notify/mark.c) → generic_shutdown_super (fs/super.c)

### Primary Function

```c
static void fsnotify_put_sb_watched_objects(struct super_block *sb)
{
	atomic_long_t *watched_objects = fsnotify_sb_watched_objects(sb);

	/* the superblock can go away after this decrement */
	if (atomic_long_dec_and_test(watched_objects))
		wake_up_var(watched_objects);
}

static void fsnotify_get_sb_watched_objects(struct super_block *sb)
{
	atomic_long_inc(fsnotify_sb_watched_objects(sb));
}

static void fsnotify_get_inode_ref(struct inode *inode)
{
	ihold(inode);
	fsnotify_get_sb_watched_objects(inode->i_sb);
}

static void fsnotify_put_inode_ref(struct inode *inode)
{
	/* read ->i_sb before the inode can go away */
	struct super_block *sb = inode->i_sb;

	iput(inode);
	fsnotify_put_sb_watched_objects(sb);
}
```

### Cross-File Context

[fsnotify_put_sb_watched_objects — callee — fs/notify/mark.c:139-147]
```c
static void fsnotify_put_sb_watched_objects(struct super_block *sb)
{
	atomic_long_t *watched_objects = fsnotify_sb_watched_objects(sb);

	/* the superblock can go away after this decrement */
	if (atomic_long_dec_and_test(watched_objects))
		wake_up_var(watched_objects);
}
```

[fsnotify_get_sb_watched_objects — function — fs/notify/mark.c:134-137]
```c
static void fsnotify_get_sb_watched_objects(struct super_block *sb)
{
	atomic_long_inc(fsnotify_sb_watched_objects(sb));
}
```

[fsnotify_get_inode_ref — function — fs/notify/mark.c:145-150]
```c
static void fsnotify_get_inode_ref(struct inode *inode)
{
	ihold(inode);
	fsnotify_get_sb_watched_objects(inode->i_sb);
}
```

[fsnotify_drop_object — caller — fs/notify/mark.c:370-377]
```c
static void fsnotify_drop_object(unsigned int type, void *objp)
{
	if (!objp)
		return;
	/* Currently only inode references are passed to be dropped */
	if (WARN_ON_ONCE(type != FSNOTIFY_OBJ_TYPE_INODE))
		return;
	fsnotify_put_inode_ref(objp);
}
```

[fsnotify_sb_watched_objects — function — include/linux/fsnotify_backend.h:507-509]
```c
static inline atomic_long_t *fsnotify_sb_watched_objects(struct super_block *sb)
{
	return &fsnotify_sb_info(sb)->watched_objects[0];
}
```

[fsnotify_sb_info — struct — include/linux/fsnotify_backend.h:485-496]
```c
struct fsnotify_sb_info {
	struct fsnotify_mark_connector __rcu *sb_marks;
	atomic_long_t watched_objects[__FSNOTIFY_PRIO_NUM];
};
```

[__FSNOTIFY_PRIO_NUM — constant — include/linux/fsnotify_backend.h:187]
__FSNOTIFY_PRIO_NUM → 3  (include/linux/fsnotify_backend.h:187)

[fsnotify_group_prio — enum — include/linux/fsnotify_backend.h:183-188]
enum fsnotify_group_prio { FSNOTIFY_PRIO_NORMAL = 0, FSNOTIFY_PRIO_CONTENT, FSNOTIFY_PRIO_PRE_CONTENT, __FSNOTIFY_PRIO_NUM };

Control flow: fsnotify_put_inode_ref receives an inode pointer and executes a linear sequence: (1) reads inode->i_sb into local variable sb before any cleanup, (2) calls iput(inode) to drop the inode reference which may trigger inode deallocation, (3) calls fsnotify_put_sb_watched_objects(sb) to decrement the superblock's watched_objects atomic counter. Inside fsnotify_put_sb_watched_objects, if the counter reaches zero, it calls wake_up_var on the counter to wake waiters that may be blocking on superblock cleanup. The caller fsnotify_drop_object validates the object type and forwards the inode to fsnotify_put_inode_ref.

Data flow: The inode pointer enters fsnotify_put_inode_ref as the sole parameter. The superblock pointer is extracted from inode->i_sb and stored in local variable sb before iput(inode) is called. iput(inode) drops the inode reference count and may eventually free the inode structure. The sb pointer is then passed to fsnotify_put_sb_watched_objects, which accesses sb to locate the watched_objects atomic counter, decrements it, and conditionally wakes waiters. The pairing function fsnotify_get_inode_ref acquires references in the opposite order (inode hold then sb counter increment), ensuring balanced reference counting.

Verdict: Not Vulnerable

Reviewed region:
[fs/notify/mark.c]
```c
static void fsnotify_put_sb_watched_objects(struct super_block *sb)
{
	atomic_long_t *watched_objects = fsnotify_sb_watched_objects(sb);

	/* the superblock can go away after this decrement */
	if (atomic_long_dec_and_test(watched_objects))
		wake_up_var(watched_objects);
}

static void fsnotify_get_sb_watched_objects(struct super_block *sb)
{
	atomic_long_inc(fsnotify_sb_watched_objects(sb));
}

static void fsnotify_get_inode_ref(struct inode *inode)
{
	ihold(inode);
	fsnotify_get_sb_watched_objects(inode->i_sb);
}

static void fsnotify_put_inode_ref(struct inode *inode)
{
	/* read ->i_sb before the inode can go away */
	struct super_block *sb = inode->i_sb;

	iput(inode);
	fsnotify_put_sb_watched_objects(sb);
}
```

Why it is not vulnerable: The code correctly captures the superblock pointer (sb = inode->i_sb) before calling iput(inode), as explicitly noted by the comment 'read ->i_sb before the inode can go away'. Since iput(inode) may trigger inode deallocation which could cascade to superblock deallocation, reading inode->i_sb after iput would be a use-after-free. The current ordering ensures sb remains valid for the subsequent fsnotify_put_sb_watched_objects(sb) call, which only accesses the watched_objects array within the superblock. The atomic counter in fsnotify_put_sb_watched_objects protects against premature superblock deallocation by coordinating with waiters that hold the superblock.

Security invariants:
- The superblock pointer must be captured from inode->i_sb before any operation that may invalidate the inode structure. Enforced by: struct super_block *sb = inode->i_sb; appearing as the first statement before iput(inode).
- The watched_objects atomic counter must prevent the superblock from being freed while fsnotify_put_sb_watched_objects is accessing it. Enforced by: atomic_long_dec_and_test(watched_objects) which is paired with wake_up_var to coordinate with superblock teardown code that waits for this counter to reach zero.
- Reference acquisition and release must be symmetric to prevent counter leaks or premature deallocation. Enforced by: fsnotify_get_inode_ref calling ihold(inode) and fsnotify_get_sb_watched_objects, with fsnotify_put_inode_ref calling iput(inode) and fsnotify_put_sb_watched_objects in the corresponding reverse order.
- The superblock pointer must remain valid for the duration of the fsnotify_put_sb_watched_objects call. Enforced by: the sb variable being a local copy captured before iput(inode), and the atomic counter mechanism ensuring the superblock is not freed while this function is executing.

Invariant verification:
- Superblock pointer captured before potentially destructive inode release: holds=true. Evidence: struct super_block *sb = inode->i_sb; is the first operation in fsnotify_put_inode_ref, executed before iput(inode)
- Atomic counter protects against premature superblock deallocation during watched_objects access: holds=true. Evidence: atomic_long_dec_and_test(watched_objects) in fsnotify_put_sb_watched_objects decrements the counter atomically and only signals wake_up_var if counter reaches zero, coordinating with superblock teardown waiters
- Balanced reference counting between get and put operations: holds=true. Evidence: fsnotify_get_inode_ref pairs ihold(inode) + fsnotify_get_sb_watched_objects with fsnotify_put_inode_ref's iput(inode) + fsnotify_put_sb_watched_objects, ensuring matched increment/decrement of the watched_objects counter
- No dereference of inode after iput may have freed it: holds=true. Evidence: After iput(inode), only the locally-captured sb pointer is used in fsnotify_put_sb_watched_objects(sb); no further access to the inode occurs

Residual risk notes: The correctness of this pattern depends on all call paths that acquire inode references via fsnotify_get_inode_ref releasing them via fsnotify_put_inode_ref. If any code path acquires the sb watched_objects counter without holding a corresponding inode reference, or drops the inode reference without calling fsnotify_put_sb_watched_objects, the reference counting invariants could be violated. The behavior of the superblock teardown code that waits on the watched_objects counter is not visible in the supplied context, but the atomic coordination mechanism appears sound.

## Analysis

### Code Behavior

`fsnotify_put_inode_ref(inode)`:
1. Saves `sb = inode->i_sb` before `iput()`.
2. Calls `iput(inode)` — this may free the inode.
3. Calls `fsnotify_put_sb_watched_objects(sb)`, which dereferences `sb` to compute `&fsnotify_sb_info(sb)->watched_objects[0]`, decrements it, and possibly wakes a waiter.

The comment explicitly notes: "read ->i_sb before the inode can go away" and "the superblock can go away after this decrement".

### Suspected Issue

The concern is whether `sb` could be freed before `fsnotify_put_sb_watched_objects(sb)` runs. That is, after `iput()` releases the inode, could the superblock be freed by `generic_shutdown_super()` before we decrement `watched_objects`?

### Invariants That Prevent the Issue

The mechanism is the `watched_objects` counter itself:

- Each `fsnotify_get_inode_ref()` performs `ihold(inode)` **and** `atomic_long_inc(watched_objects)` on `inode->i_sb`.
- Each `fsnotify_put_inode_ref()` performs `iput(inode)` **and** `atomic_long_dec_and_test(watched_objects)`.
- The pairing is: while we hold an inode reference (between `ihold` and `iput`), we also contribute `+1` to `sb->s_fsnotify_info->watched_objects`.

`generic_shutdown_super()` waits for `watched_objects` to drop to zero before tearing down `fsnotify_sb_info`/freeing the superblock. Since this function's own contribution to `watched_objects` is still `+1` at the moment it dereferences `sb` (the decrement happens inside `fsnotify_put_sb_watched_objects` itself), the superblock cannot be torn down concurrently.

The ordering is:
1. `iput(inode)` — drops the inode ref, but **does not** drop the watched_objects ref. The superblock teardown is still blocked because `watched_objects > 0`.
2. `fsnotify_put_sb_watched_objects(sb)` — dereferences `sb` (still valid), then atomically decrements. Only after this decrement may the sb teardown proceed (and the comment warns that `sb` may go away after this point — which is fine because the function does not touch `sb` again, except for the `wake_up_var(watched_objects)` call.

### Residual concern: wake_up_var after free

After `atomic_long_dec_and_test` returns true, the code calls `wake_up_var(watched_objects)`. At this point the superblock teardown could in principle proceed and free `fsnotify_sb_info`, making `watched_objects` a dangling pointer used as a wait queue key.

However, this is the standard pattern for `wake_up_var`/`wait_var_event`: the waiter (in `generic_shutdown_super`) must use a synchronization primitive (typically holding a wait queue lock via `wait_var_event` infrastructure) that ensures the wake-up completes before the memory is freed. The wait_var bit-waitqueue uses a hashed global waitqueue, so `wake_up_var` does not actually dereference `watched_objects` — it only uses its address as a hash key. So even if the memory is freed concurrently, no use-after-free of the freed region occurs (the hash lookup uses the pointer value, not the pointee).

This is the documented, intentional pattern (and is why the comment says "the superblock can go away after this decrement" — the author has already considered this).

### Verdict

**Not a vulnerability.** The function correctly:
- Caches `sb` before `iput()` so it never dereferences `inode` after release.
- Holds the `watched_objects` reference across the `iput()`, which keeps the superblock alive until `atomic_long_dec_and_test` runs.
- Uses `wake_up_var` with the address as a hash key, which is safe after the memory may be freed.

The pairing of `ihold + atomic_long_inc` with `iput + atomic_long_dec_and_test` is symmetric and preserves the invariant that the superblock cannot be torn down while any code path is between these two operations.

Residual uncertainty: I have not seen the matching wait in `generic_shutdown_super()` nor the freeing site of `fsnotify_sb_info`; the safety argument above assumes the standard `wait_var_event` pattern. Given the explicit comments in the code, this is the intended design.

fs/notify/mark.c: fsnotify_put_inode_ref and fsnotify_put_sb_watched_objects

Not exploitable based on the supplied code. The function caches inode->i_sb before iput(), and the watched_objects counter held by this caller keeps the superblock alive until the atomic_long_dec_and_test, after which sb is no longer dereferenced.

fsnotify_put_inode_ref reads sb = inode->i_sb prior to iput(inode), so the freed inode is never re-dereferenced. The matching get path (fsnotify_get_inode_ref) bumps both ihold() and watched_objects, so each inode reference contributes a +1 on sb->s_fsnotify_info->watched_objects. generic_shutdown_super waits for watched_objects to reach zero before tearing down the fsnotify_sb_info/superblock, and our +1 is only released inside fsnotify_put_sb_watched_objects itself via atomic_long_dec_and_test. Therefore sb is still valid at the moment fsnotify_sb_watched_objects(sb) dereferences it. After the decrement, sb is no longer touched; wake_up_var() uses the address only as a hash key into a global bit-waitqueue and does not dereference the memory, so post-free races on the wait_var address are safe by the standard wait_var_event pattern.

For every fsnotify_get_inode_ref there is a paired fsnotify_put_inode_ref, keeping ihold/iput and atomic_long_inc/atomic_long_dec_and_test on watched_objects symmetric.

While an inode reference is held by fsnotify, the corresponding sb->s_fsnotify_info->watched_objects counter is >= 1.

generic_shutdown_super() blocks superblock teardown until watched_objects reaches zero (wait_var_event on watched_objects).

wake_up_var() uses the variable address as a hash key, not as a dereferenced pointer, so it is safe to call on memory that may be concurrently freed.

fsnotify_put_inode_ref saves sb before iput; fsnotify_put_sb_watched_objects dereferences sb only to compute &watched_objects[0] and then atomically decrements, after which sb is not referenced again. The +1 contributed by this caller is only released by the atomic_long_dec_and_test call itself, so sb cannot be reaped between iput and the decrement. The comments in the function explicitly document this ordering.

The matching wait_var_event in generic_shutdown_super and the freeing site of fsnotify_sb_info were not shown. The safety argument relies on the standard wait_var_event/wake_up_var contract; if the destruction path were changed to free fsnotify_sb_info before waiting on watched_objects, the invariant would be broken. No evidence of such a change is present here.

fsnotify_put_mark -> fsnotify_drop_object -> fsnotify_put_inode_ref -> fsnotify_put_sb_watched_objects (-> wake_up_var); generic_shutdown_super waits on watched_objects


System:
You are a senior application security auditor reviewing code for potential vulnerabilities.

You will be given a candidate function, repository and file metadata, a reduced call path, and resolved cross-file context such as helper functions, structs, macros, constants, and related definitions. Use the supplied context actively, but stay strictly grounded in what is actually shown.

Work from the supplied code and surrounding context as the primary evidence. Do not assume a bug exists just because the sample was retrieved.

Analyze both control flow and data flow. Trace the relevant execution paths and track security-relevant data through inputs, transformations, conditions, helper calls, outputs, and side effects.

Reason about attacker influence, identify relevant guards or missing guards, and explain exploitability or non-exploitability before reaching a verdict.

When the code is vulnerable, explain the vulnerable behavior, root cause, realistic attack preconditions, likely impact, and the most precise vulnerability classification justified by the supplied code.

When the code is not vulnerable, explain the concrete invariants and guards that prevent the suspected issue, and note any residual uncertainty that remains from the supplied context.

Keep the analysis concrete and code-grounded. Separate observed evidence from inferred conclusion.

Audit the following candidate code to determine whether it presents a real vulnerability.
This sample may come from a noisy retrieval or transformation stage, so reason from the supplied code and context rather than assuming the label.

Repository: https://github.com/torvalds/linux
Language: C
File: drivers/usb/gadget/configfs.c
Function: webusb_landingPage_store

Call path: sysfs_write (fs/sysfs/file.c) → configfs_write_file (fs/configfs/file.c) → webusb_landingPage_store (drivers/usb/gadget/configfs.c) → os_desc_qw_sign_store (drivers/usb/gadget/configfs.c)

### Primary Function

```c
static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
				     size_t len)
{
	struct gadget_info *gi = webusb_item_to_gadget_info(item);
	unsigned int bytes_to_strip = 0;
	int l = len;

	if (page[l - 1] == '\n') {
		--l;
		++bytes_to_strip;
	}

	if (l > sizeof(gi->landing_page)) {
		pr_err("webusb: landingPage URL too long\n");
		return -EINVAL;
	}

	// validation
	if (strncasecmp(page, "https://",  8) == 0)
		bytes_to_strip = 8;
	else if (strncasecmp(page, "http://", 7) == 0)
		bytes_to_strip = 7;
	else
		bytes_to_strip = 0;

	if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip) {
		pr_err("webusb: landingPage URL %d bytes too long for given URL scheme\n",
			l - U8_MAX + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH - bytes_to_strip);
		return -EINVAL;
	}

	mutex_lock(&gi->lock);
	// ensure 0 bytes are set, in case the new landing page is shorter then the old one.
	memcpy_and_pad(gi->landing_page, sizeof(gi->landing_page), page, l, 0);
	mutex_unlock(&gi->lock);

	return len;
}
```

### Cross-File Context

[webusb_item_to_gadget_info — function — drivers/usb/gadget/configfs.c:999]
```c
static inline struct gadget_info *webusb_item_to_gadget_info(
		struct config_item *item)
{
	return container_of(to_config_group(item),
			struct gadget_info, webusb_group);
}
```

[os_desc_item_to_gadget_info — function — drivers/usb/gadget/configfs.c:1113]
```c
static inline struct gadget_info *os_desc_item_to_gadget_info(
		struct config_item *item)
{
	return container_of(to_config_group(item),
			struct gadget_info, os_desc_group);
}
```

[gadget_info — struct — drivers/usb/gadget/configfs.c:38]
```c
struct gadget_info {
	struct config_group group;
	struct config_group functions_group;
	struct config_group configs_group;
	struct config_group strings_group;
	struct config_group os_desc_group;
	struct config_group webusb_group;

	struct mutex lock;
	struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
	struct list_head string_list;
	struct list_head available_func;

	struct usb_composite_driver composite;
	struct usb_composite_dev cdev;
	bool use_os_desc;
	char b_vendor_code;
	char qw_sign[OS_STRING_QW_SIGN_LEN];
	bool use_webusb;
	u16 bcd_webusb_version;
	u8 b_webusb_vendor_code;
	char landing_page[WEBUSB_URL_RAW_MAX_LENGTH];

	spinlock_t spinlock;
	bool unbind;
};
```

[OS_STRING_QW_SIGN_LEN — constant — include/linux/usb/composite.h]
OS_STRING_QW_SIGN_LEN → 16  (include/linux/usb/composite.h)

[WEBUSB_URL_RAW_MAX_LENGTH — constant — include/linux/usb/webusb.h]
WEBUSB_URL_RAW_MAX_LENGTH → 256  (include/linux/usb/webusb.h)

[WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH — constant — include/linux/usb/webusb.h]
WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH → 3  (include/linux/usb/webusb.h)

[U8_MAX — constant — include/linux/kernel.h]
U8_MAX → 0xFF  (include/linux/kernel.h)

Provide the final verdict as strict JSON.

Return only one JSON object and no surrounding prose.

Return one JSON object with these keys:
- vulnerable
- cwe
- severity
- reviewed_region
- vulnerable_region
- root_cause
- exploitability
- attack_preconditions
- impact
- non_vulnerable_reason
- security_invariants
- invariant_verification
- residual_risk_notes
- call_path

Control flow: sysfs write -> configfs_write_file -> webusb_landingPage_store or os_desc_qw_sign_store -> access page[l-1] without checking len>0

Data flow: User-controlled sysfs write passes 'len' parameter directly to store function; len=0 causes page[l-1]=page[-1] OOB access

Verdict: Vulnerable
Vulnerable region:
[drivers/usb/gadget/configfs.c]
```c
unsigned int bytes_to_strip = 0;
	int l = len;

	if (page[l - 1] == '\n') {
		--l;
		++bytes_to_strip;
	}

	if (l > sizeof(gi->landing_page)) {
		pr_err("webusb: landingPage URL too long\n");
		return -EINVAL;
	}

	// validation
	if (strncasecmp(page, "https://",  8) == 0)
		bytes_to_strip = 8;
	else if (strncasecmp(page, "http://", 7) == 0)
		bytes_to_strip = 7;
	else
		bytes_to_strip = 0;
```
CWE: CWE-125
Severity: MEDIUM

Root cause: In webusb_landingPage_store(), the code accesses page[l - 1] to check for a trailing newline before validating that len > 0. When an empty string (len=0) is written, this produces page[-1], an out-of-bounds memory access. The same issue exists in os_desc_qw_sign_store().

Exploitability: An attacker with access to the USB gadget configfs sysfs interface can trigger the OOB read by writing an empty string to /sys/kernel/config/usb_gadget/.../os_desc/qw_sign or .../webusb/landingPage

Attack preconditions:
- USB gadget configfs is mounted and configured
- Attacker has write access to the qw_sign or landingPage sysfs attributes
- len parameter passed to store function is 0

Likely impact: An attacker with access to the USB gadget configfs sysfs interface can trigger the OOB read by writing an empty string to /sys/kernel/config/usb_gadget/.../os_desc/qw_sign or .../webusb/landingPage

## Analysis

### Input Constraints
- `page` and `len` come from configfs write. `len` is bounded by `PAGE_SIZE` (configfs_write_file).
- `len` could in principle be 0.

### Path Walkthrough

1. `int l = len;` — `len` is `size_t`, assigned to `int l`. With `PAGE_SIZE` bound, no truncation issue in practice.

2. **Potential underflow at `page[l - 1]`**: If `len == 0`, then `l - 1 == -1`, causing an OOB read of `page[-1]`. Need to check whether configfs_write_file allows `len == 0`.

   Looking at `configfs_write_file`: it calls the store with the count from userspace. A zero-length write (`write(fd, buf, 0)`) typically returns 0 early in VFS before reaching the handler in many cases, but configfs may still invoke. Actually, in `vfs_write`, a count of 0 short-circuits. So `len >= 1` typically. This is a minor concern but generally not exploitable.

3. **`bytes_to_strip` logic is buggy but let's check size constraints**:
   - After newline strip: `l <= len <= PAGE_SIZE`.
   - Check: `if (l > sizeof(gi->landing_page))` → `l > 256` rejects.
   - So `l <= 256` after this check.

4. **The second length check**:
   - `if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip)`
   - = `l > 255 - 3 + bytes_to_strip` = `l > 252 + bytes_to_strip`.
   - With `bytes_to_strip = 8` (https), allowed `l <= 260`. But we already know `l <= 256`. So allowed.
   - With `bytes_to_strip = 0`, allowed `l <= 252`.

5. **The `memcpy_and_pad`**:
   - `memcpy_and_pad(gi->landing_page, 256, page, l, 0)` with `l <= 256`. Safe — `memcpy_and_pad` handles `l <= dest_size` correctly (copies `l` bytes, pads remainder).

### Note on `bytes_to_strip` semantics

The variable `bytes_to_strip` is misleadingly named — it's used in the size check but the data copied (`memcpy_and_pad(..., page, l, 0)`) does NOT actually strip the URL scheme. So the URL scheme is included in the stored `landing_page`. This is a logic/spec issue but not a memory safety vulnerability.

Also, `bytes_to_strip` is overwritten — the newline-strip increment `++bytes_to_strip` is later thrown away when reassigned to 8/7/0. This is also a logic bug but not memory safety.

### Bound check verification

The buffer is 256 bytes. After the first check, `l <= 256`. The second check (with `bytes_to_strip = 0` for an arbitrary URL) requires `l <= 252`. Since `memcpy_and_pad` writes at most `min(l, 256)` to a 256-byte buffer, no overflow.

### Zero-length write

If `len == 0`:
- `page[l - 1]` = `page[-1]` — out-of-bounds read of one byte.
- Generally, VFS `vfs_write` rejects/returns early on count 0 before hitting the handler, so unreachable from userspace via standard write().

This is the only candidate concern, and it's a 1-byte read at `page[-1]` of a kernel buffer (the configfs page). Even if reachable, it reads from an attacker-controlled allocation with no observable side effect besides possibly matching `'\n'` and decrementing `l` to `-1`, then `l > 256` is false (it's negative — but compared as int), passes the next check too, and `memcpy_and_pad(..., page, -1, 0)`. With `l = -1` cast to `size_t` for memcpy, that becomes a huge value — **massive memory corruption**.

Wait — let me re-examine. `memcpy_and_pad`'s signature takes `size_t count`. Passing `int l = -1` promotes to `SIZE_MAX`. But the bound check `if (l > sizeof(gi->landing_page))` — `sizeof()` returns `size_t`. The comparison `int > size_t` promotes `l` to `size_t`, making `-1` become huge, which IS `> 256`, so it WOULD be rejected with `-EINVAL`.

Actually let me recheck: `if (l > sizeof(gi->landing_page))` with `l = -1`: `l` (int) is converted to `size_t` for comparison → `SIZE_MAX > 256` → true → returns `-EINVAL`. So even in this corner case, rejected.

But the OOB read of `page[-1]` still happens before that check. configfs page is allocated via `kmalloc`/similar; reading 1 byte before would read adjacent allocator metadata or another object — info-leak-via-comparison only, not exploitable for disclosure.

### Reachability of len == 0

In `configfs_write_file`, looking at typical kernel flow: `vfs_write` enters with count from userspace. The kernel does allow 0-length writes to reach the handler in some paths, but the comparison only affects branch direction (no read disclosed). The OOB read is bounded to one byte and its result only causes a benign `--l` that subsequently fails the size check.

### Verdict

No exploitable vulnerability. The bounds are sufficient:
- `l <= 256` enforced before memcpy.
- `memcpy_and_pad` to a 256-byte destination with count ≤ 256 is safe.
- The zero-length corner case has a minor `page[-1]` read but is benign and gets caught by the subsequent bound check (negative `int` compared as `size_t`).

The code does have **logic bugs** (URL scheme not actually stripped, `bytes_to_strip` overwritten losing the newline accounting, unused-then-reused variable), but none are security vulnerabilities.

## Conclusion

**Not vulnerable.**

Key invariants:
1. `sizeof(gi->landing_page) == 256` — the first guard caps `l` at 256.
2. `memcpy_and_pad` is called with `l ≤ sizeof(dest)`, which is its safe contract.
3. The "URL scheme" bound check with `bytes_to_strip` only further restricts; it cannot bypass the first check.
4. The mutex protects against concurrent stores.

Residual notes (not security):
- The newline-strip increment to `bytes_to_strip` is dead code (overwritten).
- The function does not actually strip the URL scheme during copy despite the variable name; this is a functional/spec concern.
- A zero-length write (if it ever reached this handler) would do a 1-byte OOB read at `page[-1]`, but the subsequent unsigned comparison rejects the request before any write occurs, and no byte from that read is disclosed to userspace.

drivers/usb/gadget/configfs.c: webusb_landingPage_store entire body

Not exploitable. The destination buffer landing_page is 256 bytes. The check `if (l > sizeof(gi->landing_page))` rejects any l > 256 before the copy. memcpy_and_pad is called with count l <= 256 into a 256-byte destination, which is its safe contract. The second size check (l > 252 + bytes_to_strip) only further constrains acceptable lengths and cannot bypass the first. A theoretical len==0 from userspace would cause a 1-byte read of page[-1], but (a) vfs_write generally short-circuits zero-length writes before reaching the handler, and (b) if it did occur, the subsequent unsigned-promoted comparison `(int)-1 > (size_t)256` evaluates true and returns -EINVAL, so no write happens and the read result is not disclosed to userspace.

Write access to the configfs webusb landingPage attribute (typically root/CAP_SYS_ADMIN on the gadget configfs mount).

None demonstrable. Functional/spec quirks exist (URL scheme not actually stripped during copy; the newline accounting in bytes_to_strip is overwritten by the scheme detection) but they do not lead to memory corruption or information disclosure.

Length is bounded to <=256 before memcpy_and_pad copies into the 256-byte gi->landing_page; memcpy_and_pad pads the remainder with zero. The mutex serializes concurrent stores. No attacker-controlled value can drive the copy length beyond the destination size.

sizeof(gi->landing_page) == WEBUSB_URL_RAW_MAX_LENGTH == 256

After the first guard, l <= 256 (signed int, but value range [INT_MIN, 256])

memcpy_and_pad(dest, dest_size, src, count, pad) is safe when count <= dest_size

First guard `l > sizeof(gi->landing_page)` rejects oversize inputs (signed-vs-unsigned promotion makes any negative l also rejected as a huge size_t). Therefore l passed to memcpy_and_pad is in [0,256], satisfying the helper's precondition. The bytes_to_strip-based check only tightens this further and cannot widen it.

Logic/spec issues (not security): URL scheme is not stripped from the stored buffer despite the bytes_to_strip naming; the increment to bytes_to_strip when stripping the trailing newline is overwritten by the scheme check; on a zero-length write reaching the handler, a 1-byte read of page[-1] occurs but its result only influences a branch that ends up returning -EINVAL without copying or leaking data.

sysfs_write -> configfs_write_file -> webusb_landingPage_store


Audit the following candidate code to determine whether it presents a real vulnerability.
This sample may come from a noisy retrieval or transformation stage, so reason from the supplied code and context rather than assuming the label.

Repository: git.kernel.org/pub/scm/linux/kernel/git/stable/linux
Language: C
File: fs/jffs2/compr_rtime.c
Function: jffs2_rtime_decompress

Call path: jffs2_rtime_decompress (fs/jffs2/compr_rtime.c) → jffs2_decompress (fs/jffs2/compr.c) → jffs2_compressors_init (fs/jffs2/compr.c)

### Primary Function

```c
static int jffs2_rtime_decompress(unsigned char *data_in,
				  unsigned char *cpage_out,
				  uint32_t srclen, uint32_t destlen)
{
	unsigned short positions[256];
	int outpos = 0;
	int pos=0;

	memset(positions,0,sizeof(positions));

	while (outpos<destlen) {
		unsigned char value;
		int backoffs;
		int repeat;

		value = data_in[pos++];
		cpage_out[outpos++] = value; /* first the verbatim copied byte */
		repeat = data_in[pos++];
		backoffs = positions[value];

		positions[value]=outpos;
		if (repeat) {
			if (backoffs + repeat >= outpos) {
				while(repeat) {
					cpage_out[outpos++] = cpage_out[backoffs++];
					repeat--;
				}
			} else {
				memcpy(&cpage_out[outpos],&cpage_out[backoffs],repeat);
				outpos+=repeat;
			}
		}
	}
	return 0;
}
```

### Cross-File Context

[JFFS2_RTIME_PRIORITY — constant — fs/jffs2/compr.h:22]
JFFS2_RTIME_PRIORITY → 50  (fs/jffs2/compr.h:22)

[jffs2_compressor — struct — fs/jffs2/compr.h:35-54]
```c
struct jffs2_compressor {
	struct list_head list;
	int priority;
	char *name;
	char compr;
	int (*compress)(unsigned char *data_in, unsigned char *cpage_out,
			uint32_t *srclen, uint32_t *destlen);
	int (*decompress)(unsigned char *cdata_in, unsigned char *data_out,
			  uint32_t cdatalen, uint32_t datalen);
	int usecount;
	int disabled;
	unsigned char *compr_buf;
	uint32_t compr_buf_size;
	uint32_t stat_compr_orig_size;
	uint32_t stat_compr_new_size;
	uint32_t stat_compr_blocks;
	uint32_t stat_decompr_blocks;
};
```

[jffs2_rtime_comp — constant — fs/jffs2/compr_rtime.c:101-113]
jffs2_rtime_comp → { .priority = JFFS2_RTIME_PRIORITY, .name = "rtime", .compr = JFFS2_COMPR_RTIME, .compress = &jffs2_rtime_compress, .decompress = &jffs2_rtime_decompress, #ifdef JFFS2_RTIME_DISABLED .disabled = 1, #else .disabled = 0, #endif }  (fs/jffs2/compr_rtime.c:101-113)

Control flow: jffs2_rtime_decompress is called from jffs2_decompress with attacker-controlled srclen/destlen and data_in buffer. The function iterates through data_in reading value/repeat pairs, copying data to cpage_out. Without the bounds fix, large repeat values can cause outpos to exceed destlen.

Data flow: data_in (attacker-controlled compressed data), cpage_out (destination buffer), srclen (compressed data length), destlen (destination buffer size). The repeat field from data_in directly controls how many bytes are written to cpage_out.

Verdict: Vulnerable
Vulnerable region:
[fs/jffs2/compr_rtime.c]
```c
uint32_t srclen, uint32_t destlen)
{
	unsigned short positions[256];
	int outpos = 0;
	int pos=0;

	memset(positions,0,sizeof(positions));

	while (outpos<destlen) {
		unsigned char value;
		int backoffs;
		int repeat;

		value = data_in[pos++];
		cpage_out[outpos++] = value; /* first the verbatim copied byte */
		repeat = data_in[pos++];
		backoffs = positions[value];

		positions[value]=outpos;
		if (repeat) {
			if (backoffs + repeat >= outpos) {
```
CWE: CWE-787
Severity: HIGH

Root cause: In jffs2_rtime_decompress(), the bounds check only verifies that backoffs+repeat does not exceed outpos in the first branch, but does not check if outpos+repeat exceeds destlen before entering the copy loops. When processing corrupted compressed data with a large repeat value, outpos can be incremented beyond destlen, causing out-of-bounds writes via the while loop or memcpy.

Exploitability: Attacker needs ability to store or provide a crafted JFFS2 filesystem image with corrupted rtime compression data. The repeat value from corrupted data can cause outpos to exceed destlen.

Attack preconditions:
- Attacker can store or provide access to a crafted JFFS2 filesystem image
- Target system must mount or process the malicious JFFS2 image
- Compressed data must have repeat > 0 and backoffs+repeat < outpos to trigger memcpy path
- No additional privileges required beyond filesystem access

Likely impact: Attacker needs ability to store or provide a crafted JFFS2 filesystem image with corrupted rtime compression data. The repeat value from corrupted data can cause outpos to exceed destlen.

## Analysis

### Code Behavior

`jffs2_rtime_decompress` reads pairs of bytes from `data_in`:
- A literal byte `value` written verbatim to output.
- A `repeat` count.
- For repeats, it copies from a back-reference offset stored in the `positions[value]` table.

The loop continues `while (outpos < destlen)`.

### Data Flow / Attacker Influence

`data_in` originates from JFFS2 nodes on disk/flash. A maliciously crafted filesystem image can supply arbitrary `data_in` content. `srclen` and `destlen` come from node metadata.

### Bounds Checking

**Input (`data_in`) bounds:** The function reads `data_in[pos++]` twice per iteration, but **never checks `pos` against `srclen`**. If the input is too short relative to `destlen`, the loop reads past the end of `data_in`. → out-of-bounds read on the source buffer.

**Output (`cpage_out`) bounds:** The loop guard is `outpos < destlen`, but inside the loop:
1. `cpage_out[outpos++] = value;` — written before any check; could write at `destlen` is OK (loop checked < destlen), so this single byte is fine.
2. The repeat loop writes `repeat` more bytes (`repeat` is an attacker-controlled byte 0–255) without checking `outpos+repeat <= destlen`. → out-of-bounds write on `cpage_out` of up to 255 bytes per iteration.

**Back-reference (`backoffs`) bounds:** `backoffs = positions[value]`; initially zero. After the first occurrence of a `value`, `positions[value] = outpos` (a valid output offset). The condition `backoffs + repeat >= outpos` selects the byte-by-byte path (overlapping/forward-referencing copy) vs. memcpy. Since `backoffs` is always a previously valid `outpos`, reads from `cpage_out[backoffs..]` stay within the already-written region — provided we don't run off the output. But because of the OOB write above, we can corrupt `outpos` indirectly; not relevant here for back-ref correctness.

### Exploitability

Preconditions: an attacker provides a malicious JFFS2 image (or crafted node) that decompresses with `rtime`. The caller (`jffs2_decompress`) allocates `cpage_out` of size `destlen`. The decompress routine can:

- Overflow `cpage_out` by writing up to ~255 bytes past `destlen` (kernel heap overflow).
- OOB-read `data_in` past `srclen` (info leak / panic).

### Historical Context

This exact pattern is CVE-2017-9445-style territory; in fact, JFFS2 rtime decompress had a known issue (commit `4a9e4868d28c` "jffs2: Fix possible null-pointer dereferences..." and later mitigations). More directly, this matches the pattern fixed by the kernel commit that bound-checks `outpos+repeat` against `destlen` (and bounds `pos` against `srclen`). The candidate code here lacks both checks.

### Verdict

**Vulnerable.**

- **Root cause:** Missing validation of attacker-controlled `repeat` against the remaining output buffer (`destlen - outpos`) and missing validation of `pos` against `srclen`.
- **Impact:** Heap buffer overflow on `cpage_out` (kernel memory corruption) when mounting a crafted JFFS2 filesystem; also OOB read of `data_in`.
- **Attack preconditions:** Ability to mount or supply a malicious JFFS2 image (e.g., USB device, attacker-supplied image, user-mountable removable media). On systems where unprivileged mounting of untrusted filesystems is possible, this is reachable by a local attacker; otherwise, it requires privilege to mount.
- **Classification:** CWE-787 (Out-of-bounds Write), with secondary CWE-125 (Out-of-bounds Read). Specifically a heap-based buffer overflow in kernel context triggered by malformed compressed data.

### Suggested Fix (for reference)

Add inside the loop:
```c
if (pos + 1 >= srclen) return -EINVAL;
...
if (repeat) {
    if (outpos + repeat > destlen) return -EINVAL;
    ...
}
```

Residual uncertainty: the supplied context does not show the caller's exact handling of `srclen`/`destlen`, but `jffs2_decompress` allocates buffers of `destlen` size based on node metadata, which is itself attacker-controlled within the image — so the overflow remains exploitable regardless of caller-side sanity checks not shown here.

fs/jffs2/compr_rtime.c: jffs2_rtime_decompress, lines covering the entire function body including the while(outpos<destlen) loop and inner repeat copy

Inner loop body of jffs2_rtime_decompress: the two reads `value = data_in[pos++]; ... repeat = data_in[pos++];` and the subsequent repeat-copy block (`while(repeat) cpage_out[outpos++] = cpage_out[backoffs++];` and `memcpy(&cpage_out[outpos],&cpage_out[backoffs],repeat); outpos+=repeat;`)

The decompressor consumes attacker-controlled compressed bytes from data_in and an attacker-controlled `repeat` count without validating either the input cursor `pos` against `srclen` or the resulting output position `outpos + repeat` against `destlen`. Trust is placed in well-formed compressed data, but the data originates from on-flash/on-disk JFFS2 nodes which can be crafted.

An attacker who can supply a crafted JFFS2 image (e.g., malicious block device, USB stick, loop-mounted image, or any path where untrusted filesystem images are mounted) can construct a node whose rtime-compressed payload is shorter than its declared destlen and whose `repeat` byte (0-255) drives outpos past destlen. The single literal write per iteration is bounded by the loop guard, but the subsequent repeat copy of up to 255 bytes is not bounded, yielding a heap overflow of up to ~255 bytes per iteration in kernel context. Mounting JFFS2 typically requires CAP_SYS_ADMIN, which lowers practical severity; however, automounters and embedded systems frequently mount untrusted media.

Ability to cause the kernel to mount/parse a maliciously crafted JFFS2 filesystem image containing a node compressed with the rtime compressor and with srclen/destlen mismatched relative to the actual content.

Kernel heap out-of-bounds write (memory corruption) on cpage_out leading to possible denial of service (panic) and potential privilege escalation through controlled adjacent-object corruption. Additionally, out-of-bounds read on data_in past srclen can cause information disclosure or oops.

For a safe rtime decompressor: (1) pos must satisfy pos+1 < srclen before each pair read; (2) outpos+repeat must be <= destlen before any repeat copy; (3) backoffs+repeat must be <= outpos to prevent reading uninitialized output; (4) on any violation return an error so the caller can abort the mount/read.

Invariant (1) is not enforced: pos is incremented unconditionally with no comparison to srclen. Invariant (2) is not enforced: only `while (outpos<destlen)` guards the literal byte; the repeat copy of up to 255 bytes has no bound check. Invariant (3) is partially handled by the `backoffs + repeat >= outpos` branch which selects the byte-by-byte path, but this does not protect output overflow. Therefore the function fails to uphold the required invariants.

Caller-side sanitization in jffs2_decompress and node header validation are not shown; if upstream callers strictly validate that srclen and destlen match well-formed payloads, exploitability is reduced, but historically JFFS2 has not enforced sufficient checks at this layer. Mount privilege requirement (CAP_SYS_ADMIN) limits attack surface on hardened systems but not on embedded/automount scenarios.

jffs2_rtime_decompress (fs/jffs2/compr_rtime.c) <- jffs2_decompress (fs/jffs2/compr.c) <- jffs2_compressors_init (fs/jffs2/compr.c)


Audit the following candidate code to determine whether it presents a real vulnerability.
This sample may come from a noisy retrieval or transformation stage, so reason from the supplied code and context rather than assuming the label.

Repository: https://github.com/torvalds/linux
Language: C
File: drivers/pci/endpoint/functions/pci-epf-test.c
Function: pci_epf_test_set_bar

Call path: pci_epf_test_epc_init (drivers/pci/endpoint/functions/pci-epf-test.c) → pci_epf_test_set_bar (drivers/pci/endpoint/functions/pci-epf-test.c) → pci_epc_set_bar (drivers/pci/endpoint/functions/pci-epf-test.c) → pci_epf_free_space (linux/pci-epf.h) → pci_epf_test_alloc_space (drivers/pci/endpoint/functions/pci-epf-test.c) → pci_epf_test_free_space (drivers/pci/endpoint/functions/pci-epf-test.c)

### Primary Function

```c
static int pci_epf_test_set_bar(struct pci_epf *epf)
{
	int bar, ret;
	struct pci_epc *epc = epf->epc;
	struct device *dev = &epf->dev;
	struct pci_epf_test *epf_test = epf_get_drvdata(epf);
	enum pci_barno test_reg_bar = epf_test->test_reg_bar;

	for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
		if (!epf_test->reg[bar])
			continue;

		ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no,
				      &epf->bar[bar]);
		if (ret) {
			pci_epf_free_space(epf, epf_test->reg[bar], bar,
					   PRIMARY_INTERFACE);
			epf_test->reg[bar] = NULL;
			dev_err(dev, "Failed to set BAR%d\n", bar);
			if (bar == test_reg_bar)
				return ret;
		}
	}

	return 0;
}
```

### Cross-File Context

[pci_epf_test_free_space — helper — drivers/pci/endpoint/functions/pci-epf-test.c:921-934]
```c
static void pci_epf_test_free_space(struct pci_epf *epf)
{
	struct pci_epf_test *epf_test = epf_get_drvdata(epf);
	int bar;

	for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
		if (!epf_test->reg[bar])
			continue;

		pci_epf_free_space(epf, epf_test->reg[bar], bar,
				   PRIMARY_INTERFACE);
		epf_test->reg[bar] = NULL;
	}
}
```

[pci_epf_test — struct — drivers/pci/endpoint/functions/pci-epf-test.c:53-69]
```c
struct pci_epf_test {
	void			*reg[PCI_STD_NUM_BARS];
	struct pci_epf		*epf;
	enum pci_barno		test_reg_bar;
	size_t			msix_table_offset;
	struct delayed_work	cmd_handler;
	struct dma_chan		*dma_chan_tx;
	struct dma_chan		*dma_chan_rx;
	struct dma_chan		*transfer_chan;
	dma_cookie_t		transfer_cookie;
	enum dma_status		transfer_status;
	struct completion	transfer_complete;
	bool			dma_supported;
	bool			dma_private;
	const struct pci_epc_features *epc_features;
};
```

[PCI_STD_NUM_BARS — constant — include/linux/pci_regs.h]
PCI_STD_NUM_BARS → 6  (include/linux/pci_regs.h)

[PRIMARY_INTERFACE — constant — include/linux/pci-epf.h]
PRIMARY_INTERFACE → 0  (include/linux/pci-epf.h)

[pci_epf_free_space — callee — include/linux/pci-epf.h]
```c
void pci_epf_free_space(struct pci_epf *epf, void *addr, enum pci_barno bar,
			     u8 interface);
```

[pci_epf_alloc_space — function — include/linux/pci-epf.h]
```c
void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar,
				  const struct pci_epc_features *features, u8 interface);
```

Control flow: The function iterates through PCI bars (0 to PCI_STD_NUM_BARS-1, which is 6). For each bar, it first checks if epf_test->reg[bar] is NULL and skips processing if so. It then calls pci_epc_set_bar() to configure the BAR. On success, the loop continues to the next bar. On error (ret != 0), the error path executes: pci_epf_free_space() is called to deallocate the memory region, then epf_test->reg[bar] is immediately set to NULL, an error is logged, and if bar equals test_reg_bar, the function returns the error code immediately. Otherwise, the loop continues to the next bar. The function returns 0 if all bars are processed successfully.

Data flow: The input pointer epf_test->reg[bar] (from the pci_epf_test struct array of 6 void pointers) holds the address of allocated memory for each BAR. When pci_epc_set_bar() fails, epf_test->reg[bar] is passed to pci_epf_free_space() for deallocation. Immediately after the free call, epf_test->reg[bar] is reassigned to NULL. The loop guard `if (!epf_test->reg[bar]) continue;` ensures that any nulled entries are skipped in subsequent iterations, preventing access to the now-freed memory through a dangling pointer.

Verdict: Not Vulnerable

Reviewed region:
[drivers/pci/endpoint/functions/pci-epf-test.c]
```c
static int pci_epf_test_set_bar(struct pci_epf *epf)
{
	int bar, ret;
	struct pci_epc *epc = epf->epc;
	struct device *dev = &epf->dev;
	struct pci_epf_test *epf_test = epf_get_drvdata(epf);
	enum pci_barno test_reg_bar = epf_test->test_reg_bar;

	for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
		if (!epf_test->reg[bar])
			continue;

		ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no,
				      &epf->bar[bar]);
		if (ret) {
			pci_epf_free_space(epf, epf_test->reg[bar], bar,
					   PRIMARY_INTERFACE);
			epf_test->reg[bar] = NULL;
			dev_err(dev, "Failed to set BAR%d\n", bar);
			if (bar == test_reg_bar)
				return ret;
		}
	}

	return 0;
}
```

Why it is not vulnerable: In the error handling path of pci_epf_test_set_bar, when pci_epc_set_bar() returns an error, the code calls pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE) to deallocate the memory, and immediately on the next line executes epf_test->reg[bar] = NULL to nullify the pointer. This ensures that no dangling pointer remains accessible after deallocation. Additionally, the loop guard `if (!epf_test->reg[bar]) continue;` at the top of each iteration prevents any subsequent processing of bars whose pointers have been nulled. The same pattern is confirmed in the helper function pci_epf_test_free_space, which also nulls each pointer immediately after freeing.

Security invariants:
- After calling pci_epf_free_space() to deallocate epf_test->reg[bar], the pointer must be immediately set to NULL. This is enforced by the statement `epf_test->reg[bar] = NULL;` appearing directly after `pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE);` in the error handling block.
- The array index bar must remain within bounds [0, PCI_STD_NUM_BARS). This is enforced by the loop condition `for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)` where PCI_STD_NUM_BARS is defined as 6.
- Freed/nulled pointers must not be dereferenced in subsequent iterations. This is enforced by the guard `if (!epf_test->reg[bar]) continue;` at the start of each loop iteration.
- The pci_epf_test struct must have reg array sized to PCI_STD_NUM_BARS elements. This is verified by the struct definition `void *reg[PCI_STD_NUM_BARS];`.

Invariant verification:
- Pointer nullification immediately after memory deallocation in error path: holds=true. Evidence: In the error handling block: `pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE);` is followed immediately by `epf_test->reg[bar] = NULL;` with no intervening code that could access the now-invalid pointer.
- Array bounds safety for reg pointer access: holds=true. Evidence: The loop iterates with `bar < PCI_STD_NUM_BARS` (which equals 6), and the struct defines `void *reg[PCI_STD_NUM_BARS]`, ensuring all accesses are within bounds.
- Null pointer guard prevents use-after-free in loop: holds=true. Evidence: The statement `if (!epf_test->reg[bar]) continue;` at the top of each iteration ensures that once a pointer is nulled (either initially or after a failed BAR setup), it will not be accessed for pci_epc_set_bar() or any other operation in subsequent iterations.
- Helper function pci_epf_test_free_space also nulls pointers after free: holds=true. Evidence: The helper function iterates all bars and for each valid entry, calls `pci_epf_free_space()` followed by `epf_test->reg[bar] = NULL;`, maintaining the same invariant as the error path in the main function.

Residual risk notes: The function only handles the error case for a single BAR at a time. If pci_epc_set_bar() fails for a bar that is not test_reg_bar, the function continues processing remaining bars. If a later bar also fails, the function continues rather than unwinding previously set bars. However, this is a resource management design choice rather than a pointer nullification issue. The pci_epc_set_bar() and pci_epf_free_space() are declared but their implementations are not in the supplied context, so we cannot verify that pci_epc_set_bar() cannot corrupt the reg array or that pci_epf_free_space() properly handles the provided parameters. The invariant verification assumes these callees behave correctly and do not access epf_test->reg[bar] after it is passed to them for deallocation.

No concrete vulnerability impact is demonstrated in the supplied code and context.

## Analysis

### Control flow

`pci_epf_test_set_bar` iterates over all 6 BARs. For each allocated BAR (`epf_test->reg[bar]` non-NULL), it calls `pci_epc_set_bar`. On failure:

1. It frees the space and clears `epf_test->reg[bar]`.
2. If the failed BAR is the `test_reg_bar`, it returns the error.
3. Otherwise, it continues looping (treats failure as non-fatal for non-test BARs).

### Suspected issue

The concern is whether returning `ret` for `test_reg_bar` failure leaks earlier successfully-set BARs, or whether the early return path leaves state inconsistent. Let's trace:

- If BAR 0 succeeds (set on EPC), and BAR 2 (the `test_reg_bar`) fails, the function returns `ret` without unwinding BAR 0. The caller `pci_epf_test_epc_init` would presumably handle that — but this function itself doesn't call `pci_epc_clear_bar` on previously-set BARs.

However, this is a cleanup-completeness concern, not necessarily a security bug. The state remains: `epf_test->reg[0]` still points to allocated memory, `epf->bar[0]` is still set on the EPC. The caller may invoke `pci_epf_test_clear_bar` / `pci_epf_test_free_space` to clean up. Looking at the cleanup helper `pci_epf_test_free_space`, it iterates all BARs guarded by `epf_test->reg[bar]` non-NULL — so the leftover BAR 0 allocation would still be properly freed later.

### Double-free / UAF check

When `pci_epc_set_bar` fails for a non-test BAR:
- `pci_epf_free_space(epf, ..., bar, PRIMARY_INTERFACE)` is called.
- `epf_test->reg[bar] = NULL` is set immediately afterward.

This means subsequent cleanup via `pci_epf_test_free_space` will skip this BAR (because `reg[bar]` is NULL). No double-free.

For the test_reg_bar failure: the function frees `reg[test_reg_bar]`, sets it to NULL, and returns. Subsequent `pci_epf_test_free_space` called by the caller will skip it. No double-free.

### Memory leak of bar[].phys_addr / EPC state

When a non-test BAR fails to set, we free the local memory but `epf->bar[bar]` may still hold stale information. However, since `pci_epc_set_bar` failed, the EPC didn't accept it, so there's nothing to clear on the EPC side. This is consistent.

### Continuing after non-test BAR failure

After freeing a non-test BAR's `reg[bar]`, the loop continues. Subsequent iterations only touch `reg[bar]` for other BARs (the loop variable advances), so no UAF on the freed pointer.

### Verdict

The code's error handling is internally consistent:
- Freed memory is immediately NULL'd out, so subsequent `pci_epf_test_free_space` won't double-free.
- The early return on `test_reg_bar` failure leaves successfully-set BARs in place, but this is recoverable by the caller's cleanup paths and isn't a security vulnerability per se.
- No attacker-controlled input flows into this function — it's driven by endpoint configuration, not external (PCI host) input. The host side cannot influence whether `pci_epc_set_bar` fails.

There is no use-after-free, double-free, OOB access, or attacker-influenced data path here. The pattern is defensive cleanup.

**Not a vulnerability.** The function correctly nulls `epf_test->reg[bar]` after freeing, preventing double-free in the cleanup helper. The early-return-without-unwinding-prior-BARs is a minor robustness concern but the higher-level cleanup (`pci_epf_test_free_space` and presumably a `clear_bar` counterpart) handles residual state, and there's no attacker-reachable misuse demonstrated by the supplied context.

Residual uncertainty: I don't have the body of `pci_epf_test_epc_init` to confirm it issues proper cleanup on this function's failure return, nor the body of `pci_epf_free_space` to verify it tolerates the NULL'd state. But within the supplied function's contract, no memory-safety violation is present.

drivers/pci/endpoint/functions/pci-epf-test.c: pci_epf_test_set_bar (full body)

No attacker-controlled data flows into this path. The function is driven by EPF/EPC configuration during endpoint init; PCI host-side actors cannot influence whether pci_epc_set_bar succeeds or fails.

None observed. Worst-case is incomplete cleanup of previously-successful BARs on early return for test_reg_bar failure, which is reconciled by higher-level cleanup paths (pci_epf_test_free_space iterates with NULL guards).

On pci_epc_set_bar failure, the code frees the BAR-backing memory via pci_epf_free_space and immediately nulls epf_test->reg[bar]. The cleanup helper pci_epf_test_free_space skips entries where reg[bar] is NULL, so no double-free can occur. The loop only continues to other BAR indices, so the freed pointer is never re-read. For non-test BARs, failure is treated as non-fatal (BAR simply omitted), which is intentional. For test_reg_bar, the early return propagates the error to the caller, with reg[test_reg_bar] safely set to NULL.

After pci_epf_free_space(epf, reg[bar], ...), epf_test->reg[bar] is set to NULL before any further access.

pci_epf_test_free_space tolerates NULL reg[bar] entries (NULL-guarded loop).

Loop never re-reads a freed reg[bar] within the same invocation since it advances bar.

Verified by inspection: reg[bar] = NULL assignment immediately follows pci_epf_free_space; pci_epf_test_free_space's loop explicitly continues on !epf_test->reg[bar].

Body of pci_epf_test_epc_init was not supplied, so I cannot fully verify it issues a clear_bar for previously-successful BARs on early return; however, this would be a resource/leak hygiene concern at most, not a memory-safety vulnerability. Body of pci_epf_free_space not shown but its NULL-guarded usage pattern is standard.

pci_epf_test_epc_init -> pci_epf_test_set_bar -> pci_epc_set_bar (failure) -> pci_epf_free_space; cleanup later via pci_epf_test_free_space


Audit the following candidate code to determine whether it presents a real vulnerability.
This sample may come from a noisy retrieval or transformation stage, so reason from the supplied code and context rather than assuming the label.

Repository: github.com/tensorflow/tensorflow
Language: C++
File: tensorflow/core/kernels/inplace_ops.cc
Function: tensorflow::ParallelConcatUpdate<tensorflow::CPUDevice>::Compute

Call path: _ParallelConcatUpdate (tensorflow/core/ops/array_ops.cc) → ParallelConcatUpdate::Compute (tensorflow/core/kernels/inplace_ops.cc) → tensorflow::functor::DoParallelConcat (tensorflow/core/kernels/inplace_ops.cc) → tensorflow::functor::DoParallelConcatUpdate (tensorflow/core/kernels/inplace_ops.cc)

### Primary Function

```c++
void Compute(OpKernelContext* ctx) override {
    auto value = ctx->input(0);
    auto update = ctx->input(1);

    OP_REQUIRES(
        ctx, value.dims() == update.dims(),
        errors::InvalidArgument("value and update shape doesn't match: ",
                                value.shape().DebugString(), " vs. ",
                                update.shape().DebugString()));
    for (int i = 1; i < value.dims(); ++i) {
      OP_REQUIRES(
          ctx, value.dim_size(i) == update.dim_size(i),
          errors::InvalidArgument("value and update shape doesn't match ",
                                  value.shape().DebugString(), " vs. ",
                                  update.shape().DebugString()));
    }
    OP_REQUIRES(ctx, 1 == update.dim_size(0),
                errors::InvalidArgument("update shape doesn't match: ",
                                        update.shape().DebugString()));

    Tensor output = value;  // This creates an alias intentionally.
    const auto& d = ctx->eigen_device<Device>();
    OP_REQUIRES_OK(
        ctx, ::tensorflow::functor::DoParallelConcat(d, update, loc_, &output));
    ctx->set_output(0, output);
  }
```

### Cross-File Context

[tensorflow::ParallelConcatUpdate — class — tensorflow/core/kernels/inplace_ops.cc:65-110]
template <typename Device> class ParallelConcatUpdate : public OpKernel { public: explicit ParallelConcatUpdate(OpKernelConstruction* ctx) : OpKernel(ctx) { OP_REQUIRES_OK(ctx, ctx->GetAttr("loc", &loc_)); } void Compute(OpKernelContext* ctx) override; private: int32 loc_; };

[tensorflow::functor::DoParallelConcat — callee — tensorflow/core/kernels/inplace_ops.cc:43-59]
```c++
template <>
Status DoParallelConcat(const CPUDevice& d, const Tensor& value, int32_t loc,
                        Tensor* output) {
  CHECK_EQ(value.dtype(), output->dtype());
  switch (value.dtype()) {
#define CASE(type)                  \
  case DataTypeToEnum<type>::value: \
    return DoParallelConcatUpdate<CPUDevice, type>(d, value, loc, output);
    TF_CALL_POD_TYPES(CASE);
    TF_CALL_tstring(CASE);
    TF_CALL_variant(CASE);
#undef CASE
    default:
      return errors::InvalidArgument("Unsupported data type: ",
                                     DataTypeString(value.dtype()));
  }
}
```

[tensorflow::functor::DoParallelConcatUpdate — callee — tensorflow/core/kernels/inplace_ops.cc:32-41]
```c++
template <typename Device, typename T>
Status DoParallelConcatUpdate(const Device& d, const Tensor& value, int32_t loc,
                              Tensor* output) {
  auto Tvalue = value.shaped<T, 2>({1, value.NumElements()});
  auto Toutput = output->flat_outer_dims<T>();
  auto nrows = Toutput.dimension(0);
  auto r = (loc % nrows + nrows) % nrows;  // Guard index range.
  Toutput.template chip<0>(r).device(d) = Tvalue.template chip<0>(0);
  return Status::OK();
}
```

[tensorflow::OpKernel — class — tensorflow/core/framework/op_kernel.h]
class OpKernel

Control flow: Compute method receives value and update tensors from context inputs. It validates shape compatibility between value and update, then calls DoParallelConcat which internally calls DoParallelConcatUpdate to perform the actual parallel concat operation using eigen tensor operations.

Data flow: User-controlled input: value tensor from ctx->input(0), update tensor from ctx->input(1), loc_ attribute from kernel construction. The value tensor's shape (specifically dim_size(0)) flows to DoParallelConcatUpdate where it determines memory access bounds. No validation of value.dim_size(0) > loc_ exists in pre-fix version.

Verdict: Vulnerable
Vulnerable region:
[tensorflow/core/kernels/inplace_ops.cc]
```c++
void Compute(OpKernelContext* ctx) override {
    auto value = ctx->input(0);
    auto update = ctx->input(1);

    OP_REQUIRES(
        ctx, value.dims() == update.dims(),
        errors::InvalidArgument("value and update shape doesn't match: ",
                                value.shape().DebugString(), " vs. ",
                                update.shape().DebugString()));
    for (int i = 1; i < value.dims(); ++i) {
      OP_REQUIRES(
          ctx, value.dim_size(i) == update.dim_size(i),
          errors::InvalidArgument("value and update shape doesn't match ",
                                  value.shape().DebugString(), " vs. ",
                                  update.shape().DebugString()));
    }
    OP_REQUIRES(ctx, 1 == update.dim_size(0),
                errors::InvalidArgument("update shape doesn't match: ",
                                        update.shape().DebugString()));

    Tensor output = value;  // This creates an alias intentionally.
    const auto& d = ctx->eigen_device<Device>();
    OP_REQUIRES_OK(
        ctx, ::tensorflow::functor::DoParallelConcat(d, update, loc_, &output));
    ctx->set_output(0, output);
  }

 private:
  int32 loc_;
};
```
CWE: CWE-369
Severity: HIGH

Root cause: The ParallelConcatUpdate::Compute method does not validate that the 0th dimension of the value tensor is greater than loc_ before passing it to DoParallelConcatUpdate. In DoParallelConcatUpdate, the code computes 'auto r = (loc % nrows + nrows) % nrows' where nrows comes from the output tensor's dimension(0). If value.dim_size(0) <= loc_, nrows can be 0 or less, leading to out-of-bounds memory access or undefined behavior (potential division by zero if nrows becomes 0).

Exploitability: An attacker can craft a TensorFlow model or input that invokes _ParallelConcatUpdate with value.dim_size(0) <= loc_, where loc_ is a user-controllable attribute. The parallel_concat operation with shape=0 and insufficient values triggers this condition.

Attack preconditions:
- Attacker can provide a TensorFlow model using _ParallelConcatUpdate or parallel_concat with shape=0
- Attacker can control the 'loc' attribute or shape parameter
- The values tensor has 0th dimension <= loc_

Likely impact: An attacker can craft a TensorFlow model or input that invokes _ParallelConcatUpdate with value.dim_size(0) <= loc_, where loc_ is a user-controllable attribute. The parallel_concat operation with shape=0 and insufficient values triggers this condition.

## Analysis

### Control / Data Flow

`Compute` reads two inputs `value` and `update` from the kernel context and validates:

1. `value.dims() == update.dims()` — same rank.
2. For `i in [1, dims)`: `value.dim_size(i) == update.dim_size(i)` — matching trailing dims.
3. `update.dim_size(0) == 1`.

Then it aliases `output = value` and calls `DoParallelConcat(d, update, loc_, &output)`, which dispatches to `DoParallelConcatUpdate<CPUDevice, T>`.

In `DoParallelConcatUpdate`:
- `Tvalue = value.shaped<T,2>({1, value.NumElements()})` — flattens `update` to `1 × N`.
- `Toutput = output->flat_outer_dims<T>()` — reshapes `output` (which is the original `value` tensor) into `[dim0, prod_of_rest]`.
- `nrows = Toutput.dimension(0)`.
- `r = (loc % nrows + nrows) % nrows` — wraps `loc_` into range.
- Assigns `Toutput.chip<0>(r) = Tvalue.chip<0>(0)`.

### Potential Issues

**1. Zero-rank / zero-dim input.** If `value.dims() == 0`, the loop in step 2 is skipped, but step 3 calls `update.dim_size(0)` — for a 0-dim tensor `dim_size(0)` is typically out of bounds. Actually `OP_REQUIRES(ctx, 1 == update.dim_size(0), ...)` — this will fail or assert. Even if it returns 0, the check `1 == 0` fails and the op errors out. So 0-dim is rejected.

**2. Empty tensor / `nrows == 0`.** If `value.dim_size(0) == 0`, then `nrows == 0`, and `loc % nrows` is integer division by zero — undefined behavior / SIGFPE crash.

Looking at the validation: `update.dim_size(0)` must equal 1. But `value.dim_size(0)` is never constrained — only dims `[1, dims)` are checked against `update`. So `value` can have shape `[0, k, ...]` while `update` has shape `[1, k, ...]`. Then `nrows = 0`, and the modulo op divides by zero.

**3. `loc_` not validated.** `loc_` is a user-supplied attribute, but the modulo wrap makes any value safe (except for the divide-by-zero case above).

**4. Mismatch of `value.NumElements()` vs `Toutput`.** `Tvalue` is `1 × value.NumElements()` where `value` here in `DoParallelConcatUpdate` is the `update` argument (NumElements = product of update shape, which is `1 * d1 * d2 * ...`). `Toutput.chip<0>(r)` has size `prod(value_shape[1:])`. Since `update.dim_size(i) == value.dim_size(i)` for i ≥ 1, and `update.dim_size(0) == 1`, those match. OK.

### Root Cause

The kernel fails to require `value.dim_size(0) >= 1`. When `value` has zero outer dimension, `nrows == 0` causes an integer division by zero in `(loc % nrows + nrows) % nrows`.

### Exploitability

An attacker who can construct a graph or call `ParallelConcatUpdate` (e.g., via `tf.raw_ops.ParallelConcat` / `_ParallelConcatUpdate`) with `value` having shape `[0, ...]` and `update` shape `[1, ...]` (with matching trailing dims) triggers `nrows = 0` and the subsequent `loc % nrows` is integer division by zero, producing a SIGFPE on x86 and crashing the TensorFlow process.

This matches the well-known TF pattern of CHECK/crash issues in raw ops triggered by malformed shapes — denial-of-service.

### Verdict

**Vulnerable** — Denial of Service via integer division by zero (CWE-369) when `value` has a zero-sized outer dimension. The Compute function validates trailing dimensions and `update.dim_size(0)==1`, but never ensures `value.dim_size(0) > 0`, allowing `nrows == 0` to reach the modulo operation in `DoParallelConcatUpdate`.

**Fix:** Add `OP_REQUIRES(ctx, value.dim_size(0) > 0, errors::InvalidArgument(...))` in `Compute`, or guard `nrows` in `DoParallelConcatUpdate`.

tensorflow/core/kernels/inplace_ops.cc:Compute (ParallelConcatUpdate<CPUDevice>::Compute) and the helpers DoParallelConcat / DoParallelConcatUpdate it calls

DoParallelConcatUpdate: 'auto r = (loc % nrows + nrows) % nrows;' where nrows = output->flat_outer_dims<T>().dimension(0); reachable because Compute does not validate value.dim_size(0) > 0

Compute validates that ranks match, that trailing dims of value and update match, and that update.dim_size(0) == 1, but it never requires value.dim_size(0) >= 1. If value has shape [0, d1, ...], then in DoParallelConcatUpdate nrows = Toutput.dimension(0) == 0, and the expression (loc % nrows + nrows) % nrows performs integer modulo by zero (undefined behavior, SIGFPE on x86).

An attacker who can submit a graph or invoke tf.raw_ops._ParallelConcatUpdate (e.g., via a model file or a service that accepts user-defined graphs) can craft tensors where value has a zero outer dimension (e.g., shape [0, k]) and update has shape [1, k]. All explicit OP_REQUIRES checks pass, control reaches DoParallelConcatUpdate, nrows is 0, and the modulo by zero crashes the process.

Ability to control the inputs to the ParallelConcatUpdate kernel, specifically the shape of the 'value' input (zero outer dimension) and matching trailing dims on 'update' with leading dim 1. The 'loc' attribute value is irrelevant.

Denial of service: the TensorFlow runtime aborts due to integer divide-by-zero (SIGFPE) or undefined behavior, terminating the hosting process (e.g., a model-serving server).

Before computing nrows-based modulo, the kernel must ensure nrows > 0; equivalently, Compute should require value.dim_size(0) >= 1 (and arguably value.dims() >= 1).

Not enforced. The for-loop checks dims i>=1, the explicit OP_REQUIRES only constrains update.dim_size(0)==1, and DoParallelConcatUpdate uses nrows directly in a modulo without a zero check. No upstream shape inference in _ParallelConcatUpdate (array_ops.cc) is shown to exclude zero-sized leading dims.

The supplied context does not include _ParallelConcatUpdate's shape function; if it independently rejects empty leading dims, exploitability could be reduced for graph construction paths but eager/raw_ops invocation would likely still reach the kernel. Behavior of integer modulo by zero is platform-dependent but reliably crashes on common x86/x86_64 builds.

_ParallelConcatUpdate (array_ops.cc) -> ParallelConcatUpdate<CPUDevice>::Compute (inplace_ops.cc) -> functor::DoParallelConcat (inplace_ops.cc) -> functor::DoParallelConcatUpdate (inplace_ops.cc) [modulo by zero]


Audit the following candidate code to determine whether it presents a real vulnerability.
This sample may come from a noisy retrieval or transformation stage, so reason from the supplied code and context rather than assuming the label.

Repository: github.com/ScratchVerifier/ScratchOAuth2
Language: PHP
File: includes/api/SpecificApps.php
Function: patch

Call path: SpecificApps::run (includes/api/SpecificApps.php) → SpecificApps::patch (includes/api/SpecificApps.php) → SOA2Apps::update (includes/common/apps.php) → SOA2DB::updateApplication (includes/common/db.php)

### Primary Function

```php
private function patch( int $client_id, int $owner_id ) {
		$data = $this->getRequest()->getBody()->getContents();
		$data = json_decode($data, true);
		if (!$data) return $this->http400();
		if (array_key_exists('flags', $data)) return $this->getResponseFactory()->createHttpError(403);
		if (
			array_key_exists('reset_secret', $data)
			&& !is_bool($data['reset_secret'])
		) return $this->http400();
		if (
			array_key_exists('app_name', $data)
			&& !SOA2Apps::appNameValid($data['app_name'])
		) return $this->http400();
		if (
			array_key_exists('redirect_uris', $data)
			&& !SOA2Apps::redirectURIsValid($data['redirect_uris'])
		) return $this->http400();
		$app = SOA2Apps::update( $client_id, $owner_id, $data );
		if (!$app) return $this->getResponseFactory()->createHttpError(404);
		return $this->getResponseFactory()->createJson($app);
	}
```

### Cross-File Context

[MediaWiki\Extension\ScratchOAuth2\Api\SpecificApps — class — includes/api/SpecificApps.php:15]
class SpecificApps extends SimpleHandler {

[SpecificApps::patch — caller — includes/api/SpecificApps.php:37-57]
private function patch( int $client_id, int $owner_id ) { $data = $this->getRequest()->getBody()->getContents(); $data = json_decode($data, true); if (!$data) return $this->http400(); if (array_key_exists('flags', $data)) return $this->getResponseFactory()->createHttpError(403); if ( array_key_exists('reset_secret', $data) && !is_bool($data['reset_secret']) ) return $this->http400(); if ( array_key_exists('app_name', $data) && !SOA2Apps::appNameValid($data['app_name']) ) return $this->http400(); if ( array_key_exists('redirect_uris', $data) && !SOA2Apps::redirectURIsValid($data['redirect_uris']) ) return $this->http400(); $app = SOA2Apps::update( $client_id, $owner_id, $data ); if (!$app) return $this->getResponseFactory()->createHttpError(404); return $this->getResponseFactory()->createJson($app); }

[MediaWiki\Extension\ScratchOAuth2\Common\SOA2Apps — class — includes/common/apps.php:8]
class SOA2Apps {

[SOA2Apps::update — callee — includes/common/apps.php:109-142]
public static function update( int $client_id, int $owner_id, array $args ) { $app = SOA2DB::getApplication( $client_id, $owner_id, false ); if (!$app) return null; $set = []; if (array_key_exists('reset_secret', $args) && $args['reset_secret']) { $client_secret = bin2hex(random_bytes(64)); $set['client_secret'] = $client_secret; } if ( array_key_exists('flags', $args) && intval($app->flags) != $args['flags'] ) { $set['flags'] = $args['flags']; } else if ( array_key_exists('app_name', $args) && $app->app_name != $args['app_name'] ) { $app_name = $args['app_name']; $set['app_name'] = $app_name; $flags = intval($app->flags); $flags &= ~AppFlags::NAME_APPROVED; if ($app_name === null) $flags |= AppFlags::NAME_APPROVED; $set['flags'] = $flags; } if (!empty($set)) SOA2DB::updateApplication( $client_id, $set ); if (array_key_exists('redirect_uris', $args)) { SOA2DB::deleteRedirectURIs( $client_id ); $redirect_uris = $args['redirect_uris'] ? array_unique(array_filter($args['redirect_uris'])) : null; if ($redirect_uris) SOA2DB::storeRedirectURIs( $client_id, $redirect_uris ); } return self::application( $client_id, $owner_id ); }

[MediaWiki\Extension\ScratchOAuth2\Common\AppFlags — class — includes/common/consts.php:19-23]
class AppFlags { public const NAME_APPROVED = 1; public const VERIFIED = 2; public const HIDE_OWNER = 4; }

[AppFlags::VERIFIED — constant — includes/common/consts.php:21]
AppFlags::VERIFIED → public const VERIFIED = 2;  (includes/common/consts.php:21)

[SOA2DB::updateApplication — sink — includes/common/db.php:126-131]
public static function updateApplication( int $client_id, array $set ) { self::dbw()->update( 'soa2_applications', $set, ['client_id' => $client_id] ); }

[MediaWiki\Extension\ScratchOAuth2\Common\SOA2Apps::userID — method — includes/common/apps.php:13-16]
public static function userID() { global $wgRequest; return $wgRequest->getSession()->get( 'soa2_user_id', null ); }

Control flow: The patch function receives HTTP request body, decodes as JSON, and performs sequential validation checks. If $data is empty/falsy, returns HTTP 400. If 'flags' key exists in parsed JSON, returns HTTP 403 immediately, blocking further processing. Additional checks validate 'reset_secret' is boolean, 'app_name' passes appNameValid(), and 'redirect_uris' passes redirectURIsValid(), each returning HTTP 400 on failure. On all validations passing, SOA2Apps::update() is called with client_id, owner_id, and validated $data. If update returns null (application not found), returns HTTP 404. Otherwise returns JSON response with updated application data.

Data flow: User-controlled input flows from HTTP request body through json_decode into $data array. The 'flags' key is blocked at HTTP 403, preventing it from reaching SOA2Apps::update. The 'app_name' field flows through SOA2Apps::appNameValid() validation before being passed to update. The 'reset_secret' field is type-checked as boolean. The 'redirect_uris' field flows through SOA2Apps::redirectURIsValid(). In SOA2Apps::update, the 'flags' key presence check is evaluated but unreachable from this call path due to the patch function's guard. When 'app_name' changes, flags are modified to clear NAME_APPROVED bit, but the VERIFIED bit (value 2) and HIDE_OWNER bit (value 4) from the existing database record are preserved. All values eventually flow to SOA2DB::updateApplication via parameterized database query.

Verdict: Not Vulnerable

Reviewed region:
[includes/api/SpecificApps.php]
```php
private function patch( int $client_id, int $owner_id ) {
		$data = $this->getRequest()->getBody()->getContents();
		$data = json_decode($data, true);
		if (!$data) return $this->http400();
		if (array_key_exists('flags', $data)) return $this->getResponseFactory()->createHttpError(403);
		if (
			array_key_exists('reset_secret', $data)
			&& !is_bool($data['reset_secret'])
		) return $this->http400();
		if (
			array_key_exists('app_name', $data)
			&& !SOA2Apps::appNameValid($data['app_name'])
		) return $this->http400();
		if (
			array_key_exists('redirect_uris', $data)
			&& !SOA2Apps::redirectURIsValid($data['redirect_uris'])
		) return $this->http400();
		$app = SOA2Apps::update( $client_id, $owner_id, $data );
		if (!$app) return $this->getResponseFactory()->createHttpError(404);
		return $this->getResponseFactory()->createJson($app);
	}
```

Why it is not vulnerable: The 'flags' field, which controls application permissions including VERIFIED status, is explicitly blocked from user input at line 'if (array_key_exists("flags", $data)) return $this->getResponseFactory()->createHttpError(403);' in the patch function. This prevents any direct user-controlled modification of the flags field. While SOA2Apps::update contains logic that modifies flags when app_name changes, it only manipulates the NAME_APPROVED bit (clearing it when the name is changed, setting it when name is null) using bitwise operations on the existing database value. The VERIFIED bit and other administrative flag bits from the database record are preserved and cannot be escalated because the flags field itself cannot be supplied by the user. Additionally, the SOA2Apps::update method only sets the 'flags' key in $set when array_key_exists('flags', $args) is true, which cannot occur via this call path.

Security invariants:
- User cannot supply 'flags' in request body - enforced by 'if (array_key_exists("flags", $data)) return $this->getResponseFactory()->createHttpError(403);'
- Application name must pass validation before update - enforced by 'if (array_key_exists("app_name", $data) && !SOA2Apps::appNameValid($data["app_name"])) return $this->http400();'
- Redirect URIs must pass validation before update - enforced by 'if (array_key_exists("redirect_uris", $data) && !SOA2Apps::redirectURIsValid($data["redirect_uris"])) return $this->http400();'
- reset_secret must be boolean type if present - enforced by 'if (array_key_exists("reset_secret", $data) && !is_bool($data["reset_secret"])) return $this->http400();'
- Database update uses parameterized query through MediaWiki DB layer - enforced by self::dbw()->update() in SOA2DB::updateApplication
- Existing VERIFIED and HIDE_OWNER flag bits are preserved during app_name changes - enforced by bitwise operations in SOA2Apps::update that only clear NAME_APPROVED bit

Invariant verification:
- flags field blocked from user input: holds=true. Evidence: Line 'if (array_key_exists("flags", $data)) return $this->getResponseFactory()->createHttpError(403);' in patch() ensures flags key never reaches SOA2Apps::update
- VERIFIED bit cannot be escalated through app_name modification: holds=true. Evidence: In SOA2Apps::update, flags modification only performs '$flags &= ~AppFlags::NAME_APPROVED' and '$flags |= AppFlags::NAME_APPROVED', which only affects bit 0, preserving bits for VERIFIED (value 2) and HIDE_OWNER (value 4)
- set array only populated for valid keys: holds=true. Evidence: SOA2Apps::update uses 'array_key_exists()' checks before modifying $set, and the flags branch 'if (array_key_exists("flags", $args) && intval($app->flags) != $args["flags"])' cannot execute because flags is blocked upstream
- app_name validates before reaching update: holds=true. Evidence: Line 'if (array_key_exists("app_name", $data) && !SOA2Apps::appNameValid($data["app_name"])) return $this->http400();' in patch() blocks invalid app_name values

Residual risk notes: The SOA2Apps::update method could be called from other code paths not shown in this call path that do not have the same 'flags' key blocking guard. If update() is invoked elsewhere with user-controlled data containing 'flags', the condition 'if (array_key_exists("flags", $args) && intval($app->flags) != $args["flags"])' in update() would allow direct flags modification. The patch function's validation of app_name and redirect_uris depends on the correctness of SOA2Apps::appNameValid() and SOA2Apps::redirectURIsValid(), which are not provided in the supplied context.

## Analysis

### Control Flow

`patch()` parses JSON body, then enforces:
1. Reject if `flags` key present → 403
2. If `reset_secret` present, must be bool
3. If `app_name` present, must pass `appNameValid()`
4. If `redirect_uris` present, must pass `redirectURIsValid()`

Then calls `SOA2Apps::update($client_id, $owner_id, $data)`.

### Data Flow into update()

In `SOA2Apps::update`:
- `reset_secret` → triggers `random_bytes(64)` server-side, no user data flows in
- `flags` → branch reads `$args['flags']` and writes to `$set['flags']`
- `app_name` → user-controlled, validated
- `redirect_uris` → user-controlled, validated

### The `flags` Branch

```php
if (
    array_key_exists('flags', $args)
    && intval($app->flags) != $args['flags']
) {
    $set['flags'] = $args['flags'];
}
```

This branch writes user-supplied `flags` into the database. Critically, `flags` controls `NAME_APPROVED` and `VERIFIED` — privilege-relevant bits. Setting `VERIFIED` (=2) would mark an app as verified, which is a privilege escalation.

However, in `patch()`:
```php
if (array_key_exists('flags', $data)) return $this->getResponseFactory()->createHttpError(403);
```

Any request containing the `flags` key is rejected with 403 *before* reaching `SOA2Apps::update`. So the dangerous branch is unreachable from `patch()`.

### Cross-checking bypasses

- `array_key_exists('flags', $data)` correctly detects keys whose value is `null` (unlike `isset`). So a JSON body like `{"flags": null}` still triggers the 403.
- `json_decode($data, true)` returns associative array, so keys are checked literally.
- Case sensitivity: JSON keys are case-sensitive and PHP array keys are case-sensitive; `Flags` vs `flags` would bypass the check, but the `update()` function also uses lowercase `'flags'`, so a different-cased key wouldn't be written either. The keys must match exactly on both ends.
- Nested arrays: `flags` is a top-level check; `update()` also uses `$args['flags']` at the top level. Aligned.

### Other branches

- `app_name` flow: The `app_name` write goes into `$set['app_name']` and reaches `SOA2DB::updateApplication`, which uses MediaWiki's `dbw()->update()` with parameterized `$set` array — safe from SQL injection. Validation via `appNameValid()` enforces semantic rules.
- `redirect_uris`: validated then passed via array to parameterized DB calls.
- `reset_secret`: server-generated; user input is just a flag.

### Authorization

`$client_id` and `$owner_id` come from `run()` (not shown), and `update()` calls `SOA2DB::getApplication($client_id, $owner_id, false)` — returns null if the owner doesn't match, which translates to a 404. Owner-binding appears intact (assuming `$owner_id` derives from session).

### Residual Uncertainty

- We don't see `run()` to confirm `$owner_id` is bound to the authenticated session user (likely from `SOA2Apps::userID()`). If `$owner_id` were attacker-controlled, the 404 guard would fail. Not visible in supplied code.
- `appNameValid` / `redirectURIsValid` semantics not shown; assumed adequate.

## Verdict

**Not vulnerable** based on the supplied code. The privilege-relevant `flags` mutation in `SOA2Apps::update` is gated by an explicit `array_key_exists('flags', $data)` rejection in `patch()`, which correctly handles null values and matches the key used downstream. SQL writes use parameterized `dbw()->update()`. The remaining inputs are validated before reaching the database.

The only residual concern is the trustworthiness of `$owner_id` passed into `patch()`, which is outside the supplied context.

includes/api/SpecificApps.php:37-57 (patch); includes/common/apps.php:109-142 (update); includes/common/db.php:126-131 (updateApplication)

The privilege-relevant code path in SOA2Apps::update that writes user-supplied 'flags' (which includes NAME_APPROVED and VERIFIED bits) is unreachable from patch() because patch() rejects any request whose JSON body contains the 'flags' key with HTTP 403 before invoking update(). Other inputs are validated and reach a parameterized DB update.

None demonstrable from the supplied code. An attacker would need to bypass the array_key_exists('flags', $data) guard, but it matches the exact lowercase key used downstream and correctly catches null values that isset() would miss.

patch() enforces array_key_exists('flags', $data) → 403 before calling SOA2Apps::update, blocking attacker-controlled writes to the privileged flags column. reset_secret is type-checked (must be bool) and only triggers server-side random_bytes(64). app_name and redirect_uris are validated by SOA2Apps::appNameValid/redirectURIsValid prior to use. The DB sink SOA2DB::updateApplication uses MediaWiki's parameterized dbw()->update() with the $set array and a client_id where-clause, avoiding SQL injection. Owner binding is enforced inside update() via SOA2DB::getApplication($client_id, $owner_id, false) which returns null (→ 404) when the caller is not the owner.

Clients cannot directly set the 'flags' column through the patch endpoint.

Only the application's owner (matched in getApplication) can mutate the application.

All DB writes use parameterized update() with array-based set/where clauses.

reset_secret values are server-generated (random_bytes), not user-supplied.

The 'flags' guard uses array_key_exists which detects keys with null values, matching the literal lowercase 'flags' key consumed downstream. JSON keys are case-sensitive; mismatched-case keys would not be acted on by update() either. Owner check returns null on mismatch leading to 404. SQL is constructed via parameterized API.

run() is not shown; if $owner_id were derived from attacker-controlled input rather than the authenticated session (SOA2Apps::userID), the owner-binding guard could be bypassed. Semantics of appNameValid and redirectURIsValid are not shown; assumed sufficient. These are outside the supplied context.

SpecificApps::run → SpecificApps::patch → SOA2Apps::update → SOA2DB::updateApplication


Audit the following candidate code to determine whether it presents a real vulnerability.
This sample may come from a noisy retrieval or transformation stage, so reason from the supplied code and context rather than assuming the label.

Repository: git.kernel.org/pub/scm/linux/kernel/git/stable/linux
Language: C
File: drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c
Function: otx2_sq_aura_pool_init

Call path: otx2_open (drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c) → otx2_init_hw_resources (drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c) → otx2_sq_aura_pool_init (drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c) → otx2_aura_init (drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c) → otx2_pool_init (drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c) → kcalloc (include/linux/slab.h)

### Primary Function

```c
int otx2_sq_aura_pool_init(struct otx2_nic *pfvf)
{
	int qidx, pool_id, stack_pages, num_sqbs;
	struct otx2_qset *qset = &pfvf->qset;
	struct otx2_hw *hw = &pfvf->hw;
	struct otx2_snd_queue *sq;
	struct otx2_pool *pool;
	dma_addr_t bufptr;
	int err, ptr;

	/* Calculate number of SQBs needed.
	 *
	 * For a 128byte SQE, and 4K size SQB, 31 SQEs will fit in one SQB.
	 * Last SQE is used for pointing to next SQB.
	 */
	num_sqbs = (hw->sqb_size / 128) - 1;
	num_sqbs = (qset->sqe_cnt + num_sqbs) / num_sqbs;

	/* Get no of stack pages needed */
	stack_pages =
		(num_sqbs + hw->stack_pg_ptrs - 1) / hw->stack_pg_ptrs;

	for (qidx = 0; qidx < hw->tot_tx_queues; qidx++) {
		pool_id = otx2_get_pool_idx(pfvf, AURA_NIX_SQ, qidx);
		/* Initialize aura context */
		err = otx2_aura_init(pfvf, pool_id, pool_id, num_sqbs);
		if (err)
			goto fail;

		/* Initialize pool context */
		err = otx2_pool_init(pfvf, pool_id, stack_pages,
				     num_sqbs, hw->sqb_size);
		if (err)
			goto fail;
	}

	/* Flush accumulated messages */
	err = otx2_sync_mbox_msg(&pfvf->mbox);
	if (err)
		goto fail;

	get_cpu();
	/* Allocate pointers and free them to aura/pool */
	for (qidx = 0; qidx < hw->tot_tx_queues; qidx++) {
		pool_id = otx2_get_pool_idx(pfvf, AURA_NIX_SQ, qidx);
		pool = &pfvf->qset.pool[pool_id];

		sq = &qset->sq[qidx];
		sq->sqb_count = 0;
		sq->sqb_ptrs = kcalloc(num_sqbs, sizeof(*sq->sqb_ptrs), GFP_KERNEL);
		if (!sq->sqb_ptrs) {
			err = -ENOMEM;
			goto err_mem;
		}

		for (ptr = 0; ptr < num_sqbs; ptr++) {
			err = otx2_alloc_rbuf(pfvf, pool, &bufptr);
			if (err)
				goto err_mem;
			pfvf->hw_ops->aura_freeptr(pfvf, pool_id, bufptr);
			sq->sqb_ptrs[sq->sqb_count++] = (u64)bufptr;
		}
	}

err_mem:
	put_cpu();
	return err ? -ENOMEM : 0;

fail:
	otx2_mbox_reset(&pfvf->mbox.mbox, 0);
	otx2_aura_pool_free(pfvf);
	return err;
}
```

### Cross-File Context

[get_cpu — macro — include/linux/preempt.h]
get_cpu → #define get_cpu() \n({ int __ret = __get_cpu_nocheck(); preempt_count_inc(); __ret; })  (include/linux/preempt.h)

[put_cpu — macro — include/linux/preempt.h]
put_cpu → #define put_cpu() \n preempt_count_dec();  (include/linux/preempt.h)

[GFP_KERNEL — constant — include/linux/gfp.h]
GFP_KERNEL → (__GFP_RECLAIM | __GFP_COMP | __GFP_MEMALLOC | __GFP_NORETRY | __GFP_NOWARN)  (include/linux/gfp.h)

[kcalloc — sink — include/linux/slab.h]
```c
static inline void *kcalloc(size_t n, size_t size, gfp_t flags)
{
	return kmalloc_array(n, size, flags | __GFP_ZERO);
}
```

[AURA_NIX_SQ — enum — drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h]
#define AURA_NIX_SQ 0x00

[otx2_nic — struct — drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h]
```c
struct otx2_nic {
	struct pci_dev	*pdev;
	struct net_device	*netdev;
	struct otx2_hw	hw;
	struct otx2_qset	qset;
	struct otx2_hw_ops	*hw_ops;
	struct dentry	*dbg_dir;
	/* ... more fields ... */
};
```

[otx2_qset — struct — drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h]
```c
struct otx2_qset {
	struct otx2_snd_queue *sq;
	struct otx2_rcv_queue *rq;
	struct otx2_cq_queue *cq;
	struct otx2_cq_poll *napi;
	int	 sqe_cnt;
	int	 rqe_cnt;
	int	 cq_cnt;
	/* ... more fields ... */
};
```

[otx2_snd_queue — struct — drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h]
```c
struct otx2_snd_queue {
	int	 sqb_count;
	u64	*sqb_ptrs;
	struct sg_list	*sg;
	/* ... more fields ... */
};
```

Control flow: otx2_open calls otx2_init_hw_resources which calls otx2_sq_aura_pool_init. Inside otx2_sq_aura_pool_init, get_cpu() disables preemption before loop, then kcalloc with GFP_KERNEL inside loop causes sleeping function warning. The fix moves get_cpu()/put_cpu() inside the loop around the aura_freeptr call only.

Data flow: pfvf (struct otx2_nic*) passed as argument, containing qset, hw, pool. num_sqbs computed from sqb_size and sqe_cnt. pool_id computed from qidx. bufptr allocated via otx2_alloc_rbuf, then freed to aura via aura_freeptr. sqb_ptrs array holds buffer pointers.

Verdict: Vulnerable
Vulnerable region:
[drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c]
```c
if (err)
		goto fail;

	get_cpu();
	/* Allocate pointers and free them to aura/pool */
	for (qidx = 0; qidx < hw->tot_tx_queues; qidx++) {
		pool_id = otx2_get_pool_idx(pfvf, AURA_NIX_SQ, qidx);
		pool = &pfvf->qset.pool[pool_id];

		sq = &qset->sq[qidx];
		sq->sqb_count = 0;
		sq->sqb_ptrs = kcalloc(num_sqbs, sizeof(*sq->sqb_ptrs), GFP_KERNEL);
		if (!sq->sqb_ptrs) {
			err = -ENOMEM;
			goto err_mem;
		}

		for (ptr = 0; ptr < num_sqbs; ptr++) {
			err = otx2_alloc_rbuf(pfvf, pool, &bufptr);
			if (err)
				goto err_mem;
			pfvf->hw_ops->aura_freeptr(pfvf, pool_id, bufptr);
			sq->sqb_ptrs[sq->sqb_count++] = (u64)bufptr;
		}
	}
```
CWE: CWE-667
Severity: LOW

Root cause: The pre-fix code called get_cpu() before the queue initialization loop, acquiring a CPU in atomic context. Inside the loop, kcalloc() with GFP_KERNEL was called, which can sleep. This violates the atomic context since GFP_KERNEL allocations can block. The put_cpu() was placed after the loop at err_mem, meaning the entire loop ran with preemption disabled while potentially calling sleeping allocation functions.

Exploitability: Not exploitable for privilege escalation; causes kernel panic/warning in atomic context when driver is loaded and network interface is opened.

Attack preconditions:
- Loading octeontx2-pf driver
- Opening network device (otx2_open)
- CONFIG_DEBUG_ATOMIC_SLEEP enabled

Likely impact: Not exploitable for privilege escalation; causes kernel panic/warning in atomic context when driver is loaded and network interface is opened.

Loading octeontx2-pf driver
Opening network device (otx2_open)
CONFIG_DEBUG_ATOMIC_SLEEP enabled

## Analysis

### Control flow review

The function calls `get_cpu()` (which increments preempt count, disabling preemption) before entering the second loop. Inside the loop while preemption is disabled:

1. `kcalloc(num_sqbs, sizeof(*sq->sqb_ptrs), GFP_KERNEL)` is called.
2. `otx2_alloc_rbuf(pfvf, pool, &bufptr)` is called — this typically allocates a DMA buffer.

### Issue 1: Sleeping allocation in atomic context

`get_cpu()` disables preemption. After this point, the code is in atomic context. However:

- `kcalloc(..., GFP_KERNEL)` is a sleeping allocation. `GFP_KERNEL` includes `__GFP_RECLAIM`, which means it can sleep waiting for reclaim.
- `otx2_alloc_rbuf` similarly allocates DMA-coherent buffers and may sleep.

Calling sleeping allocators in atomic context (preemption disabled) triggers the "scheduling while atomic" / "sleeping function called from invalid context" bug. This is a real correctness/robustness issue that the kernel has fixed in this exact function — the upstream fix moves `get_cpu()`/`put_cpu()` to bracket only the actual `aura_freeptr` call (which writes to a per-CPU LMT line), not the surrounding allocations.

### Issue 2: Error path leaks `sqb_ptrs` and allocated DMA buffers

On `err_mem`, the function does `put_cpu()` and returns. It does not free already-allocated `sq->sqb_ptrs` arrays for prior queues, nor the buffers allocated to auras. However the caller (`otx2_init_hw_resources`) likely cleans these up via `otx2_aura_pool_free` / `otx2_free_sq_resources` paths, so this is at most a partial leak depending on caller behavior — not the primary issue.

### Data-flow / attacker influence

This path is reached via `otx2_open` (e.g., bringing the interface up — `CAP_NET_ADMIN` or root-equivalent). Triggering the atomic-context allocation does not require any unprivileged input. The impact is a kernel BUG/warning ("BUG: sleeping function called from invalid context") and potential lock/scheduler corruption when the allocator sleeps with preempt_count > 0.

### Security classification

This is fundamentally a **locking/atomicity bug** (sleeping while atomic), classifiable under CWE-662 (Improper Synchronization) or more loosely CWE-667. It is not a memory-safety issue, not directly exploitable for privilege escalation, and requires admin to trigger (interface bring-up). It is a real kernel correctness defect that produces a warning/BUG splat and can destabilize the scheduler state.

### Verdict

**Vulnerable (low-severity correctness/robustness bug — sleeping in atomic context).**

- **Root cause:** `get_cpu()` disables preemption, but the loop body invokes `kcalloc(GFP_KERNEL)` and `otx2_alloc_rbuf()`, both of which may sleep.
- **Trigger:** Any privileged invocation of `otx2_open` on this NIC (`ip link set up`).
- **Impact:** Kernel "sleeping in atomic context" BUG/WARN; possible scheduler/preempt accounting corruption. Not a direct memory-safety or privilege-escalation primitive. DoS at most, requiring local privilege.
- **Fix shape (matches upstream):** Narrow the `get_cpu()/put_cpu()` window to wrap only `aura_freeptr` (the per-CPU LMT operation), keeping the allocations outside the preempt-disabled region.
- **CWE:** CWE-662 / CWE-667 (improper synchronization — sleeping while holding preemption disabled).

Residual uncertainty: the body of `otx2_alloc_rbuf` and `aura_freeptr` is not shown; if `aura_freeptr` does not actually require preemption disabled here, then `get_cpu()` is gratuitous and the issue is purely the sleep-in-atomic. The supplied code alone is sufficient to confirm the GFP_KERNEL `kcalloc` between `get_cpu()` and `put_cpu()`.

drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c: otx2_sq_aura_pool_init, the second for-loop bracketed by get_cpu()/put_cpu()

get_cpu() disables preemption (preempt_count_inc), placing the subsequent loop body in atomic context, but the body invokes kcalloc(..., GFP_KERNEL) and otx2_alloc_rbuf(), both of which may sleep (GFP_KERNEL contains __GFP_RECLAIM). Sleeping while preemption is disabled violates kernel synchronization rules.

Requires privileged ability to bring the NIC interface up (otx2_open path), typically CAP_NET_ADMIN or root. No unprivileged remote vector. Triggering produces a kernel 'sleeping function called from invalid context' warning/BUG; not a direct memory-safety primitive.

Local privileged user able to invoke ifup on the OcteonTX2 PF/VF interface; CONFIG_DEBUG_ATOMIC_SLEEP makes the bug visibly fire, but the unsafe sleep happens regardless.

Denial-of-service / kernel correctness violation: scheduling-while-atomic BUG splat, possible preempt-count corruption, and degraded scheduler state. No confidentiality/integrity compromise demonstrated by the supplied code.

Code holding preemption disabled (preempt_count > 0 via get_cpu()) must not call functions that may sleep, including kmalloc/kcalloc with GFP_KERNEL or DMA buffer allocators that wait.

Invariant is violated: between get_cpu() and put_cpu(), kcalloc(num_sqbs, sizeof(*sq->sqb_ptrs), GFP_KERNEL) is called directly, and otx2_alloc_rbuf() (a DMA buffer allocation helper) is called per pointer; both can block on reclaim/IO. The only operation that arguably needs per-CPU pinning is the aura_freeptr LMT store; the surrounding allocations should be outside the preempt-disabled window.

Bodies of otx2_alloc_rbuf and hw_ops->aura_freeptr are not shown. If aura_freeptr does not actually need preemption disabled, the get_cpu() is unnecessary; either way, the GFP_KERNEL kcalloc inside the get_cpu/put_cpu region is sufficient to confirm a sleep-in-atomic defect. Error path also leaks per-queue sqb_ptrs allocations unless the caller cleans up via otx2_aura_pool_free, which is not verified here.

otx2_open -> otx2_init_hw_resources -> otx2_sq_aura_pool_init -> (get_cpu) -> kcalloc(GFP_KERNEL) / otx2_alloc_rbuf -> sleep while preempt disabled