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

Repository: git.kernel.org/pub/scm/linux/kernel/git/stable/linux
Language: C
File: kernel/sched/fair.c
Function: reweight_eevdf

Call path: reweight_eevdf (kernel/sched/fair.c) → entity_eligible (kernel/sched/fair.c) → pick_eevdf (kernel/sched/fair.c) → update_entity_lag (kernel/sched/fair.c) → calc_delta_fair (kernel/sched/fair.c) → avg_vruntime (kernel/sched/fair.c)

### Primary Function

```c
static void reweight_eevdf(struct sched_entity *se, u64 avruntime,
			   unsigned long weight)
{
	unsigned long old_weight = se->load.weight;
	s64 vlag, vslice;

	/*
	 * VRUNTIME
	 * ========
	 *
	 * COROLLARY #1: The virtual runtime of the entity needs to be
	 * adjusted if re-weight at !0-lag point.
	 *
	 * Proof: For contradiction assume this is not true, so we can
	 * re-weight without changing vruntime at !0-lag point.
	 *
	 *             Weight	VRuntime   Avg-VRuntime
	 *     before    w          v            V
	 *      after    w'         v'           V'
	 *
	 * Since lag needs to be preserved through re-weight:
	 *
	 *	lag = (V - v)*w = (V'- v')*w', where v = v'
	 *	==>	V' = (V - v)*w/w' + v		(1)
	 *
	 * Let W be the total weight of the entities before reweight,
	 * since V' is the new weighted average of entities:
	 *
	 *	V' = (WV + w'v - wv) / (W + w' - w)	(2)
	 *
	 * by using (1) & (2) we obtain:
	 *
	 *	(WV + w'v - wv) / (W + w' - w) = (V - v)*w/w' + v
	 *	==> (WV-Wv+Wv+w'v-wv)/(W+w'-w) = (V - v)*w/w' + v
	 *	==> (WV - Wv)/(W + w' - w) + v = (V - v)*w/w' + v
	 *	==>	(V - v)*W/(W + w' - w) = (V - v)*w/w' (3)
	 *
	 * Since we are doing at !0-lag point which means V != v, we
	 * can simplify (3):
	 *
	 *	==>	W / (W + w' - w) = w / w'
	 *	==>	Ww' = Ww + ww' - ww
	 *	==>	W * (w' - w) = w * (w' - w)
	 *	==>	W = w	(re-weight indicates w' != w)
	 *
	 * So the cfs_rq contains only one entity, hence vruntime of
	 * the entity @v should always equal to the cfs_rq's weighted
	 * average vruntime @V, which means we will always re-weight
	 * at 0-lag point, thus breach assumption. Proof completed.
	 *
	 *
	 * COROLLARY #2: Re-weight does NOT affect weighted average
	 * vruntime of all the entities.
	 *
	 * Proof: According to corollary #1, Eq. (1) should be:
	 *
	 *	(V - v)*w = (V' - v')*w'
	 *	==>	    v' = V' - (V - v)*w/w'		(4)
	 *
	 * According to the weighted average formula, we have:
	 *
	 *	V' = (WV - wv + w'v') / (W - w + w')
	 *	   = (WV - wv + w'(V' - (V - v)w/w')) / (W - w + w')
	 *	   = (WV - wv + w'V' - Vw + wv) / (W - w + w')
	 *	   = (WV + w'V' - Vw) / (W - w + w')
	 *
	 *	==>	V'*(W - w + w') = WV + w'V' - Vw
	 *	==>	V' * (W - w) = (W - w) * V	(5)
	 *
	 * If the entity is the only one in the cfs_rq, then reweight
	 * always occurs at 0-lag point, so V won't change. Or else
	 * there are other entities, hence W != w, then Eq. (5) turns
	 * into V' = V. So V won't change in either case, proof done.
	 *
	 *
	 * So according to corollary #1 & #2, the effect of re-weight
	 * on vruntime should be:
	 *
	 *	v' = V' - (V - v) * w / w'		(4)
	 *	   = V  - (V - v) * w / w'
	 *	   = V  - vl * w / w'
	 *	   = V  - vl'
	 */
	if (avruntime != se->vruntime) {
		vlag = entity_lag(avruntime, se);
		vlag = div_s64(vlag * old_weight, weight);
		se->vruntime = avruntime - vlag;
	}

	/*
	 * DEADLINE
	 * ========
	 *
	 * When the weight changes, the virtual time slope changes and
	 * we should adjust the relative virtual deadline accordingly.
	 *
	 *	d' = v' + (d - v)*w/w'
	 *	   = V' - (V - v)*w/w' + (d - v)*w/w'
	 *	   = V  - (V - v)*w/w' + (d - v)*w/w'
	 *	   = V  + (d - V)*w/w'
	 */
	vslice = (s64)(se->deadline - avruntime);
	vslice = div_s64(vslice * old_weight, weight);
	se->deadline = avruntime + vslice;
}
```

### Cross-File Context

[entity_lag — function — kernel/sched/fair.c:699]
```c
static s64 entity_lag(u64 avruntime, struct sched_entity *se)
{
	s64 vlag, limit;

	vlag = avruntime - se->vruntime;
	limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se);

	return clamp(vlag, -limit, limit);
}
```

[update_entity_lag — helper — kernel/sched/fair.c:709]
```c
static void update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	SCHED_WARN_ON(!se->on_rq);

	se->vlag = entity_lag(avg_vruntime(cfs_rq), se);
}
```

[calc_delta_fair — helper — kernel/sched/fair.c:296]
```c
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
	if (unlikely(se->load.weight != NICE_0_LOAD))
		delta = __calc_delta(delta, NICE_0_LOAD, &se->load);

	return delta;
}
```

[avg_vruntime — helper — kernel/sched/fair.c:665]
```c
u64 avg_vruntime(struct cfs_rq *cfs_rq)
{
	struct sched_entity *curr = cfs_rq->curr;
	s64 avg = cfs_rq->avg_vruntime;
	long load = cfs_rq->avg_load;

	if (curr && curr->on_rq) {
		unsigned long weight = scale_load_down(curr->load.weight);

		avg += entity_key(cfs_rq, curr) * weight;
		load += weight;
	}

	if (load) {
		/* sign flips effective floor / ceil */
		if (avg < 0)
			avg -= (load - 1);
		avg = div_s64(avg, load);
	}

	return cfs_rq->min_vruntime + avg;
}
```

[entity_eligible — callee — kernel/sched/fair.c:749]
```c
int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	return vruntime_eligible(cfs_rq, se->vruntime);
}
```

[pick_eevdf — callee — kernel/sched/fair.c:884]
```c
static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq)
{
	struct rb_node *node = cfs_rq->tasks_timeline.rb_root.rb_node;
	struct sched_entity *se = __pick_first_entity(cfs_rq);
	struct sched_entity *curr = cfs_rq->curr;
	struct sched_entity *best = NULL;

	if (cfs_rq->nr_running == 1)
		return curr && curr->on_rq ? curr : se;

	if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr)))
		curr = NULL;

	if (sched_feat(RUN_TO_PARITY) && curr && curr->vlag == curr->deadline)
		return curr;

	if (se && entity_eligible(cfs_rq, se)) {
		best = se;
		goto found;
	}

	while (node) {
		struct rb_node *left = node->rb_left;

		if (left && vruntime_eligible(cfs_rq,
					__node_2_se(left)->min_vruntime)) {
			node = left;
			continue;
		}

		se = __node_2_se(node);

		if (entity_eligible(cfs_rq, se)) {
			best = se;
			break;
		}

		node = node->rb_right;
	}
found:
	if (!best || (curr && entity_before(curr, best)))
		best = curr;

	return best;
}
```

[sched_entity — struct — include/linux/sched.h:536]
```c
struct sched_entity {
	/* For load-balancing: */
	struct load_weight		load;
	struct rb_node			run_node;
	u64				deadline;
	u64				min_vruntime;

	struct list_head		group_node;
	unsigned int			on_rq;

	u64				exec_start;
	u64			sum_exec_runtime;
	u64			prev_sum_exec_runtime;
	u64			vruntime;
	s64			vlag;
	u64			slice;

	u64			nr_migrations;

#ifdef CONFIG_FAIR_GROUP_SCHED
	int			depth;
	struct sched_entity	*parent;
	/* rq on which this entity is (to be) queued: */
	struct cfs_rq		*cfs_rq;
	/* rq "owned" by this entity/group: */
	struct cfs_rq		*my_q;
	/* cached value of my_q->h_nr_running */
	unsigned long		-runnable_weight;
#endif

#ifdef CONFIG_SMP
	/*
	 * Per entity load average tracking.
	 *
	 * Put into separate cache line so it does not
	 * collide with read-mostly values above.
	 */
	struct sched_avg	avg;
#endif
};
```

[clamp — macro — linux/kernel.h]
clamp → #define clamp(val, lo, hi) min_t(typeof(val), max_t(typeof(val), val, lo), hi)  (linux/kernel.h)

[TICK_NSEC — constant — linux/jiffies.h]
TICK_NSEC → (NSEC_PER_SEC / HZ)  (linux/jiffies.h)

Control flow: The reweight_eevdf function has two sequential branches: (1) VRUNTIME adjustment block that executes only when avruntime != se->vruntime, computing entity_lag, scaling by weight ratio via div_s64, and updating se->vruntime; (2) DEADLINE adjustment block that unconditionally computes vslice from se->deadline and avruntime, scales by weight ratio via div_s64, and updates se->deadline. There are no error paths, cleanup paths, or early returns—both sections execute linearly when their conditions are met.

Data flow: Input: se (sched_entity pointer), avruntime (u64 weighted average vruntime), weight (unsigned long new weight). Intermediate: old_weight extracted from se->load.weight. In the VRUNTIME branch, entity_lag(avruntime, se) returns a clamped lag value via entity_lag() which subtracts se->vruntime from avruntime and clamps to [-limit, limit] where limit is calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se). The clamped vlag is then multiplied by old_weight and divided by weight using div_s64, producing a scaled lag. se->vruntime is set to avruntime - scaled_vlag. In the DEADLINE branch, vslice is computed as (se->deadline - avruntime) cast to s64, then scaled by old_weight/weight via div_s64. se->deadline is set to avruntime + scaled_vslice. All output writes modify fields within se.

Verdict: Not Vulnerable

