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.

Yes — this is a real vulnerability.

## Observed evidence

`ble_hs_hci_evt_le_adv_rpt_first_pass()` is meant to validate the variable-length LE Advertising Report event before `ble_hs_hci_evt_le_adv_rpt()` parses it.

The event layout is:

- `struct ble_hci_ev_le_subev_adv_rpt`
- then `num_reports` repetitions of:
  - `struct adv_report` (`sizeof(*rpt) == 9` because packed)
  - `data_len` bytes of adv data
  - 1 byte RSSI

The validator mostly tracks `len` correctly:

```c
len -= sizeof(*rpt) + 1;
...
if (rpt->data_len > len) {
    return BLE_HS_ECONTROLLER;
}
len -= rpt->data_len;
```

But it advances the parsing pointer incorrectly:

```c
data += sizeof(rpt) + 1;
```

Here `rpt` is a pointer, so `sizeof(rpt)` is the pointer size, not the struct size.

It should have been:

```c
data += sizeof(*rpt) + 1;
```

## Why this matters

This creates a mismatch between:

- the number of bytes the function subtracts from `len`, and
- the number of bytes it advances `data`.

So after the first report, `len` reflects the correct remaining bytes, but `data` points to the wrong location.

### Consequence

For `num_reports > 1`, the validator reads subsequent `rpt->data_len` fields from the wrong offset, so it is **not actually validating the later reports that the caller will parse**.

Then the caller does a second pass:

```c
rpt = data;
data += sizeof(*rpt) + rpt->data_len + 1;
...
desc.rssi = rpt->data[rpt->data_len];
```

This second pass has **no independent remaining-length checks**. It relies entirely on `ble_hs_hci_evt_le_adv_rpt_first_pass()` having validated the buffer correctly.

Because the first pass can be desynchronized, a malformed event can pass validation but cause the second pass to:

- read `rpt->data_len` from an actual later report that was never properly bounds-checked,
- advance beyond the end of the event buffer,
- read `rpt->data[rpt->data_len]` out of bounds.

That is an out-of-bounds read.

## Control-flow / data-flow reasoning

### First pass
For each report:

1. Require at least `sizeof(*rpt) + 1` bytes remain.
2. Set `rpt = data`.
3. Decrease `len` by `sizeof(*rpt) + 1`.
   This is correct.
4. Advance `data` by `sizeof(rpt) + 1`.
   This is wrong.
5. Check `rpt->data_len <= len`.
6. Decrease `len` by `rpt->data_len`.
7. Advance `data` by `rpt->data_len`.

So after iteration 1:

- on a 32-bit build, `data` is short by 5 bytes,
- on a 64-bit build, `data` is short by 1 byte.

Either way, from report 2 onward, parsing is misaligned.

### Second pass
The caller restarts from the beginning and parses using the correct stride:

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

But it does not track or verify `len` during this pass.

So if report 2’s real `data_len` is larger than the actual remaining bytes, the caller can over-read even though the first pass returned success.

## Exploitability

### What an attacker must control
The malformed buffer is an HCI LE advertising report event from the controller to the host.

So the direct attacker model is:

- malicious or compromised BLE controller,
- corrupted transport/input from controller to host,
- or some upstream bug that lets an attacker inject malformed HCI events.

### Remote over-the-air attacker?
From the supplied code alone, I cannot conclude that a remote BLE advertiser can directly cause the controller to emit this exact malformed HCI event. The controller normally constructs these events from radio data, and some fields (like RSSI) are controller-generated. So **pure remote exploitability is not established from the provided context**.

But **host-side trust of malformed controller input** is clearly broken.

## Impact

Likely impacts:

- host crash / denial of service,
- possible information disclosure via out-of-bounds reads,
- undefined behavior depending on surrounding memory and compiler/architecture.

Because this is only a read, not a demonstrated write, the strongest classification justified by the code is an **out-of-bounds read caused by incorrect pointer arithmetic during validation**.

## Precise root cause

A typo-level but security-relevant bug:

```c
data += sizeof(rpt) + 1;
```

uses the size of a pointer instead of the size of the pointed-to structure.

This causes validation to operate on the wrong offsets for all reports after the first.

## Classification

Best-fit CWE:
- **CWE-125: Out-of-bounds Read**

Contributing root cause:
- incorrect pointer offset / size calculation (`sizeof(pointer)` vs `sizeof(*pointer)`)

## Verdict

**Vulnerable.**

The first-pass validator is desynchronized from the real event layout due to `sizeof(rpt)` instead of `sizeof(*rpt)`. As a result, malformed multi-report events can pass validation and then be parsed out of bounds by `ble_hs_hci_evt_le_adv_rpt()`.

The validator advances the input pointer with sizeof(rpt), which is the size of the pointer variable, instead of sizeof(*rpt), which is the size of struct adv_report. This desynchronizes pointer position from the length accounting after the first report, so later reports are validated at the wrong offsets.

The bug is realistically reachable when a malformed HCI LE Advertising Report event with more than one report is delivered to the host. The immediate attacker influence is over controller-to-host HCI event contents, such as a malicious/compromised controller or corrupted HCI transport. From the supplied code alone, direct remote over-the-air exploitability by a BLE advertiser is not established.

The host must process an LE Advertising Report event containing multiple reports.

The attacker must be able to cause malformed HCI event bytes to reach ble_hs_hci_evt_le_adv_rpt().

The malformed event must pass the flawed first-pass validation due to the pointer/length desynchronization.

Out-of-bounds read in ble_hs_hci_evt_le_adv_rpt() during second-pass parsing.

Potential denial of service via crash or undefined behavior.

Possible limited information disclosure from adjacent memory, depending on platform behavior.

The first-pass routine is intended to prove that the variable-length event exactly matches the supplied len before the second-pass parser dereferences fields.

The first-pass routine correctly checks len >= sizeof(*ev), validates num_reports range, and ensures len becomes zero at the end.

The caller performs no independent bounds tracking during the second pass and therefore relies on first-pass validation being exact.

Header length is checked before reading ev->num_reports.

For each iteration, the code checks len >= sizeof(*rpt) + 1 before reading rpt fields.

Pointer advancement invariant fails: data is advanced by sizeof(rpt) + 1 instead of sizeof(*rpt) + 1.

Because len is decremented by the correct struct size while data is advanced by the pointer size, validation of reports after the first occurs at incorrect offsets.

The second-pass parser uses the correctly interpreted layout without rechecking remaining length, so malformed later reports can trigger out-of-bounds reads.

Architecture affects exact misalignment: on 32-bit systems the parser falls behind by 5 bytes per report header; on 64-bit systems by 1 byte.

The supplied code demonstrates only a read-side memory safety issue, not a write primitive.

Remote exploitability through radio traffic alone cannot be concluded from the provided context because the controller constructs HCI events.

[{'id': 'CWE-125', 'name': 'Out-of-bounds Read'}, {'id': 'CWE-468', 'name': 'Incorrect Pointer Scaling'}]


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: 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)

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: 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.

## Verdict

**No real vulnerability is evident in the supplied code.**
This function appears to be a **conservative verifier-state update routine**, and the shown guards are specifically designed to avoid unsound range tracking when the source bounds are inconsistent or insufficiently known.

---

## What the function does

`adjust_scalar_min_max_vals()` updates the verifier's abstract state for a destination register after a scalar ALU operation. It tracks:

- signed/unsigned 64-bit bounds
- signed/unsigned 32-bit bounds
- `tnum` bit-level uncertainty (`var_off`)

This is verifier metadata only; it does not execute the ALU operation itself.

The security question is whether it could update the verifier state **too optimistically**, causing the verifier to accept unsafe eBPF programs. In the shown code, the behavior is mostly the opposite: when precision is insufficient or bounds are inconsistent, it **taints the destination as unknown**.

---

## Security-relevant control flow

### 1. It rejects inconsistent source bounds

For 32-bit ALU ops:

```c
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) {
	__mark_reg_unknown(env, dst_reg);
	return 0;
}
```

For 64-bit ALU ops:

```c
if ((src_known &&
     (smin_val != smax_val || umin_val != umax_val)) ||
    smin_val > smax_val || umin_val > umax_val) {
	__mark_reg_unknown(env, dst_reg);
	return 0;
}
```

### Why this matters

If `src_reg.var_off` says the source is a constant (`src_known`), but the min/max bounds disagree, the verifier treats that as invalid state and stops trusting it.

Likewise, impossible bound relations like `smin > smax` or `umin > umax` are not used. Instead:

- destination becomes unknown
- analysis remains conservative
- no optimistic inference is made from corrupted/imprecise state

