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: net/nfc/nci/core.c
Function: nci_close_device

Call path: nci_unregister_device (net/nfc/nci/core.c) → nci_close_device (net/nfc/nci/core.c) → nci_cmd_work (net/nfc/nci/core.c) → nci_cmd_timer (net/nfc/nci/core.c)

### Primary Function

```c
static int nci_close_device(struct nci_dev *ndev)
{
	nci_req_cancel(ndev, ENODEV);

	/* This mutex needs to be held as a barrier for
	 * caller nci_unregister_device
	 */
	mutex_lock(&ndev->req_lock);

	if (!test_and_clear_bit(NCI_UP, &ndev->flags)) {
		/* Need to flush the cmd wq in case
		 * there is a queued/running cmd_work
		 */
		flush_workqueue(ndev->cmd_wq);
		del_timer_sync(&ndev->cmd_timer);
		del_timer_sync(&ndev->data_timer);
		mutex_unlock(&ndev->req_lock);
		return 0;
	}

	/* Drop RX and TX queues */
	skb_queue_purge(&ndev->rx_q);
	skb_queue_purge(&ndev->tx_q);

	/* Flush RX and TX wq */
	flush_workqueue(ndev->rx_wq);
	flush_workqueue(ndev->tx_wq);

	/* Reset device */
	skb_queue_purge(&ndev->cmd_q);
	atomic_set(&ndev->cmd_cnt, 1);

	set_bit(NCI_INIT, &ndev->flags);
	__nci_request(ndev, nci_reset_req, 0,
		      msecs_to_jiffies(NCI_RESET_TIMEOUT));

	/* After this point our queues are empty
	 * and no works are scheduled.
	 */
	ndev->ops->close(ndev);

	clear_bit(NCI_INIT, &ndev->flags);

	del_timer_sync(&ndev->cmd_timer);

	/* Flush cmd wq */
	flush_workqueue(ndev->cmd_wq);

	/* Clear flags except NCI_UNREG */
	ndev->flags &= BIT(NCI_UNREG);

	mutex_unlock(&ndev->req_lock);

	return 0;
}
```

### Cross-File Context

[NCI_UP — constant — include/net/nfc/nci_core.h:39]
NCI_UP → enum nci_flag { NCI_INIT, NCI_UP, NCI_DATA_EXCHANGE, NCI_DATA_EXCHANGE_TO, NCI_UNREG }  (include/net/nfc/nci_core.h:39)

[test_and_clear_bit — macro — include/linux/bitops.h]
test_and_clear_bit → #define test_and_clear_bit(nr, addr) __test_and_clear_bit(NR, addr)  (include/linux/bitops.h)

[del_timer_sync — function — include/linux/timer.h]
```c
extern int del_timer_sync(struct timer_list *);
```

[flush_workqueue — function — include/linux/workqueue.h]
```c
extern void flush_workqueue(struct workqueue_struct *wq);
```

[mod_timer — function — include/linux/timer.h]
```c
extern int mod_timer(struct timer_list *timer, unsigned long expires);
```

[cmd_timer — field — include/net/nfc/nci_core.h:225]
struct timer_list cmd_timer;

[cmd_wq — field — include/net/nfc/nci_core.h:228]
struct workqueue_struct *cmd_wq;

[cmd_work — field — include/net/nfc/nci_core.h:229]
struct work_struct cmd_work;

[nci_cmd_timer — sink — net/nfc/nci/core.c:596]
```c
static void nci_cmd_timer(struct timer_list *t)
{
	struct nci_dev *ndev = from_timer(ndev, t, cmd_timer);

	atomic_set(&ndev->cmd_cnt, 1);
	queue_work(ndev->cmd_wq, &ndev->cmd_work);
}
```

[nci_cmd_work — callee — net/nfc/nci/core.c:1536]
```c
static void nci_cmd_work(struct work_struct *work)
{
	struct nci_dev *ndev = container_of(work, struct nci_dev, cmd_work);
	struct sk_buff *skb;

	pr_debug("cmd_cnt %d\n", atomic_read(&ndev->cmd_cnt));

	/* Send queued command */
	if (atomic_read(&ndev->cmd_cnt)) {
		skb = skb_dequeue(&ndev->cmd_q);
		if (!skb)
			return;

		atomic_dec(&ndev->cmd_cnt);

		pr_debug("NCI TX: MT=cmd, PBF=%d, GID=0x%x, OID=0x%x, plen=%d\n",
			 nci_pbf(skb->data),
			 nci_opcode_gid(nci_opcode(skb->data)),
			 nci_opcode_oid(nci_opcode(skb->data)),
			 nci_plen(skb->data));

		nci_send_frame(ndev, skb);

		mod_timer(&ndev->cmd_timer,
			  jiffies + msecs_to_jiffies(NCI_CMD_TIMEOUT));
	}
}
```

[nci_dev — struct — include/net/nfc/nci_core.h:223]
```c
struct nci_dev {
	struct nfc_dev		*nfc_dev;
	struct nci_ops		*ops;
	struct nci_hci_dev	*hci_dev;
	int			tx_headroom;
	int			tx_tailroom;
	atomic_t		state;
	unsigned long		flags;
	atomic_t		cmd_cnt;
	__u8			cur_conn_id;
	struct list_head	conn_info_list;
	struct nci_conn_info	*rf_conn_info;
	struct timer_list	cmd_timer;
	struct timer_list	data_timer;
	struct workqueue_struct	*cmd_wq;
	struct work_struct	cmd_work;
	struct workqueue_struct	*rx_wq;
	struct work_struct	rx_work;
	struct workqueue_struct	*tx_wq;
	struct work_struct	tx_work;
	struct sk_buff_head	cmd_q;
	struct sk_buff_head	rx_q;
	struct sk_buff_head	tx_q;
	struct mutex		req_lock;
	struct completion	req_completion;
	__u32			req_status;
	__u32			req_result;
	void			*driver_data;
	__u32			poll_prots;
	__u32			target_active_prot;
	struct nfc_target	targets[NCI_MAX_DISCOVERED_TARGETS];
	int			n_targets;
	__u8			nci_ver;
	__u32			nfcc_features;
	__u8			num_supported_rf_interfaces;
	__u8			supported_rf_interfaces[NCI_MAX_SUPPORTED_RF_INTERFACES];
	__u8			max_logical_connections;
	__u16			max_routing_table_size;
	__u8			max_ctrl_pkt_payload_len;
	__u16			max_size_for_large_params;
	__u8			manufact_id;
	__u32			manufact_specific_info;
	struct dest_spec_params cur_params;
	__u8			cur_dest_type;
	struct sk_buff		*rx_data_reassembly;
	__u8 remote_gb[NFC_MAX_GT_LEN];
	__u8 remote_gb_len;
};
```

Control flow: nci_close_device first cancels pending requests via nci_req_cancel, then acquires req_lock as a serialization barrier. It tests NCI_UP flag: if device is already down (NCI_UP cleared), it flushes cmd_wq, then deletes cmd_timer and data_timer before returning. If device is up (NCI_UP set), it purges rx_q, tx_q, and cmd_q queues; flushes rx_wq and tx_wq workqueues; resets cmd_cnt to 1 and sets NCI_INIT flag; sends a reset request via __nci_request; calls driver's close operation; clears NCI_INIT flag; deletes cmd_timer with del_timer_sync; flushes cmd_wq; clears all flags except NCI_UNREG; releases mutex and returns.

Data flow: The function takes ndev (struct nci_dev) as input. Key state modifications include: clearing NCI_UP flag atomically, purging skb queues (rx_q, tx_q, cmd_q), resetting cmd_cnt atomic counter to 1, toggling NCI_INIT flag, and clearing device flags at the end. The cmd_timer and cmd_wq are shutdown resources whose deletion/flush ordering determines safety. nci_cmd_timer callback sets cmd_cnt to 1 and queues cmd_work. nci_cmd_work checks cmd_cnt, dequeues from cmd_q, and if successful sends the frame and re-arms cmd_timer via mod_timer.

Verdict: Not Vulnerable

Reviewed region:
[net/nfc/nci/core.c]
```c
static int nci_close_device(struct nci_dev *ndev)
{
	nci_req_cancel(ndev, ENODEV);

	/* This mutex needs to be held as a barrier for
	 * caller nci_unregister_device
	 */
	mutex_lock(&ndev->req_lock);

	if (!test_and_clear_bit(NCI_UP, &ndev->flags)) {
		/* Need to flush the cmd wq in case
		 * there is a queued/running cmd_work
		 */
		flush_workqueue(ndev->cmd_wq);
		del_timer_sync(&ndev->cmd_timer);
		del_timer_sync(&ndev->data_timer);
		mutex_unlock(&ndev->req_lock);
		return 0;
	}

	/* Drop RX and TX queues */
	skb_queue_purge(&ndev->rx_q);
	skb_queue_purge(&ndev->tx_q);

	/* Flush RX and TX wq */
	flush_workqueue(ndev->rx_wq);
	flush_workqueue(ndev->tx_wq);

	/* Reset device */
	skb_queue_purge(&ndev->cmd_q);
	atomic_set(&ndev->cmd_cnt, 1);

	set_bit(NCI_INIT, &ndev->flags);
	__nci_request(ndev, nci_reset_req, 0,
		      msecs_to_jiffies(NCI_RESET_TIMEOUT));

	/* After this point our queues are empty
	 * and no works are scheduled.
	 */
	ndev->ops->close(ndev);

	clear_bit(NCI_INIT, &ndev->flags);

	del_timer_sync(&ndev->cmd_timer);

	/* Flush cmd wq */
	flush_workqueue(ndev->cmd_wq);

	/* Clear flags except NCI_UNREG */
	ndev->flags &= BIT(NCI_UNREG);

	mutex_unlock(&ndev->req_lock);

	return 0;
}
```

Why it is not vulnerable: Although del_timer_sync(&ndev->cmd_timer) is called before flush_workqueue(ndev->cmd_wq) in the NCI_UP path, the ordering is safe because cmd_q is purged via skb_queue_purge(&ndev->cmd_q) before del_timer_sync. If cmd_timer fires between queue_purge and del_timer_sync, it queues cmd_work via queue_work. del_timer_sync ensures the timer callback completes before returning. When flush_workqueue subsequently runs, any such cmd_work executes but will find cmd_q empty (since it was purged), so nci_cmd_work returns early without calling mod_timer on the deleted timer. The early return path (when NCI_UP is cleared) correctly flushes cmd_wq before deleting timers.

Security invariants:
- cmd_q must be purged before cmd_timer deletion so any cmd_work triggered by the timer finds an empty queue and returns early without re-arming the timer
- del_timer_sync must complete before flush_workqueue to ensure the timer cannot fire and queue new work after the flush begins
- req_lock must be held throughout the close sequence to prevent re-entrant calls or concurrent modifications
- All workqueues (rx_wq, tx_wq, cmd_wq) must be flushed to drain any queued work items before device resources are finalized
- The NCI_INIT flag protects against re-entry during the reset/close sequence

Invariant verification:
- cmd_q purged before cmd_timer deletion prevents mod_timer on deleted timer: holds=true. Evidence: skb_queue_purge(&ndev->cmd_q) is called before del_timer_sync(&ndev->cmd_timer). When nci_cmd_work runs from the workqueue, it calls skb_dequeue(&ndev->cmd_q) which returns NULL on empty queue, causing early return before mod_timer is reached.
- Early return path flushes workqueue before deleting timers: holds=true. Evidence: When NCI_UP is not set: flush_workqueue(ndev->cmd_wq) is called before del_timer_sync(&ndev->cmd_timer) and del_timer_sync(&ndev->data_timer).