Reviewed region:
[kernel/sched/fair.c]
```c
static void reweight_eevdf(struct sched_entity *se, u64 avruntime,
			   unsigned long weight)
{
	unsigned long old_weight = se->load.weight;
	s64 vlag, vslice;

	/*
	 * VRUNTIME
	 * ========
	 *
	 * COROLLARY #1: The virtual runtime of the entity needs to be
	 * adjusted if re-weight at !0-lag point.
	 *
	 * Proof: For contradiction assume this is not true, so we can
	 * re-weight without changing vruntime at !0-lag point.
	 *
	 *             Weight	VRuntime   Avg-VRuntime
	 *     before    w          v            V
	 *      after    w'         v'           V'
	 *
	 * Since lag needs to be preserved through re-weight:
	 *
	 *	lag = (V - v)*w = (V'- v')*w', where v = v'
	 *	==>	V' = (V - v)*w/w' + v		(1)
	 *
	 * Let W be the total weight of the entities before reweight,
	 * since V' is the new weighted average of entities:
	 *
	 *	V' = (WV + w'v - wv) / (W + w' - w)	(2)
	 *
	 * by using (1) & (2) we obtain:
	 *
	 *	(WV + w'v - wv) / (W + w' - w) = (V - v)*w/w' + v
	 *	==> (WV-Wv+Wv+w'v-wv)/(W+w'-w) = (V - v)*w/w' + v
	 *	==> (WV - Wv)/(W + w' - w) + v = (V - v)*w/w' + v
	 *	==>	(V - v)*W/(W + w' - w) = (V - v)*w/w' (3)
	 *
	 * Since we are doing at !0-lag point which means V != v, we
	 * can simplify (3):
	 *
	 *	==>	W / (W + w' - w) = w / w'
	 *	==>	Ww' = Ww + ww' - ww
	 *	==>	W * (w' - w) = w * (w' - w)
	 *	==>	W = w	(re-weight indicates w' != w)
	 *
	 * So the cfs_rq contains only one entity, hence vruntime of
	 * the entity @v should always equal to the cfs_rq's weighted
	 * average vruntime @V, which means we will always re-weight
	 * at 0-lag point, thus breach assumption. Proof completed.
	 *
	 *
	 * COROLLARY #2: Re-weight does NOT affect weighted average
	 * vruntime of all the entities.
	 *
	 * Proof: According to corollary #1, Eq. (1) should be:
	 *
	 *	(V - v)*w = (V' - v')*w'
	 *	==>	    v' = V' - (V - v)*w/w'		(4)
	 *
	 * According to the weighted average formula, we have:
	 *
	 *	V' = (WV - wv + w'v') / (W - w + w')
	 *	   = (WV - wv + w'(V' - (V - v)w/w')) / (W - w + w')
	 *	   = (WV - wv + w'V' - Vw + wv) / (W - w + w')
	 *	   = (WV + w'V' - Vw) / (W - w + w')
	 *
	 *	==>	V'*(W - w + w') = WV + w'V' - Vw
	 *	==>	V' * (W - w) = (W - w) * V	(5)
	 *
	 * If the entity is the only one in the cfs_rq, then reweight
	 * always occurs at 0-lag point, so V won't change. Or else
	 * there are other entities, hence W != w, then Eq. (5) turns
	 * into V' = V. So V won't change in either case, proof done.
	 *
	 *
	 * So according to corollary #1 & #2, the effect of re-weight
	 * on vruntime should be:
	 *
	 *	v' = V' - (V - v) * w / w'		(4)
	 *	   = V  - (V - v) * w / w'
	 *	   = V  - vl * w / w'
	 *	   = V  - vl'
	 */
	if (avruntime != se->vruntime) {
		vlag = entity_lag(avruntime, se);
		vlag = div_s64(vlag * old_weight, weight);
		se->vruntime = avruntime - vlag;
	}

	/*
	 * DEADLINE
	 * ========
	 *
	 * When the weight changes, the virtual time slope changes and
	 * we should adjust the relative virtual deadline accordingly.
	 *
	 *	d' = v' + (d - v)*w/w'
	 *	   = V' - (V - v)*w/w' + (d - v)*w/w'
	 *	   = V  - (V - v)*w/w' + (d - v)*w/w'
	 *	   = V  + (d - V)*w/w'
	 */
	vslice = (s64)(se->deadline - avruntime);
	vslice = div_s64(vslice * old_weight, weight);
	se->deadline = avruntime + vslice;
}
```

Why it is not vulnerable: The critical operation vlag * old_weight in the VRUNTIME branch cannot overflow because vlag is clamped by entity_lag() to [-limit, limit] where limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se). Since entity_lag() uses clamp() with a bounded limit derived from the scheduling quantum (slice and TICK_NSEC), the intermediate product vlag * old_weight remains well within s64 range. The div_s64() function safely handles division including zero-divisor edge cases. Similarly, vslice * old_weight in the DEADLINE branch uses a time difference that is bounded by reasonable scheduling deadlines, and div_s64() provides safe signed division semantics.

Security invariants:
- vlag must be bounded before multiplication by old_weight; enforced by entity_lag() which returns clamp(vlag, -limit, limit) where limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se)
- Division by weight must not cause division-by-zero or undefined behavior; enforced by div_s64() which checks for zero divisor and returns a safe fallback value
- The signed arithmetic in vlag = div_s64(vlag * old_weight, weight) must not overflow; enforced by the clamping invariant limiting vlag magnitude and old_weight being bounded by the kernel weight scale
- The signed cast (s64)(se->deadline - avruntime) must represent a valid time difference; enforced by deadline and avruntime both being u64 nanosecond timestamps from the same scheduling domain

Invariant verification:
- vlag clamping before scaling multiplication: holds=true. Evidence: entity_lag() computes vlag = avruntime - se->vruntime, then limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se), then returns clamp(vlag, -limit, limit). This clamped result is passed to reweight_eevdf which multiplies it by old_weight in div_s64(vlag * old_weight, weight).
- Safe signed division with zero-divisor protection: holds=true. Evidence: Both vlag and vslice scaling use div_s64() for division by weight. div_s64() is a kernel utility that checks for zero divisors and returns a defined fallback, preventing division-by-zero or undefined behavior.
- Bounded intermediate product magnitude: holds=true. Evidence: Since vlag is clamped to [-limit, limit] and old_weight is an unsigned long bounded by MAX_WEIGHT (1024 in kernel scheduler), the product vlag * old_weight fits within s64 (±2^63). The limit itself is derived from TICK_NSEC and se->slice, both bounded time values.
- vslice computation uses valid time representation: holds=true. Evidence: vslice = (s64)(se->deadline - avruntime) where both se->deadline and avruntime are u64 nanosecond timestamps. The subtraction produces a reasonable time difference that, when scaled by weight ratio, fits within s64 range for normal scheduling parameters.

Residual risk notes: The vslice calculation in the DEADLINE branch does not apply explicit clamping like the vlag path. While vslice represents a bounded scheduling deadline difference in practice, an unusually large deadline value combined with a large old_weight could theoretically approach s64 limits. However, se->deadline is set by the scheduler using avruntime + vslice patterns that keep values in reasonable ranges, and div_s64 provides overflow-safe division semantics. The weight parameter is not validated for zero at the call site, but div_s64 handles this defensively.

## Verdict

**No real vulnerability is established from the supplied code and context.**

The only superficially risky behavior here is the pair of divisions by `weight`, but from the scheduler data model this function appears to rely on the invariant that a sched entity’s effective weight is **strictly positive**. Based on the code shown, there is **no evidence of attacker-controlled arbitrary input** reaching `weight`, and the surrounding scheduler logic strongly suggests `weight == 0` is not a valid state for a runnable fair-scheduler entity.

## What the function does

`reweight_eevdf()` adjusts a `sched_entity`’s:

- `vruntime`, and
- `deadline`

when its scheduling weight changes.

Relevant operations:

```c
vlag = entity_lag(avruntime, se);
vlag = div_s64(vlag * old_weight, weight);
se->vruntime = avruntime - vlag;

vslice = (s64)(se->deadline - avruntime);
vslice = div_s64(vslice * old_weight, weight);
se->deadline = avruntime + vslice;
```

So the function rescales lag and virtual deadline proportionally from `old_weight` to `weight`.

---

## Potentially suspicious point: division by `weight`

### Observed evidence

There are two divisions by the `weight` parameter:

```c
div_s64(vlag * old_weight, weight);
div_s64(vslice * old_weight, weight);
```

There is **no local check** that `weight != 0`.

### Why this does not, by itself, prove a vulnerability

To claim a real divide-by-zero vulnerability, we would need evidence that:

1. `weight` can be zero in a reachable path, and
2. an attacker can influence that condition.

The supplied context does **not** show the caller of `reweight_eevdf()`, nor any path that can pass `0` here.

More importantly, the types and surrounding logic indicate that this is scheduler-internal state:

- `weight` is an `unsigned long`, representing scheduler weight.
- `old_weight` is `se->load.weight`.
- `sched_entity.load.weight` is part of Linux fair scheduling accounting, where runnable entities are expected to have meaningful positive weights.

Nothing in the supplied code suggests user input is passed here directly or unsanitized.

### Scheduler invariant that matters

The function is manipulating a live `sched_entity` inside CFS/EEVDF accounting. In that subsystem, a zero weight would make little semantic sense:

- `calc_delta_fair()` uses `se->load.weight` as a divisor indirectly through `__calc_delta(...)`.
- `avg_vruntime()` adds weights into `load` and computes a weighted average.
- Eligibility and lag logic assume entities participate in weighted fair scheduling.

That all strongly implies the scheduler expects entity weights to be positive, not zero.

So although `reweight_eevdf()` lacks a defensive check, the code shown is consistent with a **trusted internal invariant**, not a missing security validation.

---

## Other possible issues

### Integer overflow in `vlag * old_weight` / `vslice * old_weight`

This is the next obvious concern.

#### Observed evidence

- `vlag` is bounded by `entity_lag()`:

  ```c
  vlag = avruntime - se->vruntime;
  limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se);
  return clamp(vlag, -limit, limit);
  ```

  So `vlag` is explicitly clamped to `[-limit, limit]`.

- `vslice` is derived from:

  ```c
  vslice = (s64)(se->deadline - avruntime);
  ```

  There is no explicit clamp here in the shown function.

#### Security assessment

For `vlag`, the clamp significantly constrains magnitude before multiplication. That is a real guard.

For `vslice`, there is no local bound shown, but this is scheduler-maintained timing state, not raw attacker data. Without more context, we cannot prove a dangerous overflow path or attacker-controlled corruption. Also, even if arithmetic wraparound were theoretically possible, the likely consequence would be scheduler mis-accounting, not a clear memory-safety or privilege-escalation issue from the shown code alone.

So there is **insufficient evidence** for an exploitable integer-overflow vulnerability here.

---

## Data/control-flow assessment

### Inputs
- `se`: scheduler entity pointer
- `avruntime`: average virtual runtime
- `weight`: new scheduling weight

### Transformations
- If `avruntime != se->vruntime`, compute bounded lag via `entity_lag()`, rescale it by `old_weight / weight`, and update `se->vruntime`.
- Always compute relative deadline delta `se->deadline - avruntime`, rescale it similarly, and update `se->deadline`.

### Side effects
- Mutates only:
  - `se->vruntime`
  - `se->deadline`

No pointer arithmetic, no buffer access, no memory allocation, no copy operations.

### Guards
- `entity_lag()` clamps lag magnitude.
- The code operates purely on scheduler-internal state.
- No evidence of untrusted direct input.

---

## Exploitability

From the supplied code, exploitability is **not demonstrated**.

To make this a real vulnerability, one would need a concrete reachable path where an attacker can cause:

- `weight == 0`, or
- extreme malformed scheduler state causing exploitable arithmetic corruption.

That path is not shown.

---

## Conclusion

### Observed facts
- `reweight_eevdf()` divides by `weight` without checking for zero.
- `vlag` is bounded before rescaling.
- All data involved is scheduler-internal accounting state.

### Inferred conclusion
- This is **not enough to classify the function as vulnerable**.
- The lack of a zero check appears to rely on a normal scheduler invariant: **weights are positive**.
- No attacker-controlled path to invalid `weight` is provided in the supplied context.

## Final classification

**No confirmed vulnerability in the supplied code.**

### Residual uncertainty
If some omitted caller can pass `weight == 0` or otherwise violate scheduler weight invariants, then a divide-by-zero bug could exist. But that would depend on external code not shown here, so it cannot be concluded from this sample alone.

Rescales sched_entity vruntime and deadline when an entity's scheduling weight changes, using entity_lag(avruntime, se), old_weight = se->load.weight, and two div_s64(..., weight) operations.

Not established from the supplied code. The only superficially risky operation is division by the incoming weight parameter, but no shown path demonstrates weight can be zero or attacker-controlled, and the function appears to rely on scheduler-internal positive-weight invariants.

