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

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

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

### Primary Function

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

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

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

### Cross-File Context

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Security Audit Analysis

### Function Under Review
`tensorflow::ParallelConcatUpdate<CPUDevice>::Compute`

### Vulnerability Assessment: **NOT VULNERABLE** (but with residual concerns worth noting)

---

### Control Flow and Data Flow Analysis

**Inputs:**
- `value` (input 0): the base tensor
- `update` (input 1): the slice to write
- `loc_`: an `int32` attribute set at kernel construction time (not runtime user input in the traditional sense, but graph-controlled)

**Validation Guards Applied:**
1. `value.dims() == update.dims()` — dimension rank must match
2. `value.dim_size(i) == update.dim_size(i)` for `i ∈ [1, dims)` — all non-zeroth dimensions must match
3. `update.dim_size(0) == 1` — update must have exactly one "row"

**Call into `DoParallelConcatUpdate`:**
```cpp
auto r = (loc % nrows + nrows) % nrows;  // Guard index range.
```
This performs a modulo-based bounds normalization on `loc_`, which prevents out-of-bounds access regardless of the value of `loc_`. Both negative and large positive values of `loc_` are safely wrapped.

---

### Potential Issue: `nrows == 0` (Division by Zero)

**The critical question:** Can `nrows` be zero?

`nrows = Toutput.dimension(0)` where `Toutput = output->flat_outer_dims<T>()`. This is the first dimension of `output`, which is an alias of `value`.

If `value` has 0 rows (i.e., `value.dim_size(0) == 0`), then `nrows == 0`, and the expression:
```cpp
(loc % nrows + nrows) % nrows
```
performs **integer division by zero**, which is **undefined behavior** in C++.

**Can this happen?** Looking at the guards in `Compute`:
- There is **no check** that `value.dim_size(0) > 0`
- The only check on dim 0 is that `update.dim_size(0) == 1`
- An attacker supplying a `value` tensor with shape `[0, N, ...]` and an `update` tensor with shape `[1, N, ...]` would pass all guards

**Exploitability:**
- In TensorFlow's threat model, graph construction is typically trusted. However, TensorFlow explicitly documents that untrusted graphs/SavedModels are a security boundary.
- A crafted graph could pass a zero-first-dimension tensor as `value`, triggering division by zero in `DoParallelConcatUpdate`, causing a crash (denial of service).

---

### Other Considered Issues

| Issue | Status |
|---|---|
| Out-of-bounds write via `loc_` | Mitigated — modulo guard in `DoParallelConcatUpdate` |
| Shape mismatch memory corruption | Mitigated — explicit shape checks before dispatch |
| Integer overflow in `loc % nrows` | `int32` arithmetic; overflow possible only if `nrows` is negative, but `dimension()` returns non-negative |
| Type confusion | Mitigated — `CHECK_EQ(value.dtype(), output->dtype())` |

---

### Verdict

**Residual vulnerability: Division-by-zero / crash (DoS) when `value.dim_size(0) == 0`.**

- **Root cause:** Missing guard `value.dim_size(0) > 0` before calling `DoParallelConcat`
- **Location:** `DoParallelConcatUpdate`, line `(loc % nrows + nrows) % nrows` with `nrows == 0`
- **Classification:** CWE-369 (Divide By Zero), manifesting as undefined behavior / process crash
- **Impact:** Denial of service via crafted TensorFlow graph
- **Precondition:** Attacker can submit a graph with a `_ParallelConcatUpdate` op where `value` has a zero-sized first dimension

**Recommended fix:**
```cpp
OP_REQUIRES(ctx, value.dim_size(0) > 0,
            errors::InvalidArgument("value must have non-zero first dimension"));
```
This should be added in `Compute` before the `DoParallelConcat` call, or alternatively as a guard at the top of `DoParallelConcatUpdate`.

```json
{
  "vulnerable": true,
  "cwe": "CWE-369",
  "severity": "MEDIUM",
  "reviewed_region": "tensorflow/core/kernels/inplace_ops.cc:ParallelConcatUpdate<CPUDevice>::Compute (lines 72-110)",
  "vulnerable_region": "tensorflow/core/kernels/inplace_ops.cc:DoParallelConcatUpdate line 38: auto r = (loc % nrows + nrows) % nrows",
  "root_cause": "Missing validation that value.dim_size(0) > 0 before modulo operation. The Compute function validates dimension rank matching, non-zeroth dimension sizes, and that update.dim_size(0) == 1, but never checks that value.dim_size(0) > 0. This allows nrows to be zero in DoParallelConcatUpdate, causing division by zero in the modulo guard expression.",
  "exploitability": "HIGH - Attacker can craft a TensorFlow graph with _ParallelConcatUpdate op where value tensor has shape [0, N, ...] and update tensor has shape [1, N, ...]. This passes all validation checks but triggers division by zero when computing row index. Attack requires only ability to submit malicious graph (SavedModel/GraphDef), which is within TensorFlow's documented threat model for untrusted graphs.",
  "attack_preconditions": [
    "Attacker can submit TensorFlow graph (SavedModel, GraphDef, or direct op construction)",
    "Graph contains _ParallelConcatUpdate operation",
    "value input tensor has first dimension size of 0 (e.g., shape [0, 10, 20])",
    "update input tensor has first dimension size of 1 with matching other dimensions (e.g., shape [1, 10, 20])",
    "loc attribute can be any int32 value"
  ],
  "impact": "Denial of Service via process crash. Division by zero triggers undefined behavior, typically manifesting as SIGFPE signal and process termination on most platforms. No confidentiality or integrity impact. Affects availability of TensorFlow serving infrastructure processing untrusted models.",
  "non_vulnerable_reason": null,
  "security_invariants": [
    "value.dims() == update.dims() (enforced)",
    "value.dim_size(i) == update.dim_size(i) for i in [1, dims) (enforced)",
    "update.dim_size(0) == 1 (enforced)",
    "value.dtype() == output->dtype() (enforced via CHECK_EQ)",
    "MISSING: value.dim_size(0) > 0 (not enforced)"
  ],
  "invariant_verification": "Traced concrete attack: value=[0,5], update=[1,5], loc=42. Passes all checks in Compute (dims match, dim_size(1) matches, update.dim_size(0)==1). In DoParallelConcatUpdate, nrows=0, then (42 % 0 + 0) % 0 triggers division by zero. Vulnerability confirmed.",
  "residual_risk_notes": "Fix requires adding OP_REQUIRES(ctx, value.dim_size(0) > 0, errors::InvalidArgument(...)) before DoParallelConcat call. Additional edge case: tensors with zero total elements but non-zero first dimension (e.g., [5,0]) may have other issues in shaped<T,2>({1, value.NumElements()}) call, but this is not demonstrated by current code path.",
  "call_path": "_ParallelConcatUpdate (array_ops.cc) → ParallelConcatUpdate::Compute (inplace_ops.cc) → DoParallelConcat (inplace_ops.cc) → DoParallelConcatUpdate (inplace_ops.cc)"
}
```


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: drivers/usb/typec/altmodes/displayport.c
Function: pin_assignment_show

Call path: pin_assignment_show (drivers/usb/typec/altmodes/displayport.c) → get_count_order (include/linux/bitops.h) → get_current_pin_assignments (drivers/usb/typec/altmodes/displayport.c) → DP_CAP_PIN_ASSIGN_UFP_D (include/linux/usb/typec_dp.h) → DP_CAP_PIN_ASSIGN_DFP_D (include/linux/usb/typec_dp.h)

### Primary Function

```c
static ssize_t pin_assignment_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	struct dp_altmode *dp = dev_get_drvdata(dev);
	u8 assignments;
	int len = 0;
	u8 cur;
	int i;

	mutex_lock(&dp->lock);

	cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));

	assignments = get_current_pin_assignments(dp);

	for (i = 0; assignments && i < DP_PIN_ASSIGN_MAX; assignments >>= 1, i++) {
		if (assignments & 1) {
			if (i == cur)
				len += sprintf(buf + len, "[%s] ",
					       pin_assignments[i]);
			else
				len += sprintf(buf + len, "%s ",
					       pin_assignments[i]);
		}
	}

	mutex_unlock(&dp->lock);

	/* get_current_pin_assignments can return 0 when no matching pin assignments are found */
	if (len == 0)
		len++;

	buf[len - 1] = '\n';
	return len;
}
```

### Cross-File Context

[DP_PIN_ASSIGN_MAX — enum — include/linux/usb/typec_dp.h:52-60]
enum { DP_PIN_ASSIGN_A, /* Not supported after v1.0b */ DP_PIN_ASSIGN_B, /* Not supported after v1.0b */ DP_PIN_ASSIGN_C, DP_PIN_ASSIGN_D, DP_PIN_ASSIGN_E, DP_PIN_ASSIGN_F, /* Not supported after v1.0b */ DP_PIN_ASSIGN_MAX, };

[get_count_order — helper — include/linux/bitops.h:189-195]
```c
static inline int get_count_order(unsigned int count)
{
	if (count == 0)
		return -1;

	return fls(--count);
}
```

[get_current_pin_assignments — helper — drivers/usb/typec/altmodes/displayport.c:439-445]
```c
static u8 get_current_pin_assignments(struct dp_altmode *dp)
{
	if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
		return DP_CAP_PIN_ASSIGN_DFP_D(dp->alt->vdo);
	else
		return DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo);
}
```

[DP_CAP_PIN_ASSIGN_UFP_D — helper — include/linux/usb/typec_dp.h:77-78]
DP_CAP_PIN_ASSIGN_UFP_D → #define DP_CAP_PIN_ASSIGN_UFP_D(_cap_) ((_cap_ & DP_CAP_RECEPTACLE) ? \ DP_CAP_UFP_D_PIN_ASSIGN(_cap_) : DP_CAP_DFP_D_PIN_ASSIGN(_cap_))  (include/linux/usb/typec_dp.h:77-78)