This is exactly the sort of defense that prevents verifier unsoundness from dead branches or imprecise merges.

---

### 2. Unknown sources are only allowed for a small safe subset of operations

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

If the source is not known and the operation is not one of `ADD`, `SUB`, or `AND`, the verifier does **not** try to compute precise bounds. It taints the destination as unknown.

### Why this matters

The dangerous verifier failure mode would be trying to model a complex operation with insufficient information and deriving overly narrow bounds. This code avoids that for most ALU ops.

Only a limited set of operations are allowed with unknown source values, and those are then handled by dedicated range/tnum helpers such as:

- `scalar_min_max_add`
- `scalar_min_max_sub`
- `scalar_min_max_and`
- and 32-bit equivalents

Based on the shown code, this is a deliberate precision/safety tradeoff, not a missing check.

---

### 3. Shift operations are guarded against undefined shift counts

For `LSH`, `RSH`, and `ARSH`:

```c
if (umax_val >= insn_bitness) {
	mark_reg_unknown(env, regs, insn->dst_reg);
	break;
}
```

`insn_bitness` is 32 or 64 depending on ALU class.

### Why this matters

Shifts by `>= 32` or `>= 64` are undefined for the modeled operation size. The code does not try to reason through those cases. It marks the register unknown instead.

The comment also notes this includes negative shifts. In verifier terms, a negative constant shift count would show up as a large unsigned value, so `umax_val >= insn_bitness` catches it.

Again, this is conservative, not permissive.

---

### 4. Unknown or unsupported opcodes fall back to unknown state

```c
default:
	mark_reg_unknown(env, regs, insn->dst_reg);
	break;
```

So if an opcode isn't one of the explicitly modeled ones, the verifier does not fabricate bounds.

---

### 5. Final state is normalized and synchronized

After the operation:

```c
if (alu32)
	zext_32_to_64(dst_reg);
reg_bounds_sync(dst_reg);
```

And `reg_bounds_sync()` performs:

- bounds update from `var_off`
- signed/unsigned deduction
- intersection of tnum and bounds
- another bounds update

From the provided helpers:

```c
__update_reg_bounds(reg);
__reg_deduce_bounds(reg);
__reg_bound_offset(reg);
__update_reg_bounds(reg);
```

This reduces inconsistency between the bitmask (`var_off`) and min/max ranges, again in a conservative direction.

---

## Data-flow assessment

### Attacker influence

An unprivileged eBPF program can influence:

- the ALU opcode
- source/destination registers
- prior verifier state that produced `src_reg` ranges/tnum

So this code absolutely sits on a security boundary: verifier soundness.

### But what happens to attacker-controlled uncertainty?

The key observed pattern is:

- if the source state is contradictory → mark destination unknown
- if the source is too imprecise for the operation → mark destination unknown
- if shift count may be invalid → mark destination unknown
- otherwise use specialized arithmetic transfer functions and then synchronize bounds

That is the correct conservative behavior for a verifier.

There is **no shown path** where attacker-controlled ambiguity causes the verifier to infer a narrower-than-justified value range.

---

## Why this is not a verifier-bypass bug from the shown code

A real verifier vulnerability here would require something like:

1. attacker provides a source state that is actually broad/unsafe,
2. this function incorrectly narrows it,
3. later pointer or memory checks trust that narrowed result,
4. unsafe eBPF is accepted.

The supplied code does not show that pattern. Instead, when faced with invalid or broad state, it commonly does:

```c
__mark_reg_unknown(...)
```

or

```c
mark_reg_unknown(...)
```

That weakens precision but preserves safety.

---

## Potential concerns considered

### Could `src_known` be misleading?
`src_known` comes from:

- `tnum_subreg_is_const(src_reg.var_off)` for ALU32
- `tnum_is_const(src_reg.var_off)` for ALU64

If `var_off` says constant but the bounds disagree, the function detects that and taints the destination unknown. So it does not blindly trust `src_known`.

### Could shift handling miss negative counts?
Not from what is shown. If a shift count is represented as a constant negative integer in two's complement, its unsigned range would be large, and `umax_val >= insn_bitness` would trigger unknown-state fallback.

### Could stale/inconsistent bounds survive?
`reg_bounds_sync()` is specifically there to reconcile `var_off` and numeric bounds. Also, clearly inconsistent bounds are rejected early.

---

## Residual uncertainty

There is always some residual uncertainty because correctness also depends on the helper transfer functions not shown here, e.g.:

- `scalar_min_max_add`
- `scalar32_min_max_mul`
- `scalar_min_max_and`
- `tnum_*` operations
- `sanitize_val_alu`
- `mark_reg_unknown` / `__mark_reg_unknown`

If one of those helpers were incorrect, a verifier bug could exist elsewhere. But **based strictly on the supplied function and context**, this function itself is written defensively and does **not** present a real vulnerability.

---

## Final classification

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

This function uses conservative fallbacks (`mark_reg_unknown`) and explicit consistency checks to avoid unsound verifier state updates, which is the key security property here.

No concrete exploit path is evident from the supplied code. The function updates verifier abstract state conservatively and falls back to unknown state when source bounds are inconsistent, unsupported, or insufficiently precise.

An attacker can supply an eBPF program that reaches this verifier path via bpf_prog_load -> bpf_check -> check_alu_op -> adjust_scalar_min_max_vals.

For a real vulnerability, the function would need to derive overly permissive or overly narrow verifier bounds from attacker-controlled register state, allowing unsafe program acceptance.

From the shown code, the likely effect of problematic inputs is loss of verifier precision (register marked unknown), which may reject valid programs but does not by itself permit verifier bypass or memory safety compromise.

Observed behavior is defensive. For ALU32 and ALU64 separately, if src_reg.var_off indicates a constant but min/max bounds disagree, or if bounds are internally invalid (min > max), the code calls __mark_reg_unknown(env, dst_reg) and returns. If the source is not known and the opcode is not one of ADD, SUB, or AND, it again marks the destination unknown and returns rather than attempting unsupported reasoning. For shift operations, if umax_val is at least the instruction bit width, the destination register is marked unknown instead of computing potentially unsound bounds for undefined shifts. After modeled operations, ALU32 results are zero-extended and reg_bounds_sync() reconciles var_off with 32-bit and 64-bit bounds through update/deduce/intersect/update steps. These checks and conservative fallbacks prevent the function from trusting contradictory or attacker-influenced state too much in the supplied code.

Contradictory constant-state evidence is not trusted: if tnum says constant but signed/unsigned min/max do not collapse to the same constant, destination becomes unknown.

Impossible bound states are not propagated: smin > smax or umin > umax, and their 32-bit equivalents, trigger unknown-state fallback.

Unsupported reasoning on unknown sources is avoided: only ADD, SUB, and AND are handled when src_known is false; all other opcodes taint the destination unknown.

Potentially undefined shifts are not modeled precisely: shift counts with umax >= 32 or 64, depending on ALU class, cause destination to become unknown.

ALU32 semantics are normalized with zero extension before final bound synchronization.

reg_bounds_sync() re-derives and intersects bounds with var_off, reducing inconsistency rather than expanding trust.

Present in both alu32 and alu64 branches before any arithmetic transfer functions execute.

Present in both alu32 and alu64 branches via min > max checks.

Present via !src_known && opcode != ADD && opcode != SUB && opcode != AND.

Present for LSH, RSH, and ARSH using umax_val >= insn_bitness.

Present via zext_32_to_64(dst_reg) for alu32 and reg_bounds_sync(dst_reg) for all cases.

This assessment is limited to the supplied function and shown helpers/context. Soundness also depends on unshown helpers such as scalar_min_max_add/sub/mul/and/or/xor, scalar32_min_max_* variants, tnum operations, sanitize_val_alu, and the exact behavior of mark_reg_unknown/__mark_reg_unknown.

No evidence in the provided code shows this function deriving unjustifiably narrow bounds or permitting unsafe pointer arithmetic acceptance.

The call path mentions adjust_ptr_min_max_vals after this function, but no supplied code shows an unsafe interaction between this scalar update routine and pointer-state handling.


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/soc/imx/soc-imx8m.c
Function: imx8_soc_init

Call path: imx8_soc_init (drivers/soc/imx/soc-imx8m.c) → imx8mm_soc_revision (drivers/soc/imx/soc-imx8m.c) → of_clk_get_by_name (drivers/clk/clk.c)

### Primary Function