A caller outside the supplied context would need to invoke reweight_eevdf() with an invalid weight such as 0, or otherwise violate fair-scheduler internal accounting invariants.

An attacker would need some separate capability to influence kernel scheduler entity weights into an invalid state; no such path is shown.

No confirmed security impact from the supplied code alone. At most, if unseen code violated invariants, arithmetic faults or scheduler-accounting corruption could occur, but that is not demonstrated here.

The code manipulates only scheduler-internal accounting fields (se->vruntime and se->deadline). The apparent concern is lack of a local zero-check before div_s64(..., weight), but the supplied context does not show any caller passing arbitrary or untrusted values, and fair-scheduler weights are expected to be strictly positive for runnable entities. Additionally, entity_lag() clamps vlag to a bounded range before reweighting, which constrains one of the arithmetic inputs. There is no memory access, buffer handling, pointer arithmetic, or privilege boundary crossing in the reviewed region.

sched_entity load weights used for fair scheduling are expected to be positive, not zero.

entity_lag() bounds vlag via clamp(vlag, -limit, limit), where limit is derived from calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se).

reweight_eevdf() updates only arithmetic scheduler state: se->vruntime and se->deadline.

From the supplied code, positive-weight invariants are not explicitly checked inside reweight_eevdf(), but they are consistent with the surrounding scheduler model: calc_delta_fair() depends on se->load.weight for weighted scheduling math, avg_vruntime() computes a weighted average over entity weights, and entity eligibility/picking uses vruntime/deadline values as scheduler metadata rather than attacker data. The code shown provides no evidence that zero or attacker-chosen weights can reach this function.

If omitted callers can pass weight == 0, the div_s64 calls would be problematic; that cannot be concluded from the supplied context.

vslice = (s64)(se->deadline - avruntime) is not locally bounded here, but no concrete attacker-controlled path or exploitable consequence is shown.

This assessment is limited by absence of the actual callers and weight-setting logic.


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

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

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

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

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

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

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

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

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

Repository: https://github.com/torvalds/linux
Language: C
File: drivers/clk/sunxi-ng/ccu_common.c
Function: sunxi_ccu_probe

Call path: devm_sunxi_ccu_probe (drivers/clk/sunxi-ng/ccu_common.c) → of_sunxi_ccu_probe (drivers/clk/sunxi-ng/ccu_common.c) → sunxi_ccu_probe (drivers/clk/sunxi-ng/ccu_common.c) → hw_to_ccu_common (drivers/clk/sunxi-ng/ccu_common.h)

### Primary Function

```c
static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev,
			   struct device_node *node, void __iomem *reg,
			   const struct sunxi_ccu_desc *desc)
{
	struct ccu_reset *reset;
	int i, ret;

	ccu->desc = desc;

	spin_lock_init(&ccu->lock);

	for (i = 0; i < desc->num_ccu_clks; i++) {
		struct ccu_common *cclk = desc->ccu_clks[i];

		if (!cclk)
			continue;

		cclk->base = reg;
		cclk->lock = &ccu->lock;
	}

	for (i = 0; i < desc->hw_clks->num ; i++) {
		struct clk_hw *hw = desc->hw_clks->hws[i];
		struct ccu_common *common = hw_to_ccu_common(hw);
		const char *name;

		if (!hw)
			continue;

		name = hw->init->name;
		if (dev)
			ret = clk_hw_register(dev, hw);
		else
			ret = of_clk_hw_register(node, hw);
		if (ret) {
			pr_err("Couldn't register clock %d - %s\n", i, name);
			goto err_clk_unreg;
		}

		if (common->max_rate)
			clk_hw_set_rate_range(hw, common->min_rate,
					      common->max_rate);
		else
			WARN(common->min_rate,
			     "No max_rate, ignoring min_rate of clock %d - %s\n",
			     i, name);
	}

	ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get,
				     desc->hw_clks);
	if (ret)
		goto err_clk_unreg;

	reset = &ccu->reset;
	reset->rcdev.of_node = node;
	reset->rcdev.ops = &ccu_reset_ops;
	reset->rcdev.owner = dev ? dev->driver->owner : THIS_MODULE;
	reset->rcdev.nr_resets = desc->num_resets;
	reset->base = reg;
	reset->lock = &ccu->lock;
	reset->reset_map = desc->resets;

	ret = reset_controller_register(&reset->rcdev);
	if (ret)
		goto err_del_provider;

	return 0;

err_del_provider:
	of_clk_del_provider(node);
err_clk_unreg:
	while (--i >= 0) {
		struct clk_hw *hw = desc->hw_clks->hws[i];

		if (!hw)
			continue;
		clk_hw_unregister(hw);
	}
	return ret;
}
```

### Cross-File Context

[hw_to_ccu_common — helper — drivers/clk/sunxi-ng/ccu_common.h:42]
hw_to_ccu_common → static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw) { return container_of(hw, struct ccu_common, hw); }  (drivers/clk/sunxi-ng/ccu_common.h:42)

[ccu_common — struct — drivers/clk/sunxi-ng/ccu_common.h:28]
```c
struct ccu_common {
	void __iomem	*base;
	u16		reg;
	u16		lock_reg;
	u32		prediv;

	unsigned long	min_rate;
	unsigned long	max_rate;

	unsigned long	features;
	spinlock_t	*lock;
	struct clk_hw	hw;
};
```

[sunxi_ccu_desc — struct — drivers/clk/sunxi-ng/ccu_common.h:47]
```c
struct sunxi_ccu_desc {
	struct ccu_common		**ccu_clks;
	unsigned long			num_ccu_clks;

	struct clk_hw_onecell_data	*hw_clks;

	struct ccu_reset_map		*resets;
	unsigned long			num_resets;
};
```

[clk_hw_set_rate_range — function — include/linux/clk-provider.h]
```c
void clk_hw_set_rate_range(struct clk_hw *hw, unsigned long min_rate, unsigned long max_rate)
```

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: sunxi_ccu_probe iterates over hw_clks->hws[] to register clocks. In the vulnerable version, it calls hw_to_ccu_common on each hw entry and immediately dereferences the result. The fix separates clock registration from rate range setting, using a second loop over ccu_clks[] which are guaranteed to be ccu_common structs.

Data flow: The attacker-controlled data flows from DeviceTree descriptor (desc->hw_clks->hws[]) through hw_to_ccu_common() which performs an unchecked type cast via container_of. The resulting pointer is dereferenced to access min_rate and max_rate fields without validation.

Verdict: Vulnerable
Vulnerable region:
[drivers/clk/sunxi-ng/ccu_common.c]
```c
for (i = 0; i < desc->hw_clks->num ; i++) {
		struct clk_hw *hw = desc->hw_clks->hws[i];
		struct ccu_common *common = hw_to_ccu_common(hw);
		const char *name;

		if (!hw)
			continue;

		name = hw->init->name;
		if (dev)
			ret = clk_hw_register(dev, hw);
		else
			ret = of_clk_hw_register(node, hw);
		if (ret) {
			pr_err("Couldn't register clock %d - %s\n", i, name);
			goto err_clk_unreg;
		}

		if (common->max_rate)
			clk_hw_set_rate_range(hw, common->min_rate,
					      common->max_rate);
		else
			WARN(common->min_rate,
			     "No max_rate, ignoring min_rate of clock %d - %s\n",
			     i, name);
	}

	ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get,
				     desc->hw_clks);
	if (ret)
		goto err_clk_unreg;
```
CWE: CWE-476
Severity: MEDIUM

Root cause: The sunxi_ccu_probe function incorrectly assumes all entries in desc->hw_clks->hws[] are ccu_common structs by using hw_to_ccu_common(hw) which is a container_of cast. When hw points to a non-ccu_common clk_hw, the cast produces an invalid pointer, and subsequent access to common->max_rate or common->min_rate causes an invalid pointer dereference.

Exploitability: The vulnerability is triggered during device probe when a sunxi CCU clock device has a hw_clks->hws[] array containing entries that are not ccu_common structs. The buggy code causes immediate dereference of an invalid pointer constructed via container_of on the wrong type.

Attack preconditions:
- Target must have a DeviceTree node with a sunxi CCU clock controller
- The ccu_clks array in the device descriptor must contain at least one non-ccu_common clk_hw entry
- The clock driver probe function (sunxi_ccu_probe or its wrappers) must be invoked

Likely impact: The vulnerability is triggered during device probe when a sunxi CCU clock device has a hw_clks->hws[] array containing entries that are not ccu_common structs. The buggy code causes immediate dereference of an invalid pointer constructed via container_of on the wrong type.

Verdict: not a real vulnerability based on the supplied code.

Why this might look suspicious
- In the second loop, the code does:
  ```c
  struct clk_hw *hw = desc->hw_clks->hws[i];
  struct ccu_common *common = hw_to_ccu_common(hw);
  ...
  if (!hw)
      continue;
  ```
- `hw_to_ccu_common()` is:
  ```c
  static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
  {
      return container_of(hw, struct ccu_common, hw);
  }
  ```
- So `common` is derived from `hw` before the explicit `NULL` check.

Observed evidence
1. `common` is computed before checking `hw != NULL`.
2. If `hw == NULL`, the loop immediately `continue`s.
3. `common` is only used later, after the `if (!hw) continue;` guard:
   ```c
   if (common->max_rate)
       clk_hw_set_rate_range(hw, common->min_rate, common->max_rate);
   else
       WARN(common->min_rate, ...);
   ```
4. Therefore, on the `hw == NULL` path, `common` is never dereferenced or passed anywhere.

Security analysis
- Data/control flow:
  - `hw` comes from `desc->hw_clks->hws[i]`.
  - If `hw` is null, execution skips the rest of that iteration.
  - No read or write through `common` occurs on that path.
- The only potentially questionable behavior is forming `common` from a null `hw` via `container_of`.
- In kernel code, this pattern can be sloppy, but from the supplied code alone it does not produce a reachable invalid memory access, corruption, or info leak, because the invalid pointer is never used when `hw` is null.

Why this is not a practical vulnerability
- There is no attacker-controlled dereference here.
- No out-of-bounds access, use-after-free, double free, or unchecked copy is shown.
- The structures involved (`desc`, `hw_clks`, clock descriptors) are driver-internal registration data, not direct untrusted user input.
- Error handling unregisters only successfully registered clocks via the loop index rollback pattern.

Residual note
- This line ordering is arguably fragile or stylistically poor:
  ```c
  struct ccu_common *common = hw_to_ccu_common(hw);
  if (!hw)
      continue;
  ```
  It would be cleaner to move `hw_to_ccu_common(hw)` after the null check.
- But based strictly on the shown code, this is not an exploitable bug.

Conclusion
- No real vulnerability is demonstrated in `sunxi_ccu_probe` from the provided context.
- At most, this is a minor robustness/readability issue, not a security flaw.

Initializes CCU clock objects, registers each clk_hw from desc->hw_clks, sets optional rate ranges using the enclosing ccu_common, adds an OF clock provider, and registers a reset controller with cleanup on failure.

No concrete security flaw is shown in the supplied code. The only suspicious pattern is computing `common = hw_to_ccu_common(hw)` before checking `if (!hw) continue;`, but `common` is never dereferenced on the `hw == NULL` path.

Not realistically exploitable from the supplied code. The data being processed (`desc`, `hw_clks`, `ccu_clks`) are driver/platform descriptor structures, not direct attacker-controlled input, and the potentially invalid `common` value derived from a NULL `hw` is not used before the loop continues.

An invalid or malformed internal clock descriptor would need to be supplied to this driver.

Even if `desc->hw_clks->hws[i]` is NULL, execution immediately continues before any dereference of `common` occurs.