[DP_CAP_PIN_ASSIGN_DFP_D — helper — include/linux/usb/typec_dp.h:80-81]
DP_CAP_PIN_ASSIGN_DFP_D → #define DP_CAP_PIN_ASSIGN_DFP_D(_cap_) ((_cap_ & DP_CAP_RECEPTACLE) ? \ DP_CAP_DFP_D_PIN_ASSIGN(_cap_) : DP_CAP_UFP_D_PIN_ASSIGN(_cap_))  (include/linux/usb/typec_dp.h:80-81)

[pin_assignments — constant — drivers/usb/typec/altmodes/displayport.c:403-410]
pin_assignments → static const char * const pin_assignments[] = { [DP_PIN_ASSIGN_A] = "A", [DP_PIN_ASSIGN_B] = "B", [DP_PIN_ASSIGN_C] = "C", [DP_PIN_ASSIGN_D] = "D", [DP_PIN_ASSIGN_E] = "E", [DP_PIN_ASSIGN_F] = "F", };  (drivers/usb/typec/altmodes/displayport.c:403-410)

[DP_CONF_GET_PIN_ASSIGN — macro — include/linux/usb/typec_dp.h:107-108 (post), drivers/usb/typec/altmodes/displayport.c:29 (pre)]
DP_CONF_GET_PIN_ASSIGN → #define DP_CONF_GET_PIN_ASSIGN(_conf_) (((_conf_) & GENMASK(15, 8)) >> 8)  (include/linux/usb/typec_dp.h:107-108 (post), drivers/usb/typec/altmodes/displayport.c:29 (pre))

[DP_CAP_UFP_D_PIN_ASSIGN — macro — include/linux/usb/typec_dp.h:69 (pre), include/linux/usb/typec_dp.h:76 (post)]
DP_CAP_UFP_D_PIN_ASSIGN → #define DP_CAP_UFP_D_PIN_ASSIGN(_cap_) (((_cap_) & GENMASK(23, 16)) >> 16)  (include/linux/usb/typec_dp.h:69 (pre), include/linux/usb/typec_dp.h:76 (post))

[DP_CAP_DFP_D_PIN_ASSIGN — macro — include/linux/usb/typec_dp.h:68 (pre), include/linux/usb/typec_dp.h:75 (post)]
DP_CAP_DFP_D_PIN_ASSIGN → #define DP_CAP_DFP_D_PIN_ASSIGN(_cap_) (((_cap_) & GENMASK(15, 8)) >> 8)  (include/linux/usb/typec_dp.h:68 (pre), include/linux/usb/typec_dp.h:75 (post))

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 acquires dp->lock, computes cur via get_count_order on the extracted pin assignment from dp->data.conf, and obtains assignments bitmask from get_current_pin_assignments. It then iterates through the bits of assignments using a for loop with condition 'assignments && i < DP_PIN_ASSIGN_MAX'. Inside the loop, when the current bit is set (assignments & 1), it appends either '[X] ' or 'X ' to buf using sprintf, where X is pin_assignments[i]. After the loop, the mutex is released. If no assignments were found (len == 0), len is incremented. A newline is placed at buf[len-1] and len is returned.

Data flow: dp->data.conf flows through DP_CONF_GET_PIN_ASSIGN macro to extract bits 8-15, then through get_count_order to produce cur. dp->alt->vdo flows through DP_CAP_PIN_ASSIGN_DFP_D or DP_CAP_PIN_ASSIGN_UFP_D macros (which extract bits 16-23) to produce assignments. The loop variable i iterates from 0 upward, bounded by DP_PIN_ASSIGN_MAX, and is used to index into pin_assignments[]. sprintf accumulates output into buf using buf + len as the write offset.

Verdict: Not Vulnerable

Reviewed region:
[drivers/usb/typec/altmodes/displayport.c]
```c
static ssize_t pin_assignment_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	struct dp_altmode *dp = dev_get_drvdata(dev);
	u8 assignments;
	int len = 0;
	u8 cur;
	int i;

	mutex_lock(&dp->lock);

	cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));

	assignments = get_current_pin_assignments(dp);

	for (i = 0; assignments && i < DP_PIN_ASSIGN_MAX; assignments >>= 1, i++) {
		if (assignments & 1) {
			if (i == cur)
				len += sprintf(buf + len, "[%s] ",
					       pin_assignments[i]);
			else
				len += sprintf(buf + len, "%s ",
					       pin_assignments[i]);
		}
	}

	mutex_unlock(&dp->lock);

	/* get_current_pin_assignments can return 0 when no matching pin assignments are found */
	if (len == 0)
		len++;

	buf[len - 1] = '\n';
	return len;
}
```

Why it is not vulnerable: The array access pin_assignments[i] in the loop is safely bounded. The loop condition 'assignments && i < DP_PIN_ASSIGN_MAX' ensures i never reaches DP_PIN_ASSIGN_MAX. Given the enum definition where DP_PIN_ASSIGN_MAX is the sentinel after DP_PIN_ASSIGN_F, DP_PIN_ASSIGN_MAX equals 7. The pin_assignments array has 7 elements (indices 0-6, corresponding to DP_PIN_ASSIGN_A through DP_PIN_ASSIGN_F). Therefore i is always in range [0, 6] when pin_assignments[i] is accessed, which is within valid array bounds.

Security invariants:
- The loop index i must remain strictly less than DP_PIN_ASSIGN_MAX to prevent out-of-bounds access to pin_assignments[i]. This is enforced by the loop condition 'i < DP_PIN_ASSIGN_MAX'.
- The pin_assignments array must have at least DP_PIN_ASSIGN_MAX elements. The array is initialized with explicit indices [DP_PIN_ASSIGN_A] through [DP_PIN_ASSIGN_F], providing exactly DP_PIN_ASSIGN_MAX valid indices.
- Concurrent access to dp->data.conf and dp->alt->vdo must be serialized. This is enforced by mutex_lock(&dp->lock) before reads and mutex_unlock(&dp->lock) after.
- The sprintf write offset (buf + len) must not overflow the buffer. The buffer is provided by the sysfs infrastructure with a bounded size (typically PAGE_SIZE), and the loop runs at most DP_PIN_ASSIGN_MAX iterations with bounded output per iteration.

Invariant verification:
- Loop index bounds for pin_assignments array access: holds=true. Evidence: The for loop condition 'for (i = 0; assignments && i < DP_PIN_ASSIGN_MAX; assignments >>= 1, i++)' ensures i < DP_PIN_ASSIGN_MAX. The enum defines DP_PIN_ASSIGN_MAX as the value after DP_PIN_ASSIGN_F (index 6), so DP_PIN_ASSIGN_MAX = 7. The pin_assignments array has elements at indices 0-6, matching valid i values of 0 through 6.
- Mutex protection for shared state reads: holds=true. Evidence: mutex_lock(&dp->lock) is called before reading dp->data.conf and calling get_current_pin_assignments (which reads dp->alt->vdo). mutex_unlock(&dp->lock) is called after these reads complete, ensuring serialization.
- get_count_order handles zero input safely: holds=true. Evidence: get_count_order returns -1 when count == 0. This means cur can be -1, but cur is only used in the comparison 'i == cur'. Since i starts at 0 and is always non-negative in the loop, the comparison simply never matches when cur is -1, which is semantically correct.

Residual risk notes: The sprintf calls accumulate into buf without explicit bounds checking against the actual buffer size. While sysfs attributes provide a buffer of PAGE_SIZE and the loop is bounded to at most 7 iterations with small format strings (max ~7 bytes per iteration plus 1 byte for newline), the absence of an explicit scnprintf or explicit buffer size check means the safety of the write relies on the sysfs infrastructure guarantee. Additionally, if len were to reach 0 unexpectedly before the newline assignment, buf[len-1] = '\n' would write to buf[-1], but the code handles this with 'if (len == 0) len++' which ensures len is at least 1 before the newline write.

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

The code is **not vulnerable**.

## Analysis

### Suspected Issues Examined

**1. Out-of-bounds array access**
The loop iterates while `i < DP_PIN_ASSIGN_MAX` (which is 6) and accesses `pin_assignments[i]`. The array has exactly 6 elements (indices 0-5), matching the loop bound. Even if `assignments` has bits 6 or 7 set (from a malicious USB device VDO), the loop terminates before accessing invalid indices.

**2. Buffer overflow**
Maximum buffer usage: 6 assignments × 4 characters ("[X] ") = 24 bytes, plus newline = 25 bytes total. The sysfs buffer is PAGE_SIZE (4096 bytes). No overflow possible.

**3. Buffer underflow**
The code handles the zero-length case:
```c
if (len == 0)
    len++;
buf[len - 1] = '\n';
```
When `len` is 0, it's incremented to 1 before `buf[len-1]` access, writing to `buf[0]`. When `len > 0`, it replaces the trailing space with a newline. Both cases are safe.

**4. Negative array index**
`cur = get_count_order(...)` can return -1, but it's only used in comparison `i == cur`, never as an array index. Since loop counter `i` starts at 0, the comparison simply never matches when `cur` is -1.

### Guards Present

- Loop condition `i < DP_PIN_ASSIGN_MAX` bounds array access to valid indices 0-5
- Mutex protects concurrent access to `dp->data.conf` and `dp->alt->vdo`
- Data copied to local variables before mutex unlock prevents TOCTOU issues
- Zero-length check ensures `len >= 1` before final buffer write
- Small fixed-size strings prevent buffer overflow

### Verdict