```c
static int imx8m_soc_probe(struct platform_device *pdev)
{
	struct soc_device_attribute *soc_dev_attr;
	struct soc_device *soc_dev;
	const struct of_device_id *id;
	u32 soc_rev = 0;
	const struct imx8_soc_data *data;
	int ret;

	soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL);
	if (!soc_dev_attr)
		return -ENOMEM;

	soc_dev_attr->family = "Freescale i.MX";

	ret = of_property_read_string(of_root, "model", &soc_dev_attr->machine);
	if (ret)
		goto free_soc;

	id = of_match_node(imx8_soc_match, of_root);
	if (!id) {
		ret = -ENODEV;
		goto free_soc;
	}

	data = id->data;
	if (data) {
		soc_dev_attr->soc_id = data->name;
		if (data->soc_revision) {
			ret = data->soc_revision(&soc_rev);
			if (ret)
				goto free_soc;
		}
	}

	soc_dev_attr->revision = imx8_revision(soc_rev);
	if (!soc_dev_attr->revision) {
		ret = -ENOMEM;
		goto free_soc;
	}

	soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX", soc_uid);
	if (!soc_dev_attr->serial_number) {
		ret = -ENOMEM;
		goto free_rev;
	}

	soc_dev = soc_device_register(soc_dev_attr);
	if (IS_ERR(soc_dev)) {
		ret = PTR_ERR(soc_dev);
		goto free_serial_number;
	}

	pr_info("SoC: %s revision %s\n", soc_dev_attr->soc_id,
		soc_dev_attr->revision);

	if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT))
		platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0);

	return 0;

free_serial_number:
	kfree(soc_dev_attr->serial_number);
free_rev:
	if (strcmp(soc_dev_attr->revision, "unknown"))
		kfree(soc_dev_attr->revision);
free_soc:
	kfree(soc_dev_attr);
	return ret;
}
```

### Cross-File Context

[imx8_soc_data — struct — drivers/soc/imx/soc-imx8m.c:30-33]
```c
struct imx8_soc_data {
	char *name;
	int (*soc_revision)(u32 *socrev);
};
```

[imx8mm_soc_revision — callee — drivers/soc/imx/soc-imx8m.c:154-176]
```c
static int imx8mm_soc_revision(u32 *socrev)
{
	struct device_node *np;
	void __iomem *anatop_base;
	int ret;

	np = of_find_compatible_node(NULL, NULL, "fsl,imx8mm-anatop");
	if (!np)
		return -EINVAL;

	anatop_base = of_iomap(np, 0);
	if (!anatop_base) {
		ret = -EINVAL;
		goto err_iomap;
	}

	*socrev = readl_relaxed(anatop_base + ANADIG_DIGPROG_IMX8MM);

	iounmap(anatop_base);
	of_node_put(np);

	return imx8mm_soc_uid();

err_iomap:
	of_node_put(np);
	return ret;
}
```

[imx8mq_soc_revision — function — drivers/soc/imx/soc-imx8m.c:54-100]
```c
static int imx8mq_soc_revision(u32 *socrev)
{
	struct device_node *np;
	void __iomem *ocotp_base;
	u32 magic;
	u32 rev;
	struct clk *clk;
	int ret;

	np = of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp");
	if (!np)
		return -EINVAL;

	ocotp_base = of_iomap(np, 0);
	if (!ocotp_base) {
		ret = -EINVAL;
		goto err_iomap;
	}

	clk = of_clk_get_by_name(np, NULL);
	if (IS_ERR(clk)) {
		ret = PTR_ERR(clk);
		goto err_clk;
	}

	clk_prepare_enable(clk);

	/*
	 * SOC revision on older imx8mq is not available in fuses so query
	 * the value from ATF instead.
	 */
	rev = imx8mq_soc_revision_from_atf();
	if (!rev) {
		magic = readl_relaxed(ocotp_base + IMX8MQ_SW_INFO_B1);
		if (magic == IMX8MQ_SW_MAGIC_B1)
			rev = REV_B1;
	}

	soc_uid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH);
	soc_uid <<= 32;
	soc_uid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW);

	*socrev = rev;

	clk_disable_unprepare(clk);
	clk_put(clk);
	iounmap(ocotp_base);
	of_node_put(np);

	return 0;

err_clk:
	iounmap(ocotp_base);
err_iomap:
	of_node_put(np);
	return ret;
}
```

[imx8mm_soc_uid — function — drivers/soc/imx/soc-imx8m.c:117-152]
```c
static int imx8mm_soc_uid(void)
{
	void __iomem *ocotp_base;
	struct device_node *np;
	struct clk *clk;
	int ret = 0;
	u32 offset = of_machine_is_compatible("fsl,imx8mp") ?
		     IMX8MP_OCOTP_UID_OFFSET : 0;

	np = of_find_compatible_node(NULL, NULL, "fsl,imx8mm-ocotp");
	if (!np)
		return -EINVAL;

	ocotp_base = of_iomap(np, 0);
	if (!ocotp_base) {
		ret = -EINVAL;
		goto err_iomap;
	}

	clk = of_clk_get_by_name(np, NULL);
	if (IS_ERR(clk)) {
		ret = PTR_ERR(clk);
		goto err_clk;
	}

	clk_prepare_enable(clk);

	soc_uid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH + offset);
	soc_uid <<= 32;
	soc_uid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW + offset);

	clk_disable_unprepare(clk);
	clk_put(clk);

err_clk:
	iounmap(ocotp_base);
err_iomap:
	of_node_put(np);

	return ret;
}
```

[imx8_revision — macro — drivers/soc/imx/soc-imx8m.c:184-187]
imx8_revision → #define imx8_revision(soc_rev) \ soc_rev ? \ kasprintf(GFP_KERNEL, "%d.%d", (soc_rev >> 4) & 0xf, soc_rev & 0xf) : \ "unknown"  (drivers/soc/imx/soc-imx8m.c:184-187)

[imx8m_soc_driver — struct — drivers/soc/imx/soc-imx8m.c:288-293]
```c
static struct platform_driver imx8m_soc_driver = {
	.probe = imx8m_soc_probe,
	.driver = {
		.name = "imx8m-soc",
	},
};
```

Control flow: The probe function begins by allocating a `soc_device_attribute` structure and validating the pointer. It reads the device model string and matches the device tree node to retrieve driver-specific data. If `data` is present, it checks for an optional `soc_revision` callback. If the callback exists, it is invoked with a pointer to `soc_rev`. The return value is immediately validated; a non-zero result triggers a jump to `free_soc`. On success, the function allocates memory for the revision string and serial number, validates both allocations, registers the SOC device, and returns 0. Error paths jump to labeled cleanup sections that free resources in reverse allocation order, carefully skipping labels for resources not yet allocated.

Data flow: `soc_rev` is initialized to 0 and passed by reference to the `soc_revision` callback. The callback reads hardware registers to populate `soc_rev` and a global `soc_uid` variable. `soc_rev` is then passed to the `imx8_revision` macro, which formats it into a heap-allocated string or returns a static "unknown" literal. `soc_uid` is formatted into a serial number string. All dynamically allocated strings and the main attribute structure are tracked and freed during error handling or module unload. Pointer returns from `kasprintf` and `soc_device_register` are validated against NULL and IS_ERR respectively.

Verdict: Not Vulnerable

Reviewed region:
[drivers/soc/imx/soc-imx8m.c]
```c
static int imx8m_soc_probe(struct platform_device *pdev)
{
	struct soc_device_attribute *soc_dev_attr;
	struct soc_device *soc_dev;
	const struct of_device_id *id;
	u32 soc_rev = 0;
	const struct imx8_soc_data *data;
	int ret;

	soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL);
	if (!soc_dev_attr)
		return -ENOMEM;

	soc_dev_attr->family = "Freescale i.MX";

	ret = of_property_read_string(of_root, "model", &soc_dev_attr->machine);
	if (ret)
		goto free_soc;

	id = of_match_node(imx8_soc_match, of_root);
	if (!id) {
		ret = -ENODEV;
		goto free_soc;
	}

	data = id->data;
	if (data) {
		soc_dev_attr->soc_id = data->name;
		if (data->soc_revision) {
			ret = data->soc_revision(&soc_rev);
			if (ret)
				goto free_soc;
		}
	}

	soc_dev_attr->revision = imx8_revision(soc_rev);
	if (!soc_dev_attr->revision) {
		ret = -ENOMEM;
		goto free_soc;
	}

	soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX", soc_uid);
	if (!soc_dev_attr->serial_number) {
		ret = -ENOMEM;
		goto free_rev;
	}

	soc_dev = soc_device_register(soc_dev_attr);
	if (IS_ERR(soc_dev)) {
		ret = PTR_ERR(soc_dev);
		goto free_serial_number;
	}

	pr_info("SoC: %s revision %s\n", soc_dev_attr->soc_id,
		soc_dev_attr->revision);

	if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT))
		platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0);

	return 0;

free_serial_number:
	kfree(soc_dev_attr->serial_number);
free_rev:
	if (strcmp(soc_dev_attr->revision, "unknown"))
		kfree(soc_dev_attr->revision);
free_soc:
	kfree(soc_dev_attr);
	return ret;
}
```