No demonstrated memory corruption, NULL dereference, information disclosure, privilege escalation, or other security impact based on the provided code and context.

Control flow prevents use of `common` when `hw` is NULL. `hw_to_ccu_common(hw)` is evaluated before the NULL check, but the resulting pointer is only used after `if (!hw) continue;`. The cleanup path unregisters only clocks from previously attempted indices, skipping NULL entries. No attacker-influenced copy, bounds violation, or unsafe dereference is evidenced in the shown region.

`hw` is checked with `if (!hw) continue;` before any use of `name`, registration, or `common->min_rate`/`common->max_rate`.

`common` is only dereferenced after successful passage through the NULL-`hw` guard.

On registration failure, the function unwinds using the current index and skips NULL `hw` entries during unregister.

The reset controller and clock provider are only registered after clock registration succeeds.

In the second loop, if `desc->hw_clks->hws[i]` is NULL, execution hits `continue` immediately. `common` is not dereferenced on that path.

`common->max_rate` and `common->min_rate` are accessed only after successful registration attempt setup for non-NULL `hw`.

If `clk_hw_register`/`of_clk_hw_register` or later setup fails, the code unregisters prior entries in reverse order and skips NULL `hw` slots.

The ordering of `hw_to_ccu_common(hw)` before the NULL check is fragile and should ideally be moved below the guard for clarity and robustness.

The supplied context does not prove that every `desc->hw_clks->hws[i]` actually points to a `struct clk_hw` embedded in `struct ccu_common`; however, that is a descriptor construction invariant outside this function, and no violation is evidenced here.


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/bus.c
Function: typec_altmode_attention

Call path: tcpm_handle_vdm_request (drivers/usb/typec/tcpm/tcpm.c) → typec_altmode_attention (drivers/usb/typec/bus.c) → partner->adev.ops->attention (drivers/usb/typec/bus.c)

### Primary Function

```c
int typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
{
	struct altmode *partner = to_altmode(adev)->partner;
	struct typec_altmode *pdev;

	if (!partner)
		return -ENODEV;

	pdev = &partner->adev;

	if (pdev->ops && pdev->ops->attention)
		pdev->ops->attention(pdev, vdo);

	return 0;
}
```

### Cross-File Context

[altmode — struct — drivers/usb/typec/bus.h]
```c
struct altmode {
	unsigned int			id;
	struct typec_altmode		adev;
	struct typec_mux		*mux;

	enum typec_port_data		roles;

	struct attribute		*attrs[5];
	char				group_name[8];
	struct attribute_group		group;
	const struct attribute_group	*groups[2];

	struct altmode			*partner;
	struct altmode			*plug[2];
};
```

[to_altmode — macro — drivers/usb/typec/bus.h]
to_altmode → #define to_altmode(d) container_of(d, struct altmode, adev)  (drivers/usb/typec/bus.h)

[typec_altmode — struct — include/linux/usb/typec_altmode.h]
```c
struct typec_altmode {
	struct device			dev;
	u16				svid;
	int				mode;
	u32				vdo;
	unsigned int			active:1;

	char				*desc;
	const struct typec_altmode_ops	*ops;
};
```

[typec_altmode_ops — struct — include/linux/usb/typec_altmode.h]
```c
struct typec_altmode_ops {
	int (*enter)(struct typec_altmode *altmode, u32 *vdo);
	int (*exit)(struct typec_altmode *altmode);
	void (*attention)(struct typec_altmode *altmode, u32 vdo);
	int (*vdm)(struct typec_altmode *altmode, const u32 hdr,
		   const u32 *vdo, int cnt);
	int (*notify)(struct typec_altmode *altmode, unsigned long conf,
		      void *data);
	int (*activate)(struct typec_altmode *altmode, int activate);
};
```

Control flow: The function receives adev and vdo, extracts the partner altmode via container_of(adev)->partner, checks if partner is NULL and returns -ENODEV if so, otherwise derives pdev from partner->adev, checks that pdev->ops and pdev->ops->attention are non-NULL before invoking the callback, and returns 0 on success. The only error path is the early return with -ENODEV when partner is NULL.

Data flow: Input adev is transformed via to_altmode(adev) to get the enclosing struct altmode, then partner is extracted from its partner field. After the NULL check passes, pdev is derived as &partner->adev. The vdo parameter is passed through unchanged to the attention callback if it exists. The function returns -ENODEV on NULL partner or 0 on success.

Verdict: Not Vulnerable

Reviewed region:
[drivers/usb/typec/bus.c]
```c
int typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
{
	struct altmode *partner = to_altmode(adev)->partner;
	struct typec_altmode *pdev;

	if (!partner)
		return -ENODEV;

	pdev = &partner->adev;

	if (pdev->ops && pdev->ops->attention)
		pdev->ops->attention(pdev, vdo);

	return 0;
}
```

Why it is not vulnerable: The code explicitly checks `if (!partner) return -ENODEV;` before any dereference of the partner pointer. This NULL guard ensures that `pdev = &partner->adev;` is only reached when partner is valid. Additionally, the callback invocation is guarded by `if (pdev->ops && pdev->ops->attention)` preventing dereference of a NULL function pointer.

Security invariants:
- partner must be non-NULL before dereferencing partner->adev — enforced by `if (!partner) return -ENODEV;`
- pdev->ops must be non-NULL before accessing pdev->ops->attention — enforced by `if (pdev->ops && pdev->ops->attention)`
- pdev->ops->attention must be non-NULL before invoking the callback — enforced by the same compound guard `if (pdev->ops && pdev->ops->attention)`

Invariant verification:
- NULL pointer check on partner before dereference: holds=true. Evidence: The statement `if (!partner) return -ENODEV;` immediately follows `struct altmode *partner = to_altmode(adev)->partner;` and precedes `pdev = &partner->adev;`, ensuring partner is validated before access.
- Callback function pointer validation before invocation: holds=true. Evidence: The condition `if (pdev->ops && pdev->ops->attention)` guards the call to `pdev->ops->attention(pdev, vdo);`, verifying both ops and the attention function pointer are non-NULL.

Residual risk notes: The validity of the adev input parameter and the correctness of container_of(adev) are assumed — a corrupted or freed adev pointer would result in undefined behavior before the partner NULL check. Additionally, the attention callback is invoked without holding any visible lock in this function; the caller (tcpm_handle_vdm_request) is responsible for appropriate synchronization. These are contextual concerns rather than vulnerabilities in this function itself.

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

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

### What the function does
```c
int typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
{
	struct altmode *partner = to_altmode(adev)->partner;
	struct typec_altmode *pdev;

	if (!partner)
		return -ENODEV;

	pdev = &partner->adev;

	if (pdev->ops && pdev->ops->attention)
		pdev->ops->attention(pdev, vdo);

	return 0;
}
```

It:

1. Converts the supplied `struct typec_altmode *adev` back to its containing `struct altmode`.
2. Reads its `partner` pointer.
3. If no partner exists, returns `-ENODEV`.
4. Otherwise gets the partner’s embedded `typec_altmode` object.
5. If the partner has an `ops` table and an `attention` handler, calls it with `vdo`.

### Security-relevant data flow
- `vdo` is attacker-influenced in the sense that it may originate from a USB-PD VDM message (`tcpm_handle_vdm_request`).
- In this function, `vdo` is **not parsed, used as a pointer, used as a size, or used in memory indexing**.
- It is simply forwarded to a callback:
  ```c
  pdev->ops->attention(pdev, vdo);
  ```
So any risk from malformed `vdo` would have to be in the downstream `attention` implementation, not here.

### Guards present
The code includes the relevant null checks for the immediate dereferences:
- `partner` is checked before using `&partner->adev`
- `pdev->ops` is checked before dereferencing `pdev->ops->attention`
- `pdev->ops->attention` is checked before calling it

So the obvious null-pointer issues are already handled.

### Important invariants from the supplied context
The `to_altmode(adev)` cast relies on this invariant:
```c
#define to_altmode(d) container_of(d, struct altmode, adev)
```
This is only safe if `adev` really points to the `adev` member of a valid `struct altmode`.

From the context given, that appears to be the subsystem’s intended internal representation:
```c
struct altmode {
	...
	struct typec_altmode adev;
	...
	struct altmode *partner;
	...
};
```

Since this function is reached from internal Type-C/PD handling (`tcpm_handle_vdm_request`), the supplied code supports the conclusion that `adev` is expected to be such an embedded object, not arbitrary attacker-controlled memory.

### Why this is not a demonstrated vulnerability
There is no evidence here of:
- out-of-bounds access
- unsafe copy
- integer overflow
- null dereference along the shown path
- direct attacker-controlled function-pointer overwrite or misuse
- privilege boundary violation in this function itself

The function is essentially a guarded dispatcher.

### Residual uncertainty
The only plausible concern would be **concurrency/lifetime safety** around `partner` and `pdev->ops`—for example, if another thread could unregister or free the partner altmode while this function runs. But the supplied snippet does **not** show:
- removal paths
- locking rules
- reference counting
- whether `partner` can change concurrently

Without that broader context, it would be speculative to call this a use-after-free or race vulnerability.

### Conclusion
Based strictly on the provided code and context, `typec_altmode_attention()` does **not** present a real vulnerability. It performs appropriate null checks and only forwards the received `vdo` to a registered callback. Any actual security issue would have to lie in:
- the lifecycle/locking of `partner` elsewhere, or
- the implementation of `pdev->ops->attention`, not in this function as shown.

No concrete vulnerability is demonstrated in the supplied code. The function is a small dispatcher that checks for a partner altmode and an installed attention callback before invoking it.

No direct exploitability shown from this function alone. The input vdo may originate from USB-PD traffic, but in this function it is only forwarded to a callback and is not used as a pointer, length, index, or allocation size.

An internal caller must pass a valid struct typec_altmode * that is actually embedded in struct altmode, as required by to_altmode().

A partner altmode must exist for the callback path to be taken.

A downstream pdev->ops->attention implementation would need to contain its own bug for malformed vdo to have security impact.

No security impact established in this function itself. Any actual impact would depend on external lifetime/concurrency bugs or on the implementation of the downstream attention callback, neither of which is shown here.

The function performs the immediate safety checks relevant to its own dereferences: it verifies partner is non-NULL before using it, and verifies both pdev->ops and pdev->ops->attention before invoking the callback. The attacker-influenced value vdo is not interpreted in a dangerous way here; it is merely passed through. There is no shown out-of-bounds access, unsafe copy, integer overflow, or unchecked function-pointer dereference in this region.

adev must point to the adev member of a valid struct altmode object, because to_altmode(adev) uses container_of.

partner may be NULL, and the function correctly returns -ENODEV in that case.

The callback invocation only occurs when both pdev->ops and pdev->ops->attention are non-NULL.

Supported by the provided definitions: struct altmode contains struct typec_altmode adev, and to_altmode is container_of(d, struct altmode, adev). The call path is internal kernel Type-C/PD code, not a raw user-controlled API in the supplied context.

Explicit checks are present for partner, pdev->ops, and pdev->ops->attention before use.

vdo is forwarded unchanged to the callback and is not consumed locally in a memory-unsafe operation.

There is residual uncertainty about concurrency and object lifetime: if partner or its ops table could be freed or changed concurrently, a race/UAF might exist elsewhere. The supplied code does not include locking, refcounting, or teardown paths, so that cannot be established here.

Safety of processing the attention VDO depends on the downstream attention callback implementation, which is outside the reviewed region.


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

Repository: github.com/the-tcpdump-group/tcpdump
Language: C
File: print-pim.c
Function: pimv2_print

Call path: main (tcpdump.c) → pim_print (print-pim.c) → pimv2_print (print-pim.c)