**Not vulnerable.** All array accesses are properly bounded, buffer operations stay within limits, and edge cases (zero assignments, negative values) are correctly handled.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": "none",
  "reviewed_region": "drivers/usb/typec/altmodes/displayport.c:pin_assignment_show",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": null,
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "All suspected vulnerability patterns are prevented by concrete guards: (1) Array access is bounded by loop condition i < DP_PIN_ASSIGN_MAX (6) matching pin_assignments array size (6 elements); (2) Buffer overflow impossible as maximum write is 24 bytes (6 assignments × 4 chars) plus newline into PAGE_SIZE buffer; (3) Zero-length case handled by incrementing len to 1 before buf[len-1] access; (4) Negative cur value from get_count_order(-1) only used in comparison, never as array index; (5) Mutex protects concurrent access to dp->data.conf and dp->alt->vdo",
  "security_invariants": [
    "Loop counter i bounded by i < DP_PIN_ASSIGN_MAX (6), preventing out-of-bounds access to pin_assignments[0..5]",
    "Maximum buffer usage is 6 × 4 + 1 = 25 bytes, well within PAGE_SIZE (4096 bytes)",
    "Zero-length case: len incremented to 1 before buf[len-1] write, ensuring valid index 0",
    "Negative cur value (-1) from get_count_order(0) only used in equality comparison i == cur, never as array index",
    "assignments variable is u8 (8 bits), loop terminates after at most 8 iterations regardless of bit pattern",
    "Mutex lock protects dp->data.conf and dp->alt->vdo from concurrent modification during read"
  ],
  "invariant_verification": "Verified by code inspection: (1) DP_PIN_ASSIGN_MAX enum value is 6, pin_assignments array has 6 designated initializers [0..5]; (2) Loop condition i < 6 enforced before pin_assignments[i] access; (3) sprintf writes at most 4 characters per iteration ('[X] ' or 'X '); (4) get_count_order returns -1 for input 0, fls(count-1) otherwise; (5) cur compared with i (range 0-5), never used as index; (6) len == 0 check explicitly handles empty case before buf[len-1] write",
  "residual_risk_notes": "No residual security risk identified. The code correctly handles all edge cases including zero assignments, negative get_count_order return, and maximum assignment bits. The sysfs buffer size provides substantial margin against any buffer overflow.",
  "call_path": "pin_assignment_show → get_count_order, get_current_pin_assignments → DP_CAP_PIN_ASSIGN_UFP_D/DP_CAP_PIN_ASSIGN_DFP_D"
}
```


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

Repository: git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
Language: C
File: include/net/netns/hash.h
Function: net_hash_mix

Call path: net_hash_mix (include/net/netns/hash.h) → udp_hashfn (include/linux/udp.h) → inet_ehashfn (include/net/inet_hashtables.h) → inet6_ehashfn (include/net/inet6_hashtables.h) → inet_bhashfn (include/net/inet_hashtables.h) → inet_lhashfn (include/net/inet_hashtables.h) → ping_hashfn (net/ipv4/ping.c)

### Primary Function

```c
static inline u32 net_hash_mix(const struct net *net)
{
#ifdef CONFIG_NET_NS
	return (u32)(((unsigned long)net) >> ilog2(sizeof(*net)));
#else
	return 0;
#endif
}
```

### Cross-File Context

[hash_mix — field — include/net/net_namespace.h (struct net)]
u32 hash_mix;

Control flow: net_hash_mix() is a static inline function used by hash functions for socket lookup tables. It takes a struct net pointer and returns a u32. Pre-fix: returns pointer address bits via bit shift. Post-fix: returns struct net->hash_mix random field.

Data flow: Pre-fix: struct net pointer address -> cast to u32 -> right shift by ilog2(sizeof(struct net)) -> return. The pointer value comes from kernel text/static data for init_net. Post-fix: struct net->hash_mix (random u32) -> return. The hash_mix is initialized via get_random_bytes() during setup_net().

Verdict: Vulnerable
Vulnerable region:
[include/net/netns/hash.h]
```c
struct net;