Residual risk notes: The ordering of del_timer_sync before flush_workqueue in the NCI_UP path relies on the invariant that cmd_q is purged first. If future modifications change the order of operations or add new ways to queue work to cmd_wq from within __nci_request or ndev->ops->close, the current ordering could become unsafe. Additionally, the safety depends on nci_cmd_work's early return behavior when cmd_q is empty; any changes to that function's control flow could affect this analysis.

## Vulnerability Analysis: Use-After-Free in nci_close_device

### Vulnerable Code Path

The **early return path** in `nci_close_device` contains a race condition:

```c
if (!test_and_clear_bit(NCI_UP, &ndev->flags)) {
    flush_workqueue(ndev->cmd_wq);      // [1] Flush work
    del_timer_sync(&ndev->cmd_timer);   // [2] Delete timer
    // ...
    return 0;
}
```

### Root Cause: Incorrect Cleanup Order

The vulnerability stems from flushing the workqueue before deleting the timer. This creates a race window:

**Race Sequence:**
1. `flush_workqueue(cmd_wq)` waits for `nci_cmd_work` to complete
2. `nci_cmd_work` executes and calls `mod_timer(&ndev->cmd_timer, ...)` to reschedule the timer
3. `flush_workqueue` returns (work completed)
4. **[Race Window]** Timer fires before `del_timer_sync` executes
5. `nci_cmd_timer` callback runs, calling `queue_work(ndev->cmd_wq, &ndev->cmd_work)`
6. `del_timer_sync` executes and deletes timer
7. Function returns, device may be freed
8. **Queued work from step 5 executes on freed device → Use-After-Free**

### Evidence from Code

**Timer callback queues work:**
```c
static void nci_cmd_timer(struct timer_list *t) {
    struct nci_dev *ndev = from_timer(ndev, t, cmd_timer);
    atomic_set(&ndev->cmd_cnt, 1);
    queue_work(ndev->cmd_wq, &ndev->cmd_work);  // Queues new work
}
```

**Work function reschedules timer:**
```c
static void nci_cmd_work(struct work_struct *work) {
    // ... process command ...
    mod_timer(&ndev->cmd_timer,
              jiffies + msecs_to_jiffies(NCI_CMD_TIMEOUT));  // Reschedules timer
}
```

### Contrast with Main Path (Correct Order)

The main path uses the correct order:
```c
del_timer_sync(&ndev->cmd_timer);      // Delete timer FIRST
flush_workqueue(ndev->cmd_wq);         // Then flush workqueue
```

This prevents the race because:
1. Timer is deleted first → no new work can be queued by timer callback
2. Workqueue flush waits for any already-queued work
3. No work remains after both operations complete

### Attack Preconditions

- Device must be in state where `NCI_UP` flag is not set (early return path)
- Active command processing that reschedules the timer
- Precise timing to hit the race window between flush and del_timer_sync
- Ability to trigger device unregistration (e.g., device removal, driver unload)

### Impact

**Use-After-Free consequences:**
- Kernel memory corruption when freed `nci_dev` structure is accessed
- Potential kernel crash (denial of service)
- Possible privilege escalation if attacker can control freed memory contents
- Queue operations on destroyed workqueue

### Verdict: VULNERABLE

**Classification:** CWE-416 (Use After Free)
**Severity:** Medium-High (kernel memory safety violation)
**Type:** Race condition in cleanup path

**Fix:** Swap the order in the early return path to match the main path:
```c
del_timer_sync(&ndev->cmd_timer);    // Timer first
del_timer_sync(&ndev->data_timer);
flush_workqueue(ndev->cmd_wq);       // Workqueue second
```

```json
{
  "vulnerable": true,
  "cwe": "CWE-416",
  "severity": "HIGH",
  "reviewed_region": "net/nfc/nci/core.c:nci_close_device, lines handling early return path when NCI_UP flag is not set",
  "vulnerable_region": "Early return path in nci_close_device: flush_workqueue(ndev->cmd_wq) followed by del_timer_sync(&ndev->cmd_timer)",
  "root_cause": "Incorrect cleanup order creates race condition where timer callback can queue work after workqueue flush completes but before timer deletion, leading to use-after-free when queued work executes on freed device structure",
  "exploitability": "Medium - requires precise timing to exploit race window between flush_workqueue completion and del_timer_sync execution. Attacker needs ability to trigger device closure with NCI_UP flag unset and active command processing that reschedules timer. Race window is narrow but exploitable with repeated attempts.",
  "attack_preconditions": [
    "Device in state where NCI_UP flag is not set (triggers early return path)",
    "Active NCI command processing with pending commands in cmd_q",
    "nci_cmd_work executing and calling mod_timer to reschedule cmd_timer",
    "Ability to trigger nci_close_device (via device removal, driver unload, or nci_unregister_device)",
    "Timing precision to hit race window between workqueue flush and timer deletion"
  ],
  "impact": "Use-after-free vulnerability enabling kernel memory corruption. Timer callback (nci_cmd_timer) queues work on cmd_wq after device may be freed, causing nci_cmd_work to access freed nci_dev structure. Consequences include kernel crash (DoS), memory corruption, and potential privilege escalation if attacker controls freed memory contents.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Main execution path (when NCI_UP is set) uses correct cleanup order: del_timer_sync before flush_workqueue, preventing this race. Vulnerability is isolated to early return path. Fix requires swapping cleanup order in early return to match main path.",
  "call_path": "nci_unregister_device → nci_close_device → [race: nci_cmd_work reschedules timer] → nci_cmd_timer → queue_work(cmd_wq, cmd_work) → [device freed] → nci_cmd_work executes on freed memory"
}
```


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: github.com/PrestaShop/PrestaShop
Language: PHP
File: controllers/admin/AdminModulesPositionsController.php
Function: ajaxProcessGetPossibleHookingListForModule

Call path: ajaxProcessGetPossibleHookingListForModule (controllers/admin/AdminModulesPositionsController.php) → Tools::getValue (classes/Tools.php) → Module::getInstanceById (classes/module/Module.php) → Module::getPossibleHooksList (classes/module/Module.php)

### Primary Function

```php
public function ajaxProcessGetPossibleHookingListForModule()
    {
        $module_id = (int) Tools::getValue('module_id');
        if ($module_id == 0) {
            die('{"hasError" : true, "errors" : ["Wrong module ID."]}');
        }

        $module_instance = Module::getInstanceById($module_id);
        die(json_encode($module_instance->getPossibleHooksList()));
    }
```

### Cross-File Context

[AdminModulesPositionsControllerCore — class — controllers/admin/AdminModulesPositionsController.php:31]
class AdminModulesPositionsControllerCore extends AdminController

[access — method — classes/controller/AdminController.php:783-801]
public function access($action, $disable = false) { if (empty($this->tabAccess[$action])) { $slugs = []; foreach ((array) Access::getAuthorizationFromLegacy($action) as $roleSuffix) { $slugs[] = $this->getTabSlug() . $roleSuffix; } $this->tabAccess[$action] = Access::isGranted( $slugs, $this->context->employee->id_profile ); } return $this->tabAccess[$action]; }

[getTabSlug — method — classes/controller/AdminController.php:4722-4728]
public function getTabSlug() { if (empty($this->tabSlug)) { $this->tabSlug = Access::findSlugByIdTab($this->id); } return $this->tabSlug; }

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: ajaxProcessGetPossibleHookingListForModule is an AJAX handler called via HTTP request. Pre-fix: directly retrieves module_id, validates it is non-zero, fetches module instance, and returns hooks list. Post-fix: wraps entire logic in $this->access('view') authorization check.

Data flow: module_id flows from $_GET['module_id'] through (int) cast to Module::getInstanceById(). The resulting module instance's getPossibleHooksList() is returned as JSON. Pre-fix has no authorization gate; post-fix adds access('view') check.

Verdict: Vulnerable
Vulnerable region:
[controllers/admin/AdminModulesPositionsController.php]
```php
*/
    public function ajaxProcessGetPossibleHookingListForModule()
    {
        $module_id = (int) Tools::getValue('module_id');
        if ($module_id == 0) {
            die('{"hasError" : true, "errors" : ["Wrong module ID."]}');
        }

        $module_instance = Module::getInstanceById($module_id);
        die(json_encode($module_instance->getPossibleHooksList()));
    }
```
CWE: CWE-269
Severity: MEDIUM

Root cause: Missing authorization check in ajaxProcessGetPossibleHookingListForModule(). The method directly reads module_id from request and returns possible hooks for any module without verifying if the current user has view permission on the AdminModulesPositions controller.

Exploitability: An authenticated admin user can make a direct AJAX request to this endpoint with a module_id parameter and receive the module's possible hooks list without any authorization check.

Attack preconditions:
- Attacker must have valid admin credentials to access PrestaShop admin panel
- Attacker must be able to make HTTP requests to the admin AJAX endpoint

Likely impact: An authenticated admin user can make a direct AJAX request to this endpoint with a module_id parameter and receive the module's possible hooks list without any authorization check.

## Vulnerability Assessment: Missing Authorization Check

### Verdict: VULNERABLE

**Classification:** CWE-862 (Missing Authorization) / OWASP A01:2021 (Broken Access Control)

### Root Cause

The `ajaxProcessGetPossibleHookingListForModule` method lacks explicit authorization verification before exposing module hook information. While the controller extends `AdminController` (requiring admin authentication), there's no check verifying the authenticated user has permission to access module position data.

```php
public function ajaxProcessGetPossibleHookingListForModule()
{
    $module_id = (int) Tools::getValue('module_id');
    if ($module_id == 0) {
        die('{"hasError" : true, "errors" : ["Wrong module ID."]}');
    }

    // NO AUTHORIZATION CHECK HERE
    $module_instance = Module::getInstanceById($module_id);
    die(json_encode($module_instance->getPossibleHooksList()));
}
```

The cross-file context shows `AdminController::access()` exists for permission checking, but it's never invoked in this method.

### Vulnerable Behavior

**Data Flow:**
1. User-controlled `module_id` parameter → integer cast → zero check
2. `Module::getInstanceById($module_id)` retrieves arbitrary module by ID
3. `getPossibleHooksList()` exposes module's hook configuration
4. JSON-encoded response sent to requester

**Missing Guard:**
No call to `$this->access('view')` or equivalent permission check before exposing module data.

### Attack Preconditions

1. **Authentication required:** Attacker needs valid admin credentials (any privilege level)
2. **Endpoint accessible:** AJAX endpoint must be reachable via admin routing
3. **No framework-level enforcement:** PrestaShop framework doesn't automatically enforce granular permissions on `ajaxProcess*` methods

### Exploitation

**Module Enumeration Attack:**
```
POST /admin-dev/ajax.php
controller=AdminModulesPositions
action=getPossibleHookingListForModule
module_id=1  // iterate 1..N
```

Low-privilege admin (e.g., "View Orders" only) can:
- Enumerate all installed modules by iterating module IDs
- Extract hook lists revealing module capabilities and integration points
- Map system architecture without proper authorization

### Impact

**Information Disclosure:**
- Reveals installed modules and their technical capabilities
- Exposes hook architecture (integration points, event handlers)
- Aids reconnaissance for targeted attacks on specific modules
- Violates principle of least privilege