Why it is not vulnerable: The optional `data->soc_revision` callback is explicitly guarded by a null pointer check before invocation. Upon invocation, its return value is stored in `ret` and immediately validated with `if (ret) goto free_soc;`. This ensures that any error condition encountered by the callback is correctly propagated, halting further initialization and directing control to the appropriate cleanup path. No return value is silently ignored.

Security invariants:
- Optional callback pointers must be verified for non-NULL status before dereference.
- Return values from invoked callbacks must be checked to differentiate success from failure.
- Error handling paths must correctly skip cleanup steps for resources that have not yet been allocated.
- Static string literals returned by macros must not be passed to kfree.

Invariant verification:
- Callback null check: holds=true. Evidence: The code wraps the invocation in `if (data->soc_revision) { ... }`, preventing NULL pointer dereference if the callback is not defined for a specific SoC variant.
- Return value validation: holds=true. Evidence: The assignment `ret = data->soc_revision(&soc_rev);` is immediately followed by `if (ret) goto free_soc;`, ensuring non-zero error codes trigger error propagation.
- Cleanup path safety: holds=true. Evidence: The `goto free_soc;` on callback error bypasses `free_serial_number` and `free_rev` labels, which is correct because `soc_dev_attr->serial_number` and `soc_dev_attr->revision` are allocated after the callback call.
- Static string protection: holds=true. Evidence: The `free_rev` label uses `if (strcmp(soc_dev_attr->revision, "unknown")) kfree(soc_dev_attr->revision);`, correctly preventing deallocation of the static literal returned by the `imx8_revision` macro when `soc_rev` is 0.

Residual risk notes: No latent concerns identified. The code correctly manages the lifecycle of dynamically allocated resources, validates all critical pointers and return values, and safely handles optional driver data callbacks.

**Verdict: no real vulnerability shown in the supplied code.**

### What the code does

`imx8m_soc_probe()` allocates and fills a `soc_device_attribute`, derives SoC revision/UID through a SoC-specific callback, formats strings, and registers the SoC device.

Relevant flow:

1. Allocate `soc_dev_attr` with `kzalloc`.
2. Read `/model` from the root device tree into `soc_dev_attr->machine`.
3. Match the root DT node against `imx8_soc_match`.
4. If the matched SoC has a `soc_revision` callback, call it.
   - For i.MX8MM, this goes through `imx8mm_soc_revision()`, which:
     - finds an `anatop` node,
     - maps it,
     - reads the revision register,
     - unmaps,
     - then calls `imx8mm_soc_uid()`.
   - `imx8mm_soc_uid()`:
     - finds an `ocotp` node,
     - maps it,
     - gets a clock with `of_clk_get_by_name(np, NULL)`,
     - enables it,
     - reads UID registers,
     - disables/releases/unmaps.
5. Format revision with `imx8_revision(soc_rev)`.
6. Format serial number from global `soc_uid`.
7. Register via `soc_device_register()`.

---

## Security-relevant observations

### 1. `of_clk_get_by_name(..., NULL)` is error-checked
In both SoC UID/revision helpers, the clock lookup result is checked:

```c
clk = of_clk_get_by_name(np, NULL);
if (IS_ERR(clk)) {
    ret = PTR_ERR(clk);
    goto err_clk;
}
```

So there is **no unchecked error pointer dereference** from this call path.

### 2. Resource cleanup is consistent
The helpers correctly unwind mappings and DT node references on failure:

- `of_node_put(np)` is called on all exits after successful `of_find_compatible_node`.
- `iounmap()` is called after successful `of_iomap()`.
- `clk_put()` is called on the success path after `of_clk_get_by_name()` succeeds.

In `imx8m_soc_probe()`, cleanup labels also match allocated state:
- `serial_number` is freed only if allocated.
- `revision` is freed only if it is dynamically allocated, using:
  ```c
  if (strcmp(soc_dev_attr->revision, "unknown"))
      kfree(soc_dev_attr->revision);
  ```
  This avoids freeing the string literal returned by the macro when `soc_rev == 0`.

### 3. `imx8_revision()` is safe in this usage
Macro:

```c
#define imx8_revision(soc_rev) \
    soc_rev ? \
    kasprintf(GFP_KERNEL, "%d.%d", (soc_rev >> 4) & 0xf, soc_rev & 0xf) : \
    "unknown"
```

This means:
- if `soc_rev != 0`, `revision` is heap-allocated and checked for `NULL`;
- if `soc_rev == 0`, `revision` is the constant `"unknown"`.

The caller explicitly handles both cases correctly. No double-free or invalid free is visible here.

### 4. No obvious attacker-controlled memory corruption
The values read from hardware registers (`readl_relaxed`) are used only to populate:
- `soc_rev`,
- global `soc_uid`,
- formatted strings.

There is no buffer write using attacker-controlled length or pointer arithmetic beyond fixed register offsets.

---

## Potentially suspicious points that are *not* established vulnerabilities

### A. `clk_prepare_enable(clk)` return value is ignored
Both `imx8mq_soc_revision()` and `imx8mm_soc_uid()` call:

```c
clk_prepare_enable(clk);
```

without checking its return value.

This is a real robustness issue: if clock enabling fails, the code may still access registers that require the clock. That could cause incorrect reads or, on some hardware, faults/hangs.

However, from the supplied code alone, this is **not enough to conclude a security vulnerability**:

- There is no evidence that an unprivileged attacker can influence the clock object or the DT contents at runtime.
- Device tree / hardware description is normally trusted boot-time input.
- The likely impact is initialization failure or platform instability, not a clear privilege escalation or memory-safety issue.

So this is better classified as a **reliability/defensive-programming issue**, not a confirmed security bug from the shown context.

### B. DT properties like `/model` or compatible nodes are firmware-controlled
`of_property_read_string(of_root, "model", ...)` and the compatible-node lookups depend on the boot-time device tree. If the threat model includes a malicious bootloader or malicious DTB, many kernel subsystems become attack surface. But the supplied code itself does not mishandle those strings/pointers in a way that creates a distinct vulnerability here.

### C. Global `soc_uid`
`serial_number` is derived from global `soc_uid`:

```c
soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX", soc_uid);
```

This is a fixed-format conversion into a newly allocated string. No overflow or format-string issue is present.

---

## Why this is not a confirmed vulnerability

To call this a real vulnerability, we would need evidence of one of the following:

- an attacker-controlled path to out-of-bounds access, invalid free, use-after-free, or NULL/ERR dereference;
- a privilege boundary crossed by untrusted input;
- a clear denial-of-service condition reachable by an unprivileged attacker.

The supplied code does not show that.

The closest issue is unchecked `clk_prepare_enable()` failure, but:
- it does not directly lead to memory corruption in the shown code,
- exploitability is not established,
- attacker influence is not established.

---

## Final assessment

**No real vulnerability is demonstrated by the provided code and context.**

**Observed issues:** minor robustness concern from ignoring `clk_prepare_enable()` return value.

**Security conclusion:** based on the supplied evidence, this is **not a confirmed exploitable vulnerability**.

No exploitable vulnerability is established from the supplied code. The only notable weakness is unchecked return values from clk_prepare_enable() in helper functions, which is a robustness issue rather than a demonstrated security flaw here.

Kernel probes the i.MX8M SoC driver during platform initialization.

Boot-time device tree nodes such as /model, fsl,imx8mm-anatop, or fsl,imx8mm-ocotp are present and matched.

For the robustness concern, clock enable would need to fail while subsequent MMIO reads still execute.

No confirmed security impact from the shown code. At most, unchecked clock-enable failure could cause incorrect SoC identification or platform instability during initialization, but attacker control and a concrete privilege boundary are not shown.

The code checks allocation results, validates device-tree lookups, checks ERR_PTR returns from of_clk_get_by_name(), and unwinds resources consistently with iounmap(), of_node_put(), and kfree(). The imx8_revision macro returns either a heap string or the constant "unknown", and the caller avoids freeing the literal by checking strcmp() before kfree(). No attacker-controlled buffer write, invalid free, unchecked ERR_PTR dereference, or clear privilege-boundary violation is visible in the provided path.

soc_dev_attr is allocated before use and freed on all error exits.

of_property_read_string(of_root, "model", ...) must succeed or probe aborts.