### Primary Function

```c
static void
pimv2_print(netdissect_options *ndo,
            register const u_char *bp, register u_int len, const u_char *bp2)
{
	register const u_char *ep;
	register const struct pim *pim = (const struct pim *)bp;
	int advance;
	enum checksum_status cksum_status;

	ep = (const u_char *)ndo->ndo_snapend;
	if (bp >= ep)
		return;
	if (ep > bp + len)
		ep = bp + len;
	ND_TCHECK(pim->pim_rsv);
	pimv2_addr_len = pim->pim_rsv;
	if (pimv2_addr_len != 0)
		ND_PRINT((ndo, ", RFC2117-encoding"));

	ND_PRINT((ndo, ", cksum 0x%04x ", EXTRACT_16BITS(&pim->pim_cksum)));
	if (EXTRACT_16BITS(&pim->pim_cksum) == 0) {
		ND_PRINT((ndo, "(unverified)"));
	} else {
		if (PIM_TYPE(pim->pim_typever) == PIMV2_TYPE_REGISTER) {
			/*
			 * The checksum only covers the packet header,
			 * not the encapsulated packet.
			 */
			cksum_status = pimv2_check_checksum(ndo, bp, bp2, 8);
			if (cksum_status == INCORRECT) {
				/*
				 * To quote RFC 4601, "For interoperability
				 * reasons, a message carrying a checksum
				 * calculated over the entire PIM Register
				 * message should also be accepted."
				 */
				cksum_status = pimv2_check_checksum(ndo, bp, bp2, len);
			}
		} else {
			/*
			 * The checksum covers the entire packet.
			 */
			cksum_status = pimv2_check_checksum(ndo, bp, bp2, len);
		}
		switch (cksum_status) {

		case CORRECT:
			ND_PRINT((ndo, "(correct)"));
			break;

		case INCORRECT:
			ND_PRINT((ndo, "(incorrect)"));
			break;

		case UNVERIFIED:
			ND_PRINT((ndo, "(unverified)"));
			break;
		}
	}

	switch (PIM_TYPE(pim->pim_typever)) {
	case PIMV2_TYPE_HELLO:
	    {
		uint16_t otype, olen;
		bp += 4;
		while (bp < ep) {
			ND_TCHECK2(bp[0], 4);
			otype = EXTRACT_16BITS(&bp[0]);
			olen = EXTRACT_16BITS(&bp[2]);
			ND_TCHECK2(bp[0], 4 + olen);
			ND_PRINT((ndo, "\n\t  %s Option (%u), length %u, Value: ",
			          tok2str(pimv2_hello_option_values, "Unknown", otype),
			          otype,
			          olen));
			bp += 4;

			switch (otype) {
			case PIMV2_HELLO_OPTION_HOLDTIME:
				if (olen != 2) {
					ND_PRINT((ndo, "ERROR: Option Length != 2 Bytes (%u)", olen));
				} else {
					unsigned_relts_print(ndo, EXTRACT_16BITS(bp));
				}
				break;

			case PIMV2_HELLO_OPTION_LANPRUNEDELAY:
				if (olen != 4) {
					ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen));
				} else {
					char t_bit;
					uint16_t lan_delay, override_interval;
					lan_delay = EXTRACT_16BITS(bp);
					override_interval = EXTRACT_16BITS(bp+2);
					t_bit = (lan_delay & 0x8000)? 1 : 0;
					lan_delay &= ~0x8000;
					ND_PRINT((ndo, "\n\t    T-bit=%d, LAN delay %dms, Override interval %dms",
					t_bit, lan_delay, override_interval));
				}
				break;

			case PIMV2_HELLO_OPTION_DR_PRIORITY_OLD:
			case PIMV2_HELLO_OPTION_DR_PRIORITY:
				switch (olen) {
				case 0:
					ND_PRINT((ndo, "Bi-Directional Capability (Old)"));
					break;
				case 4:
					ND_PRINT((ndo, "%u", EXTRACT_32BITS(bp)));
					break;
				default:
					ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen));
					break;
				}
				break;

			case PIMV2_HELLO_OPTION_GENID:
				if (olen != 4) {
					ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen));
				} else {
					ND_PRINT((ndo, "0x%08x", EXTRACT_32BITS(bp)));
				}
				break;

			case PIMV2_HELLO_OPTION_REFRESH_CAP:
				if (olen != 4) {
					ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen));
				} else {
					ND_PRINT((ndo, "v%d", *bp));
					if (*(bp+1) != 0) {
						ND_PRINT((ndo, ", interval "));
						unsigned_relts_print(ndo, *(bp+1));
					}
					if (EXTRACT_16BITS(bp+2) != 0) {
						ND_PRINT((ndo, " ?0x%04x?", EXTRACT_16BITS(bp+2)));
					}
				}
				break;

			case  PIMV2_HELLO_OPTION_BIDIR_CAP:
				break;

			case PIMV2_HELLO_OPTION_ADDRESS_LIST_OLD:
			case PIMV2_HELLO_OPTION_ADDRESS_LIST:
				if (ndo->ndo_vflag > 1) {
					const u_char *ptr = bp;
					while (ptr < (bp+olen)) {
						ND_PRINT((ndo, "\n\t    "));
						advance = pimv2_addr_print(ndo, ptr, pimv2_unicast, 0);
						if (advance < 0) {
							ND_PRINT((ndo, "..."));
							break;
						}
						ptr += advance;
					}
				}
				break;
			default:
				if (ndo->ndo_vflag <= 1)
					print_unknown_data(ndo, bp, "\n\t    ", olen);
				break;
			}
			/* do we want to see an additionally hexdump ? */
			if (ndo->ndo_vflag> 1)
				print_unknown_data(ndo, bp, "\n\t    ", olen);
			bp += olen;
		}
		break;
	    }

	case PIMV2_TYPE_REGISTER:
	{
		const struct ip *ip;

		ND_TCHECK2(*(bp + 4), PIMV2_REGISTER_FLAG_LEN);

		ND_PRINT((ndo, ", Flags [ %s ]\n\t",
		          tok2str(pimv2_register_flag_values,
		          "none",
		          EXTRACT_32BITS(bp+4))));

		bp += 8; len -= 8;
		/* encapsulated multicast packet */
		ip = (const struct ip *)bp;
		switch (IP_V(ip)) {
                case 0: /* Null header */
			ND_PRINT((ndo, "IP-Null-header %s > %s",
			          ipaddr_string(ndo, &ip->ip_src),
			          ipaddr_string(ndo, &ip->ip_dst)));
			break;

		case 4:	/* IPv4 */
			ip_print(ndo, bp, len);
			break;

		case 6:	/* IPv6 */
			ip6_print(ndo, bp, len);
			break;

		default:
			ND_PRINT((ndo, "IP ver %d", IP_V(ip)));
			break;
		}
		break;
	}

	case PIMV2_TYPE_REGISTER_STOP:
		bp += 4; len -= 4;
		if (bp >= ep)
			break;
		ND_PRINT((ndo, " group="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance; len -= advance;
		if (bp >= ep)
			break;
		ND_PRINT((ndo, " source="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance; len -= advance;
		break;

	case PIMV2_TYPE_JOIN_PRUNE:
	case PIMV2_TYPE_GRAFT:
	case PIMV2_TYPE_GRAFT_ACK:

        /*
         * 0                   1                   2                   3
         *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |PIM Ver| Type  | Addr length   |           Checksum            |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |             Unicast-Upstream Neighbor Address                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |  Reserved     | Num groups    |          Holdtime             |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |            Encoded-Multicast Group Address-1                  |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |   Number of Joined  Sources   |   Number of Pruned Sources    |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |               Encoded-Joined Source Address-1                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |                             .                                 |
         *  |                             .                                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |               Encoded-Joined Source Address-n                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |               Encoded-Pruned Source Address-1                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |                             .                                 |
         *  |                             .                                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |               Encoded-Pruned Source Address-n                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |                           .                                   |
         *  |                           .                                   |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |                Encoded-Multicast Group Address-n              |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         */

	    {
		uint8_t ngroup;
		uint16_t holdtime;
		uint16_t njoin;
		uint16_t nprune;
		int i, j;

		bp += 4; len -= 4;
		if (PIM_TYPE(pim->pim_typever) != 7) {	/*not for Graft-ACK*/
			if (bp >= ep)
				break;
			ND_PRINT((ndo, ", upstream-neighbor: "));
			if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
				ND_PRINT((ndo, "..."));
				break;
			}
			bp += advance; len -= advance;
		}
		if (bp + 4 > ep)
			break;
		ngroup = bp[1];
		holdtime = EXTRACT_16BITS(&bp[2]);
		ND_PRINT((ndo, "\n\t  %u group(s)", ngroup));
		if (PIM_TYPE(pim->pim_typever) != 7) {	/*not for Graft-ACK*/
			ND_PRINT((ndo, ", holdtime: "));
			if (holdtime == 0xffff)
				ND_PRINT((ndo, "infinite"));
			else
				unsigned_relts_print(ndo, holdtime);
		}
		bp += 4; len -= 4;
		for (i = 0; i < ngroup; i++) {
			if (bp >= ep)
				goto jp_done;
			ND_PRINT((ndo, "\n\t    group #%u: ", i+1));
			if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0)) < 0) {
				ND_PRINT((ndo, "...)"));
				goto jp_done;
			}
			bp += advance; len -= advance;
			if (bp + 4 > ep) {
				ND_PRINT((ndo, "...)"));
				goto jp_done;
			}
			njoin = EXTRACT_16BITS(&bp[0]);
			nprune = EXTRACT_16BITS(&bp[2]);
			ND_PRINT((ndo, ", joined sources: %u, pruned sources: %u", njoin, nprune));
			bp += 4; len -= 4;
			for (j = 0; j < njoin; j++) {
				ND_PRINT((ndo, "\n\t      joined source #%u: ", j+1));
				if ((advance = pimv2_addr_print(ndo, bp, pimv2_source, 0)) < 0) {
					ND_PRINT((ndo, "...)"));
					goto jp_done;
				}
				bp += advance; len -= advance;
			}
			for (j = 0; j < nprune; j++) {
				ND_PRINT((ndo, "\n\t      pruned source #%u: ", j+1));
				if ((advance = pimv2_addr_print(ndo, bp, pimv2_source, 0)) < 0) {
					ND_PRINT((ndo, "...)"));
					goto jp_done;
				}
				bp += advance; len -= advance;
			}
		}
	jp_done:
		break;
	    }

	case PIMV2_TYPE_BOOTSTRAP:
	{
		int i, j, frpcnt;
		bp += 4;

		/* Fragment Tag, Hash Mask len, and BSR-priority */
		if (bp + sizeof(uint16_t) >= ep) break;
		ND_PRINT((ndo, " tag=%x", EXTRACT_16BITS(bp)));
		bp += sizeof(uint16_t);
		if (bp >= ep) break;
		ND_PRINT((ndo, " hashmlen=%d", bp[0]));
		if (bp + 1 >= ep) break;
		ND_PRINT((ndo, " BSRprio=%d", bp[1]));
		bp += 2;

		/* Encoded-Unicast-BSR-Address */
		if (bp >= ep) break;
		ND_PRINT((ndo, " BSR="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;

		for (i = 0; bp < ep; i++) {
			/* Encoded-Group Address */
			ND_PRINT((ndo, " (group%d: ", i));
			if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0))
			    < 0) {
				ND_PRINT((ndo, "...)"));
				goto bs_done;
			}
			bp += advance;

			/* RP-Count, Frag RP-Cnt, and rsvd */
			if (bp >= ep) {
				ND_PRINT((ndo, "...)"));
				goto bs_done;
			}
			ND_PRINT((ndo, " RPcnt=%d", bp[0]));
			if (bp + 1 >= ep) {
				ND_PRINT((ndo, "...)"));
				goto bs_done;
			}
			ND_PRINT((ndo, " FRPcnt=%d", frpcnt = bp[1]));
			bp += 4;

			for (j = 0; j < frpcnt && bp < ep; j++) {
				/* each RP info */
				ND_PRINT((ndo, " RP%d=", j));
				if ((advance = pimv2_addr_print(ndo, bp,
								pimv2_unicast,
								0)) < 0) {
					ND_PRINT((ndo, "...)"));
					goto bs_done;
				}
				bp += advance;

				if (bp + 1 >= ep) {
					ND_PRINT((ndo, "...)"));
					goto bs_done;
				}
				ND_PRINT((ndo, ",holdtime="));
				unsigned_relts_print(ndo, EXTRACT_16BITS(bp));
				if (bp + 2 >= ep) {
					ND_PRINT((ndo, "...)"));
					goto bs_done;
				}
				ND_PRINT((ndo, ",prio=%d", bp[2]));
				bp += 4;
			}
			ND_PRINT((ndo, ")"));
		}
	   bs_done:
		break;
	}
	case PIMV2_TYPE_ASSERT:
		bp += 4; len -= 4;
		if (bp >= ep)
			break;
		ND_PRINT((ndo, " group="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance; len -= advance;
		if (bp >= ep)
			break;
		ND_PRINT((ndo, " src="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance; len -= advance;
		if (bp + 8 > ep)
			break;
		if (bp[0] & 0x80)
			ND_PRINT((ndo, " RPT"));
		ND_PRINT((ndo, " pref=%u", EXTRACT_32BITS(&bp[0]) & 0x7fffffff));
		ND_PRINT((ndo, " metric=%u", EXTRACT_32BITS(&bp[4])));
		break;

	case PIMV2_TYPE_CANDIDATE_RP:
	{
		int i, pfxcnt;
		bp += 4;

		/* Prefix-Cnt, Priority, and Holdtime */
		if (bp >= ep) break;
		ND_PRINT((ndo, " prefix-cnt=%d", bp[0]));
		pfxcnt = bp[0];
		if (bp + 1 >= ep) break;
		ND_PRINT((ndo, " prio=%d", bp[1]));
		if (bp + 3 >= ep) break;
		ND_PRINT((ndo, " holdtime="));
		unsigned_relts_print(ndo, EXTRACT_16BITS(&bp[2]));
		bp += 4;

		/* Encoded-Unicast-RP-Address */
		if (bp >= ep) break;
		ND_PRINT((ndo, " RP="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;

		/* Encoded-Group Addresses */
		for (i = 0; i < pfxcnt && bp < ep; i++) {
			ND_PRINT((ndo, " Group%d=", i));
			if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0))
			    < 0) {
				ND_PRINT((ndo, "..."));
				break;
			}
			bp += advance;
		}
		break;
	}

	case PIMV2_TYPE_PRUNE_REFRESH:
		ND_PRINT((ndo, " src="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;
		ND_PRINT((ndo, " grp="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;
		ND_PRINT((ndo, " forwarder="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;
		ND_TCHECK2(bp[0], 2);
		ND_PRINT((ndo, " TUNR "));
		unsigned_relts_print(ndo, EXTRACT_16BITS(bp));
		break;

	 default:
		ND_PRINT((ndo, " [type %d]", PIM_TYPE(pim->pim_typever)));
		break;
	}

	return;

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

### Cross-File Context

[PIMV2_HELLO_OPTION_HOLDTIME — constant — print-pim.c:88]
PIMV2_HELLO_OPTION_HOLDTIME → 1  (print-pim.c:88)

[PIMV2_HELLO_OPTION_GENID — constant — print-pim.c:92]
PIMV2_HELLO_OPTION_GENID → 20  (print-pim.c:92)

[PIMV2_HELLO_OPTION_REFRESH_CAP — constant — print-pim.c:93]
PIMV2_HELLO_OPTION_REFRESH_CAP → 21  (print-pim.c:93)

[PIM_TYPE — macro — print-pim.c:135]
PIM_TYPE → #define PIM_TYPE(x) ((x) & 0x0f)  (print-pim.c:135)

[pimv2_check_checksum — function — print-pim.c:625-653]
```c
static enum checksum_status
pimv2_check_checksum(netdissect_options *ndo, const u_char *bp,
		     const u_char *bp2, u_int len)
{
	const struct ip *ip;
	u_int cksum;

	if (!ND_TTEST2(bp[0], len)) {
		return (UNVERIFIED);
	}
	ip = (const struct ip *)bp2;
	if (IP_V(ip) == 4) {
		struct cksum_vec vec[1];

		vec[0].ptr = bp;
		vec[0].len = len;
		cksum = in_cksum(vec, 1);
		return (cksum ? INCORRECT : CORRECT);
	} else if (IP_V(ip) == 6) {
		const struct ip6_hdr *ip6;

		ip6 = (const struct ip6_hdr *)bp2;
		cksum = nextproto6_cksum(ndo, ip6, bp, len, len, IPPROTO_PIM);
		return (cksum ? INCORRECT : CORRECT);
	} else {
		return (UNVERIFIED);
	}
}
```

[pimv2_addr_print — function — print-pim.c:525-607]
```c
static int
pimv2_addr_print(netdissect_options *ndo,
                 const u_char *bp, enum pimv2_addrtype at, int silent)
{
	int af;
	int len, hdrlen;

	ND_TCHECK(bp[0]);

	if (pimv2_addr_len == 0) {
		ND_TCHECK(bp[1]);
		switch (bp[0]) {
		case 1:
			af = AF_INET;
			len = sizeof(struct in_addr);
			break;
		case 2:
			af = AF_INET6;
			len = sizeof(struct in6_addr);
			break;
		default:
			return -1;
		}
		if (bp[1] != 0)
			return -1;
		hdrlen = 2;
	} else {
		switch (pimv2_addr_len) {
		case sizeof(struct in_addr):
			af = AF_INET;
			break;
		case sizeof(struct in6_addr):
			af = AF_INET6;
			break;
		default:
			return -1;
			break;
		}
		len = pimv2_addr_len;
		hdrlen = 0;
	}

	bp += hdrlen;
	switch (at) {
	case pimv2_unicast:
		ND_TCHECK2(bp[0], len);
		if (af == AF_INET) {
			if (!silent)
				ND_PRINT((ndo, "%s", ipaddr_string(ndo, bp)));
		}
		else if (af == AF_INET6) {
			if (!silent)
				ND_PRINT((ndo, "%s", ip6addr_string(ndo, bp)));
		}
		return len;

	case pimv2_group:
		ND_TCHECK2(bp[0], len);
		if (af == AF_INET) {
			if (!silent)
				ND_PRINT((ndo, "%s", ipaddr_string(ndo, bp)));
		}
		else if (af == AF_INET6) {
			if (!silent)
				ND_PRINT((ndo, "%s", ip6addr_string(ndo, bp)));
		}
		return len;

	case pimv2_source:
		ND_TCHECK2(bp[0], 2);
		if (bp[0] & 0x80)
			ND_PRINT((ndo, "(S,G) "));
		if (bp[0] & 0x40)
			ND_PRINT((ndo, "(W) "));
		if (bp[0] & 0x20)
			ND_PRINT((ndo, "(R) "));
		ND_TCHECK2(bp[0], len);
		if (af == AF_INET) {
			if (!silent)
				ND_PRINT((ndo, "%s", ipaddr_string(ndo, bp)));
		}
		else if (af == AF_INET6) {
			if (!silent)
				ND_PRINT((ndo, "%s", ip6addr_string(ndo, bp)));
		}
		return len;
	}
	return 0;
}
```

Control flow: pimv2_print enters and clamps the endpoint `ep` to the packet boundary. It checks for truncation early, then reads the PIM header fields. For checksums, it calls pimv2_check_checksum conditionally based on packet type. The main switch dispatches on PIM_TYPE(pim->pim_typever). In PIMV2_TYPE_HELLO, a while loop iterates over TLV options bounded by bp < ep, with ND_TCHECK2 at loop entry validating each option's header and value fit in the packet. Each option type case performs length validation before reading value data. The loop advances bp by the option length and continues. Other PIM message types (Register, Register-Stop, Join-Prune, Bootstrap, Assert, Candidate-RP, Prune-Refresh) parse fixed-format fields with pre-read bounds checks before each access. The function returns normally or jumps to the trunc label on insufficient data.

Data flow: Input pointer bp and length len define the PIM packet boundaries. The endpoint ep is derived from ndo_snapend and clamped to bp + len. The PIM header (rsv field, checksum, typever) is read after ND_TCHECK validation. For HELLO messages, otype and olen are extracted from the TLV header. olen is validated against expected sizes per option type (2 for HOLDTIME, 4 for LANPRUNEDELAY/GENID/REFRESH_CAP, 0 or 4 for DR_PRIORITY). The ND_TCHECK2(bp[0], 4 + olen) at loop entry ensures the entire option fits before any option-specific processing. For ADDRESS_LIST and unknown types, print_unknown_data or pimv2_addr_print consume olen bytes, protected by the earlier bounds check. For non-HELLO types, encoded addresses are parsed via pimv2_addr_print which internally validates address data availability via ND_TCHECK macros. Checksum verification reads bp and bp2 with ND_TTEST2 bounds validation.

Verdict: Not Vulnerable

Reviewed region:
[print-pim.c]
```c
static void
pimv2_print(netdissect_options *ndo,
            register const u_char *bp, register u_int len, const u_char *bp2)
{
	register const u_char *ep;
	register const struct pim *pim = (const struct pim *)bp;
	int advance;
	enum checksum_status cksum_status;

	ep = (const u_char *)ndo->ndo_snapend;
	if (bp >= ep)
		return;
	if (ep > bp + len)
		ep = bp + len;
	ND_TCHECK(pim->pim_rsv);
	pimv2_addr_len = pim->pim_rsv;
	if (pimv2_addr_len != 0)
		ND_PRINT((ndo, ", RFC2117-encoding"));

	ND_PRINT((ndo, ", cksum 0x%04x ", EXTRACT_16BITS(&pim->pim_cksum)));
	if (EXTRACT_16BITS(&pim->pim_cksum) == 0) {
		ND_PRINT((ndo, "(unverified)"));
	} else {
		if (PIM_TYPE(pim->pim_typever) == PIMV2_TYPE_REGISTER) {
			/*
			 * The checksum only covers the packet header,
			 * not the encapsulated packet.
			 */
			cksum_status = pimv2_check_checksum(ndo, bp, bp2, 8);
			if (cksum_status == INCORRECT) {
				/*
				 * To quote RFC 4601, "For interoperability
				 * reasons, a message carrying a checksum
				 * calculated over the entire PIM Register
				 * message should also be accepted."
				 */
				cksum_status = pimv2_check_checksum(ndo, bp, bp2, len);
			}
		} else {
			/*
			 * The checksum covers the entire packet.
			 */
			cksum_status = pimv2_check_checksum(ndo, bp, bp2, len);
		}
		switch (cksum_status) {

		case CORRECT:
			ND_PRINT((ndo, "(correct)"));
			break;

		case INCORRECT:
			ND_PRINT((ndo, "(incorrect)"));
			break;

		case UNVERIFIED:
			ND_PRINT((ndo, "(unverified)"));
			break;
		}
	}

	switch (PIM_TYPE(pim->pim_typever)) {
	case PIMV2_TYPE_HELLO:
	    {
		uint16_t otype, olen;
		bp += 4;
		while (bp < ep) {
			ND_TCHECK2(bp[0], 4);
			otype = EXTRACT_16BITS(&bp[0]);
			olen = EXTRACT_16BITS(&bp[2]);
			ND_TCHECK2(bp[0], 4 + olen);
			ND_PRINT((ndo, "\n\t  %s Option (%u), length %u, Value: ",
			          tok2str(pimv2_hello_option_values, "Unknown", otype),
			          otype,
			          olen));
			bp += 4;

			switch (otype) {
			case PIMV2_HELLO_OPTION_HOLDTIME:
				if (olen != 2) {
					ND_PRINT((ndo, "ERROR: Option Length != 2 Bytes (%u)", olen));
				} else {
					unsigned_relts_print(ndo, EXTRACT_16BITS(bp));
				}
				break;

			case PIMV2_HELLO_OPTION_LANPRUNEDELAY:
				if (olen != 4) {
					ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen));
				} else {
					char t_bit;
					uint16_t lan_delay, override_interval;
					lan_delay = EXTRACT_16BITS(bp);
					override_interval = EXTRACT_16BITS(bp+2);
					t_bit = (lan_delay & 0x8000)? 1 : 0;
					lan_delay &= ~0x8000;
					ND_PRINT((ndo, "\n\t    T-bit=%d, LAN delay %dms, Override interval %dms",
					t_bit, lan_delay, override_interval));
				}
				break;

			case PIMV2_HELLO_OPTION_DR_PRIORITY_OLD:
			case PIMV2_HELLO_OPTION_DR_PRIORITY:
				switch (olen) {
				case 0:
					ND_PRINT((ndo, "Bi-Directional Capability (Old)"));
					break;
				case 4:
					ND_PRINT((ndo, "%u", EXTRACT_32BITS(bp)));
					break;
				default:
					ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen));
					break;
				}
				break;

			case PIMV2_HELLO_OPTION_GENID:
				if (olen != 4) {
					ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen));
				} else {
					ND_PRINT((ndo, "0x%08x", EXTRACT_32BITS(bp)));
				}
				break;

			case PIMV2_HELLO_OPTION_REFRESH_CAP:
				if (olen != 4) {
					ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen));
				} else {
					ND_PRINT((ndo, "v%d", *bp));
					if (*(bp+1) != 0) {
						ND_PRINT((ndo, ", interval "));
						unsigned_relts_print(ndo, *(bp+1));
					}
					if (EXTRACT_16BITS(bp+2) != 0) {
						ND_PRINT((ndo, " ?0x%04x?", EXTRACT_16BITS(bp+2)));
					}
				}
				break;

			case  PIMV2_HELLO_OPTION_BIDIR_CAP:
				break;

			case PIMV2_HELLO_OPTION_ADDRESS_LIST_OLD:
			case PIMV2_HELLO_OPTION_ADDRESS_LIST:
				if (ndo->ndo_vflag > 1) {
					const u_char *ptr = bp;
					while (ptr < (bp+olen)) {
						ND_PRINT((ndo, "\n\t    "));
						advance = pimv2_addr_print(ndo, ptr, pimv2_unicast, 0);
						if (advance < 0) {
							ND_PRINT((ndo, "..."));
							break;
						}
						ptr += advance;
					}
				}
				break;
			default:
				if (ndo->ndo_vflag <= 1)
					print_unknown_data(ndo, bp, "\n\t    ", olen);
				break;
			}
			/* do we want to see an additionally hexdump ? */
			if (ndo->ndo_vflag> 1)
				print_unknown_data(ndo, bp, "\n\t    ", olen);
			bp += olen;
		}
		break;
	    }

	case PIMV2_TYPE_REGISTER:
	{
		const struct ip *ip;

		ND_TCHECK2(*(bp + 4), PIMV2_REGISTER_FLAG_LEN);

		ND_PRINT((ndo, ", Flags [ %s ]\n\t",
		          tok2str(pimv2_register_flag_values,
		          "none",
		          EXTRACT_32BITS(bp+4))));

		bp += 8; len -= 8;
		/* encapsulated multicast packet */
		ip = (const struct ip *)bp;
		switch (IP_V(ip)) {
                case 0: /* Null header */
			ND_PRINT((ndo, "IP-Null-header %s > %s",
			          ipaddr_string(ndo, &ip->ip_src),
			          ipaddr_string(ndo, &ip->ip_dst)));
			break;

		case 4:	/* IPv4 */
			ip_print(ndo, bp, len);
			break;

		case 6:	/* IPv6 */
			ip6_print(ndo, bp, len);
			break;

		default:
			ND_PRINT((ndo, "IP ver %d", IP_V(ip)));
			break;
		}
		break;
	}

	case PIMV2_TYPE_REGISTER_STOP:
		bp += 4; len -= 4;
		if (bp >= ep)
			break;
		ND_PRINT((ndo, " group="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance; len -= advance;
		if (bp >= ep)
			break;
		ND_PRINT((ndo, " source="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance; len -= advance;
		break;

	case PIMV2_TYPE_JOIN_PRUNE:
	case PIMV2_TYPE_GRAFT:
	case PIMV2_TYPE_GRAFT_ACK:

        /*
         * 0                   1                   2                   3
         *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |PIM Ver| Type  | Addr length   |           Checksum            |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |             Unicast-Upstream Neighbor Address                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |  Reserved     | Num groups    |          Holdtime             |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |            Encoded-Multicast Group Address-1                  |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |   Number of Joined  Sources   |   Number of Pruned Sources    |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |               Encoded-Joined Source Address-1                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |                             .                                 |
         *  |                             .                                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |               Encoded-Joined Source Address-n                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |               Encoded-Pruned Source Address-1                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |                             .                                 |
         *  |                             .                                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |               Encoded-Pruned Source Address-n                 |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |                           .                                   |
         *  |                           .                                   |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |                Encoded-Multicast Group Address-n              |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         */

	    {
		uint8_t ngroup;
		uint16_t holdtime;
		uint16_t njoin;
		uint16_t nprune;
		int i, j;

		bp += 4; len -= 4;
		if (PIM_TYPE(pim->pim_typever) != 7) {	/*not for Graft-ACK*/
			if (bp >= ep)
				break;
			ND_PRINT((ndo, ", upstream-neighbor: "));
			if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
				ND_PRINT((ndo, "..."));
				break;
			}
			bp += advance; len -= advance;
		}
		if (bp + 4 > ep)
			break;
		ngroup = bp[1];
		holdtime = EXTRACT_16BITS(&bp[2]);
		ND_PRINT((ndo, "\n\t  %u group(s)", ngroup));
		if (PIM_TYPE(pim->pim_typever) != 7) {	/*not for Graft-ACK*/
			ND_PRINT((ndo, ", holdtime: "));
			if (holdtime == 0xffff)
				ND_PRINT((ndo, "infinite"));
			else
				unsigned_relts_print(ndo, holdtime);
		}
		bp += 4; len -= 4;
		for (i = 0; i < ngroup; i++) {
			if (bp >= ep)
				goto jp_done;
			ND_PRINT((ndo, "\n\t    group #%u: ", i+1));
			if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0)) < 0) {
				ND_PRINT((ndo, "...)"));
				goto jp_done;
			}
			bp += advance; len -= advance;
			if (bp + 4 > ep) {
				ND_PRINT((ndo, "...)"));
				goto jp_done;
			}
			njoin = EXTRACT_16BITS(&bp[0]);
			nprune = EXTRACT_16BITS(&bp[2]);
			ND_PRINT((ndo, ", joined sources: %u, pruned sources: %u", njoin, nprune));
			bp += 4; len -= 4;
			for (j = 0; j < njoin; j++) {
				ND_PRINT((ndo, "\n\t      joined source #%u: ", j+1));
				if ((advance = pimv2_addr_print(ndo, bp, pimv2_source, 0)) < 0) {
					ND_PRINT((ndo, "...)"));
					goto jp_done;
				}
				bp += advance; len -= advance;
			}
			for (j = 0; j < nprune; j++) {
				ND_PRINT((ndo, "\n\t      pruned source #%u: ", j+1));
				if ((advance = pimv2_addr_print(ndo, bp, pimv2_source, 0)) < 0) {
					ND_PRINT((ndo, "...)"));
					goto jp_done;
				}
				bp += advance; len -= advance;
			}
		}
	jp_done:
		break;
	    }

	case PIMV2_TYPE_BOOTSTRAP:
	{
		int i, j, frpcnt;
		bp += 4;

		/* Fragment Tag, Hash Mask len, and BSR-priority */
		if (bp + sizeof(uint16_t) >= ep) break;
		ND_PRINT((ndo, " tag=%x", EXTRACT_16BITS(bp)));
		bp += sizeof(uint16_t);
		if (bp >= ep) break;
		ND_PRINT((ndo, " hashmlen=%d", bp[0]));
		if (bp + 1 >= ep) break;
		ND_PRINT((ndo, " BSRprio=%d", bp[1]));
		bp += 2;

		/* Encoded-Unicast-BSR-Address */
		if (bp >= ep) break;
		ND_PRINT((ndo, " BSR="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;

		for (i = 0; bp < ep; i++) {
			/* Encoded-Group Address */
			ND_PRINT((ndo, " (group%d: ", i));
			if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0))
			    < 0) {
				ND_PRINT((ndo, "...)"));
				goto bs_done;
			}
			bp += advance;

			/* RP-Count, Frag RP-Cnt, and rsvd */
			if (bp >= ep) {
				ND_PRINT((ndo, "...)"));
				goto bs_done;
			}
			ND_PRINT((ndo, " RPcnt=%d", bp[0]));
			if (bp + 1 >= ep) {
				ND_PRINT((ndo, "...)"));
				goto bs_done;
			}
			ND_PRINT((ndo, " FRPcnt=%d", frpcnt = bp[1]));
			bp += 4;

			for (j = 0; j < frpcnt && bp < ep; j++) {
				/* each RP info */
				ND_PRINT((ndo, " RP%d=", j));
				if ((advance = pimv2_addr_print(ndo, bp,
								pimv2_unicast,
								0)) < 0) {
					ND_PRINT((ndo, "...)"));
					goto bs_done;
				}
				bp += advance;

				if (bp + 1 >= ep) {
					ND_PRINT((ndo, "...)"));
					goto bs_done;
				}
				ND_PRINT((ndo, ",holdtime="));
				unsigned_relts_print(ndo, EXTRACT_16BITS(bp));
				if (bp + 2 >= ep) {
					ND_PRINT((ndo, "...)"));
					goto bs_done;
				}
				ND_PRINT((ndo, ",prio=%d", bp[2]));
				bp += 4;
			}
			ND_PRINT((ndo, ")"));
		}
	   bs_done:
		break;
	}
	case PIMV2_TYPE_ASSERT:
		bp += 4; len -= 4;
		if (bp >= ep)
			break;
		ND_PRINT((ndo, " group="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance; len -= advance;
		if (bp >= ep)
			break;
		ND_PRINT((ndo, " src="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance; len -= advance;
		if (bp + 8 > ep)
			break;
		if (bp[0] & 0x80)
			ND_PRINT((ndo, " RPT"));
		ND_PRINT((ndo, " pref=%u", EXTRACT_32BITS(&bp[0]) & 0x7fffffff));
		ND_PRINT((ndo, " metric=%u", EXTRACT_32BITS(&bp[4])));
		break;

	case PIMV2_TYPE_CANDIDATE_RP:
	{
		int i, pfxcnt;
		bp += 4;

		/* Prefix-Cnt, Priority, and Holdtime */
		if (bp >= ep) break;
		ND_PRINT((ndo, " prefix-cnt=%d", bp[0]));
		pfxcnt = bp[0];
		if (bp + 1 >= ep) break;
		ND_PRINT((ndo, " prio=%d", bp[1]));
		if (bp + 3 >= ep) break;
		ND_PRINT((ndo, " holdtime="));
		unsigned_relts_print(ndo, EXTRACT_16BITS(&bp[2]));
		bp += 4;

		/* Encoded-Unicast-RP-Address */
		if (bp >= ep) break;
		ND_PRINT((ndo, " RP="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;

		/* Encoded-Group Addresses */
		for (i = 0; i < pfxcnt && bp < ep; i++) {
			ND_PRINT((ndo, " Group%d=", i));
			if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0))
			    < 0) {
				ND_PRINT((ndo, "..."));
				break;
			}
			bp += advance;
		}
		break;
	}

	case PIMV2_TYPE_PRUNE_REFRESH:
		ND_PRINT((ndo, " src="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;
		ND_PRINT((ndo, " grp="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_group, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;
		ND_PRINT((ndo, " forwarder="));
		if ((advance = pimv2_addr_print(ndo, bp, pimv2_unicast, 0)) < 0) {
			ND_PRINT((ndo, "..."));
			break;
		}
		bp += advance;
		ND_TCHECK2(bp[0], 2);
		ND_PRINT((ndo, " TUNR "));
		unsigned_relts_print(ndo, EXTRACT_16BITS(bp));
		break;

	 default:
		ND_PRINT((ndo, " [type %d]", PIM_TYPE(pim->pim_typever)));
		break;
	}

	return;

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

Why it is not vulnerable: The TLV length validation is enforced at two levels: (1) explicit length checks within each option type case validate olen matches the expected size for that type, and (2) ND_TCHECK2(bp[0], 4 + olen) at the start of the while loop ensures the entire option (header + value) fits within the packet before any option-specific processing occurs. For options where explicit length validation is absent (ADDRESS_LIST, default unknown options), the ND_TCHECK2 check still prevents out-of-bounds reads because it validates 4 + olen bytes exist in the packet. Additionally, pimv2_addr_print performs its own internal ND_TCHECK validation before reading address bytes. All other PIM message types use explicit pre-read bounds checks (bp >= ep, bp + N > ep) before accessing fields.

Security invariants:
- TLV option length must not cause buffer over-read: enforced by ND_TCHECK2(bp[0], 4 + olen) at the start of the HELLO option parsing loop, which validates that the option header (4 bytes) plus value (olen bytes) all exist in the packet before proceeding
- TLV option length must match expected size for known types: enforced by explicit olen checks in each switch case - HOLDTIME requires olen == 2, LANPRUNEDELAY requires olen == 4, GENID requires olen == 4, REFRESH_CAP requires olen == 4, DR_PRIORITY accepts olen 0 or 4
- Address field reads must be bounds-checked: enforced by ND_TCHECK and ND_TCHECK2 macros within pimv2_addr_print, which validate the address header and body before reading
- All field accesses must be preceded by bounds validation: enforced by explicit checks like bp >= ep, bp + 4 > ep, bp + 8 > ep throughout the function for non-HELLO message types
- Checksum verification must validate data availability: enforced by ND_TTEST2(bp[0], len) in pimv2_check_checksum before computing checksum over the packet data
- Endpoint clamping must prevent reads past packet end: enforced by if (ep > bp + len) ep = bp + len near function entry

Invariant verification:
- TLV option total size (header + value) validated before processing: holds=true. Evidence: ND_TCHECK2(bp[0], 4 + olen) is called immediately after extracting otype and olen, before any option-specific code executes. This ensures the entire option fits in the packet.
- HOLDTIME option length validated to be exactly 2 bytes: holds=true. Evidence: case PIMV2_HELLO_OPTION_HOLDTIME: if (olen != 2) { ND_PRINT((ndo, "ERROR: Option Length != 2 Bytes (%u)", olen)); } else { unsigned_relts_print(ndo, EXTRACT_16BITS(bp)); }
- LANPRUNEDELAY option length validated to be exactly 4 bytes: holds=true. Evidence: case PIMV2_HELLO_OPTION_LANPRUNEDELAY: if (olen != 4) { ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen)); } else { ... reads bp and bp+2 }
- DR_PRIORITY option length validated to be 0 or 4 bytes: holds=true. Evidence: case PIMV2_HELLO_OPTION_DR_PRIORITY_OLD/DR_PRIORITY: switch (olen) { case 0: ... break; case 4: EXTRACT_32BITS(bp); break; default: ND_PRINT error }
- GENID option length validated to be exactly 4 bytes: holds=true. Evidence: case PIMV2_HELLO_OPTION_GENID: if (olen != 4) { ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen)); } else { ND_PRINT((ndo, "0x%08x", EXTRACT_32BITS(bp))); }
- REFRESH_CAP option length validated to be exactly 4 bytes: holds=true. Evidence: case PIMV2_HELLO_OPTION_REFRESH_CAP: if (olen != 4) { ND_PRINT((ndo, "ERROR: Option Length != 4 Bytes (%u)", olen)); } else { reads *bp, *(bp+1), EXTRACT_16BITS(bp+2) }
- Unknown/default option type data read bounded by packet length: holds=true. Evidence: The ND_TCHECK2(bp[0], 4 + olen) check at loop entry ensures olen bytes exist before print_unknown_data(ndo, bp, "...", olen) is called in the default case or after the switch
- ADDRESS_LIST option iteration bounded correctly: holds=true. Evidence: The while (ptr < (bp+olen)) loop is protected by the earlier ND_TCHECK2(bp[0], 4 + olen), and pimv2_addr_print performs internal ND_TCHECK validation. The break on advance < 0 prevents infinite loops.
- Non-HELLO message field accesses preceded by bounds checks: holds=true. Evidence: Register-Stop: bp >= ep checks before each pimv2_addr_print; Join-Prune: bp + 4 > ep before reading ngroup/holdtime; Bootstrap: bp + sizeof(uint16_t) >= ep and bp + 1 >= ep checks before reads; Assert: bp + 8 > ep before reading preference/metric

Residual risk notes: The BIDIR_CAP option (case PIMV2_HELLO_OPTION_BIDIR_CAP) has no length validation and simply breaks. However, the ND_TCHECK2 at loop entry already ensures the option data fits in the packet, so this is not exploitable. The print_unknown_data function is trusted to not exceed olen bytes, as it is only called after ND_TCHECK2 validates that length. The implicit assumption is that ND_TCHECK2 properly detects buffer truncation and jumps to the trunc label, which is standard tcpdump behavior.

Yes — this is a real vulnerability: an out-of-bounds read in `pimv2_print()` when parsing PIMv2 `REGISTER` messages.

## Observed evidence

In the `PIMV2_TYPE_REGISTER` branch:

```c
ND_TCHECK2(*(bp + 4), PIMV2_REGISTER_FLAG_LEN);

ND_PRINT((ndo, ", Flags [ %s ]\n\t",
          tok2str(pimv2_register_flag_values,
          "none",
          EXTRACT_32BITS(bp+4))));

bp += 8; len -= 8;
/* encapsulated multicast packet */
ip = (const struct ip *)bp;
switch (IP_V(ip)) {
```

### What is checked
- `ND_TCHECK2(*(bp + 4), PIMV2_REGISTER_FLAG_LEN);`
  ensures only that bytes `bp+4` through `bp+7` are present.
- Combined with the earlier access to the PIM header, this is enough to validate the 8-byte PIM Register header.

### What is **not** checked
After `bp += 8`, the code immediately treats `bp` as pointing to an encapsulated IP packet and evaluates:

```c
IP_V(ip)
```

There is no check that at least 1 byte of encapsulated payload exists.

## Reachable bad path

`ep` is set to the end of captured PIM data:

```c
ep = (const u_char *)ndo->ndo_snapend;
if (ep > bp + len)
    ep = bp + len;
```

So if the PIM packet length is exactly 8 bytes, or capture truncation leaves exactly 8 bytes available:

- the Register header is fully present,
- `bp += 8` makes `bp == ep`,
- then `IP_V(ip)` reads from `bp[0]` past the validated buffer.

That is a classic bounds-check omission.

## Why existing guards do not save it

- The checksum verification earlier is unrelated; it may validate the first 8 bytes only for Register packets.
- `ND_TCHECK2(*(bp + 4), ...)` does not cover the encapsulated IP header.
- Unlike many other branches in this function, there is no `if (bp >= ep) break;` or `ND_TCHECK(*bp)` before dereferencing the encapsulated packet.

## Impact

This is an **out-of-bounds read** from packet data during dissection.

Realistic impact in tcpdump:
- possible crash / denial of service on malformed or truncated traffic,
- sanitizer-detectable invalid read,
- likely limited information disclosure, since only a small field is read and printed indirectly.

## Classification

Most precise justified classification:

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

## Exploitability / preconditions

An attacker must be able to supply or cause tcpdump to process a crafted PIMv2 Register packet whose captured payload ends immediately after the 8-byte Register header. That is realistic for:
- live packet capture on attacker-influenced networks,
- malicious pcap files.

## Conclusion

This code contains a real vulnerability: the `REGISTER` parser dereferences the encapsulated IP header without first checking that any encapsulated byte is present. A minimal fix would be to check `bp < ep` or `ND_TCHECK(*bp)` before calling `IP_V(ip)`.

PIMv2 packet parsing, with emphasis on the PIMV2_TYPE_REGISTER branch and surrounding bounds checks

After validating only the 8-byte PIM Register header, the code advances bp by 8 and immediately reads the encapsulated packet's version via IP_V(ip) without checking that any byte of encapsulated payload remains within [bp, ep). If the packet ends exactly at the Register header or is truncated there, IP_V(ip) reads past the validated capture buffer.

An attacker who can supply crafted network traffic to tcpdump or a malicious pcap can trigger this by providing a PIMv2 Register message whose captured payload is exactly 8 bytes long, or truncated to 8 bytes. This can cause an invalid read during dissection and potentially crash the process.

tcpdump must parse attacker-controlled live traffic or a malicious capture file

The packet must reach pimv2_print via pim_print

The message type must be PIMV2_TYPE_REGISTER

The available/captured PIM payload must contain the Register header but no encapsulated packet byte after it

Denial of service via crash or invalid memory access during packet dissection

Small out-of-bounds read of packet-adjacent memory; limited information exposure is theoretically possible but not strongly evidenced from this code alone

ep is set to the minimum of ndo->ndo_snapend and bp + len, so accesses should remain within the captured PIM payload

Most branches use ND_TCHECK/ND_TCHECK2 or explicit bp >= ep checks before dereferencing packet data

pimv2_addr_print performs its own bounds checks before reading encoded addresses

Initial access to pim->pim_rsv is guarded by ND_TCHECK

Checksum verification uses pimv2_check_checksum, which refuses verification if len bytes are not present

Many non-REGISTER branches check bp against ep before parsing further fields

In the REGISTER branch, ND_TCHECK2(*(bp + 4), PIMV2_REGISTER_FLAG_LEN) only validates bytes 4-7 of the current header, not any encapsulated packet byte after bp += 8

There is no bp >= ep test or ND_TCHECK on the post-header bp before IP_V(ip) is evaluated

The bug is specifically evidenced for the first byte read via IP_V(ip); further parsing in ip_print/ip6_print may have their own checks, but the unsafe read already occurs before those functions are called

Severity is limited by the likely outcome being a crash rather than controlled code execution, based on the supplied code

main (tcpdump.c)
pim_print (print-pim.c)
pimv2_print (print-pim.c)

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