**Severity:** Medium
- Requires authenticated admin access (reduces attack surface)
- Discloses technical information rather than sensitive user data
- Enables further attacks but isn't directly exploitable for data theft

### Residual Uncertainty

Cannot definitively confirm from provided code:
1. Whether PrestaShop framework enforces authorization before routing to `ajaxProcess*` methods
2. If `Module::getInstanceById()` contains internal authorization logic
3. Exact permission model for "AdminModulesPositions" tab

However, **secure coding practice mandates explicit authorization checks at the method level**, and their absence constitutes a vulnerability regardless of potential framework-level protections.

### Recommended Fix

```php
public function ajaxProcessGetPossibleHookingListForModule()
{
    // Add explicit authorization check
    if (!$this->access('view')) {
        die('{"hasError" : true, "errors" : ["Access denied."]}');
    }

    $module_id = (int) Tools::getValue('module_id');
    if ($module_id == 0) {
        die('{"hasError" : true, "errors" : ["Wrong module ID."]}');
    }

    $module_instance = Module::getInstanceById($module_id);

    // Add null check for robustness
    if (!$module_instance) {
        die('{"hasError" : true, "errors" : ["Module not found."]}');
    }

    die(json_encode($module_instance->getPossibleHooksList()));
}
```

```json
{
  "vulnerable": true,
  "cwe": "CWE-862",
  "severity": "Medium",
  "reviewed_region": "controllers/admin/AdminModulesPositionsController.php:ajaxProcessGetPossibleHookingListForModule",
  "vulnerable_region": "controllers/admin/AdminModulesPositionsController.php:ajaxProcessGetPossibleHookingListForModule (lines containing module retrieval and hook list exposure without authorization check)",
  "root_cause": "The ajaxProcessGetPossibleHookingListForModule method exposes module hook configuration data without verifying the authenticated admin user has permission to view module position information. While the controller extends AdminController (requiring admin authentication), no explicit authorization check using the available access() method is performed before retrieving and returning sensitive module data.",
  "exploitability": "High exploitability given admin authentication. An attacker with any level of admin credentials can enumerate all installed modules by iterating module_id values (1..N) and extract their hook lists. The only input validation is integer casting and zero-check, which does not prevent unauthorized access. The attack requires: (1) valid admin session cookie, (2) knowledge of the AJAX endpoint structure, (3) ability to send POST requests with module_id parameter. No CSRF protection is evident in the provided code.",
  "attack_preconditions": [
    "Attacker must have valid admin credentials (any privilege level)",
    "Access to admin panel AJAX endpoints",
    "Knowledge of AdminModulesPositions controller and getPossibleHookingListForModule action",
    "Ability to send HTTP requests with module_id parameter"
  ],
  "impact": "Information disclosure vulnerability allowing unauthorized enumeration of installed modules and their hook configurations. Low-privilege admin users (e.g., with only order viewing permissions) can map the entire module architecture, revealing integration points, event handlers, and technical capabilities. This information aids reconnaissance for targeted attacks against specific modules or hooks. Does not directly expose user data but violates principle of least privilege and defense in depth.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Cannot confirm from provided code whether: (1) PrestaShop framework enforces authorization before routing to ajaxProcess* methods at framework level, (2) Module::getInstanceById() contains internal authorization logic, (3) Module::getPossibleHooksList() filters results based on caller permissions. However, absence of explicit authorization check at method level violates secure coding principles regardless of potential framework protections. The access() method is available in parent AdminController but never invoked.",
  "call_path": "ajaxProcessGetPossibleHookingListForModule → Tools::getValue → Module::getInstanceById → Module::getPossibleHooksList"
}
```


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/torvalds/linux
Language: C
File: security/keys/keyctl.c
Function: keyctl_set_reqkey_keyring

Call path: sys_keyctl (security/keys/keyctl.c) → keyctl_set_reqkey_keyring (security/keys/keyctl.c) → install_thread_keyring_to_cred (security/keys/process_keys.c) → keyring_alloc (security/keys/keyring.c) → commit_creds (kernel/cred.c)

### Primary Function

```c
long keyctl_set_reqkey_keyring(int reqkey_defl)
{
	struct cred *new;
	int ret, old_setting;

	old_setting = current_cred_xxx(jit_keyring);

	if (reqkey_defl == KEY_REQKEY_DEFL_NO_CHANGE)
		return old_setting;

	new = prepare_creds();
	if (!new)
		return -ENOMEM;

	switch (reqkey_defl) {
	case KEY_REQKEY_DEFL_THREAD_KEYRING:
		ret = install_thread_keyring_to_cred(new);
		if (ret < 0)
			goto error;
		goto set;

	case KEY_REQKEY_DEFL_PROCESS_KEYRING:
		ret = install_process_keyring_to_cred(new);
		if (ret < 0) {
			if (ret != -EEXIST)
				goto error;
			ret = 0;
		}
		goto set;

	case KEY_REQKEY_DEFL_DEFAULT:
	case KEY_REQKEY_DEFL_SESSION_KEYRING:
	case KEY_REQKEY_DEFL_USER_KEYRING:
	case KEY_REQKEY_DEFL_USER_SESSION_KEYRING:
	case KEY_REQKEY_DEFL_REQUESTOR_KEYRING:
		goto set;

	case KEY_REQKEY_DEFL_NO_CHANGE:
	case KEY_REQKEY_DEFL_GROUP_KEYRING:
	default:
		ret = -EINVAL;
		goto error;
	}

set:
	new->jit_keyring = reqkey_defl;
	commit_creds(new);
	return old_setting;
error:
	abort_creds(new);
	return ret;
}
```

### Cross-File Context

[KEY_REQKEY_DEFL_THREAD_KEYRING — constant — include/uapi/linux/keyctl.h:30]
KEY_REQKEY_DEFL_THREAD_KEYRING → 1  (include/uapi/linux/keyctl.h:30)

[KEY_REQKEY_DEFL_PROCESS_KEYRING — constant — include/uapi/linux/keyctl.h:31]
KEY_REQKEY_DEFL_PROCESS_KEYRING → 2  (include/uapi/linux/keyctl.h:31)

[install_thread_keyring_to_cred — callee — security/keys/process_keys.c:134]
```c
int install_thread_keyring_to_cred(struct cred *new)
{
	struct key *keyring;

	keyring = keyring_alloc("_tid", new->uid, new->gid, new,
				KEY_POS_ALL | KEY_USR_VIEW,
				KEY_ALLOC_QUOTA_OVERRUN,
				NULL, NULL);
	if (IS_ERR(keyring))
		return PTR_ERR(keyring);

	new->thread_keyring = keyring;
	return 0;
}
```

[install_process_keyring_to_cred — function — security/keys/process_keys.c:178]
```c
int install_process_keyring_to_cred(struct cred *new)
{
	struct key *keyring;

	if (new->process_keyring)
		return -EEXIST;

	keyring = keyring_alloc("_pid", new->uid, new->gid, new,
				KEY_POS_ALL | KEY_USR_VIEW,
				KEY_ALLOC_QUOTA_OVERRUN,
				NULL, NULL);
	if (IS_ERR(keyring))
		return PTR_ERR(keyring);

	new->process_keyring = keyring;
	return 0;
}
```

[install_thread_keyring — function — security/keys/process_keys.c:149]
```c
static int install_thread_keyring(void)
{
	struct cred *new;
	int ret;

	new = prepare_creds();
	if (!new)
		return -ENOMEM;

	BUG_ON(new->thread_keyring);

	ret = install_thread_keyring_to_cred(new);
	if (ret < 0) {
		abort_creds(new);
		return ret;
	}

	return commit_creds(new);
}
```

[install_process_keyring — function — security/keys/process_keys.c:198]
```c
static int install_process_keyring(void)
{
	struct cred *new;
	int ret;

	new = prepare_creds();
	if (!new)
		return -ENOMEM;

	ret = install_process_keyring_to_cred(new);
	if (ret < 0) {
		abort_creds(new);
		return ret != -EEXIST ? ret : 0;
	}

	return commit_creds(new);
}
```

[struct cred — struct — include/linux/cred.h:138]
```c
struct cred {
	...
	struct key	*thread_keyring; /* keyring private to this thread */
	struct key	*process_keyring; /* keyring private to this process */
	...
};
```

[struct key — struct — include/linux/key.h]
```c
struct key {
	...
};
```

[keyring_alloc — helper — security/keys/keyring.c]
```c
struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid, const struct cred *cred, key_perm_t perm, unsigned long flags);
```

[prepare_creds — function — kernel/cred.c]
```c
struct cred *prepare_creds(void);
```

[commit_creds — helper — kernel/cred.c]
```c
int commit_creds(struct cred *new);
```

Control flow: User calls keyctl(KEYCTL_SET_REQKEY_KEYRING, KEY_REQKEY_DEFL_THREAD_KEYRING) which routes to keyctl_set_reqkey_keyring(). This function prepares new credentials, then for THREAD_KEYRING case calls install_thread_keyring_to_cred() which allocates a new keyring via keyring_alloc() and assigns it to new->thread_keyring. The creds are then committed via commit_creds(), which replaces the old creds. The orphaned old thread keyring is never freed.

Data flow: reqkey_defl (user-controlled) -> keyctl_set_reqkey_keyring() -> install_thread_keyring_to_cred(new) -> keyring_alloc() creates new keyring. The new keyring pointer is stored in new->thread_keyring, overwriting the old pointer which becomes leaked. commit_creds(new) atomically replaces current->cred with new, orphaning the old cred structure containing the old thread keyring.

Verdict: Vulnerable
Vulnerable region:
[security/keys/keyctl.c]
```c
case KEY_REQKEY_DEFL_THREAD_KEYRING:
		ret = install_thread_keyring_to_cred(new);
		if (ret < 0)
			goto error;
		goto set;
```
CWE: CWE-404
Severity: MEDIUM

Root cause: In security/keys/process_keys.c, install_thread_keyring_to_cred() does not check if new->thread_keyring already exists before calling keyring_alloc(). When keyctl_set_reqkey_keyring(KEY_REQKEY_DEFL_THREAD_KEYRING) is called repeatedly, each invocation creates a new keyring and overwrites the cred->thread_keyring pointer without freeing the previous keyring, orphaning it and causing memory consumption. Additionally, install_thread_keyring() had a BUG_ON(new->thread_keyring) that would cause a kernel panic if a thread keyring already existed.

Exploitability: A local user can trigger the vulnerability by calling keyctl_set_reqkey_keyring(KEY_REQKEY_DEFL_THREAD_KEYRING) in a loop. Each call creates a new thread keyring without freeing the previous one, leading to unbounded memory growth until system resources are exhausted.

Attack preconditions:
- Local user access to the system
- Ability to call keyctl() system call with KEYCTL_SET_REQKEY_KEYRING command
- No special privileges required

Likely impact: A local user can trigger the vulnerability by calling keyctl_set_reqkey_keyring(KEY_REQKEY_DEFL_THREAD_KEYRING) in a loop. Each call creates a new thread keyring without freeing the previous one, leading to unbounded memory growth until system resources are exhausted.

## Vulnerability: Reference Leak in Thread Keyring Installation

**Verdict: VULNERABLE**

### Root Cause

`install_thread_keyring_to_cred` unconditionally overwrites `new->thread_keyring` without checking for or releasing an existing keyring reference:

```c
int install_thread_keyring_to_cred(struct cred *new)
{
    struct key *keyring;

    keyring = keyring_alloc(...);  // Allocate new keyring
    if (IS_ERR(keyring))
        return PTR_ERR(keyring);

    new->thread_keyring = keyring;  // Unconditional overwrite - LEAK!
    return 0;
}
```