of_match_node(imx8_soc_match, of_root) must return a matching SoC entry or probe aborts.

If a soc_revision callback exists, its error return aborts probe before revision/serial registration.

of_clk_get_by_name(np, NULL) results are checked with IS_ERR() before clk use in both helper paths shown.

Mapped MMIO bases are only dereferenced after successful of_iomap().

The revision string is only kfree()'d when it is not the literal "unknown".

serial_number is allocated with a fixed format string and checked for allocation failure.

kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL) checked against NULL

kasprintf(..., "%016llX", soc_uid) checked against NULL

imx8_revision(soc_rev) result checked for NULL when soc_rev != 0

of_clk_get_by_name(np, NULL) is checked with IS_ERR() in imx8mq_soc_revision()

of_clk_get_by_name(np, NULL) is checked with IS_ERR() in imx8mm_soc_uid()

of_node_put(np) on helper exits after node lookup

iounmap(...) after successful of_iomap()

clk_put(clk) on success path after clock acquisition

kfree(serial_number) and conditional kfree(revision) in probe error paths

Hardware register values flow only into soc_rev, soc_uid, and formatted strings

No variable-length copy or pointer arithmetic based on attacker-controlled data is shown

clk_prepare_enable(clk) return values are ignored in imx8mq_soc_revision() and imx8mm_soc_uid(); this may be a reliability issue if register access requires the clock.

If a threat model treats the bootloader or DTB as malicious, the broader kernel trust boundary changes, but this code does not itself show a distinct memory-safety or privilege-escalation bug.

The analysis is limited to the supplied snippets and does not assess soc_device_register() internals or external synchronization around global soc_uid.


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/nvmem/core.c
Function: nvmem_shift_read_buffer_in_place

Call path: nvmem_cell_read_variable_le_u32 (drivers/nvmem/core.c) → nvmem_cell_read_variable_common (drivers/nvmem/core.c) → nvmem_cell_read (drivers/nvmem/core.c) → __nvmem_cell_read (drivers/nvmem/core.c) → nvmem_shift_read_buffer_in_place (drivers/nvmem/core.c)

### Primary Function

```c
static void nvmem_shift_read_buffer_in_place(struct nvmem_cell *cell, void *buf)
{
	u8 *p, *b;
	int i, extra, bit_offset = cell->bit_offset;

	p = b = buf;
	if (bit_offset) {
		/* First shift */
		*b++ >>= bit_offset;

		/* setup rest of the bytes if any */
		for (i = 1; i < cell->bytes; i++) {
			/* Get bits from next byte and shift them towards msb */
			*p |= *b << (BITS_PER_BYTE - bit_offset);

			p = b;
			*b++ >>= bit_offset;
		}
	} else {
		/* point to the msb */
		p += cell->bytes - 1;
	}

	/* result fits in less bytes */
	extra = cell->bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
	while (--extra >= 0)
		*p-- = 0;

	/* clear msb bits if any leftover in the last byte */
	if (cell->nbits % BITS_PER_BYTE)
		*p &= GENMASK((cell->nbits % BITS_PER_BYTE) - 1, 0);
}
```

### Cross-File Context

[GENMASK — macro — include/linux/kernel.h]
GENMASK → #define GENMASK(h, l) \ (((~0UL) << (l)) & (~0UL >> (sizeof(unsigned long) * BITS_PER_BYTE - 1 - (h))))  (include/linux/kernel.h)

[BITS_PER_BYTE — constant — include/linux/const.h]
BITS_PER_BYTE → 8  (include/linux/const.h)

[struct nvmem_cell — struct — drivers/nvmem/core.c:22]
```c
struct nvmem_cell {
	const char		*name;
	int			offset;
	int			bytes;
	int			bit_offset;
	int			nbits;
	struct device_node	*np;
	struct nvmem_device	*nvmem;
	struct list_head	node;
}
```

[__nvmem_cell_read — caller — drivers/nvmem/core.c:959-979]
```c
static int __nvmem_cell_read(struct nvmem_device *nvmem,
			      struct nvmem_cell *cell,
			      void *buf, size_t *len)
{
	int rc;

	rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->bytes);

	if (rc)
		return rc;

	/* shift bits in-place */
	if (cell->bit_offset || cell->nbits)
		nvmem_shift_read_buffer_in_place(cell, buf);

	if (len)
		*len = cell->bytes;

	return 0;
}
```

[nvmem_cell_info — struct — include/linux/nvmem-consumer.h]
```c
struct nvmem_cell_info {
	const char		*name;
	unsigned int		offset;
	unsigned int		bytes;
	unsigned int		bit_offset;
	unsigned int		nbits;
};
```

Control flow: The function performs two conditional paths. If bit_offset is non-zero, it shifts bytes right by bit_offset and merges adjacent bytes using left shift. If bit_offset is zero, it advances pointer p to the last byte. Then it zeroes extra bytes at the end of the buffer (calculated as bytes needed minus bytes actually used by nbits). Finally, if nbits is not byte-aligned, it masks the highest-order bits in the final byte. The function always completes without error returns.

Data flow: Input: cell struct fields (bit_offset, bytes, nbits) and buf pointer. The buffer is transformed in-place through bitwise operations: right shifts by bit_offset, left shifts by (8 - bit_offset), OR operations to merge adjacent bytes, zeroing of unused trailing bytes, and masking of leftover high-order bits. The GENMASK macro generates a bitmask using the remainder of nbits divided by BITS_PER_BYTE.

Verdict: Not Vulnerable

Reviewed region:
[drivers/nvmem/core.c]
```c
static void nvmem_shift_read_buffer_in_place(struct nvmem_cell *cell, void *buf)
{
	u8 *p, *b;
	int i, extra, bit_offset = cell->bit_offset;

	p = b = buf;
	if (bit_offset) {
		/* First shift */
		*b++ >>= bit_offset;

		/* setup rest of the bytes if any */
		for (i = 1; i < cell->bytes; i++) {
			/* Get bits from next byte and shift them towards msb */
			*p |= *b << (BITS_PER_BYTE - bit_offset);

			p = b;
			*b++ >>= bit_offset;
		}
	} else {
		/* point to the msb */
		p += cell->bytes - 1;
	}

	/* result fits in less bytes */
	extra = cell->bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
	while (--extra >= 0)
		*p-- = 0;

	/* clear msb bits if any leftover in the last byte */
	if (cell->nbits % BITS_PER_BYTE)
		*p &= GENMASK((cell->nbits % BITS_PER_BYTE) - 1, 0);
}
```

Why it is not vulnerable: The shift operations use bit_offset which ranges from 0-7 (a byte offset), and the code guards the GENMASK call with 'if (cell->nbits % BITS_PER_BYTE)' which ensures the mask high-bit parameter is in range [0, 6] rather than -1. All shift amounts in GENMASK are within valid bounds: l=0 is safe, and the right shift amount is sizeof(unsigned long)*BITS_PER_BYTE-1-h where h is at most 6, yielding at minimum 58 on a 64-bit system. The buffer access patterns are bounded by cell->bytes which matches the allocation size from the caller.

Security invariants:
- bit_offset must be in [0, 7] for safe byte-level shifts - enforced by the struct definition and the calling path which uses bit_offset as a per-byte alignment value
- The GENMASK expression must not use a negative high-bit value - enforced by the guard 'if (cell->nbits % BITS_PER_BYTE)' which only enters when nbits is not byte-aligned, ensuring (nbits % 8) is in [1, 7], so (nbits % 8 - 1) is in [0, 6]
- Shift amounts in GENMASK must not exceed the operand width - l=0 is always valid, and the right shift amount 'sizeof(unsigned long)*BITS_PER_BYTE - 1 - h' where h is in [0,6] yields values in [58, 63] on 64-bit, all valid
- Buffer pointer arithmetic must stay within allocated bounds - the extra bytes zeroing loop decrements p at most 'cell->bytes - DIV_ROUND_UP(cell->nbits, 8)' times, which is at most cell->bytes, and p is initialized to the last byte of the buffer
- The buffer must be at least cell->bytes in size - enforced by caller __nvmem_cell_read which passes the same cell->bytes size to nvmem_reg_read

Invariant verification:
- GENMASK high-bit parameter is non-negative: holds=true. Evidence: The expression 'if (cell->nbits % BITS_PER_BYTE)' gates the GENMASK call, ensuring nbits % 8 is in [1, 7], so (cell->nbits % BITS_PER_BYTE) - 1 is in [0, 6]
- Shift amounts do not exceed operand width: holds=true. Evidence: In '*b >>= bit_offset' and '*p |= *b << (BITS_PER_BYTE - bit_offset)', bit_offset is a byte alignment in [0,7]. In GENMASK, l=0 and the right shift argument is at most sizeof(unsigned long)*8-1 which equals the bit width minus 1, always safe
- Buffer access remains within bounds: holds=true. Evidence: The loop 'for (i = 1; i < cell->bytes; i++)' accesses at most cell->bytes elements. The zeroing loop 'while (--extra >= 0) *p-- = 0' decrements at most cell->bytes times from the last valid position