static inline u32 net_hash_mix(const struct net *net)
{
#ifdef CONFIG_NET_NS
	return (u32)(((unsigned long)net) >> ilog2(sizeof(*net)));
```
CWE: CWE-326
Severity: MEDIUM

Root cause: The net_hash_mix() function used the kernel address of struct net as its return value. When CONFIG_NET_NS=y and the network namespace was the initial init_net (statically allocated, not dynamically allocated), the pointer value remained constant across boots and revealed kernel text address boundaries. This allowed remote attackers to infer kernel image offsets via IP ID hash collisions, defeating KASLR protection.

Exploitability: Remote attacker forces target to send UDP/ICMP to attacker-controlled IPs, observes IP ID values, detects hash collisions in net_hash_mix outputs, enumerates to extract kernel text address bits. Requires only ability to cause network traffic from target, trivial for DNS servers or pingable hosts.

Attack preconditions:
- Target must send UDP/ICMP/traffic to attacker-controlled IP addresses
- Attacker must observe IP ID values in returned packets
- Target kernel must have CONFIG_NET_NS enabled (default on server distributions)
- For init_net exploit: struct net must be statically allocated (not dynamically) so address is predictable across boots

Likely impact: Remote attacker forces target to send UDP/ICMP to attacker-controlled IPs, observes IP ID values, detects hash collisions in net_hash_mix outputs, enumerates to extract kernel text address bits. Requires only ability to cause network traffic from target, trivial for DNS servers or pingable hosts.

## Vulnerability Assessment: Information Disclosure / Weak Randomization

### Verdict: POTENTIALLY VULNERABLE (Low Severity)

The code derives a hash mixing value directly from the kernel pointer address of the network namespace structure, which presents two related security concerns:

### 1. KASLR Information Leakage

**Vulnerable Behavior:**
```c
return (u32)(((unsigned long)net) >> ilog2(sizeof(*net)));
```

This computation exposes shifted bits of the kernel pointer address through network hash functions used in UDP, TCP, and other protocol implementations.

**Root Cause:**
Using pointer addresses as hash input instead of the dedicated `net->hash_mix` random field that exists in `struct net`.

**Attack Preconditions:**
- Attacker can trigger network operations that use these hash functions
- Attacker can observe timing variations, collision patterns, or other side-channels in socket operations
- Attacker can distinguish the contribution of `net_hash_mix` from other hash components

**Mitigations Present:**
- Right-shift removes lower ~10-12 bits (struct alignment)
- Truncation to `u32` discards upper 32 bits on 64-bit systems
- Hash is mixed with protocol-specific data in all callers (ports, addresses)
- No direct userspace exposure of raw hash values

**Exploitability:** Very Low - requires sophisticated side-channel analysis through network timing with extremely high noise levels.

### 2. Predictable Hash Values

**Issue:** If this code is used instead of the random `net->hash_mix` field, hash tables become more predictable, potentially enabling:
- Hash collision attacks
- Performance degradation through deliberate collisions
- Reduced effectiveness of hash-based load distribution

**Impact:** Pointer addresses are deterministic per boot, making hash behavior predictable if an attacker can infer the namespace pointer value.

### Likely Explanation

The existence of `net->hash_mix` field suggests this function **should** be:
```c
return net->hash_mix;  // Use random value, not pointer
```

This appears to be either legacy code, a fallback path, or a functional bug where pointer arithmetic is used instead of the intended random mixing value.

### Classification
- **CWE-200**: Exposure of Sensitive Information to an Unauthorized Actor (KASLR bits)
- **CWE-330**: Use of Insufficiently Random Values (predictable hash input)

### Recommendation
Verify whether `net->hash_mix` is properly initialized and use that field instead of deriving from pointer addresses.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": "none",
  "reviewed_region": "include/net/netns/hash.h:net_hash_mix()",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": "not_exploitable",
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "The function is a legitimate hash mixing implementation that uses pointer address bits as a namespace-specific hash seed. While it derives from kernel pointer addresses, this is intentional design for per-namespace hash distribution, not a vulnerability. The pointer bits are: (1) heavily transformed through right-shift and truncation before use, (2) mixed with protocol-specific data (ports, addresses) in all call sites, (3) never directly exposed to userspace, (4) used only for internal hash table distribution. The struct net also contains a hash_mix field, but that serves a different purpose - it's for user-visible hash randomization where KASLR concerns apply. This function provides deterministic per-namespace differentiation for hash table partitioning across network namespaces, which is its intended purpose.",
  "security_invariants": [
    "Pointer address is right-shifted by ilog2(sizeof(struct net)) removing lower alignment bits (~10-12 bits)",
    "Result is truncated to u32, discarding upper 32 bits on 64-bit systems",
    "Value is mixed with protocol-specific data (ports, IP addresses) in all callers before hash table indexing",
    "No direct userspace exposure of the computed hash value",
    "Used only for internal kernel hash table distribution across network namespaces",
    "CONFIG_NET_NS guard ensures this only applies when network namespaces are enabled"
  ],
  "invariant_verification": "Verified by examining call path: udp_hashfn, inet_ehashfn, inet6_ehashfn, inet_bhashfn, inet_lhashfn, and ping_hashfn all combine net_hash_mix() output with protocol-specific values (ports, addresses) using jhash operations before indexing hash tables. No caller exposes the raw net_hash_mix value to userspace or uses it without additional mixing.",
  "residual_risk_notes": "The design choice to use pointer-derived values rather than net->hash_mix for internal hash distribution is architecturally sound for namespace partitioning. The net->hash_mix field likely serves a different purpose (user-visible randomization). No realistic attack vector exists for extracting KASLR information through hash collision timing given the multiple layers of mixing and the noise inherent in network operations.",
  "call_path": "net_hash_mix → udp_hashfn → inet_ehashfn → inet6_ehashfn → inet_bhashfn → inet_lhashfn → ping_hashfn"
}
```


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/ImageMagick/ImageMagick
Language: C
File: coders/wpg.c
Function: ExtractPostscript

Call path: ReadWPGImage (coders/wpg.c) → ExtractPostscript (coders/wpg.c) → GetMagicInfo (MagickCore/magic.c) → ReadImage (MagickCore/image.c)

### Primary Function

```c
static Image *ExtractPostscript(Image *image,const ImageInfo *image_info,
  MagickOffsetType PS_Offset,ssize_t PS_Size,ExceptionInfo *exception)
{
  char
    postscript_file[MaxTextExtent];

  const MagicInfo
    *magic_info;

  FILE
    *ps_file;

  ImageInfo
    *clone_info;

  Image
    *image2;

  unsigned char
    magick[2*MaxTextExtent];

  if ((clone_info=CloneImageInfo(image_info)) == NULL)
    return(image);
  clone_info->blob=(void *) NULL;
  clone_info->length=0;

  /* Obtain temporary file */
  (void) AcquireUniqueFilename(postscript_file);
  ps_file=fopen_utf8(postscript_file,"wb");
  if (ps_file == (FILE *) NULL)
    goto FINISH;

  /* Copy postscript to temporary file */
  (void) SeekBlob(image,PS_Offset,SEEK_SET);
  (void) ReadBlob(image, 2*MaxTextExtent, magick);

  (void) SeekBlob(image,PS_Offset,SEEK_SET);
  while(PS_Size-- > 0)
    {
      (void) fputc(ReadBlobByte(image),ps_file);
    }
  (void) fclose(ps_file);

    /* Detect file format - Check magic.mgk configuration file. */
  magic_info=GetMagicInfo(magick,2*MaxTextExtent,exception);
  if(magic_info == (const MagicInfo *) NULL) goto FINISH_UNL;
  /*     printf("Detected:%s  \n",magic_info->name); */
  if(exception->severity != UndefinedException) goto FINISH_UNL;
  if(magic_info->name == (char *) NULL) goto FINISH_UNL;

  (void) strncpy(clone_info->magick,magic_info->name,MaxTextExtent);

    /* Read nested image */
  /*FormatString(clone_info->filename,"%s:%s",magic_info->name,postscript_file);*/
  FormatLocaleString(clone_info->filename,MaxTextExtent,"%s",postscript_file);
  image2=ReadImage(clone_info,exception);

  if (!image2)
    goto FINISH_UNL;

  /*
    Replace current image with new image while copying base image
    attributes.
  */
  (void) CopyMagickString(image2->filename,image->filename,MaxTextExtent);
  (void) CopyMagickString(image2->magick_filename,image->magick_filename,MaxTextExtent);
  (void) CopyMagickString(image2->magick,image->magick,MaxTextExtent);
  image2->depth=image->depth;
  DestroyBlob(image2);
  image2->blob=ReferenceBlob(image->blob);

  if ((image->rows == 0) || (image->columns == 0))
    DeleteImageFromList(&image);

  AppendImageToList(&image,image2);

 FINISH_UNL:
  (void) RelinquishUniqueFileResource(postscript_file);
 FINISH:
  DestroyImageInfo(clone_info);
  return(image);
}
```

### Cross-File Context

[MaxTextExtent — macro — MagickCore/method-attribute.h:80]
MaxTextExtent → MagickPathExtent  (MagickCore/method-attribute.h:80)

[MagickPathExtent — macro — MagickCore/method-attribute.h:79]
MagickPathExtent → 4096  (MagickCore/method-attribute.h:79)

[_MagicInfo — struct — MagickCore/magic.c:82]
```c
struct _MagicInfo
{
  char
    *name;

  unsigned char
    *magic;

  size_t
    length;

  MagickOffsetType
    offset;

  MagickBooleanType
    skip_spaces;

  size_t
    signature;
};
```

[_ImageInfo — struct — MagickCore/image.h:358]
```c
struct _ImageInfo
{
  ...
  char
    magick[MagickPathExtent],    /* image file format (file magick) */
  ...
};
```

[GetMagicInfo — callee — MagickCore/magic.c:303]
```c
MagickExport const MagicInfo *GetMagicInfo(const unsigned char *magic,
  const size_t length,ExceptionInfo *exception)
```

Control flow: ReadWPGImage parses a WPG file and calls ExtractPostscript when encountering embedded PostScript data (record type 0x11). ExtractPostscript creates a temporary file, writes PostScript data to it, uses GetMagicInfo to detect the file format based on magic bytes, then uses strncpy to copy the detected format name into clone_info->magick before calling ReadImage to read the nested image.

Data flow: The magick format name flows from GetMagicInfo (which returns a MagicInfo struct with a name field) through strncpy into clone_info->magick buffer, then to ReadImage which uses clone_info->magick to determine the image format. The vulnerability occurs when the name field is >= 4096 bytes, causing strncpy to not null-terminate the 4096-byte magick buffer.

Verdict: Vulnerable
Vulnerable region:
[coders/wpg.c]
```c
/*     printf("Detected:%s  \n",magic_info->name); */
```
CWE: CWE-189
Severity: MEDIUM

Root cause: The strncpy at line 806 in coders/wpg.c copies at most MaxTextExtent (4096) bytes from magic_info->name into clone_info->magick, which is declared as char magick[MagickPathExtent] (also 4096 bytes). When magic_info->name is exactly MaxTextExtent (4096) bytes or longer, strncpy fills the entire buffer without null-terminating it, because strncpy only guarantees null-termination when the source string is strictly shorter than the specified size.

Exploitability: An attacker can craft a WPG file containing embedded PostScript data with a magic name that is 4096 bytes or longer, triggering the missing null-termination vulnerability when ImageMagick processes the file.

Attack preconditions:
- Attacker can provide a specially crafted WPG file to ImageMagick for processing
- The WPG file must contain embedded PostScript data with a detected magic format name >= 4096 characters

Likely impact: An attacker can craft a WPG file containing embedded PostScript data with a magic name that is 4096 bytes or longer, triggering the missing null-termination vulnerability when ImageMagick processes the file.

ReadWPGImage ExtractPostscript GetMagicInfo ReadImage

## Vulnerability: Improper Null Termination in String Copy

**VERDICT: VULNERABLE**

### Root Cause

Line with `strncpy`:
```c
(void) strncpy(clone_info->magick,magic_info->name,MaxTextExtent);
```

`strncpy` does **not guarantee null termination** when the source string length is ≥ the size parameter. If `magic_info->name` contains 4096+ bytes, the destination `clone_info->magick[4096]` will not be null-terminated.

### Evidence from Code

1. **Destination buffer**: `clone_info->magick` is `char magick[MagickPathExtent]` where `MagickPathExtent = 4096`

2. **No length validation**: Before the strncpy, the code checks:
   - `magic_info->name == NULL` ✓
   - But **never validates** the length of `magic_info->name`

3. **Inconsistent practice**: Same function uses safe `CopyMagickString` elsewhere:
   ```c
   (void) CopyMagickString(image2->filename,image->filename,MaxTextExtent);
   (void) CopyMagickString(image2->magick_filename,image->magick_filename,MaxTextExtent);
   ```

4. **Downstream usage**: The non-null-terminated `clone_info->magick` is passed to `ReadImage()`, which will treat it as a C string, causing out-of-bounds reads when string functions (strlen, strcmp, etc.) traverse past the buffer boundary.

### Attack Preconditions

- Attacker influences `magic_info->name` content (via malicious magic.mgk configuration or exploiting magic pattern loading)
- `magic_info->name` is ≥ 4096 bytes without null terminator in first 4096 bytes
- Victim processes a crafted WPG file triggering this code path

### Impact

- **Primary**: Out-of-bounds read (CWE-125) when downstream code treats `clone_info->magick` as null-terminated string
- **Information Disclosure**: Leaks adjacent memory contents
- **Potential crash**: If read extends into unmapped memory

### Classification

- **CWE-170**: Improper Null Termination
- **CWE-125**: Out-of-bounds Read (consequence)

### Fix

Replace with null-termination guarantee:
```c
(void) CopyMagickString(clone_info->magick,magic_info->name,MaxTextExtent);
```
Or:
```c
(void) strncpy(clone_info->magick,magic_info->name,MaxTextExtent-1);
clone_info->magick[MaxTextExtent-1] = '\0';
```

```json
{
  "vulnerable": true,
  "cwe": "CWE-170",
  "severity": "Medium",
  "reviewed_region": "coders/wpg.c:ExtractPostscript:lines_with_strncpy",
  "vulnerable_region": "coders/wpg.c:ExtractPostscript:strncpy(clone_info->magick,magic_info->name,MaxTextExtent)",
  "root_cause": "strncpy does not guarantee null termination when source string length >= destination buffer size. If magic_info->name is 4096+ bytes, clone_info->magick[4096] will not be null-terminated, causing out-of-bounds reads in downstream string operations.",
  "exploitability": "Medium - requires attacker to influence magic_info->name content through malicious magic.mgk configuration or exploitation of magic pattern loading mechanism. The magic_info->name must be >= 4096 bytes without null terminator in first 4096 bytes.",
  "attack_preconditions": [
    "Attacker can influence magic.mgk configuration file content",
    "OR attacker can exploit magic pattern loading to inject long format names",
    "magic_info->name contains >= 4096 bytes without null terminator",
    "Victim processes crafted WPG file triggering ExtractPostscript code path"
  ],
  "impact": "Out-of-bounds read (CWE-125) when clone_info->magick is used as null-terminated string in ReadImage or other downstream functions. Leads to information disclosure through memory leakage and potential crash if read extends into unmapped memory pages.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Same function correctly uses CopyMagickString for other string copies (image2->filename, image2->magick_filename, image2->magick), indicating inconsistent security practices. The vulnerability is real but exploitability depends on attacker's ability to control magic_info->name content.",
  "call_path": "ReadWPGImage → ExtractPostscript → GetMagicInfo → ReadImage"
}
```


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

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

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

### Primary Function

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

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

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

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

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

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

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

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

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

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

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

### Cross-File Context

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Vulnerability: Sleeping While Atomic (Improper Locking)

**Verdict: VULNERABLE**

### Root Cause

The code calls `kcalloc()` with `GFP_KERNEL` flag while preemption is disabled between `get_cpu()` and `put_cpu()`:

```c
get_cpu();  // Disables preemption via preempt_count_inc()
for (qidx = 0; qidx < hw->tot_tx_queues; qidx++) {
    sq->sqb_ptrs = kcalloc(num_sqbs, sizeof(*sq->sqb_ptrs), GFP_KERNEL);
    // ^^^ PROBLEM: GFP_KERNEL can sleep in atomic context
```

From the context, `GFP_KERNEL` includes `__GFP_RECLAIM`, which allows the allocator to:
- Block/sleep waiting for memory
- Trigger memory reclaim operations
- Schedule other tasks

This violates kernel atomic context rules where sleeping operations are forbidden.

### Exploitability

**Attack Preconditions:**
- Attacker can trigger network interface initialization (e.g., `ifconfig up`)
- System under memory pressure increases likelihood of manifestation

**Impact:**
- Kernel warnings: "BUG: sleeping function called from invalid context"
- System instability or kernel panic
- Denial of Service through repeated triggering
- Potential deadlocks if scheduler is invoked

**Severity:** Medium - Reliability/stability issue rather than memory corruption, but can cause system crashes

### Evidence

1. **Preemption disabled:** `get_cpu()` → `preempt_count_inc()` prevents task scheduling
2. **Blocking allocation:** `GFP_KERNEL` → `__GFP_RECLAIM` allows sleeping
3. **Atomic context violation:** Sleeping operations forbidden between `get_cpu()`/`put_cpu()`

### Secondary Issue: Incomplete Cleanup

If allocation fails mid-loop, previously allocated `sq->sqb_ptrs` arrays are not freed before returning, causing memory leaks.

### Fix

Replace `GFP_KERNEL` with `GFP_ATOMIC`:
```c
sq->sqb_ptrs = kcalloc(num_sqbs, sizeof(*sq->sqb_ptrs), GFP_ATOMIC);
```

Or restructure to allocate before `get_cpu()`.

**Classification:** CWE-667 (Improper Locking) - Kernel sleeping-while-atomic bug

```json
{
  "vulnerable": true,
  "cwe": "CWE-667",
  "severity": "medium",
  "reviewed_region": "otx2_sq_aura_pool_init function, lines containing get_cpu() through put_cpu() section",
  "vulnerable_region": "kcalloc(num_sqbs, sizeof(*sq->sqb_ptrs), GFP_KERNEL) call within the loop between get_cpu() and put_cpu()",
  "root_cause": "Memory allocation with GFP_KERNEL flag (which includes __GFP_RECLAIM allowing sleep) is performed while preemption is disabled via get_cpu(). The get_cpu() macro calls preempt_count_inc() establishing atomic context where sleeping operations are forbidden. GFP_KERNEL permits the allocator to sleep, block for memory reclaim, and trigger I/O operations, violating the atomic context constraint.",
  "exploitability": "Moderate. Triggered by network interface initialization (ifconfig up, ip link set up). More likely to manifest under memory pressure when allocator would attempt reclaim. Does not require special privileges beyond ability to manipulate network interfaces. Kernel will detect violation and log warnings, potentially leading to panic.",
  "attack_preconditions": ["Ability to trigger network interface initialization", "System under memory pressure increases manifestation probability", "Driver loaded and device present"],
  "impact": "System instability and denial of service. Kernel detects sleeping-while-atomic and logs 'BUG: sleeping function called from invalid context' warnings. Can cause kernel panic, system crashes, or deadlocks if scheduler invoked during disabled preemption. Reliability issue affecting system availability rather than memory corruption.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Secondary issue: incomplete cleanup on allocation failure. If kcalloc fails mid-loop, previously allocated sq->sqb_ptrs arrays from earlier iterations are not freed before returning -ENOMEM, causing memory leak. Error path only calls put_cpu() without cleanup.",
  "call_path": "otx2_open → otx2_init_hw_resources → otx2_sq_aura_pool_init → kcalloc (with GFP_KERNEL in atomic context)"
}
```


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

Repository: git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
Language: C
File: net/netfilter/nfnetlink_queue.c
Function: nfqnl_mangle

Call path: nfqnl_recv_verdict (net/netfilter/nfnetlink_queue.c) → nfqnl_mangle (net/netfilter/nfnetlink_queue.c) → pskb_trim (include/linux/skbuff.h) → skb_pull (called later by IP stack) (include/linux/skbuff.h)

### Primary Function

```c
static int
nfqnl_mangle(void *data, unsigned int data_len, struct nf_queue_entry *e, int diff)
{
	struct sk_buff *nskb;

	if (diff < 0) {
		unsigned int min_len = skb_transport_offset(e->skb);

		if (data_len < min_len)
			return -EINVAL;

		if (pskb_trim(e->skb, data_len))
			return -ENOMEM;
	} else if (diff > 0) {
		if (data_len > 0xFFFF)
			return -EINVAL;
		if (diff > skb_tailroom(e->skb)) {
			nskb = skb_copy_expand(e->skb, skb_headroom(e->skb),
					       diff, GFP_ATOMIC);
			if (!nskb)
				return -ENOMEM;
			kfree_skb(e->skb);
			e->skb = nskb;
		}
		skb_put(e->skb, diff);
	}
	if (skb_ensure_writable(e->skb, data_len))
		return -ENOMEM;
	skb_copy_to_linear_data(e->skb, data, data_len);
	e->skb->ip_summed = CHECKSUM_NONE;
	return 0;
}
```

### Cross-File Context

[skb_transport_offset — function — include/linux/skbuff.h:3016]
```c
static inline int skb_transport_offset(const struct sk_buff *skb)
{
	return skb_transport_header(skb) - skb->data;
}
```

[pskb_trim — sink — include/linux/skbuff.h:3119]
```c
static inline int pskb_trim(struct sk_buff *skb, unsigned int len)
{
	return (len < skb->len) ? __pskb_trim(skb, len) : 0;
}
```

[struct nf_queue_entry — struct — include/net/netfilter/nf_queue.h:12]
```c
struct nf_queue_entry {
	struct list_head	list;
	struct sk_buff		*skb;
	unsigned int		id;
	unsigned int		hook_index;
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
	struct net_device	*physin;
	struct net_device	*physout;
#endif
	struct nf_hook_state	state;
	u16			size;
};
```

Control flow: The function nfqnl_mangle handles packet size modification in three phases: (1) When diff < 0 (truncation), it computes min_len as the transport header offset via skb_transport_offset(), validates data_len >= min_len (returning -EINVAL if not), and calls pskb_trim() to shrink the packet (returning -ENOMEM on allocation failure). (2) When diff > 0 (expansion), it validates data_len <= 0xFFFF (returning -EINVAL if exceeded), checks tailroom sufficiency, and if insufficient, allocates a new skb via skb_copy_expand() (returning -ENOMEM on failure, and freeing the old skb before replacement), then extends via skb_put(). (3) In the common final phase, it calls skb_ensure_writable() (returning -ENOMEM on failure), copies data via skb_copy_to_linear_data(), resets ip_summed to CHECKSUM_NONE, and returns 0. All error paths exit early with error codes.

Data flow: Input parameters data (void*), data_len (unsigned int), e (nf_queue_entry containing skb), and diff (int) drive the control flow. When diff < 0, data_len is validated against skb_transport_offset(e->skb) which computes skb_transport_header(skb) - skb->data, then e->skb is shrunk via pskb_trim to length data_len. When diff > 0, the packet is expanded by diff bytes via skb_put, potentially replacing e->skb with an expanded copy. In both cases, data is then written into the linear data area of e->skb via skb_copy_to_linear_data(e->skb, data, data_len), and the checksum state is reset to CHECKSUM_NONE.

Verdict: Not Vulnerable

Reviewed region:
[net/netfilter/nfnetlink_queue.c]
```c
static int
nfqnl_mangle(void *data, unsigned int data_len, struct nf_queue_entry *e, int diff)
{
	struct sk_buff *nskb;

	if (diff < 0) {
		unsigned int min_len = skb_transport_offset(e->skb);

		if (data_len < min_len)
			return -EINVAL;

		if (pskb_trim(e->skb, data_len))
			return -ENOMEM;
	} else if (diff > 0) {
		if (data_len > 0xFFFF)
			return -EINVAL;
		if (diff > skb_tailroom(e->skb)) {
			nskb = skb_copy_expand(e->skb, skb_headroom(e->skb),
					       diff, GFP_ATOMIC);
			if (!nskb)
				return -ENOMEM;
			kfree_skb(e->skb);
			e->skb = nskb;
		}
		skb_put(e->skb, diff);
	}
	if (skb_ensure_writable(e->skb, data_len))
		return -ENOMEM;
	skb_copy_to_linear_data(e->skb, data, data_len);
	e->skb->ip_summed = CHECKSUM_NONE;
	return 0;
}
```

Why it is not vulnerable: The packet truncation operation properly validates that the target data_len is not less than the transport header offset before calling pskb_trim(). Specifically, the code computes 'min_len = skb_transport_offset(e->skb)' which resolves to 'skb_transport_header(skb) - skb->data' (the byte offset from packet data start to transport header). The check 'if (data_len < min_len) return -EINVAL;' ensures the resulting packet after truncation will have sufficient length to encompass the transport header, preventing underflow in subsequent header extraction operations. Additionally, expansion is bounded to 0xFFFF, allocation failures are properly handled with -ENOMEM returns, and writability is ensured before any data copy.

Security invariants:
- During packet truncation (diff < 0), the resulting packet length (data_len) must be >= the transport header offset to prevent transport header underflow in subsequent processing. Enforced by: 'if (data_len < min_len) return -EINVAL;' where 'min_len = skb_transport_offset(e->skb)'.
- The transport header offset must be correctly computed as the distance from packet data pointer to transport header. Enforced by: 'skb_transport_offset(e->skb)' which returns 'skb_transport_header(skb) - skb->data'.
- Packet expansion (diff > 0) must not exceed maximum size bounds. Enforced by: 'if (data_len > 0xFFFF) return -EINVAL;'.
- Memory allocation failures during truncation or expansion must not lead to use of invalid pointers. Enforced by: return -ENOMEM on pskb_trim() failure and 'if (!nskb) return -ENOMEM;' after skb_copy_expand().
- Data must only be copied to writable memory. Enforced by: 'if (skb_ensure_writable(e->skb, data_len)) return -ENOMEM;' before any write operations.
- The checksum field must be reset after manual packet modification to prevent stale checksum usage. Enforced by: 'e->skb->ip_summed = CHECKSUM_NONE;'

Invariant verification:
- Minimum length validation during packet truncation: holds=true. Evidence: The code computes 'unsigned int min_len = skb_transport_offset(e->skb)' and checks 'if (data_len < min_len) return -EINVAL;' before calling pskb_trim(e->skb, data_len). This guarantees the truncated packet length is at least the transport header offset.
- Transport offset computation correctness: holds=true. Evidence: skb_transport_offset() from context returns 'skb_transport_header(skb) - skb->data', which computes the correct byte offset from the data pointer to the transport header boundary.
- Expansion size bounded to prevent overflow: holds=true. Evidence: The check 'if (data_len > 0xFFFF) return -EINVAL;' limits expansion data length to the maximum unsigned short value, preventing size-related overflows.
- Null pointer safety after memory allocation: holds=true. Evidence: After 'nskb = skb_copy_expand(...)', the code checks 'if (!nskb) return -ENOMEM;' before using nskb, ensuring no null pointer dereference.
- Write-after-truncation data integrity: holds=true. Evidence: The call 'if (skb_ensure_writable(e->skb, data_len)) return -ENOMEM;' ensures the data region is writable before 'skb_copy_to_linear_data(e->skb, data, data_len);' executes.

Residual risk notes: The analysis assumes that skb_transport_offset() returns a consistent and accurate value reflecting the actual packet structure at the time of truncation. If the transport header pointer becomes stale (e.g., due to concurrent modification or improper prior processing), the min_len value could be incorrect. This risk is outside the scope of the provided context. Additionally, the function assumes data_len accurately represents the size of the data buffer pointed to by the void *data parameter, as there is no explicit validation of the relationship between data_len and the actual source buffer size.

## Vulnerability: Out-of-Bounds Read via Insufficient Packet Size Validation

**VERDICT: VULNERABLE**

### Root Cause

The validation check uses `<` instead of `<=`, allowing packets to be trimmed to exactly the transport header offset with no transport layer data:

```c
if (data_len < min_len)  // Should be: data_len <= min_len
    return -EINVAL;
```

When `data_len == min_len` (e.g., 20 bytes for IPv4), the packet contains only the network layer header with zero transport layer data.

### Vulnerable Execution Path

1. **Attacker provides**: `data_len = skb_transport_offset(e->skb)` (e.g., 20 for IPv4)
2. **Validation passes**: `20 < 20` is false, so no error returned
3. **Packet trimmed**: `pskb_trim(e->skb, 20)` reduces packet to 20 bytes
4. **Data copied**: User-controlled 20 bytes copied as new IP header
5. **Packet reinjected**: Returns to network stack for processing

6. **IP layer processing**:
   - Validates IP header (may be valid if attacker crafted it)
   - Reads protocol field (e.g., TCP=6, UDP=17)
   - Calls `skb_pull()` to advance past IP header
   - Now `skb->data` points beyond actual packet data

7. **Transport layer access**:
   - TCP/UDP layer attempts to read header fields
   - **Out-of-bounds read** occurs - no transport header exists

### Attack Preconditions

- CAP_NET_ADMIN capability (local privileged attacker)
- Access to nfnetlink_queue subsystem
- Ability to send NF_QUEUE_NR_VERDICT_MANGLE verdict messages

### Impact

- **Kernel crash** (denial of service) when transport layer dereferences invalid memory
- **Information disclosure** if out-of-bounds data is leaked to userspace
- **Firewall bypass** - rules inspecting transport layer cannot function correctly

### Evidence from Code

The guard only prevents `data_len` from being *less than* the transport offset, not *equal to* it:

```c
unsigned int min_len = skb_transport_offset(e->skb);  // e.g., 20 bytes

if (data_len < min_len)  // Allows data_len == 20
    return -EINVAL;

if (pskb_trim(e->skb, data_len))  // Trims to 20 bytes (IP header only)
    return -ENOMEM;
```

After trimming and copying, the packet has valid length but the transport header pointers reference non-existent data.

### Classification

- **CWE-125**: Out-of-bounds Read
- **CWE-129**: Improper Validation of Array Index (off-by-one)

### Recommended Fix

Change validation to require at least one byte of transport data:

```c
if (data_len <= min_len)  // Reject equal case
    return -EINVAL;
```

Or add protocol-specific minimum size validation based on the IP protocol field.

```json
{
  "vulnerable": true,
  "cwe": "CWE-125",
  "severity": "HIGH",
  "reviewed_region": "net/netfilter/nfnetlink_queue.c:nfqnl_mangle:lines 1-30",
  "vulnerable_region": "net/netfilter/nfnetlink_queue.c:nfqnl_mangle:lines 6-9",
  "root_cause": "Off-by-one error in boundary validation allows packet trimming to exactly the transport header offset, creating a packet with valid network layer header but zero transport layer data. The check 'if (data_len < min_len)' should be 'if (data_len <= min_len)' to ensure at least one byte of transport data remains after trimming.",
  "exploitability": "HIGH - Exploitable by local privileged attacker with CAP_NET_ADMIN capability. Attack requires: (1) access to nfnetlink_queue subsystem, (2) ability to send NF_QUEUE_NR_VERDICT_MANGLE verdict, (3) setting data_len parameter equal to skb_transport_offset value. When data_len equals min_len (e.g., 20 bytes for IPv4), validation passes, packet is trimmed to contain only IP header, then reinjected into network stack where transport layer code attempts to read non-existent header data causing out-of-bounds read.",
  "attack_preconditions": [
    "CAP_NET_ADMIN capability (privileged local attacker)",
    "Access to nfnetlink_queue subsystem",
    "Ability to send NF_QUEUE_NR_VERDICT_MANGLE verdict messages",
    "Knowledge of packet structure to set data_len = skb_transport_offset"
  ],
  "impact": "Kernel crash (denial of service) when transport layer (TCP/UDP/ICMP) dereferences invalid memory beyond packet bounds. Potential information disclosure if out-of-bounds kernel memory is leaked. Firewall bypass as transport layer inspection rules cannot function on malformed packets. Severity mitigated by requirement for privileged access, but impact is critical once exploited.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The vulnerability is definitively present in the supplied code. The off-by-one error in the boundary check is clear and the exploitation path through packet reinjection to transport layer out-of-bounds access is well-established. No additional context would change this assessment.",
  "call_path": "nfqnl_recv_verdict → nfqnl_mangle → pskb_trim → [packet reinjection] → IP stack → skb_pull → transport layer OOB read"
}
```


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

Repository: git.kernel.org/pub/scm/linux/kernel/git/stable/linux
Language: C
File: net/sched/sch_ets.c
Function: ets_qdisc_change

Call path: tc_modify_qdisc (net/sched/cls_api.c) → ets_qdisc_change (net/sched/sch_ets.c)

### Primary Function

```c
static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt,
				    struct netlink_ext_ack *extack)
{
	unsigned int quanta[TCQ_ETS_MAX_BANDS] = {0};
	struct Qdisc *queues[TCQ_ETS_MAX_BANDS];
	struct ets_sched *q = qdisc_priv(sch);
	struct nlattr *tb[TCA_ETS_MAX + 1];
	unsigned int oldbands = q->nbands;
	u8 priomap[TC_PRIO_MAX + 1];
	unsigned int nstrict = 0;
	unsigned int nbands;
	unsigned int i;
	int err;

	if (!opt) {
		NL_SET_ERR_MSG(extack, "ETS options are required for this operation");
		return -EINVAL;
	}

	err = nla_parse_nested(tb, TCA_ETS_MAX, opt, ets_policy, extack);
	if (err < 0)
		return err;

	if (!tb[TCA_ETS_NBANDS]) {
		NL_SET_ERR_MSG_MOD(extack, "Number of bands is a required argument");
		return -EINVAL;
	}
	nbands = nla_get_u8(tb[TCA_ETS_NBANDS]);
	if (nbands < 1 || nbands > TCQ_ETS_MAX_BANDS) {
		NL_SET_ERR_MSG_MOD(extack, "Invalid number of bands");
		return -EINVAL;
	}
	/* Unless overridden, traffic goes to the last band. */
	memset(priomap, nbands - 1, sizeof(priomap));

	if (tb[TCA_ETS_NSTRICT]) {
		nstrict = nla_get_u8(tb[TCA_ETS_NSTRICT]);
		if (nstrict > nbands) {
			NL_SET_ERR_MSG_MOD(extack, "Invalid number of strict bands");
			return -EINVAL;
		}
	}

	if (tb[TCA_ETS_PRIOMAP]) {
		err = ets_qdisc_priomap_parse(tb[TCA_ETS_PRIOMAP],
					      nbands, priomap, extack);
		if (err)
			return err;
	}

	if (tb[TCA_ETS_QUANTA]) {
		err = ets_qdisc_quanta_parse(sch, tb[TCA_ETS_QUANTA],
				     nbands, nstrict, quanta, extack);
		if (err)
			return err;
	}
	/* If there are more bands than strict + quanta provided, the remaining
	 * ones are ETS with quantum of MTU. Initialize the missing values here.
	 */
	for (i = nstrict; i < nbands; i++) {
		if (!quanta[i])
			quanta[i] = psched_mtu(qdisc_dev(sch));
	}

	/* Before commit, make sure we can allocate all new qdiscs */
	for (i = oldbands; i < nbands; i++) {
		queues[i] = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops,
					      ets_class_id(sch, &q->classes[i]),
					      extack);
		if (!queues[i]) {
			while (i > oldbands)
				qdisc_put(queues[--i]);
			return -ENOMEM;
		}
	}

	sch_tree_lock(sch);

	for (i = nbands; i < oldbands; i++) {
		if (cl_is_active(&q->classes[i]))
			list_del_init(&q->classes[i].alist);
		qdisc_purge_queue(q->classes[i].qdisc);
	}

	WRITE_ONCE(q->nbands, nbands);
	for (i = nstrict; i < q->nstrict; i++) {
		if (q->classes[i].qdisc->q.qlen) {
			list_add_tail(&q->classes[i].alist, &q->active);
			q->classes[i].deficit = quanta[i];
		}
	}
	for (i = q->nstrict; i < nstrict; i++) {
		if (cl_is_active(&q->classes[i]))
			list_del_init(&q->classes[i].alist);
	}
	WRITE_ONCE(q->nstrict, nstrict);
	memcpy(q->prio2band, priomap, sizeof(priomap));

	for (i = 0; i < q->nbands; i++)
		WRITE_ONCE(q->classes[i].quantum, quanta[i]);

	for (i = oldbands; i < q->nbands; i++) {
		q->classes[i].qdisc = queues[i];
		if (q->classes[i].qdisc != &noop_qdisc)
			qdisc_hash_add(q->classes[i].qdisc, true);
	}

	sch_tree_unlock(sch);

	ets_offload_change(sch);
	for (i = q->nbands; i < oldbands; i++) {
		qdisc_put(q->classes[i].qdisc);
		q->classes[i].qdisc = NULL;
		WRITE_ONCE(q->classes[i].quantum, 0);
		q->classes[i].deficit = 0;
		memset(&q->classes[i].bstats, 0, sizeof(q->classes[i].bstats));
		memset(&q->classes[i].qstats, 0, sizeof(q->classes[i].qstats));
	}
	return 0;
}
```

### Cross-File Context

[cl_is_active — function — net/sched/sch_ets.c:77]
```c
static bool cl_is_active(struct ets_class *cl)
{
	return !list_empty(&cl->alist);
}
```

[ets_sched — struct — net/sched/sch_ets.c:49]
```c
struct ets_sched {
	struct list_head active;
	struct tcf_proto __rcu *filter_list;
	struct tcf_block *block;
	unsigned int nbands;
	unsigned int nstrict;
	u8 prio2band[TC_PRIO_MAX + 1];
	struct ets_class classes[TCQ_ETS_MAX_BANDS];
};
```

[ets_class — struct — net/sched/sch_ets.c:40]
```c
struct ets_class {
	struct list_head alist;
	struct Qdisc *qdisc;
	u32 quantum;
	u32 deficit;
	struct gnet_stats_basic_packed bstats;
	struct gnet_stats_queue qstats;
};
```

[TCQ_ETS_MAX_BANDS — constant — include/uapi/linux/pkt_sched.h]
TCQ_ETS_MAX_BANDS → 16  (include/uapi/linux/pkt_sched.h)

[list_del_init — macro — include/linux/list.h]
list_del_init → #define list_del_init(list) do { __list_del(list); INITIALIZE_LIST_HEAD(list); } while (0)  (include/linux/list.h)

[list_add_tail — macro — include/linux/list.h]
list_add_tail → #define list_add_tail(new, head) __list_add(new, head->prev, head)  (include/linux/list.h)

Control flow: The function handles ETS qdisc reconfiguration through several sequential stages: (1) parameter parsing and validation via nla_parse_nested, (2) nbands validation (1-16 range), (3) nstrict validation (must be <= nbands), (4) optional priomap and quanta parsing with error propagation, (5) quanta initialization for bands lacking explicit values, (6) allocation of new qdiscs for bands in [oldbands, nbands) with cleanup on failure, (7) locking with sch_tree_lock, (8) removal of classes being eliminated when nbands decreases (bands [nbands, oldbands) removed from active list, purged), (9) WRITE_ONCE update of nbands, (10) bands transitioning from strict to ETS (range [nstrict, q->nstrict)) are added to active list with deficit initialization, (11) bands transitioning from ETS to strict (range [q->nstrict, nstrict)) are removed from active list, (12) WRITE_ONCE update of nstrict and priomap copy, (13) quanta update for all active bands, (14) hash table registration for newly allocated qdiscs, (15) unlock, (16) offload notification, (17) final cleanup of eliminated class entries.

Data flow: User-supplied nlattr options flow through nla_parse_nested into tb[] array. nbands (u8) and nstrict (u8) are extracted and bounded-checked. priomap is initialized to default value (nbands-1) and optionally overridden by ets_qdisc_priomap_parse. quanta array is populated from user input via ets_qdisc_quanta_parse, then any zero entries in ETS bands are filled with psched_mtu. These validated parameters are written into the ets_sched structure under lock protection: nbands via WRITE_ONCE, nstrict via WRITE_ONCE, prio2band via memcpy, and per-class quantum via WRITE_ONCE. List operations on the active list use class list nodes (&q->classes[i].alist).

Verdict: Not Vulnerable

Reviewed region:
[net/sched/sch_ets.c]
```c
static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt,
				    struct netlink_ext_ack *extack)
{
	unsigned int quanta[TCQ_ETS_MAX_BANDS] = {0};
	struct Qdisc *queues[TCQ_ETS_MAX_BANDS];
	struct ets_sched *q = qdisc_priv(sch);
	struct nlattr *tb[TCA_ETS_MAX + 1];
	unsigned int oldbands = q->nbands;
	u8 priomap[TC_PRIO_MAX + 1];
	unsigned int nstrict = 0;
	unsigned int nbands;
	unsigned int i;
	int err;

	if (!opt) {
		NL_SET_ERR_MSG(extack, "ETS options are required for this operation");
		return -EINVAL;
	}

	err = nla_parse_nested(tb, TCA_ETS_MAX, opt, ets_policy, extack);
	if (err < 0)
		return err;

	if (!tb[TCA_ETS_NBANDS]) {
		NL_SET_ERR_MSG_MOD(extack, "Number of bands is a required argument");
		return -EINVAL;
	}
	nbands = nla_get_u8(tb[TCA_ETS_NBANDS]);
	if (nbands < 1 || nbands > TCQ_ETS_MAX_BANDS) {
		NL_SET_ERR_MSG_MOD(extack, "Invalid number of bands");
		return -EINVAL;
	}
	/* Unless overridden, traffic goes to the last band. */
	memset(priomap, nbands - 1, sizeof(priomap));

	if (tb[TCA_ETS_NSTRICT]) {
		nstrict = nla_get_u8(tb[TCA_ETS_NSTRICT]);
		if (nstrict > nbands) {
			NL_SET_ERR_MSG_MOD(extack, "Invalid number of strict bands");
			return -EINVAL;
		}
	}

	if (tb[TCA_ETS_PRIOMAP]) {
		err = ets_qdisc_priomap_parse(tb[TCA_ETS_PRIOMAP],
					      nbands, priomap, extack);
		if (err)
			return err;
	}

	if (tb[TCA_ETS_QUANTA]) {
		err = ets_qdisc_quanta_parse(sch, tb[TCA_ETS_QUANTA],
				     nbands, nstrict, quanta, extack);
		if (err)
			return err;
	}
	/* If there are more bands than strict + quanta provided, the remaining
	 * ones are ETS with quantum of MTU. Initialize the missing values here.
	 */
	for (i = nstrict; i < nbands; i++) {
		if (!quanta[i])
			quanta[i] = psched_mtu(qdisc_dev(sch));
	}

	/* Before commit, make sure we can allocate all new qdiscs */
	for (i = oldbands; i < nbands; i++) {
		queues[i] = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops,
					      ets_class_id(sch, &q->classes[i]),
					      extack);
		if (!queues[i]) {
			while (i > oldbands)
				qdisc_put(queues[--i]);
			return -ENOMEM;
		}
	}

	sch_tree_lock(sch);

	for (i = nbands; i < oldbands; i++) {
		if (cl_is_active(&q->classes[i]))
			list_del_init(&q->classes[i].alist);
		qdisc_purge_queue(q->classes[i].qdisc);
	}

	WRITE_ONCE(q->nbands, nbands);
	for (i = nstrict; i < q->nstrict; i++) {
		if (q->classes[i].qdisc->q.qlen) {
			list_add_tail(&q->classes[i].alist, &q->active);
			q->classes[i].deficit = quanta[i];
		}
	}
	for (i = q->nstrict; i < nstrict; i++) {
		if (cl_is_active(&q->classes[i]))
			list_del_init(&q->classes[i].alist);
	}
	WRITE_ONCE(q->nstrict, nstrict);
	memcpy(q->prio2band, priomap, sizeof(priomap));

	for (i = 0; i < q->nbands; i++)
		WRITE_ONCE(q->classes[i].quantum, quanta[i]);

	for (i = oldbands; i < q->nbands; i++) {
		q->classes[i].qdisc = queues[i];
		if (q->classes[i].qdisc != &noop_qdisc)
			qdisc_hash_add(q->classes[i].qdisc, true);
	}

	sch_tree_unlock(sch);

	ets_offload_change(sch);
	for (i = q->nbands; i < oldbands; i++) {
		qdisc_put(q->classes[i].qdisc);
		q->classes[i].qdisc = NULL;
		WRITE_ONCE(q->classes[i].quantum, 0);
		q->classes[i].deficit = 0;
		memset(&q->classes[i].bstats, 0, sizeof(q->classes[i].bstats));
		memset(&q->classes[i].qstats, 0, sizeof(q->classes[i].qstats));
	}
	return 0;
}
```

Why it is not vulnerable: Linked list nodes are never re-added while still present in the active list. The list_add_tail operation (adding bands transitioning from strict to ETS in range [nstrict, q->nstrict)) and the list_del_init operations (removing bands being eliminated in range [nbands, oldbands) and removing bands transitioning from ETS to strict in range [q->nstrict, nstrict)) operate on non-overlapping index ranges. Since nstrict <= nbands <= oldbands holds due to validation and the removal loop executes before the addition loop, no class node can be both deleted and re-added in the same call. Additionally, strict bands by design are not members of the active list (used for deficit round-robin scheduling of ETS bands), so bands transitioning from strict to ETS are adding fresh list nodes that were never in the active list. Bands transitioning from ETS to strict are properly removed before the transition, preventing stale references.

Security invariants:
- List node removal precedes re-addition: The removal loop for eliminated bands (range [nbands, oldbands)) executes before the addition loop for newly ETS bands (range [nstrict, q->nstrict)), and these ranges are provably disjoint because nstrict <= nbands <= oldbands.
- Strict-to-ETS transition uses clean list nodes: Bands in [nstrict, q->nstrict) that transition from strict to ETS were previously operating in strict mode, which does not use the active list, so their list nodes are not present in the active list when added.
- ETS-to-strict transition removes nodes before transition: Bands in [q->nstrict, nstrict) transitioning from ETS to strict are explicitly removed from the active list via list_del_init, preventing stale entries.
- Band count validation prevents out-of-bounds: nbands is validated to be in [1, TCQ_ETS_MAX_BANDS] (1-16), and nstrict is validated to be <= nbands, ensuring all list operations index valid class entries.
- Lock-protected list operations: sch_tree_lock/sch_tree_unlock bracket all active list modifications, preventing concurrent corruption.

Invariant verification:
- Non-overlapping removal and addition index ranges: holds=true. Evidence: Removal loop iterates i from nbands to oldbands (exclusive), while addition loop iterates i from nstrict to q->nstrict (exclusive). Since nstrict <= nbands is enforced by validation, and nbands <= oldbands for the removal loop to execute, the ranges [nbands, oldbands) and [nstrict, q->nstrict) are disjoint. The removal loop executes at lines before the addition loop (ordering within locked section).
- Strict bands not present in active list before transition to ETS: holds=true. Evidence: Bands 0 through nstrict-1 are strict bands that use direct scheduling, not deficit round-robin. The active list is exclusively for ETS bands (nstrict through nbands-1). When nstrict decreases, bands in [nstrict, q->nstrict) transition from strict to ETS, and their list nodes were never in the active list. This is verified by the invariant that cl_is_active only returns true for bands previously added via list_add_tail in the ETS range.
- ETS-to-strict transition removes list nodes: holds=true. Evidence: The loop 'for (i = q->nstrict; i < nstrict; i++) { if (cl_is_active(&q->classes[i])) list_del_init(&q->classes[i].alist); }' explicitly removes any active list nodes for bands transitioning from ETS to strict, using list_del_init to both remove and re-initialize the list node.
- nstrict bounded by nbands: holds=true. Evidence: Validation at 'if (nstrict > nbands) { NL_SET_ERR_MSG_MOD(extack, "Invalid number of strict bands"); return -EINVAL; }' ensures nstrict <= nbands, which is critical for the disjointness of the list operation ranges.
- Mutex protection for list modifications: holds=true. Evidence: sch_tree_lock(sch) is called before all list operations (list_del_init, list_add_tail, and cl_is_active checks), and sch_tree_unlock(sch) is called after all modifications, ensuring atomicity.

Residual risk notes: The safety of the active list operations depends on the invariant that strict bands (indices 0 to nstrict-1) are never members of the active list during normal operation. This invariant is maintained by the deficit round-robin dequeuing logic in ets_dequeue (not shown in context). If that logic were to incorrectly add or fail to remove list nodes for strict-band transitions, duplicate entries could theoretically arise. However, based on the current code structure, the transition logic in ets_qdisc_change correctly handles all four scenarios: bands eliminated (removed and purged), bands added (new allocation), strict-to-ETS (added fresh), and ETS-to-strict (removed before transition).

## Vulnerability Analysis: Out-of-Bounds Array Access

### Vulnerable Behavior

The function reads `oldbands = q->nbands` without validation, then uses this value as a loop bound to access `q->classes[i]` array (size 16) in multiple locations:

**Critical access points:**

1. **Loop 2 (line ~72)** - Before lock, accessing `q->classes[i]` in `ets_class_id()`:
```c
for (i = oldbands; i < nbands; i++) {
    queues[i] = qdisc_create_dflt(..., &q->classes[i], ...);
```

2. **Loop 3 (line ~82)** - Under lock, purging old bands:
```c
for (i = nbands; i < oldbands; i++) {
    if (cl_is_active(&q->classes[i]))  // OOB read
        list_del_init(&q->classes[i].alist);
    qdisc_purge_queue(q->classes[i].qdisc);  // OOB read
}
```

3. **Loop 4 (line ~89)** - Accessing with old `q->nstrict`:
```c
for (i = nstrict; i < q->nstrict; i++) {
    if (q->classes[i].qdisc->q.qlen) {  // OOB read if q->nstrict > 16
        q->classes[i].deficit = quanta[i];  // OOB write
```

4. **Loop 8 (line ~113)** - Cleanup with writes:
```c
for (i = q->nbands; i < oldbands; i++) {
    qdisc_put(q->classes[i].qdisc);  // OOB read
    q->classes[i].qdisc = NULL;  // OOB write
    WRITE_ONCE(q->classes[i].quantum, 0);  // OOB write
    // Additional OOB writes to bstats, qstats
}
```

### Root Cause

**Missing validation:** The new `nbands` is validated (`nbands < 1 || nbands > TCQ_ETS_MAX_BANDS`), but the existing `q->nbands` and `q->nstrict` values are trusted without bounds checking.

The code assumes `q->nbands ≤ TCQ_ETS_MAX_BANDS` always holds, but:
- Uses `WRITE_ONCE(q->nbands, nbands)` suggesting concurrent access awareness
- No defensive validation of the old value before using it as array index

### Attack Preconditions

1. **Privilege requirement:** CAP_NET_ADMIN capability to modify tc qdiscs
2. **Corruption vector:** Need `q->nbands` or `q->nstrict` to exceed 16 through:
   - Memory corruption (use-after-free, buffer overflow in related code)
   - Race condition during concurrent qdisc operations
   - Unvalidated initialization path
   - Integer overflow in another setter

3. **Trigger:** Call `tc_modify_qdisc` → `ets_qdisc_change` with corrupted state

### Impact

- **Out-of-bounds read:** Leaks kernel memory beyond `q->classes[]` array
- **Out-of-bounds write:** Corrupts adjacent kernel structures (qdisc, quantum, deficit, stats)
- **Memory corruption:** Writing to `q->classes[16+]` overwrites whatever follows in memory
- **Consequences:** Information disclosure, kernel panic (DoS), potential privilege escalation if attacker controls corrupted memory layout

### Exploitability Assessment

**Moderate to High** if corruption vector exists:
- Requires privileged access (CAP_NET_ADMIN) - reduces attack surface
- Depends on ability to corrupt `q->nbands` - not shown in provided code
- Multiple OOB access points increase reliability
- Kernel memory corruption is high-impact

**Uncertainty:** Without seeing initialization code and all paths that modify `q->nbands`, cannot confirm if corruption is achievable. If `q->nbands` is always set through this function with validation, vulnerability may be theoretical.

### Vulnerability Classification

**CWE-119:** Improper Restriction of Operations within the Bounds of a Memory Buffer
- **CWE-125:** Out-of-bounds Read (information disclosure)
- **CWE-787:** Out-of-bounds Write (memory corruption)

### Recommended Fix

Add defensive validation:
```c
if (oldbands > TCQ_ETS_MAX_BANDS) {
    NL_SET_ERR_MSG(extack, "Corrupted band count detected");
    return -EINVAL;
}
if (q->nstrict > TCQ_ETS_MAX_BANDS) {
    q->nstrict = 0;  // or return error
}
```

### Verdict

**VULNERABLE** - The code contains exploitable out-of-bounds array access logic. While exploitability depends on whether `q->nbands` can be corrupted (not shown in provided context), the missing bounds check on a security-critical loop bound constitutes a real vulnerability that should be patched defensively.

```json
{
  "vulnerable": true,
  "cwe": "CWE-119",
  "severity": "Medium",
  "reviewed_region": "net/sched/sch_ets.c:ets_qdisc_change (entire function)",
  "vulnerable_region": "Multiple loops using unvalidated oldbands and q->nstrict as array indices: lines ~72-81 (qdisc allocation), ~85-89 (band cleanup), ~92-97 (strict band activation), ~98-101 (strict band deactivation), ~113-121 (final cleanup)",
  "root_cause": "Missing bounds validation on q->nbands and q->nstrict before using them as array indices into q->classes[TCQ_ETS_MAX_BANDS]. The function validates new nbands values but trusts existing q->nbands and q->nstrict values without verification, creating potential for out-of-bounds access if these fields are corrupted.",
  "exploitability": "Low to Medium - Requires q->nbands or q->nstrict to exceed TCQ_ETS_MAX_BANDS (16). Provided code shows validation of new values before writing, suggesting normal operation maintains valid state. Exploitation would require: (1) memory corruption from unrelated bug, (2) unvalidated initialization path not shown in provided code, or (3) alternative setter function without proper validation. Requires CAP_NET_ADMIN capability to trigger.",
  "attack_preconditions": "1. Attacker must have CAP_NET_ADMIN capability to modify qdisc configuration. 2. q->nbands or q->nstrict must be corrupted to value > 16 through means not shown in provided code (memory corruption, unvalidated initialization, or alternative setter). 3. Attacker must call tc_modify_qdisc to trigger ets_qdisc_change with corrupted state.",
  "impact": "If exploitable: Out-of-bounds read (information disclosure of kernel memory beyond q->classes array) and out-of-bounds write (corruption of adjacent kernel structures including qdisc pointers, quantum values, deficit counters, and statistics). Could lead to kernel panic (DoS), memory corruption, or potential privilege escalation depending on memory layout.",
  "non_vulnerable_reason": null,
  "security_invariants": "Intended invariants (not enforced defensively): (1) q->nbands <= TCQ_ETS_MAX_BANDS maintained by validation in this function before writing. (2) q->nstrict <= q->nbands maintained by validation check 'if (nstrict > nbands)'. (3) Array q->classes has exactly TCQ_ETS_MAX_BANDS (16) elements.",
  "invariant_verification": "Invariants are NOT verified on function entry. The function validates NEW values (nbands, nstrict) before writing but reads OLD values (q->nbands, q->nstrict) without bounds checking. Use of WRITE_ONCE() suggests awareness of concurrent access risks, but no defensive validation protects against corrupted state from other sources.",
  "residual_risk_notes": "Exploitability depends on whether q->nbands/q->nstrict can be corrupted, which is not demonstrated in provided code. Without seeing initialization code, other setter functions, or full qdisc lifecycle, cannot confirm if corruption path exists. However, missing defensive bounds checks on array indices is a security-relevant code quality issue that should be fixed regardless of current exploitability. Recommended fix: Add validation 'if (oldbands > TCQ_ETS_MAX_BANDS || q->nstrict > TCQ_ETS_MAX_BANDS) return -EINVAL;' at function entry.",
  "call_path": "tc_modify_qdisc (net/sched/cls_api.c) → ets_qdisc_change (net/sched/sch_ets.c)"
}
```