Compare with the process keyring path which has protection:

```c
int install_process_keyring_to_cred(struct cred *new)
{
    if (new->process_keyring)
        return -EEXIST;  // Prevents overwrite
    ...
}
```

### Exploitation Path

1. Thread calls `keyctl_set_reqkey_keyring(KEY_REQKEY_DEFL_THREAD_KEYRING)` with existing thread keyring
2. `prepare_creds()` copies current credentials, incrementing refcount on existing keyring A (refcount: N → N+1)
3. `install_thread_keyring_to_cred(new)` allocates new keyring B and overwrites pointer
4. `commit_creds(new)` commits credentials with keyring B
5. Old credentials released, decrementing keyring A's refcount (N+1 → N)
6. **The reference to keyring A that was in `new->thread_keyring` is lost - permanent leak**

Attacker repeats steps 1-6 to leak arbitrary amounts of kernel memory.

### Attack Preconditions

- Unprivileged user access to `keyctl()` system call
- No special capabilities required
- Repeatable without rate limiting

### Impact

- **Memory exhaustion**: Each call leaks one keyring structure reference
- **Denial of Service**: Sustained exploitation exhausts kernel memory, causing system instability or crash
- **Persistent**: Leaked references never freed (refcount never reaches zero)

### Evidence

The kernel developers recognized this pattern - `install_thread_keyring()` includes `BUG_ON(new->thread_keyring)` asserting the keyring must be NULL. But `keyctl_set_reqkey_keyring()` lacks this guard, allowing user-triggered overwrites.

**Classification**: CWE-401 (Missing Release of Memory after Effective Lifetime) / Memory Leak leading to Denial of Service

```json
{
  "vulnerable": true,
  "cwe": "CWE-401",
  "severity": "HIGH",
  "reviewed_region": "security/keys/keyctl.c:keyctl_set_reqkey_keyring",
  "vulnerable_region": "security/keys/keyctl.c:keyctl_set_reqkey_keyring lines 17-19 (KEY_REQKEY_DEFL_THREAD_KEYRING case) calling install_thread_keyring_to_cred without checking for existing thread_keyring",
  "root_cause": "install_thread_keyring_to_cred unconditionally overwrites new->thread_keyring without releasing the existing keyring reference that was copied by prepare_creds(). When prepare_creds() duplicates credentials, it increments the reference count on the existing thread_keyring. The subsequent overwrite in install_thread_keyring_to_cred loses this reference without decrementing the count, causing a permanent reference leak.",
  "exploitability": "HIGH - Directly exploitable by unprivileged users through the keyctl() system call with KEY_REQKEY_DEFL_THREAD_KEYRING parameter. No special capabilities required. The vulnerability can be triggered repeatedly to leak arbitrary amounts of kernel memory. Each invocation leaks one keyring structure reference that will never be freed.",
  "attack_preconditions": [
    "Attacker has access to keyctl() system call (standard on Linux systems)",
    "Thread must have an existing thread_keyring (can be established by prior keyctl call)",
    "No rate limiting on keyctl_set_reqkey_keyring calls",
    "No special privileges or capabilities required"
  ],
  "impact": "Kernel memory exhaustion leading to denial of service. Each call to keyctl_set_reqkey_keyring(KEY_REQKEY_DEFL_THREAD_KEYRING) when a thread_keyring already exists leaks one keyring structure reference. Sustained exploitation exhausts kernel memory pools, causing system instability, performance degradation, or complete system crash. Leaked references are permanent (refcount never reaches zero) and cannot be reclaimed without reboot.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The install_process_keyring_to_cred function correctly checks for existing process_keyring and returns -EEXIST, which keyctl_set_reqkey_keyring handles by treating as success (ret = 0). This demonstrates the developers understood the overwrite risk but failed to apply the same protection to install_thread_keyring_to_cred. The standalone install_thread_keyring() function includes BUG_ON(new->thread_keyring) assertion, further confirming that overwriting thread_keyring is a known dangerous operation that should be prevented.",
  "call_path": "sys_keyctl → keyctl_set_reqkey_keyring → install_thread_keyring_to_cred → keyring_alloc"
}
```


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: kernel/bpf/verifier.c
Function: adjust_scalar_min_max_vals

Call path: bpf_prog_load (kernel/bpf/syscall.c) → bpf_check (kernel/bpf/verifier.c) → check_alu_op (kernel/bpf/verifier.c) → adjust_scalar_min_max_vals (kernel/bpf/verifier.c) → adjust_ptr_min_max_vals (kernel/bpf/verifier.c)

### Primary Function

```c
static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
				      struct bpf_insn *insn,
				      struct bpf_reg_state *dst_reg,
				      struct bpf_reg_state src_reg)
{
	struct bpf_reg_state *regs = cur_regs(env);
	u8 opcode = BPF_OP(insn->code);
	bool src_known;
	s64 smin_val, smax_val;
	u64 umin_val, umax_val;
	s32 s32_min_val, s32_max_val;
	u32 u32_min_val, u32_max_val;
	u64 insn_bitness = (BPF_CLASS(insn->code) == BPF_ALU64) ? 64 : 32;
	bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64);
	int ret;

	smin_val = src_reg.smin_value;
	smax_val = src_reg.smax_value;
	umin_val = src_reg.umin_value;
	umax_val = src_reg.umax_value;

	s32_min_val = src_reg.s32_min_value;
	s32_max_val = src_reg.s32_max_value;
	u32_min_val = src_reg.u32_min_value;
	u32_max_val = src_reg.u32_max_value;

	if (alu32) {
		src_known = tnum_subreg_is_const(src_reg.var_off);
		if ((src_known &&
		     (s32_min_val != s32_max_val || u32_min_val != u32_max_val)) ||
		    s32_min_val > s32_max_val || u32_min_val > u32_max_val) {
			/* Taint dst register if offset had invalid bounds
			 * derived from e.g. dead branches.
			 */
			__mark_reg_unknown(env, dst_reg);
			return 0;
		}
	} else {
		src_known = tnum_is_const(src_reg.var_off);
		if ((src_known &&
		     (smin_val != smax_val || umin_val != umax_val)) ||
		    smin_val > smax_val || umin_val > umax_val) {
			/* Taint dst register if offset had invalid bounds
			 * derived from e.g. dead branches.
			 */
			__mark_reg_unknown(env, dst_reg);
			return 0;
		}
	}

	if (!src_known &&
	    opcode != BPF_ADD && opcode != BPF_SUB && opcode != BPF_AND) {
		__mark_reg_unknown(env, dst_reg);
		return 0;
	}

	if (sanitize_needed(opcode)) {
		ret = sanitize_val_alu(env, insn);
		if (ret < 0)
			return sanitize_err(env, insn, ret, NULL, NULL);
	}

	/* Calculate sign/unsigned bounds and tnum for alu32 and alu64 bit ops.
	 * There are two classes of instructions: The first class we track both
	 * alu32 and alu64 sign/unsigned bounds independently this provides the
	 * greatest amount of precision when alu operations are mixed with jmp32
	 * operations. These operations are BPF_ADD, BPF_SUB, BPF_MUL, BPF_ADD,
	 * and BPF_OR. This is possible because these ops have fairly easy to
	 * understand and calculate behavior in both 32-bit and 64-bit alu ops.
	 * See alu32 verifier tests for examples. The second class of
	 * operations, BPF_LSH, BPF_RSH, and BPF_ARSH, however are not so easy
	 * with regards to tracking sign/unsigned bounds because the bits may
	 * cross subreg boundaries in the alu64 case. When this happens we mark
	 * the reg unbounded in the subreg bound space and use the resulting
	 * tnum to calculate an approximation of the sign/unsigned bounds.
	 */
	switch (opcode) {
	case BPF_ADD:
		scalar32_min_max_add(dst_reg, &src_reg);
		scalar_min_max_add(dst_reg, &src_reg);
		dst_reg->var_off = tnum_add(dst_reg->var_off, src_reg.var_off);
		break;
	case BPF_SUB:
		scalar32_min_max_sub(dst_reg, &src_reg);
		scalar_min_max_sub(dst_reg, &src_reg);
		dst_reg->var_off = tnum_sub(dst_reg->var_off, src_reg.var_off);
		break;
	case BPF_MUL:
		dst_reg->var_off = tnum_mul(dst_reg->var_off, src_reg.var_off);
		scalar32_min_max_mul(dst_reg, &src_reg);
		scalar_min_max_mul(dst_reg, &src_reg);
		break;
	case BPF_AND:
		dst_reg->var_off = tnum_and(dst_reg->var_off, src_reg.var_off);
		scalar32_min_max_and(dst_reg, &src_reg);
		scalar_min_max_and(dst_reg, &src_reg);
		break;
	case BPF_OR:
		dst_reg->var_off = tnum_or(dst_reg->var_off, src_reg.var_off);
		scalar32_min_max_or(dst_reg, &src_reg);
		scalar_min_max_or(dst_reg, &src_reg);
		break;
	case BPF_XOR:
		dst_reg->var_off = tnum_xor(dst_reg->var_off, src_reg.var_off);
		scalar32_min_max_xor(dst_reg, &src_reg);
		scalar_min_max_xor(dst_reg, &src_reg);
		break;
	case BPF_LSH:
		if (umax_val >= insn_bitness) {
			/* Shifts greater than 31 or 63 are undefined.
			 * This includes shifts by a negative number.
			 */
			mark_reg_unknown(env, regs, insn->dst_reg);
			break;
		}
		if (alu32)
			scalar32_min_max_lsh(dst_reg, &src_reg);
		else
			scalar_min_max_lsh(dst_reg, &src_reg);
		break;
	case BPF_RSH:
		if (umax_val >= insn_bitness) {
			/* Shifts greater than 31 or 63 are undefined.
			 * This includes shifts by a negative number.
			 */
			mark_reg_unknown(env, regs, insn->dst_reg);
			break;
		}
		if (alu32)
			scalar32_min_max_rsh(dst_reg, &src_reg);
		else
			scalar_min_max_rsh(dst_reg, &src_reg);
		break;
	case BPF_ARSH:
		if (umax_val >= insn_bitness) {
			/* Shifts greater than 31 or 63 are undefined.
			 * This includes shifts by a negative number.
			 */
			mark_reg_unknown(env, regs, insn->dst_reg);
			break;
		}
		if (alu32)
			scalar32_min_max_arsh(dst_reg, &src_reg);
		else
			scalar_min_max_arsh(dst_reg, &src_reg);
		break;
	default:
		mark_reg_unknown(env, regs, insn->dst_reg);
		break;
	}

	/* ALU32 ops are zero extended into 64bit register */
	if (alu32)
		zext_32_to_64(dst_reg);
	reg_bounds_sync(dst_reg);
	return 0;
}
```

### Cross-File Context

[bpf_reg_state — struct — include/linux/bpf_verifier.h:46]
```c
struct bpf_reg_state {
	enum bpf_reg_type type;
	s32 off;
	union { ... };
	u32 id;
	u32 ref_obj_id;
	struct tnum var_off;
	s64 smin_value;
	s64 smax_value;
	u64 umin_value;
	u64 umax_value;
	s32 s32_min_value;
	s32 s32_max_value;
	u32 u32_min_value;
	u32 u32_max_value;
	struct bpf_reg_state *parent;
	...}
```