Residual risk notes: The code assumes cell->bytes matches the actual buffer allocation size from the caller. If nvmem_reg_read were to allocate a different size, bounds could be violated. Also, the code assumes bit_offset is strictly within [0, 7]; values outside this range would cause undefined behavior in shifts, though this appears enforced by the data model. The GENMASK macro's intermediate computation uses unsigned long arithmetic which is safe but could have different behavior on 32-bit vs 64-bit systems (affects only the temporary computation, not the final masked u8 result).

**Verdict: no confirmed real vulnerability from the supplied code/context alone**

### What the function does
`nvmem_shift_read_buffer_in_place()` post-processes a buffer already filled by:

```c
rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->bytes);
```

So the buffer is expected to contain exactly `cell->bytes` bytes. The helper then:
- right-shifts the data by `cell->bit_offset` bits across byte boundaries,
- zeroes any excess high bytes beyond `cell->nbits`,
- masks off unused high bits in the last significant byte.

### Security-relevant assumptions in this function
The code clearly assumes all of the following about `cell`:

1. `cell->bytes > 0`
2. `0 <= cell->bit_offset < 8`
3. `0 < cell->nbits <= cell->bytes * 8` or at least that `nbits` is consistent with `bytes`
4. `buf` points to at least `cell->bytes` writable bytes

Those assumptions are **not validated inside this function**.

### Potentially dangerous behavior if metadata is invalid
If invalid `cell` metadata reaches this function, there are memory-safety hazards:

- If `cell->bytes == 0` and `bit_offset == 0`:
  ```c
  p += cell->bytes - 1;
  ```
  moves `p` before the buffer, and later:
  ```c
  *p &= ...
  ```
  could write out of bounds.

- If `cell->bytes == 0` and `bit_offset != 0`:
  ```c
  *b++ >>= bit_offset;
  ```
  dereferences `buf` even though zero bytes were requested.

- If `cell->nbits` is inconsistent with `cell->bytes`, pointer/masking logic may no longer match the actual valid region.

So **there is a latent memory-corruption risk if malformed cell definitions are allowed through**.

### Why this is not enough to call it a real vulnerability here
From the supplied context, we do **not** see how `struct nvmem_cell` objects are created or validated. That matters a lot.

What we do know:
- The public-ish metadata type `struct nvmem_cell_info` uses `unsigned int` for `bytes`, `bit_offset`, and `nbits`, suggesting these are configuration fields, not raw attacker-controlled runtime input.
- The call path shown is entirely internal kernel code operating on `struct nvmem_cell`.
- No evidence is provided that untrusted userspace can directly supply arbitrary `cell->bytes`, `cell->bit_offset`, or `cell->nbits`.

So the observed issue is: **this helper trusts its caller and metadata**. But the supplied code does not show that those fields can be malformed in a reachable, attacker-controlled way.

### Concrete code-grounded conclusion
- **Observed evidence:** the function lacks defensive bounds checks and could perform out-of-bounds accesses for malformed `cell` metadata.
- **Missing evidence:** any path showing such malformed metadata can actually be instantiated and reached from an attacker-controlled source, or that upstream validation is absent.
- **Conclusion:** based only on the supplied code/context, this is **not a confirmed vulnerability**. It is better characterized as a function with **unsafe preconditions** that relies on prior validation elsewhere.

### Residual uncertainty
If elsewhere in the repository:
- cell definitions can come from device tree / firmware / provider drivers without strict validation, and
- an attacker can influence those definitions,

then this helper could become part of a real memory-safety bug. But that cannot be established from the provided snippet alone.

No confirmed vulnerability is established from the supplied code. The helper assumes valid nvmem cell metadata (notably sane values for bytes, bit_offset, and nbits) and a writable buffer of cell->bytes bytes. Although malformed metadata could make the pointer arithmetic unsafe, the provided context does not show that invalid metadata can reach this function or that upstream validation is missing.

Not demonstrated from the supplied context. Exploitability would require attacker influence over struct nvmem_cell fields such as bytes, bit_offset, or nbits, plus absence of prior validation. That evidence is not present here.

An attacker would need a reachable path to instantiate or control malformed struct nvmem_cell metadata.

Specifically, values such as bytes == 0, out-of-range bit_offset, or inconsistent nbits would need to bypass any upstream validation.

The malformed cell would then need to be read through __nvmem_cell_read so that nvmem_shift_read_buffer_in_place executes on the invalid metadata.

No confirmed security impact from the shown code alone. If the unmet preconditions above were possible elsewhere, the consequences could include out-of-bounds read/write within the supplied buffer processing path, but that is not established by the provided evidence.

The code is only called after __nvmem_cell_read reads exactly cell->bytes bytes into buf via nvmem_reg_read(nvmem, cell->offset, buf, cell->bytes). Within the supplied material, there is no proof that cell->bytes, cell->bit_offset, or cell->nbits are attacker-controlled or invalid at this point. The function therefore appears to rely on caller-provided invariants rather than mishandling validated inputs. Absent evidence about cell construction or missing validation elsewhere, this is an unsafe-preconditions concern, not a confirmed vulnerability.

buf must reference at least cell->bytes writable bytes.

cell->bytes must be positive and consistent with the buffer length.

cell->bit_offset must be in the expected bit range for a byte-oriented shift.

cell->nbits must be consistent with cell->bytes so that extra-byte zeroing and final masking stay within the intended buffer.

__nvmem_cell_read calls nvmem_reg_read with size cell->bytes before invoking the shift helper.

The helper is only reached from __nvmem_cell_read when cell->bit_offset or cell->nbits is nonzero.

That cell->bit_offset is always within 0..7.

That cell->nbits is always valid relative to cell->bytes.

That struct nvmem_cell instances are fully validated at creation time.

The function would be memory-unsafe for malformed metadata; e.g., bytes == 0 can make dereferences or p += cell->bytes - 1 invalid.

This residual risk depends entirely on upstream metadata validation, which is not included in the supplied context.

If other repository code permits untrusted or malformed firmware/device-tree/provider metadata to populate struct nvmem_cell without checks, the assessment could change.


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/the-tcpdump-group/tcpdump
Language: C
File: print-icmp6.c
Function: icmp6_nodeinfo_print

Call path: icmp6_nodeinfo_print (print-icmp6.c)

### Primary Function