[tnum — struct — include/linux/tnum.h:14]
```c
struct tnum {
	u64 value;
	u64 mask;
};
```

[__update_reg_bounds — function — kernel/bpf/verifier.c:1471]
```c
static void __update_reg_bounds(struct bpf_reg_state *reg)
{
	__update_reg32_bounds(reg);
	__update_reg64_bounds(reg);
}
```

[__reg_deduce_bounds — function — kernel/bpf/verifier.c:1546]
```c
static void __reg_deduce_bounds(struct bpf_reg_state *reg)
{
	__reg32_deduce_bounds(reg);
	__reg64_deduce_bounds(reg);
}
```

[__reg_bound_offset — function — kernel/bpf/verifier.c:1553]
```c
static void __reg_bound_offset(struct bpf_reg_state *reg)
{
	struct tnum var64_off = tnum_intersect(reg->var_off,
					       tnum_range(reg->umin_value,
							  reg->umax_value));
	struct tnum var32_off = tnum_intersect(tnum_subreg(reg->var_off),
					tnum_range(reg->u32_min_value,
						   reg->u32_max_value));

	reg->var_off = tnum_or(tnum_clear_subreg(var64_off), var32_off);
}
```

[reg_bounds_sync — function — kernel/bpf/verifier.c:1565]
```c
static void reg_bounds_sync(struct bpf_reg_state *reg)
{
	/* We might have learned new bounds from the var_off. */
	__update_reg_bounds(reg);
	/* We might have learned something about the sign bit. */
	__reg_deduce_bounds(reg);
	/* We might have learned some bits from the bounds. */
	__reg_bound_offset(reg);
	/* Intersecting with the old var_off might have improved our bounds
	 * slightly, e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc),
	 * then new var_off is (0; 0x7f...fc) which improves our umax.
	 */
	__update_reg_bounds(reg);
}
```

[SCALAR_VALUE — enum — include/linux/bpf.h:557]
SCALAR_VALUE, /* reg doesn't contain a valid pointer */

[BPF_OP — macro — include/uapi/linux/bpf_common.h:31]
BPF_OP → #define BPF_OP(code) ((code) & 0xf0)  (include/uapi/linux/bpf_common.h:31)

Control flow: The function extracts min/max bounds from the source register, then validates bounds consistency (checking smin<=smax, umin<=umax, and constancy constraints). If bounds are invalid, it marks dst_reg unknown and returns 0. If source is unknown for non-ADD/SUB/AND operations, it marks dst unknown and returns 0. The function then optionally sanitizes the instruction. A switch statement on opcode dispatches to operation-specific bound calculations (ADD, SUB, MUL, AND, OR, XOR, LSH, RSH, ARSH) or marks the register unknown for default/unsupported cases. Shift operations check that shift amounts don't exceed the instruction bitness. After the switch, ALU32 operations are zero-extended, then reg_bounds_sync is called before returning 0.

Data flow: Input: src_reg provides smin/smax/umin/umax/s32_min/s32_max/u32_min/u32_max bounds and var_off tnum. The instruction opcode determines the ALU operation. These inputs flow through operation-specific scalar32_min_max_* and scalar_min_max_* functions that compute new dst_reg bounds, and tnum_* operations that update dst_reg->var_off. Output: dst_reg's bound fields and var_off are updated, then synchronized via reg_bounds_sync which propagates information between var_off tnum and the scalar min/max fields.

Verdict: Not Vulnerable

Reviewed region:
[kernel/bpf/verifier.c]
```c
static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
				      struct bpf_insn *insn,
				      struct bpf_reg_state *dst_reg,
				      struct bpf_reg_state src_reg)
{
	struct bpf_reg_state *regs = cur_regs(env);
	u8 opcode = BPF_OP(insn->code);
	bool src_known;
	s64 smin_val, smax_val;
	u64 umin_val, umax_val;
	s32 s32_min_val, s32_max_val;
	u32 u32_min_val, u32_max_val;
	u64 insn_bitness = (BPF_CLASS(insn->code) == BPF_ALU64) ? 64 : 32;
	bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64);
	int ret;

	smin_val = src_reg.smin_value;
	smax_val = src_reg.smax_value;
	umin_val = src_reg.umin_value;
	umax_val = src_reg.umax_value;

	s32_min_val = src_reg.s32_min_value;
	s32_max_val = src_reg.s32_max_value;
	u32_min_val = src_reg.u32_min_value;
	u32_max_val = src_reg.u32_max_value;

	if (alu32) {
		src_known = tnum_subreg_is_const(src_reg.var_off);
		if ((src_known &&
		     (s32_min_val != s32_max_val || u32_min_val != u32_max_val)) ||
		    s32_min_val > s32_max_val || u32_min_val > u32_max_val) {
			/* Taint dst register if offset had invalid bounds
			 * derived from e.g. dead branches.
			 */
			__mark_reg_unknown(env, dst_reg);
			return 0;
		}
	} else {
		src_known = tnum_is_const(src_reg.var_off);
		if ((src_known &&
		     (smin_val != smax_val || umin_val != umax_val)) ||
		    smin_val > smax_val || umin_val > umax_val) {
			/* Taint dst register if offset had invalid bounds
			 * derived from e.g. dead branches.
			 */
			__mark_reg_unknown(env, dst_reg);
			return 0;
		}
	}

	if (!src_known &&
	    opcode != BPF_ADD && opcode != BPF_SUB && opcode != BPF_AND) {
		__mark_reg_unknown(env, dst_reg);
		return 0;
	}

	if (sanitize_needed(opcode)) {
		ret = sanitize_val_alu(env, insn);
		if (ret < 0)
			return sanitize_err(env, insn, ret, NULL, NULL);
	}

	/* Calculate sign/unsigned bounds and tnum for alu32 and alu64 bit ops.
	 * There are two classes of instructions: The first class we track both
	 * alu32 and alu64 sign/unsigned bounds independently this provides the
	 * greatest amount of precision when alu operations are mixed with jmp32
	 * operations. These operations are BPF_ADD, BPF_SUB, BPF_MUL, BPF_ADD,
	 * and BPF_OR. This is possible because these ops have fairly easy to
	 * understand and calculate behavior in both 32-bit and 64-bit alu ops.
	 * See alu32 verifier tests for examples. The second class of
	 * operations, BPF_LSH, BPF_RSH, and BPF_ARSH, however are not so easy
	 * with regards to tracking sign/unsigned bounds because the bits may
	 * cross subreg boundaries in the alu64 case. When this happens we mark
	 * the reg unbounded in the subreg bound space and use the resulting
	 * tnum to calculate an approximation of the sign/unsigned bounds.
	 */
	switch (opcode) {
	case BPF_ADD:
		scalar32_min_max_add(dst_reg, &src_reg);
		scalar_min_max_add(dst_reg, &src_reg);
		dst_reg->var_off = tnum_add(dst_reg->var_off, src_reg.var_off);
		break;
	case BPF_SUB:
		scalar32_min_max_sub(dst_reg, &src_reg);
		scalar_min_max_sub(dst_reg, &src_reg);
		dst_reg->var_off = tnum_sub(dst_reg->var_off, src_reg.var_off);
		break;
	case BPF_MUL:
		dst_reg->var_off = tnum_mul(dst_reg->var_off, src_reg.var_off);
		scalar32_min_max_mul(dst_reg, &src_reg);
		scalar_min_max_mul(dst_reg, &src_reg);
		break;
	case BPF_AND:
		dst_reg->var_off = tnum_and(dst_reg->var_off, src_reg.var_off);
		scalar32_min_max_and(dst_reg, &src_reg);
		scalar_min_max_and(dst_reg, &src_reg);
		break;
	case BPF_OR:
		dst_reg->var_off = tnum_or(dst_reg->var_off, src_reg.var_off);
		scalar32_min_max_or(dst_reg, &src_reg);
		scalar_min_max_or(dst_reg, &src_reg);
		break;
	case BPF_XOR:
		dst_reg->var_off = tnum_xor(dst_reg->var_off, src_reg.var_off);
		scalar32_min_max_xor(dst_reg, &src_reg);
		scalar_min_max_xor(dst_reg, &src_reg);
		break;
	case BPF_LSH:
		if (umax_val >= insn_bitness) {
			/* Shifts greater than 31 or 63 are undefined.
			 * This includes shifts by a negative number.
			 */
			mark_reg_unknown(env, regs, insn->dst_reg);
			break;
		}
		if (alu32)
			scalar32_min_max_lsh(dst_reg, &src_reg);
		else
			scalar_min_max_lsh(dst_reg, &src_reg);
		break;
	case BPF_RSH:
		if (umax_val >= insn_bitness) {
			/* Shifts greater than 31 or 63 are undefined.
			 * This includes shifts by a negative number.
			 */
			mark_reg_unknown(env, regs, insn->dst_reg);
			break;
		}
		if (alu32)
			scalar32_min_max_rsh(dst_reg, &src_reg);
		else
			scalar_min_max_rsh(dst_reg, &src_reg);
		break;
	case BPF_ARSH:
		if (umax_val >= insn_bitness) {
			/* Shifts greater than 31 or 63 are undefined.
			 * This includes shifts by a negative number.
			 */
			mark_reg_unknown(env, regs, insn->dst_reg);
			break;
		}
		if (alu32)
			scalar32_min_max_arsh(dst_reg, &src_reg);
		else
			scalar_min_max_arsh(dst_reg, &src_reg);
		break;
	default:
		mark_reg_unknown(env, regs, insn->dst_reg);
		break;
	}

	/* ALU32 ops are zero extended into 64bit register */
	if (alu32)
		zext_32_to_64(dst_reg);
	reg_bounds_sync(dst_reg);
	return 0;
}
```

Why it is not vulnerable: The function consistently calls reg_bounds_sync(dst_reg) at the end of the normal execution path, after all switch-case operations that modify dst_reg->var_off and scalar bounds. This ensures that after any operation refines the variable offset (tnum), the min/max values are synchronized to remain consistent. Early return paths either mark the register as unknown (a terminal state that doesn't require further bound tracking) or return an error from sanitization. The reg_bounds_sync function performs a multi-pass reconciliation: it updates bounds from var_off, deduces sign bit information, propagates bit knowledge to var_off, and performs a final bounds update.

Security invariants:
- After modifying dst_reg->var_off in any switch case, bounds must be synchronized to ensure min/max values remain consistent with the tnum state. Enforced by the unconditional reg_bounds_sync(dst_reg) call at the end of the function before the normal return.
- Register marked as unknown represents a terminal unknown state; bounds need not be further refined. Enforced by __mark_reg_unknown marking the register unknown and returning early, and by mark_reg_unknown in shift overflow cases and default case.
- Shift operations must not allow undefined shift amounts (>= bitness). Enforced by the umax_val >= insn_bitness checks in BPF_LSH, BPF_RSH, and BPF_ARSH cases, which mark the register unknown when violated.
- Source register bounds must be consistent (smin <= smax, umin <= umax) and match constancy expectations. Enforced by the bounds validation checks at the start that mark dst unknown if inconsistencies exist.

Invariant verification:
- Bounds synchronization after var_off refinement: holds=true. Evidence: Every switch case that modifies dst_reg->var_off (ADD, SUB, MUL, AND, OR, XOR, and the non-overflow paths of LSH/RSH/ARSH) falls through to reg_bounds_sync(dst_reg) at the end of the function before return 0. Cases that cannot compute bounds (shift overflow, default opcode) call mark_reg_unknown and break, reaching reg_bounds_sync which handles the unknown state.
- Early return paths do not leave inconsistent partial state: holds=true. Evidence: The two early return paths (invalid source bounds check and sanitize error) both occur before any modification to dst_reg's bound fields. The invalid bounds path calls __mark_reg_unknown(env, dst_reg) to transition dst to unknown state before returning. The sanitize path returns an error code preventing continued execution.
- ALU32 zero-extension applied before synchronization: holds=true. Evidence: The if (alu32) zext_32_to_64(dst_reg) call occurs after the switch statement and before reg_bounds_sync(dst_reg), ensuring zero-extension effects are incorporated before bounds reconciliation.

Residual risk notes: The correctness of bounds synchronization depends on the internal correctness of scalar_min_max_* functions and tnum_* operations. Additionally, when mark_reg_unknown is called for shift overflow, the function still proceeds to reg_bounds_sync on the now-unknown register, which is benign but represents redundant computation. The function trusts that sanitize_val_alu properly handles its designated sanitization responsibilities.

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

## Analysis

This is BPF verifier code responsible for tracking scalar value bounds during program verification - a critical security component that prevents out-of-bounds memory access and other exploits.

### Control Flow Analysis

The function validates source register bounds, then updates destination register bounds based on ALU operations:

1. **Sanity checks**: Validates that min ≤ max for all bound types, marks register unknown if inconsistent
2. **Constant check**: For non-ADD/SUB/AND operations, requires source to be constant or marks destination unknown
3. **Sanitization**: Applies speculative execution mitigations when needed
4. **Operation-specific bound updates**: Delegates to helper functions
5. **Synchronization**: Calls `reg_bounds_sync` to reconcile bounds with tnum representation

### Key Observations

**Shift operation handling:**
```c
case BPF_LSH:
    if (umax_val >= insn_bitness) {
        mark_reg_unknown(env, regs, insn->dst_reg);
        break;
    }
    if (alu32)
        scalar32_min_max_lsh(dst_reg, &src_reg);
    else
        scalar_min_max_lsh(dst_reg, &src_reg);
    break;
```

The check `umax_val >= insn_bitness` correctly rejects undefined shifts (≥32 for 32-bit, ≥64 for 64-bit). Negative shift values would appear as large unsigned values, triggering this check.

**Asymmetric tnum handling:**
- ADD/SUB/MUL/AND/OR/XOR explicitly update `dst_reg->var_off` with tnum operations
- LSH/RSH/ARSH do NOT explicitly update `var_off`, relying solely on `reg_bounds_sync` at the end

The comment acknowledges this: "bits may cross subreg boundaries...we mark the reg unbounded in the subreg bound space and use the resulting tnum to calculate an approximation."

### Security Assessment

**Guards present:**
- Invalid bound detection (min > max) → mark unknown
- Constant/bound consistency checks → mark unknown
- Undefined shift detection (amount ≥ bitness) → mark unknown
- Conservative handling of non-constant sources for complex operations
- Final bound synchronization via `reg_bounds_sync`

**Potential concerns (not exploitable from shown code):**
1. **Helper function implementations**: The actual bound calculations in `scalar_min_max_lsh`, `scalar32_min_max_lsh`, etc. are not shown. Historical BPF CVEs often involve integer overflow or incorrect bound propagation in these helpers.

2. **Deferred tnum update for shifts**: While `reg_bounds_sync` should reconcile this, the asymmetry between operations that explicitly update `var_off` and those that don't could indicate subtle edge cases.

3. **Complex 32/64-bit interaction**: Separate tracking of 32-bit and 64-bit bounds is inherently complex and error-prone.

### Verdict

**Not vulnerable based on the code shown.**

The function implements appropriate defensive checks:
- Validates bound consistency before processing
- Conservatively marks registers unknown when bounds are invalid or operations are too complex
- Correctly identifies undefined shift amounts
- Synchronizes all bound representations at the end

Any vulnerability would likely exist in:
- The helper functions performing actual bound arithmetic (not shown)
- Edge cases in `reg_bounds_sync` or related bound synchronization logic (partially shown)
- Interaction with pointer arithmetic in `adjust_ptr_min_max_vals` (not shown)

The code follows BPF verifier best practices of being conservative when uncertain. Without evidence of incorrect bound propagation in the helpers or a concrete exploit scenario, this code appears secure within its scope.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": "none",
  "reviewed_region": "adjust_scalar_min_max_vals function in kernel/bpf/verifier.c",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": "not_exploitable",
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "The function implements multiple defensive layers that prevent exploitation: (1) Bound consistency validation detects invalid min>max conditions and marks registers unknown, (2) Constant/bound coherence checks catch inconsistent tnum vs scalar bounds, (3) Undefined shift detection correctly identifies shifts >= bitness including negative values represented as large unsigned, (4) Conservative fallback marks registers unknown for non-constant sources on complex operations, (5) Final reg_bounds_sync reconciles all bound representations. The asymmetric tnum handling for shifts is intentional per code comments and relies on synchronization.",
  "security_invariants": [
    "For all bound types: min_value <= max_value, enforced by early validation checks",
    "Constant registers have consistent tnum and scalar bounds, enforced by src_known checks",
    "Shift amounts must be < insn_bitness (32 or 64), enforced by umax_val >= insn_bitness check",
    "Negative shift amounts appear as large unsigned values and trigger the >= insn_bitness guard",
    "Non-constant sources for LSH/RSH/ARSH/XOR operations result in unknown destination unless ADD/SUB/AND",
    "All bound representations (s32/u32/s64/u64/tnum) are synchronized via reg_bounds_sync before return"
  ],
  "invariant_verification": "Invariants are enforced through explicit checks with conservative fallback behavior. Invalid bounds trigger __mark_reg_unknown which taints the register. Undefined operations trigger mark_reg_unknown. The function returns 0 (success) only after all validations pass and bounds are synchronized. The defensive approach prioritizes correctness over precision.",
  "residual_risk_notes": "Vulnerability could exist in: (1) Helper functions performing actual bound arithmetic (scalar_min_max_lsh, scalar32_min_max_lsh, etc.) not shown in supplied code, (2) reg_bounds_sync implementation details and potential edge cases in bound reconciliation, (3) Interaction with pointer arithmetic in adjust_ptr_min_max_vals called from this function's call path, (4) Integer overflow in helper functions during bound calculations, (5) 32/64-bit boundary crossing scenarios in shift operations. Historical BPF CVEs often involve subtle errors in these helper implementations rather than the dispatch logic shown here.",
  "call_path": "bpf_prog_load → bpf_check → check_alu_op → adjust_scalar_min_max_vals → adjust_ptr_min_max_vals"
}
```


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/apache/mynewt-nimble
Language: C
File: nimble/host/src/ble_hs_hci_evt.c
Function: ble_hs_hci_evt_le_adv_rpt_first_pass

Call path: ble_hs_hci_evt_le_adv_rpt (nimble/host/src/ble_hs_hci_evt.c) → ble_hs_hci_evt_le_adv_rpt_first_pass (nimble/host/src/ble_hs_hci_evt.c) → ble_gap_rx_adv_report (nimble/host/src/ble_gap.c)

### Primary Function

```c
static int
ble_hs_hci_evt_le_adv_rpt_first_pass(const void *data, unsigned int len)
{
    const struct ble_hci_ev_le_subev_adv_rpt *ev = data;
    const struct adv_report *rpt;
    int i;

    if (len < sizeof(*ev)) {
        return BLE_HS_ECONTROLLER;
    }

    len -= sizeof(*ev);
    data += sizeof(*ev);

    if (ev->num_reports < BLE_HCI_LE_ADV_RPT_NUM_RPTS_MIN ||
        ev->num_reports > BLE_HCI_LE_ADV_RPT_NUM_RPTS_MAX) {
        return BLE_HS_EBADDATA;
    }

    for (i = 0; i < ev->num_reports; i++) {
        /* extra byte for RSSI after adv data */
        if (len < sizeof(*rpt) + 1) {
            return BLE_HS_ECONTROLLER;
        }

        rpt = data;

        len -= sizeof(*rpt) + 1;
        data += sizeof(rpt) + 1;

        if (rpt->data_len > len) {
            return BLE_HS_ECONTROLLER;
        }

        len -= rpt->data_len;
        data += rpt->data_len;
    }

    /* Make sure length was correct */
    if (len) {
        return BLE_HS_ECONTROLLER;
    }

    return 0;
}
```

### Cross-File Context

[adv_report — struct — nimble/include/nimble/hci_common.h:1860]
```c
struct adv_report {
    uint8_t type;
    uint8_t addr_type;
    uint8_t addr[6];
    uint8_t data_len;
    uint8_t data[0];
} __attribute__((packed));
```

[ble_hci_ev_le_subev_adv_rpt — struct — nimble/include/nimble/hci_common.h:1867]
```c
struct ble_hci_ev_le_subev_adv_rpt {
    uint8_t  subev_code;
    uint8_t  num_reports;
    struct adv_report reports[0];
} __attribute__((packed));
```

[BLE_HS_ECONTROLLER — constant — nimble/host/include/host/ble_hs.h:103]
BLE_HS_ECONTROLLER → 12  (nimble/host/include/host/ble_hs.h:103)

[BLE_HS_EBADDATA — constant — nimble/host/include/host/ble_hs.h:97]
BLE_HS_EBADDATA → 10  (nimble/host/include/host/ble_hs.h:97)

[BLE_HCI_LE_ADV_RPT_NUM_RPTS_MIN — constant — nimble/include/nimble/hci_common.h:2363]
BLE_HCI_LE_ADV_RPT_NUM_RPTS_MIN → (1)  (nimble/include/nimble/hci_common.h:2363)

[BLE_HCI_LE_ADV_RPT_NUM_RPTS_MAX — constant — nimble/include/nimble/hci_common.h:2364]
BLE_HCI_LE_ADV_RPT_NUM_RPTS_MAX → (0x19)  (nimble/include/nimble/hci_common.h:2364)

[ble_hs_hci_evt_le_adv_rpt — caller — nimble/host/src/ble_hs_hci_evt.c:505]
```c
static int
ble_hs_hci_evt_le_adv_rpt(uint8_t subevent, const void *data, unsigned int len)
{
    const struct ble_hci_ev_le_subev_adv_rpt *ev = data;
    struct ble_gap_disc_desc desc = {0};
    const struct adv_report *rpt;
    int rc;
    int i;

    /* Validate the event is formatted correctly */
    rc = ble_hs_hci_evt_le_adv_rpt_first_pass(data, len);
    if (rc != 0) {
        return rc;
    }

    data += sizeof(*ev);

    desc.direct_addr = *BLE_ADDR_ANY;

    for (i = 0; i < ev->num_reports; i++) {
        rpt = data;

        data += sizeof(*rpt) + rpt->data_len + 1;

        desc.event_type = rpt->type;
        desc.addr.type = rpt->addr_type;
        memcpy(desc.addr.val, rpt->addr, BLE_DEV_ADDR_LEN);
        desc.length_data = rpt->data_len;
        desc.data = rpt->data;
        desc.rssi = rpt->data[rpt->data_len];

        ble_gap_rx_adv_report(&desc);
    }

    return 0;
}
```

Control flow: ble_hs_hci_evt_le_adv_rpt calls ble_hs_hci_evt_le_adv_rpt_first_pass for validation. If validation passes (returns 0), the function proceeds to iterate over each report and call ble_gap_rx_adv_report for each. The vulnerable function loops over num_reports and performs pointer arithmetic to advance through the report data.

Data flow: Raw HCI event data flows from the controller through ble_hs_hci_evt_le_adv_rpt_first_pass for validation, then through ble_hs_hci_evt_le_adv_rpt for parsing. The bug affects the len (remaining length) and data (current position) variables used to track buffer boundaries during multi-report parsing.

Verdict: Vulnerable
Vulnerable region:
[nimble/host/src/ble_hs_hci_evt.c]
```c
rpt = data;

        len -= sizeof(*rpt) + 1;
        data += sizeof(rpt) + 1;

        if (rpt->data_len > len) {
            return BLE_HS_ECONTROLLER;
        }

        len -= rpt->data_len;
        data += rpt->data_len;
    }