```c
icmp6_nodeinfo_print(netdissect_options *ndo, u_int icmp6len, const u_char *bp, const u_char *ep)
{
	const struct icmp6_nodeinfo *ni6;
	const struct icmp6_hdr *dp;
	const u_char *cp;
	size_t siz, i;
	int needcomma;

	if (ep < bp)
		return;
	dp = (const struct icmp6_hdr *)bp;
	ni6 = (const struct icmp6_nodeinfo *)bp;
	siz = ep - bp;

	switch (ni6->ni_type) {
	case ICMP6_NI_QUERY:
		if (siz == sizeof(*dp) + 4) {
			/* KAME who-are-you */
			ND_PRINT((ndo," who-are-you request"));
			break;
		}
		ND_PRINT((ndo," node information query"));

		ND_TCHECK2(*dp, sizeof(*ni6));
		ni6 = (const struct icmp6_nodeinfo *)dp;
		ND_PRINT((ndo," ("));/*)*/
		switch (EXTRACT_16BITS(&ni6->ni_qtype)) {
		case NI_QTYPE_NOOP:
			ND_PRINT((ndo,"noop"));
			break;
		case NI_QTYPE_SUPTYPES:
			ND_PRINT((ndo,"supported qtypes"));
			i = EXTRACT_16BITS(&ni6->ni_flags);
			if (i)
				ND_PRINT((ndo," [%s]", (i & 0x01) ? "C" : ""));
			break;
		case NI_QTYPE_FQDN:
			ND_PRINT((ndo,"DNS name"));
			break;
		case NI_QTYPE_NODEADDR:
			ND_PRINT((ndo,"node addresses"));
			i = ni6->ni_flags;
			if (!i)
				break;
			/* NI_NODEADDR_FLAG_TRUNCATE undefined for query */
			ND_PRINT((ndo," [%s%s%s%s%s%s]",
			    (i & NI_NODEADDR_FLAG_ANYCAST) ? "a" : "",
			    (i & NI_NODEADDR_FLAG_GLOBAL) ? "G" : "",
			    (i & NI_NODEADDR_FLAG_SITELOCAL) ? "S" : "",
			    (i & NI_NODEADDR_FLAG_LINKLOCAL) ? "L" : "",
			    (i & NI_NODEADDR_FLAG_COMPAT) ? "C" : "",
			    (i & NI_NODEADDR_FLAG_ALL) ? "A" : ""));
			break;
		default:
			ND_PRINT((ndo,"unknown"));
			break;
		}

		if (ni6->ni_qtype == NI_QTYPE_NOOP ||
		    ni6->ni_qtype == NI_QTYPE_SUPTYPES) {
			if (siz != sizeof(*ni6))
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", invalid len"));
			/*(*/
			ND_PRINT((ndo,")"));
			break;
		}

		/* XXX backward compat, icmp-name-lookup-03 */
		if (siz == sizeof(*ni6)) {
			ND_PRINT((ndo,", 03 draft"));
			/*(*/
			ND_PRINT((ndo,")"));
			break;
		}

		switch (ni6->ni_code) {
		case ICMP6_NI_SUBJ_IPV6:
			if (!ND_TTEST2(*dp,
			    sizeof(*ni6) + sizeof(struct in6_addr)))
				break;
			if (siz != sizeof(*ni6) + sizeof(struct in6_addr)) {
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", invalid subject len"));
				break;
			}
			ND_PRINT((ndo,", subject=%s",
                                  ip6addr_string(ndo, ni6 + 1)));
			break;
		case ICMP6_NI_SUBJ_FQDN:
			ND_PRINT((ndo,", subject=DNS name"));
			cp = (const u_char *)(ni6 + 1);
			if (cp[0] == ep - cp - 1) {
				/* icmp-name-lookup-03, pascal string */
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", 03 draft"));
				cp++;
				ND_PRINT((ndo,", \""));
				while (cp < ep) {
					safeputchar(ndo, *cp);
					cp++;
				}
				ND_PRINT((ndo,"\""));
			} else
				dnsname_print(ndo, cp, ep);
			break;
		case ICMP6_NI_SUBJ_IPV4:
			if (!ND_TTEST2(*dp, sizeof(*ni6) + sizeof(struct in_addr)))
				break;
			if (siz != sizeof(*ni6) + sizeof(struct in_addr)) {
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", invalid subject len"));
				break;
			}
			ND_PRINT((ndo,", subject=%s",
                                  ipaddr_string(ndo, ni6 + 1)));
			break;
		default:
			ND_PRINT((ndo,", unknown subject"));
			break;
		}

		/*(*/
		ND_PRINT((ndo,")"));
		break;

	case ICMP6_NI_REPLY:
		if (icmp6len > siz) {
			ND_PRINT((ndo,"[|icmp6: node information reply]"));
			break;
		}

		needcomma = 0;

		ni6 = (const struct icmp6_nodeinfo *)dp;
		ND_PRINT((ndo," node information reply"));
		ND_PRINT((ndo," ("));/*)*/
		switch (ni6->ni_code) {
		case ICMP6_NI_SUCCESS:
			if (ndo->ndo_vflag) {
				ND_PRINT((ndo,"success"));
				needcomma++;
			}
			break;
		case ICMP6_NI_REFUSED:
			ND_PRINT((ndo,"refused"));
			needcomma++;
			if (siz != sizeof(*ni6))
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", invalid length"));
			break;
		case ICMP6_NI_UNKNOWN:
			ND_PRINT((ndo,"unknown"));
			needcomma++;
			if (siz != sizeof(*ni6))
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", invalid length"));
			break;
		}

		if (ni6->ni_code != ICMP6_NI_SUCCESS) {
			/*(*/
			ND_PRINT((ndo,")"));
			break;
		}

		switch (EXTRACT_16BITS(&ni6->ni_qtype)) {
		case NI_QTYPE_NOOP:
			if (needcomma)
				ND_PRINT((ndo,", "));
			ND_PRINT((ndo,"noop"));
			if (siz != sizeof(*ni6))
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", invalid length"));
			break;
		case NI_QTYPE_SUPTYPES:
			if (needcomma)
				ND_PRINT((ndo,", "));
			ND_PRINT((ndo,"supported qtypes"));
			i = EXTRACT_16BITS(&ni6->ni_flags);
			if (i)
				ND_PRINT((ndo," [%s]", (i & 0x01) ? "C" : ""));
			break;
		case NI_QTYPE_FQDN:
			if (needcomma)
				ND_PRINT((ndo,", "));
			ND_PRINT((ndo,"DNS name"));
			cp = (const u_char *)(ni6 + 1) + 4;
			if (cp[0] == ep - cp - 1) {
				/* icmp-name-lookup-03, pascal string */
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", 03 draft"));
				cp++;
				ND_PRINT((ndo,", \""));
				while (cp < ep) {
					safeputchar(ndo, *cp);
					cp++;
				}
				ND_PRINT((ndo,"\""));
			} else
				dnsname_print(ndo, cp, ep);
			if ((EXTRACT_16BITS(&ni6->ni_flags) & 0x01) != 0)
				ND_PRINT((ndo," [TTL=%u]", EXTRACT_32BITS(ni6 + 1)));
			break;
		case NI_QTYPE_NODEADDR:
			if (needcomma)
				ND_PRINT((ndo,", "));
			ND_PRINT((ndo,"node addresses"));
			i = sizeof(*ni6);
			while (i < siz) {
				if (i + sizeof(struct in6_addr) + sizeof(int32_t) > siz)
					break;
				ND_PRINT((ndo," %s", ip6addr_string(ndo, bp + i)));
				i += sizeof(struct in6_addr);
				ND_PRINT((ndo,"(%d)", (int32_t)EXTRACT_32BITS(bp + i)));
				i += sizeof(int32_t);
			}
			i = ni6->ni_flags;
			if (!i)
				break;
			ND_PRINT((ndo," [%s%s%s%s%s%s%s]",
                                  (i & NI_NODEADDR_FLAG_ANYCAST) ? "a" : "",
                                  (i & NI_NODEADDR_FLAG_GLOBAL) ? "G" : "",
                                  (i & NI_NODEADDR_FLAG_SITELOCAL) ? "S" : "",
                                  (i & NI_NODEADDR_FLAG_LINKLOCAL) ? "L" : "",
                                  (i & NI_NODEADDR_FLAG_COMPAT) ? "C" : "",
                                  (i & NI_NODEADDR_FLAG_ALL) ? "A" : "",
                                  (i & NI_NODEADDR_FLAG_TRUNCATE) ? "T" : ""));
			break;
		default:
			if (needcomma)
				ND_PRINT((ndo,", "));
			ND_PRINT((ndo,"unknown"));
			break;
		}

		/*(*/
		ND_PRINT((ndo,")"));
		break;
	}
	return;

trunc:
	ND_PRINT((ndo, "[|icmp6]"));
}
```

### Cross-File Context

[struct icmp6_nodeinfo — struct — print-icmp6.c:383-389]
```c
struct icmp6_nodeinfo {
	struct icmp6_hdr icmp6_ni_hdr;
	nd_byte icmp6_ni_nonce[8];
	/* could be followed by reply data */
};
```

[ICMP6_NI_REPLY — constant — print-icmp6.c:119]
ICMP6_NI_REPLY → 140 /* node information reply - RFC 4620 */  (print-icmp6.c:119)

[NI_QTYPE_FQDN — constant — print-icmp6.c:397]
NI_QTYPE_FQDN → 2 /* Node Name */  (print-icmp6.c:397)

[ND_TCHECK2 — macro — netdissect.h]
ND_TCHECK2 → #define ND_TCHECK2(p, l) ND_TCHECK_LEN((p), (l))  (netdissect.h)

[ND_TCHECK — macro — netdissect.h]
ND_TCHECK → #define ND_TCHECK(p) ND_TCHECK_LEN((p), 1)  (netdissect.h)

Control flow: The function icmp6_nodeinfo_print parses ICMP6 node information packets. For NI_REPLY type packets, it extracts ni_qtype and processes different query types. For NI_QTYPE_FQDN, it checks if the DNS name field uses pascal string format by comparing cp[0] against length. Without bounds checks, truncated packets cause out-of-bounds reads.

Data flow: Packet data flows from bp/ep buffer bounds to dp and ni6 pointers. In ICMP6_NI_REPLY case, ni6 is derived from dp which may not have enough bytes. For NI_QTYPE_FQDN, cp is set to (ni6+1)+4 and cp[0] is read without validation.