```
CWE: CWE-125
Severity: MEDIUM

Root cause: In ble_hs_hci_evt_le_adv_rpt_first_pass, the expression 'data += sizeof(rpt) + 1' uses sizeof(rpt) which is the size of the pointer (8 bytes on 64-bit systems) instead of sizeof(*rpt) which is the actual size of the struct adv_report (9 bytes: 1+1+6+1 for type+addr_type+addr+data_len). This incorrect pointer arithmetic causes the validation to fail when processing events with more than one advertising report because the accumulated offset error compounds with each report processed.

Exploitability: The vulnerability is triggered when receiving a Bluetooth HCI event containing more than one advertising report. The controller sends multi-report events which fail validation due to the pointer arithmetic error, causing legitimate advertising data to be discarded.

Attack preconditions:
- Attacker must be able to send HCI events to the affected device
- The HCI event must contain more than one advertising report (num_reports >= 2)
- The event must be a legacy advertising report event (LE Advertising Report)

Likely impact: The vulnerability is triggered when receiving a Bluetooth HCI event containing more than one advertising report. The controller sends multi-report events which fail validation due to the pointer arithmetic error, causing legitimate advertising data to be discarded.

## Vulnerability: Incorrect Pointer Arithmetic in Validation Loop

**VERDICT: VULNERABLE**

### Root Cause

Line 28 contains a critical typo:
```c
data += sizeof(rpt) + 1;  // BUG: sizeof(pointer) instead of sizeof(struct)
```

Should be:
```c
data += sizeof(*rpt) + 1;  // sizeof(struct adv_report)
```

### Technical Analysis

**The Bug:**
- `rpt` is a pointer: `const struct adv_report *rpt`
- `sizeof(rpt)` = pointer size (4 bytes on 32-bit, 8 bytes on 64-bit)
- `sizeof(*rpt)` = struct size (9 bytes: 1+1+6+1)

**Desynchronization:**
The code correctly decrements `len` by `sizeof(*rpt) + 1` (10 bytes) but incorrectly advances `data` by `sizeof(rpt) + 1` (5 or 9 bytes). This creates misalignment between the length counter and data pointer.

**On 32-bit systems:** Advances 5 bytes instead of 10 (5 byte lag)
**On 64-bit systems:** Advances 9 bytes instead of 10 (1 byte lag)

### Exploitability

**Attack Vector:**
Attacker controls BLE controller or intercepts HCI events to send malformed advertising reports.

**Exploitation Flow:**
1. Validation loop parses wrong memory locations due to pointer misalignment
2. Malformed data with invalid `data_len` passes validation
3. Caller `ble_hs_hci_evt_le_adv_rpt` uses correct arithmetic and processes the malformed data
4. Out-of-bounds read occurs at: `desc.rssi = rpt->data[rpt->data_len]`

**Example:**
- Report claims `data_len = 200` but buffer only contains 10 bytes
- Validation checks wrong offset, misses the violation
- Caller reads 200 bytes past actual data → information disclosure or crash

### Impact

- **Information Disclosure:** Reading beyond buffer boundaries leaks memory contents
- **Denial of Service:** Out-of-bounds access causes crashes in embedded BLE stack
- **Validation Bypass:** Security checks become ineffective

### Classification

**CWE-823:** Use of Out-of-bounds Pointer
**CWE-129:** Improper Validation of Array Index

**Severity:** High (embedded systems context, validation bypass, memory safety violation)

```json
{
  "vulnerable": true,
  "cwe": "CWE-823",
  "severity": "HIGH",
  "reviewed_region": "ble_hs_hci_evt_le_adv_rpt_first_pass, lines 1-42, specifically the validation loop at lines 19-35",
  "vulnerable_region": "Line 28: 'data += sizeof(rpt) + 1;' uses sizeof(pointer) instead of sizeof(struct)",
  "root_cause": "Typo in pointer arithmetic: sizeof(rpt) returns pointer size (4 or 8 bytes) instead of sizeof(*rpt) which returns struct adv_report size (9 bytes). This creates desynchronization between length accounting (which correctly uses sizeof(*rpt)) and data pointer advancement, causing the validator to check wrong memory offsets in subsequent loop iterations.",
  "exploitability": "MEDIUM_TO_HIGH: Attacker must control BLE HCI advertising report events (via compromised controller or MITM). With multiple reports, the cumulative pointer lag causes validation to check misaligned memory. Attacker can craft report N's data to contain fake header bytes that validator reads as report N+1's header, while real report N+1 contains oversized data_len. On 32-bit systems: 5-byte lag per iteration. On 64-bit systems: 1-byte lag per iteration. Both are exploitable with sufficient reports.",
  "attack_preconditions": [
    "Attacker can inject or modify BLE advertising reports at HCI layer",
    "Multiple advertising reports in single event (num_reports >= 2)",
    "Ability to craft specific byte patterns in report data fields",
    "Victim system processes the malformed event"
  ],
  "impact": "Out-of-bounds memory read in caller function ble_hs_hci_evt_le_adv_rpt at line 'desc.rssi = rpt->data[rpt->data_len]'. Validation bypass allows attacker-controlled data_len to exceed actual buffer size. Consequences: (1) Information disclosure - leaking memory contents beyond buffer, (2) Denial of service - crash from invalid memory access in embedded BLE stack, (3) Validation becomes ineffective for subsequent reports in the event.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Exploitation complexity depends on system architecture (32-bit vs 64-bit affects lag magnitude) and ability to control HCI event content. Real-world exploitability requires BLE controller compromise or privileged position for MITM attacks. Impact severity is high in embedded/IoT contexts where BLE stacks run with elevated privileges and memory corruption can compromise entire device.",
  "call_path": "ble_hs_hci_evt_le_adv_rpt → ble_hs_hci_evt_le_adv_rpt_first_pass → [validation bypass] → ble_gap_rx_adv_report [out-of-bounds read]"
}
```


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/nilfs2/the_nilfs.c
Function: init_nilfs

Call path: nilfs_mount (fs/nilfs2/super.c) → init_nilfs (fs/nilfs2/the_nilfs.c) → load_nilfs (fs/nilfs2/the_nilfs.c) → nilfs_get_blocksize (fs/nilfs2/the_nilfs.c)

### Primary Function

```c
int init_nilfs(struct the_nilfs *nilfs, struct super_block *sb, char *data)
{
	struct nilfs_super_block *sbp;
	int blocksize;
	int err;

	down_write(&nilfs->ns_sem);

	blocksize = sb_min_blocksize(sb, NILFS_MIN_BLOCK_SIZE);
	if (!blocksize) {
		nilfs_err(sb, "unable to set blocksize");
		err = -EINVAL;
		goto out;
	}
	err = nilfs_load_super_block(nilfs, sb, blocksize, &sbp);
	if (err)
		goto out;

	err = nilfs_store_magic_and_option(sb, sbp, data);
	if (err)
		goto failed_sbh;

	err = nilfs_check_feature_compatibility(sb, sbp);
	if (err)
		goto failed_sbh;

	blocksize = BLOCK_SIZE << le32_to_cpu(sbp->s_log_block_size);
	if (blocksize < NILFS_MIN_BLOCK_SIZE ||
	    blocksize > NILFS_MAX_BLOCK_SIZE) {
		nilfs_err(sb,
			  "couldn't mount because of unsupported filesystem blocksize %d",
			  blocksize);
		err = -EINVAL;
		goto failed_sbh;
	}
	if (sb->s_blocksize != blocksize) {
		int hw_blocksize = bdev_logical_block_size(sb->s_bdev);

		if (blocksize < hw_blocksize) {
			nilfs_err(sb,
				  "blocksize %d too small for device (sector-size = %d)",
				  blocksize, hw_blocksize);
			err = -EINVAL;
			goto failed_sbh;
		}
		nilfs_release_super_block(nilfs);
		sb_set_blocksize(sb, blocksize);

		err = nilfs_load_super_block(nilfs, sb, blocksize, &sbp);
		if (err)
			goto out;
			/*
			 * Not to failed_sbh; sbh is released automatically
			 * when reloading fails.
			 */
	}
	nilfs->ns_blocksize_bits = sb->s_blocksize_bits;
	nilfs->ns_blocksize = blocksize;

	get_random_bytes(&nilfs->ns_next_generation,
			 sizeof(nilfs->ns_next_generation));

	err = nilfs_store_disk_layout(nilfs, sbp);
	if (err)
		goto failed_sbh;

	sb->s_maxbytes = nilfs_max_size(sb->s_blocksize_bits);

	nilfs->ns_mount_state = le16_to_cpu(sbp->s_state);

	err = nilfs_store_log_cursor(nilfs, sbp);
	if (err)
		goto failed_sbh;

	err = nilfs_sysfs_create_device_group(sb);
	if (err)
		goto failed_sbh;

	set_nilfs_init(nilfs);
	err = 0;
 out:
	up_write(&nilfs->ns_sem);
	return err;

 failed_sbh:
	nilfs_release_super_block(nilfs);
	goto out;
}
```

### Cross-File Context

[BLOCK_SIZE — macro — include/uapi/linux/fs.h:41]
BLOCK_SIZE → (1<<BLOCK_SIZE_BITS)  (include/uapi/linux/fs.h:41)

[BLOCK_SIZE_BITS — macro — include/uapi/linux/fs.h:40]
BLOCK_SIZE_BITS → 10  (include/uapi/linux/fs.h:40)

[NILFS_MAX_BLOCK_SIZE — constant — include/uapi/linux/nilfs2_ondisk.h:292]
NILFS_MAX_BLOCK_SIZE → 65536  (include/uapi/linux/nilfs2_ondisk.h:292)

[NILFS_MIN_BLOCK_SIZE — constant — include/uapi/linux/nilfs2_ondisk.h:291]
NILFS_MIN_BLOCK_SIZE → 1024  (include/uapi/linux/nilfs2_ondisk.h:291)

[load_nilfs — callee — fs/nilfs2/the_nilfs.c:205-345]
```c
int load_nilfs(struct the_nilfs *nilfs, struct super_block *sb)
{
	struct nilfs_recovery_info ri;
	unsigned int s_flags = sb->s_flags;
	int really_read_only = bdev_read_only(nilfs->ns_bdev);
	int valid_fs = nilfs_valid_fs(nilfs);
	int err;

	if (!valid_fs) {
		nilfs_warn(sb, "mounting unchecked fs");
		if (s_flags & SB_RDONLY) {
			nilfs_info(sb,
				   "recovery required for readonly filesystem");
			nilfs_info(sb,
				   "write access will be enabled during recovery");
		}
	}

	nilfs_init_recovery_info(&ri);

	err = nilfs_search_super_root(nilfs, &ri);
	if (unlikely(err)) {
		struct nilfs_super_block **sbp = nilfs->ns_sbp;
		int blocksize;

		if (err != -EINVAL)
			goto scan_error;

		if (!nilfs_valid_sb(sbp[1])) {
			nilfs_warn(sb,
				   "unable to fall back to spare super block");
			goto scan_error;
		}
		nilfs_info(sb, "trying rollback from an earlier position");

		/*
		 * restore super block with its spare and reconfigure
		 * relevant states of the nilfs object.
		 */
		memcpy(sbp[0], sbp[1], nilfs->ns_sbsize);
		nilfs->ns_crc_seed = le32_to_cpu(sbp[0]->s_crc_seed);
		nilfs->ns_sbwtime = le64_to_cpu(sbp[0]->s_wtime);

		/* verify consistency between two super blocks */
		blocksize = BLOCK_SIZE << le32_to_cpu(sbp[0]->s_log_block_size);
		if (blocksize != nilfs->ns_blocksize) {
			nilfs_warn(sb,
				   "blocksize differs between two super blocks (%d != %d)",
				   blocksize, nilfs->ns_blocksize);
			goto scan_error;
		}

		err = nilfs_store_log_cursor(nilfs, sbp[0]);
		if (err)
			goto scan_error;

		/* drop clean flag to allow roll-forward and recovery */
		nilfs->ns_mount_state &= ~NILFS_VALID_FS;
		valid_fs = 0;

		err = nilfs_search_super_root(nilfs, &ri);
		if (err)
			goto scan_error;
	}

	err = nilfs_load_super_root(nilfs, sb, ri.ri_super_root);
	if (unlikely(err)) {
		nilfs_err(sb, "error %d while loading super root", err);
		goto failed;
	}

	if (valid_fs)
		goto skip_recovery;

	if (s_flags & SB_RDONLY) {
		__u64 features;

		if (nilfs_test_opt(nilfs, NORECOVERY)) {
			nilfs_info(sb,
				   "norecovery option specified, skipping roll-forward recovery");
			goto skip_recovery;
		}
		features = le64_to_cpu(nilfs->ns_sbp[0]->s_feature_compat_ro) &
			~NILFS_FEATURE_COMPAT_RO_SUPP;
		if (features) {
			nilfs_err(sb,
				  "couldn't proceed with recovery because of unsupported optional features (%llx)",
				  (unsigned long long)features);
			err = -EROFS;
			goto failed_unload;
		}
		if (really_read_only) {
			nilfs_err(sb,
				  "write access unavailable, cannot proceed");
			err = -EROFS;
			goto failed_unload;
		}
		sb->s_flags &= ~SB_RDONLY;
	} else if (nilfs_test_opt(nilfs, NORECOVERY)) {
		nilfs_err(sb,
			  "recovery cancelled because norecovery option was specified for a read/write mount");
		err = -EINVAL;
		goto failed_unload;
	}

	err = nilfs_salvage_orphan_logs(nilfs, sb, &ri);
	if (err)
		goto failed_unload;

	down_write(&nilfs->ns_sem);
	nilfs->ns_mount_state |= NILFS_VALID_FS; /* set "clean" flag */
	err = nilfs_cleanup_super(sb);
	up_write(&nilfs->ns_sem);

	if (err) {
		nilfs_err(sb,
			  "error %d updating super block. recovery unfinished.",
			  err);
		goto failed_unload;
	}
	nilfs_info(sb, "recovery complete");

 skip_recovery:
	nilfs_clear_recovery_info(&ri);
	sb->s_flags = s_flags;
	return 0;

 scan_error:
	nilfs_err(sb, "error %d while searching super root", err);
	goto failed;

 failed_unload:
	iput(nilfs->ns_cpfile);
	iput(nilfs->ns_sufile);
	iput(nilfs->ns_dat);

 failed:
	nilfs_clear_recovery_info(&ri);
	sb->s_flags = s_flags;
	return err;
}
```

Control flow: nilfs_mount() calls init_nilfs() which reads superblock and computes blocksize from s_log_block_size via left shift. The fix introduces nilfs_get_blocksize() helper to validate shift bounds before computation. load_nilfs() has similar vulnerable path during recovery fallback.

Data flow: Untrusted s_log_block_size field from superblock disk data flows into shift operation without bounds validation. The value controls blocksize calculation which affects subsequent filesystem operations.

Verdict: Vulnerable
Vulnerable region:
[fs/nilfs2/the_nilfs.c]
```c
blocksize = BLOCK_SIZE << le32_to_cpu(sbp->s_log_block_size);
	if (blocksize < NILFS_MIN_BLOCK_SIZE ||
	    blocksize > NILFS_MAX_BLOCK_SIZE) {
```
CWE: CWE-190
Severity: HIGH

Root cause: In init_nilfs() at line 612 and load_nilfs() at line 249, the block size is calculated as BLOCK_SIZE << le32_to_cpu(sbp->s_log_block_size) without validating that s_log_block_size is within safe bounds. A maliciously crafted superblock with a large s_log_block_size value causes a shift-out-of-bounds, triggering UBSAN warnings and potential kernel panic.

Exploitability: Attacker needs to provide a nilfs2 filesystem image with a corrupted s_log_block_size field set to an excessively large value (e.g., 38973). When the kernel attempts to mount this image, the shift operation triggers undefined behavior.

Attack preconditions:
- Attacker provides a nilfs2 filesystem image with corrupted superblock
- s_log_block_size field set to value >= 32 causing shift-out-of-bounds
- Victim mounts the malicious nilfs2 image

Likely impact: Attacker needs to provide a nilfs2 filesystem image with a corrupted s_log_block_size field set to an excessively large value (e.g., 38973). When the kernel attempts to mount this image, the shift operation triggers undefined behavior.

## Vulnerability: Integer Overflow in Block Size Calculation

**Verdict: VULNERABLE**

### Root Cause

The code performs an unchecked left shift operation on attacker-controlled data from the filesystem superblock:

```c
blocksize = BLOCK_SIZE << le32_to_cpu(sbp->s_log_block_size);
```

Where `BLOCK_SIZE = 1024` and `sbp->s_log_block_size` is a 32-bit value read from disk. The variable `blocksize` is declared as `int` (signed 32-bit on most systems).

### Vulnerability Mechanism

**Signed Integer Overflow (Undefined Behavior):**
- For `s_log_block_size >= 21`: The shift `1024 << 21 = 2,147,483,648` exceeds `INT_MAX`, causing signed overflow
- For `s_log_block_size >= 32`: Shift amount equals or exceeds the bit width, which is undefined behavior per C standard
- Both cases trigger undefined behavior **before** the validation check executes

**Example values:**
- `s_log_block_size = 21`: `1024 << 21 = 2,147,483,648` → wraps to `-2,147,483,648`
- `s_log_block_size = 22`: `1024 << 22 = 4,294,967,296` → wraps to `0`
- `s_log_block_size = 32`: Undefined behavior (shift >= type width)

### Why the Guard is Insufficient

The validation check occurs **after** the undefined behavior:

```c
if (blocksize < NILFS_MIN_BLOCK_SIZE || blocksize > NILFS_MAX_BLOCK_SIZE) {
    // error handling
}
```

In standard C, undefined behavior means the compiler can:
1. Assume overflow never happens and optimize accordingly
2. Eliminate or reorder the validation check
3. Generate unpredictable code

While the Linux kernel typically compiles with flags like `-fno-strict-overflow` that define wraparound behavior for signed overflow, shifts by amounts >= the type width remain undefined.

### Attack Preconditions

1. Attacker provides a malicious NILFS2 filesystem image
2. Image contains crafted `s_log_block_size` value in superblock (e.g., ≥ 21)
3. User attempts to mount the filesystem

### Impact

**Likely:** Denial of Service
- In practice, overflow typically wraps to 0 or negative values
- Validation check catches these and mount fails
- System remains stable but filesystem is unmountable

**Possible (due to UB):** Undefined behavior exploitation
- Compiler optimizations could eliminate checks
- Unpredictable program behavior
- Theoretical potential for more serious consequences

### Classification

**CWE-190: Integer Overflow or Wraparound**
- Specifically: unchecked arithmetic on attacker-controlled input
- Leads to undefined behavior before validation

### Observed in load_nilfs

The same pattern appears in `load_nilfs` at line 251:
```c
blocksize = BLOCK_SIZE << le32_to_cpu(sbp[0]->s_log_block_size);
```

This creates a second instance of the same vulnerability during recovery operations.

### Correct Fix

Validate the shift amount before performing the operation:

```c
u32 log_block_size = le32_to_cpu(sbp->s_log_block_size);
if (log_block_size > 6) {  // 1024 << 6 = 65536 (NILFS_MAX_BLOCK_SIZE)
    nilfs_err(sb, "invalid log_block_size %u", log_block_size);
    err = -EINVAL;
    goto failed_sbh;
}
blocksize = BLOCK_SIZE << log_block_size;
```

This prevents undefined behavior by ensuring the shift operation always produces a valid result within the expected range.

```json
{
  "vulnerable": true,
  "cwe": "CWE-190",
  "severity": "medium",
  "reviewed_region": "init_nilfs function, lines calculating blocksize from s_log_block_size",
  "vulnerable_region": "blocksize = BLOCK_SIZE << le32_to_cpu(sbp->s_log_block_size); at line ~30 in init_nilfs and line ~251 in load_nilfs",
  "root_cause": "Unchecked left shift operation on attacker-controlled filesystem superblock field s_log_block_size causes undefined behavior before validation. For s_log_block_size >= 21, signed integer overflow occurs (1024 << 21 exceeds INT_MAX). For s_log_block_size >= 32, shift amount equals or exceeds type width, which is undefined behavior per C standard. The validation check occurs after the undefined behavior has already been triggered.",
  "exploitability": "High exploitability from attacker perspective. Attacker crafts malicious NILFS2 filesystem image with s_log_block_size set to value >= 21 (e.g., 25, 30, or 32). When victim attempts to mount the filesystem, undefined behavior is triggered in kernel context. While Linux kernel compilation flags like -fwrapv may define wraparound behavior for signed overflow, shifts by amounts >= type width remain undefined. Compiler may assume UB never occurs and optimize away validation checks or generate unpredictable code.",
  "attack_preconditions": "1. Attacker provides malicious NILFS2 filesystem image with crafted s_log_block_size value in superblock (>= 21 for signed overflow, >= 32 for shift-width UB). 2. Victim user attempts to mount the filesystem with sufficient privileges. 3. No additional authentication or complex setup required beyond mount operation.",
  "impact": "Most likely: Denial of Service - overflow typically wraps to zero or negative values which fail validation check, causing mount to fail. System remains stable but filesystem unmountable. Possible (due to undefined behavior): Compiler optimizations could eliminate validation checks or generate unpredictable code paths. Theoretical potential for memory corruption or privilege escalation if UB leads to unexpected control flow, though less likely in practice. Impact occurs in kernel context.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Vulnerability appears in two locations: init_nilfs (line ~30) and load_nilfs (line ~251). Both perform same unchecked shift on s_log_block_size. While practical exploitation may be limited to DoS due to typical compiler behavior, presence of undefined behavior means guarantees cannot be made about program behavior. Correct fix requires validating s_log_block_size <= 6 before shift operation (since 1024 << 6 = 65536 = NILFS_MAX_BLOCK_SIZE).",
  "call_path": "nilfs_mount → init_nilfs → [vulnerable shift operation]; also in init_nilfs → load_nilfs → [vulnerable shift operation]"
}
```