Verdict: Vulnerable
Vulnerable region:
[print-icmp6.c]
```c
needcomma = 0;

		ni6 = (const struct icmp6_nodeinfo *)dp;
		ND_PRINT((ndo," node information reply"));
		ND_PRINT((ndo," ("));	/*)*/
		switch (ni6->ni_code) {
				ND_PRINT((ndo,", "));
			ND_PRINT((ndo,"DNS name"));
			cp = (const u_char *)(ni6 + 1) + 4;
			if (cp[0] == ep - cp - 1) {
				/* icmp-name-lookup-03, pascal string */
				if (ndo->ndo_vflag)
					ND_PRINT((ndo,", 03 draft"));
				cp++;
```
CWE: CWE-125
Severity: HIGH

Root cause: Two missing bounds checks in icmp6_nodeinfo_print() when processing ICMP6 node information replies. First, the ni6 pointer is assigned from dp without checking buffer bounds before the struct access at line 1702. Second, when processing NI_QTYPE_FQDN (DNS name), cp[0] is accessed at line 1756 to check for pascal string format without first verifying cp points to valid memory.

Exploitability: Attacker sends a crafted ICMP6 packet with truncated node information data. When tcpdump processes the packet, missing bounds checks allow reading beyond buffer boundaries.

Attack preconditions:
- Attacker can send network packets to tcpdump for processing
- Attacker crafts ICMP6 packet with type 140 (NI_REPLY) and truncated data
- Packet triggers processing in icmp6_nodeinfo_print() without sufficient bytes for struct icmp6_nodeinfo

Likely impact: Attacker sends a crafted ICMP6 packet with truncated node information data. When tcpdump processes the packet, missing bounds checks allow reading beyond buffer boundaries.

Yes — this function contains a real out-of-bounds read risk.

## Verdict

**Vulnerable: CWE-125 (Out-of-bounds Read)**

## Observed evidence

The function uses `bp`/`ep` as the available buffer bounds:

```c
if (ep < bp)
    return;
...
siz = ep - bp;
```

So `siz` is the only local indication of how many bytes are actually available.

### 1. It reads `ni6->ni_type` before any bounds check

Immediately after setting `ni6 = (const struct icmp6_nodeinfo *)bp;`, it does:

```c
switch (ni6->ni_type) {
```

There is **no** prior `ND_TCHECK`/`ND_TTEST` ensuring even 1 byte is available. If `ep == bp` (`siz == 0`), this dereferences past the available capture buffer.

The later check in the query case:

```c
ND_TCHECK2(*dp, sizeof(*ni6));
```

does not protect this earlier access.

### 2. The reply path reads multiple header fields without validating `sizeof(*ni6)`

In the reply case:

```c
case ICMP6_NI_REPLY:
    if (icmp6len > siz) {
        ...
        break;
    }
    ...
    switch (ni6->ni_code) {
```

`icmp6len > siz` only rejects when the claimed ICMPv6 length exceeds the captured size. It does **not** ensure that at least `sizeof(*ni6)` bytes are present.

So if a truncated packet is captured with `siz < sizeof(*ni6)` but `icmp6len <= siz`, the code still reads:

- `ni6->ni_code`
- `ni6->ni_qtype`
- `ni6->ni_flags`

without proving those bytes exist.

### 3. A concrete unguarded OOB read in the FQDN reply branch

In the reply/FQDN branch:

```c
cp = (const u_char *)(ni6 + 1) + 4;
if (cp[0] == ep - cp - 1) {
```

There is no check that `siz >= sizeof(*ni6) + 4 + 1`.

Given `struct icmp6_nodeinfo` is:

```c
struct icmp6_nodeinfo {
    struct icmp6_hdr icmp6_ni_hdr;
    nd_byte icmp6_ni_nonce[8];
};
```

`sizeof(*ni6)` is the fixed header plus nonce. If the packet is exactly that long, then:

- `cp = bp + sizeof(*ni6) + 4`
- `cp > ep`

and `cp[0]` is an out-of-bounds read.

This is a stronger, concrete issue than the initial `ni_type` read because it occurs on a reachable code path with only a short/truncated reply packet.

## Data/control-flow reasoning

An attacker can influence the packet bytes and length seen by this printer. If tcpdump processes a malformed or truncated ICMPv6 Node Information packet:

1. `bp` points to the packet data.
2. `ep` may indicate fewer bytes than the parser expects.
3. The function computes `siz = ep - bp`, but then reads fields from `bp` as `struct icmp6_nodeinfo` before verifying `siz` is large enough.
4. In some branches, especially reply/FQDN, it computes pointers beyond `ep` and dereferences them.

## Why existing guards are insufficient

- `if (ep < bp)` only checks pointer ordering, not minimum length.
- `if (icmp6len > siz)` only compares claimed protocol length vs captured length; it does not ensure the captured length is enough for the struct accesses performed.
- `ND_TCHECK2(*dp, sizeof(*ni6))` appears only in the query path and only **after** the initial `ni_type` read.
- Some branches use `ND_TTEST2` for specific subjects, but the vulnerable reads happen earlier or in other branches.

## Realistic impact

In tcpdump, the likely impact is:

- **Crash / denial of service** while parsing a malformed packet or malformed capture file.
- Potentially other memory-safety consequences depending on platform/build and what the downstream printing helpers do with invalid pointers.

From the shown code alone, the clearest justified impact is **out-of-bounds read leading to process instability or crash**.

## Residual uncertainty

I cannot fully rule out that a caller elsewhere always guarantees a minimum ICMPv6 header size before calling this function, because only this function and limited context were provided. However:

- the function itself clearly relies on `bp`/`ep` bounds,
- it performs some explicit truncation checks internally,
- and those checks are inconsistent/incomplete.

So based on the supplied code, this is a real parser bug in this function, not merely a theoretical concern.

## Conclusion

`icmp6_nodeinfo_print` is vulnerable because it dereferences packet data as `struct icmp6_nodeinfo` before confirming enough bytes are available, and in at least one branch (`ICMP6_NI_REPLY` + `NI_QTYPE_FQDN`) it computes and dereferences a pointer beyond `ep` without a bounds check.

Parses ICMPv6 node information query/reply packets using bp/ep as capture bounds and prints decoded fields.

ni6 = (const struct icmp6_nodeinfo *)bp; ... switch (ni6->ni_type)

case ICMP6_NI_REPLY: if (icmp6len > siz) ... switch (ni6->ni_code) ... switch (EXTRACT_16BITS(&ni6->ni_qtype))

The function dereferences packet-backed struct fields before verifying that the capture buffer contains enough bytes for those reads. Bounds checks are present in some subpaths, but they occur after earlier field access or do not cover all subsequent pointer arithmetic and dereferences.

An attacker who can supply malformed network traffic to tcpdump or a crafted capture file can trigger parser reads past the captured packet boundary. This is realistically exploitable for process crash/DoS; the provided code most clearly supports out-of-bounds read rather than stronger memory corruption.

tcpdump must process attacker-controlled live traffic or an attacker-controlled pcap containing an ICMPv6 node information packet.

The captured length presented via bp/ep must be shorter than the parser assumes.

For the strongest demonstrated case, execution must reach the ICMP6_NI_REPLY + NI_QTYPE_FQDN branch with insufficient trailing bytes.

Out-of-bounds read from packet parser input buffer

Potential crash / denial of service during packet dissection

Undefined behavior from dereferencing pointers beyond ep

Safe parsing requires validating that at least the accessed header bytes are within [bp, ep) before reading ni_type, ni_code, ni_qtype, or ni_flags.

Any derived pointer such as (ni6 + 1) + 4 must be proven <= ep before dereference.

icmp6len <= siz is not sufficient to guarantee sizeof(struct icmp6_nodeinfo) bytes are present.

ND_TCHECK2(*dp, sizeof(*ni6)) in query path

Selective ND_TTEST2 checks for IPv4/IPv6 subject payloads

The first access to ni6->ni_type happens before any ND_TCHECK/ND_TTEST.

The reply path reads ni6->ni_code and ni6->ni_qtype without first checking siz >= sizeof(*ni6).

The FQDN reply path computes cp beyond the end of the checked data and dereferences cp[0] without validating at least one byte is available there.

The supplied context does not show caller-side guarantees about minimum ICMPv6 header size. However, this function performs its own truncation handling and still contains unchecked accesses, so the bug is code-local and not dependent on assuming a flawed caller.

From the provided code alone, the justified classification is out-of-bounds read; stronger impact such as code execution is not supported by the evidence shown.

[{'id': 'CWE-125', 'name': 'Out-of-bounds Read'}]