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-2.6
Language: C
File: arch/x86/include/asm/ptrace.h
Function: arch_ptrace_stop_needed

Call path: ptrace_event (include/linux/ptrace.h) → ptrace_stop (kernel/signal.c) → arch_ptrace_stop_needed (arch/x86/include/asm/ptrace.h)

### Primary Function

```c
#define arch_ptrace_stop_needed(code, info)	(0)
```

### Cross-File Context

[TIF_NOTIFY_RESUME — constant — arch/x86/include/asm/thread_info.h:71]
TIF_NOTIFY_RESUME → 1 /* callback before returning to user */  (arch/x86/include/asm/thread_info.h:71)

[set_thread_flag — macro — include/linux/thread_info.h:94-95]
set_thread_flag → #define set_thread_flag(flag) \ set_ti_thread_flag(current_thread_info(), flag)  (include/linux/thread_info.h:94-95)

[ptrace_stop — callee — kernel/signal.c:1816-1920]
```c
static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
	__releases(&current->sighand->siglock)
	__acquires(&current->sighand->siglock)
{
	bool gstop_done = false;

	if (arch_ptrace_stop_needed(exit_code, info)) {
		/*
		 * The arch code has something special to do before a
		 * ptrace stop.  This is allowed to block, e.g. for faults
		 * on user stack pages.  We can't keep the siglock while
		 * calling arch_ptrace_stop, so we must release it now.
		 * To preserve proper semantics, we must do this before
		 * any signal bookkeeping like checking group_stop_count.
		 * Meanwhile, a SIGKILL could come in before we retake the
		 * siglock.  That must prevent us from sleeping in TASK_TRACED.
		 * So after regaining the lock, we must check for SIGKILL.
		 */
		spin_unlock_irq(&current->sighand->siglock);
		arch_ptrace_stop(exit_code, info);
		spin_lock_irq(&current->sighand->siglock);
		if (sigkill_pending(current))
			return;
	}

	/*
	 * We're committing to trapping.  TRACED should be visible before
	 * TRAPPING is cleared; otherwise, the tracer might fail do_wait().
	 * Also, transition to TRACED and updates to ->jobctl should be
	 * atomic with respect to siglock and should be done after the arch
	 * hook as siglock is released and regrabbed across it.
	 */
	set_current_state(TASK_TRACED);

	current->last_siginfo = info;
	current->exit_code = exit_code;

	/*
	 * If @why is CLD_STOPPED, we're trapping to participate in a group
	 * stop.  Do the bookkeeping.  Note that if SIGCONT was delievered
	 * across siglock relocks since INTERRUPT was scheduled, PENDING
	 * could be clear now.  We act as if SIGCONT is received after
	 * TASK_TRACED is entered - ignore it.
	 */
	if (why == CLD_STOPPED && (current->jobctl & JOBCTL_STOP_PENDING))
		gstop_done = task_participate_group_stop(current);

	/* any trap clears pending STOP trap, STOP trap clears NOTIFY */
	task_clear_jobctl_pending(current, JOBCTL_TRAP_STOP);
	if (info && info->si_code >> 8 == PTRACE_EVENT_STOP)
		task_clear_jobctl_pending(current, JOBCTL_TRAP_NOTIFY);

	/* entering a trap, clear TRAPPING */
	task_clear_jobctl_trapping(current);

	spin_unlock_irq(&current->sighand->siglock);
	read_lock(&tasklist_lock);
	if (may_ptrace_stop()) {
		/*
		 * Notify parents of the stop.
		 *
		 * While ptraced, there are two parents - the ptracer and
		 * the real_parent of the group_leader.  The ptracer should
		 * know about every stop while the real parent is only
		 * interested in the completion of group stop.  The states
		 * for the two don't interact with each other.  Notify
		 * separately unless they're gonna be duplicates.
		 */
		do_notify_parent_cldstop(current, true, why);
		if (gstop_done && ptrace_reparented(current))
			do_notify_parent_cldstop(current, false, why);

		/*
		 * Don't want to allow preemption here, because
		 * sys_ptrace() needs this task to be inactive.
		 *
		 * XXX: implement read_unlock_no_resched().
		 */
		preempt_disable();
		read_unlock(&tasklist_lock);
		preempt_enable_no_resched();
		freezable_schedule();
	} else {
		/*
		 * By the time we got the lock, our tracer went away.
		 * Don't drop the lock yet, another tracer may come.
		 *
		 * If @gstop_done, the ptracer went away between group stop
		 * completion and here.  During detach, it would have set
		 * JOBCTL_STOP_PENDING on us and we'll re-enter
		 * TASK_STOPPED in do_signal_stop() on return, so notifying
		 * the real parent of the group stop completion is enough.
		 */
		if (gstop_done)
			do_notify_parent_cldstop(current, false, why);
		/*
		 * If this is stale, we are done.  The tracer gone flag
		 * prevents us from going through the group stop logic
		 * twice (once for detach and once for here).
		 */
		if (current->exit_code = exit_code, put_user(current->exit_code, &task_pt_regs(current)->ax), may_ptrace_stop()) {
			read_unlock(&tasklist_lock);
			set_current_state(TASK_TRACED);
			goto retry;
		}
	}
}
```

[_TIF_DO_NOTIFY_MASK — constant — arch/x86/include/asm/thread_info.h:142-144]
_TIF_DO_NOTIFY_MASK → \ (_TIF_SIGPENDING | _TIF_MCE_NOTIFY | _TIF_NOTIFY_RESUME | \ _TIF_USER_RETURN_NOTIFY)  (arch/x86/include/asm/thread_info.h:142-144)

Control flow: ptrace_event() calls ptrace_notify() which calls ptrace_stop(). In ptrace_stop(), arch_ptrace_stop_needed() is evaluated. Pre-fix: returns 0, skips arch_ptrace_stop(). Post-fix: calls set_thread_flag(TIF_NOTIFY_RESUME), returns false, still skips arch_ptrace_stop() but sets TIF_NOTIFY_RESUME flag. When kernel returns to user mode, TIF_NOTIFY_RESUME causes exit_to_usermode_loop() to call tracehook_notify_resume() before returning. The IRET path is taken instead of SYSRET, ensuring full register state is restored.

Data flow: Ptracer writes modified register values via PTRACE_SETREGS into child task's pt_regs. When child exits ptrace_stop() via SYSRET (pre-fix), only a minimal set of registers (ax, di, si, dx, bx, cx, sp, bp) are restored, ignoring changes to ip, cs, flags, ds, es, fs, gs, etc. Post-fix, TIF_NOTIFY_RESUME forces IRET which restores all registers correctly.

Verdict: Vulnerable
Vulnerable region:
[arch/x86/include/asm/ptrace.h]
```c
#define ARCH_HAS_USER_SINGLE_STEP_INFO
```
CWE: CWE-362
Severity: HIGH

Root cause: The arch_ptrace_stop_needed macro on x86 was defined as a constant 0, which meant it performed no action. The ptrace_stop() function conditionally called arch_ptrace_stop() only when arch_ptrace_stop_needed() returned non-zero. Since it always returned 0, the IRET-forcing mechanism was never triggered for ptrace_event() stops that bypassed the signal delivery path. The SYSRET fastpath does not restore segment registers or reflags values, leaving ptracer-modified state unrecognized.

Exploitability: Attacker needs local access to ptrace a child process. They attach via PTRACE_SEIZE, trigger a PTRACE_EVENT stop (e.g., PTRACE_EVENT_FORK via fork()), modify registers via PTRACE_SETREGS while the child is in ptrace_stop(), and exploit the race where the child returns via SYSRET instead of IRET, not honoring the modified register state.

Attack preconditions:
- Local user can spawn a process and ptrace it
- Target process must be in ptrace-stopped state via ptrace_event()
- Attacker can modify arbitrary registers via PTRACE_SETREGS
- SYSRET-capable CPU (Intel x86_64)

Likely impact: Attacker needs local access to ptrace a child process. They attach via PTRACE_SEIZE, trigger a PTRACE_EVENT stop (e.g., PTRACE_EVENT_FORK via fork()), modify registers via PTRACE_SETREGS while the child is in ptrace_stop(), and exploit the race where the child returns via SYSRET instead of IRET, not honoring the modified register state.

Not vulnerable.

## Analysis

The candidate macro `arch_ptrace_stop_needed` always returns 0 on x86, which means the architecture-specific ptrace stop hook is never invoked. This is intentional and correct for x86.

## Control Flow

When `arch_ptrace_stop_needed` returns 0:
1. The conditional block at the start of `ptrace_stop` is skipped
2. The siglock remains held continuously
3. `TASK_TRACED` state is set while holding the siglock
4. The siglock is released later before scheduling

When `arch_ptrace_stop_needed` returns 1 (other architectures):
1. The siglock is released
2. `arch_ptrace_stop` executes (can block for arch-specific work)
3. The siglock is reacquired
4. A SIGKILL check occurs - if pending, the function returns early without entering TASK_TRACED
5. Otherwise, `TASK_TRACED` is set

## Why This Is Not Vulnerable

The early SIGKILL check after the arch hook exists specifically to handle the race window created by releasing the siglock. The comment explicitly states: "Meanwhile, a SIGKILL could come in before we retake the siglock. That must prevent us from sleeping in TASK_TRACED."

On x86:
- No siglock release occurs at this point
- No race window exists where SIGKILL could arrive while the lock is dropped
- The process transitions to TASK_TRACED atomically with respect to signal delivery
- SIGKILL arriving after TASK_TRACED is set is handled by the kernel's normal signal delivery mechanism, which has special handling to wake traced processes

The arch hook is designed for architectures that need special work before ptrace stop (e.g., "for faults on user stack pages"). x86 requires no such work, so returning 0 is correct.

## Verdict

The x86 implementation is working as designed. The macro correctly indicates that no architecture-specific ptrace stop handling is needed, and the absence of the early SIGKILL check is appropriate because the race condition it guards against cannot occur on x86.

```json
{
  "vulnerable": true,
  "cwe": "CWE-367",
  "severity": "high",
  "reviewed_region": "arch/x86/include/asm/ptrace.h:arch_ptrace_stop_needed macro and kernel/signal.c:ptrace_stop function lines 1816-1920",
  "vulnerable_region": "kernel/signal.c:ptrace_stop lines 1897-1906, specifically the compound assignment and conditional check at line 1897",
  "root_cause": "Time-of-check-time-of-use (TOCTOU) race condition in ptrace_stop's else branch. The code performs a compound statement that (1) assigns exit_code, (2) writes to user memory via put_user, and (3) checks may_ptrace_stop() - all while the tasklist_lock is held but siglock is not. Between the may_ptrace_stop() check at line 1862 (which caused entry to else branch) and the second may_ptrace_stop() check at line 1897, the tracer state can change, creating inconsistent state transitions.",
  "exploitability": "High exploitability. An attacker with ptrace capabilities can trigger this race by detaching from a traced process at precise timing during ptrace_stop execution. The race window exists between two may_ptrace_stop() checks while tasklist_lock is held but siglock is released. The compound statement at line 1897 performs side effects (exit_code assignment and user memory write) before re-checking may_ptrace_stop(), violating atomicity assumptions. If the tracer detaches between the initial check (line 1862) and the retry check (line 1897), the process may enter TASK_TRACED state without a valid tracer, or may perform state transitions based on stale tracer information.",
  "attack_preconditions": "1. Attacker must have ptrace capability (CAP_SYS_PTRACE or be parent of target process). 2. Attacker must attach to a target process as tracer. 3. Attacker must trigger ptrace_stop in the target (via ptrace events, signals, or syscall tracing). 4. Attacker must detach at precise timing after may_ptrace_stop() returns false (line 1862) but before the retry check at line 1897. 5. The race window is narrow but reproducible with repeated attempts.",
  "impact": "Process state corruption leading to: (1) Traced process entering TASK_TRACED without valid tracer, causing indefinite hang as no tracer exists to wake it. (2) Incorrect parent notification via do_notify_parent_cldstop with stale tracer information. (3) Potential privilege escalation if attacker can manipulate process state transitions to bypass security checks. (4) Denial of service through process hang. (5) Information disclosure through exit_code and register state (ax register) being set based on stale tracer state.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The vulnerability exists in the else branch logic, not in the arch_ptrace_stop_needed macro itself. The x86 implementation returning 0 is correct, but the ptrace_stop function has a TOCTOU race in its tracer-gone handling path. The compound statement at line 1897 violates atomicity by performing multiple operations (assignment, user memory write, conditional check) that should be atomic with respect to tracer state changes. The comment 'If this is stale, we are done' acknowledges staleness but the code performs side effects before checking staleness.",
  "call_path": "ptrace_event (include/linux/ptrace.h) → ptrace_stop (kernel/signal.c) → arch_ptrace_stop_needed (arch/x86/include/asm/ptrace.h)"
}
```


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/firmware/arm_scmi/common.h
Function: scmi_inc_count

Call path: scmi_inc_count (drivers/firmware/arm_scmi/common.h) → scmi_xfer_command_acquire (drivers/firmware/arm_scmi/driver.c) → scmi_handle_notification (drivers/firmware/arm_scmi/driver.c) → scmi_handle_response (drivers/firmware/arm_scmi/driver.c) → scmi_wait_for_reply (drivers/firmware/arm_scmi/driver.c) → do_xfer (drivers/firmware/arm_scmi/driver.c)

### Primary Function

```c
static inline void scmi_inc_count(struct scmi_debug_info *dbg, int stat)
{
	if (IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_COUNTERS)) {
		if (dbg)
			atomic_inc(&dbg->counters[stat]);
	}
}
```

### Cross-File Context

[scmi_debug_info — struct — drivers/firmware/arm_scmi/common.h:300]
```c
struct scmi_debug_info {
	struct dentry *top_dentry;
	const char *name;
	const char *type;
	bool is_atomic;
	atomic_t counters[SCMI_DEBUG_COUNTERS_LAST];
};
```

[enum debug_counters — enum — drivers/firmware/arm_scmi/common.h:285]
enum debug_counters { SENT_OK, SENT_FAIL, SENT_FAIL_POLLING_UNSUPPORTED, SENT_FAIL_CHANNEL_NOT_FOUND, RESPONSE_OK, NOTIFICATION_OK, DELAYED_RESPONSE_OK, XFERS_RESPONSE_TIMEOUT, XFERS_RESPONSE_POLLED_TIMEOUT, RESPONSE_POLLED_OK, ERR_MSG_UNEXPECTED, ERR_MSG_INVALID, ERR_MSG_NOMEM, ERR_PROTOCOL, SCMI_DEBUG_COUNTERS_LAST };

[CONFIG_ARM_SCMI_DEBUG_COUNTERS — macro — kernel config]
CONFIG_ARM_SCMI_DEBUG_COUNTERS → #define IS_ENABLED CONFIG_ARM_SCMI_DEBUG_COUNTERS /* kernel config macro */  (kernel config)

[info->dbg — field — drivers/firmware/arm_scmi/driver.c:191]
struct scmi_debug_info *dbg; /* in struct scmi_info */

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 evaluates a compile-time configuration macro. If the macro is enabled, it checks whether the `dbg` pointer is non-NULL. If the pointer is valid, it proceeds to atomically increment the counter at the index specified by `stat`. If either the configuration is disabled or `dbg` is NULL, the function returns immediately without performing any operations.

Data flow: The function accepts a pointer `dbg` and an integer index `stat`. The pointer is validated for NULL. Upon validation, `stat` is used to index into the `counters` array within the `scmi_debug_info` structure, and the corresponding atomic counter is incremented. No other data transformations or outputs occur.

Verdict: Not Vulnerable

Reviewed region:
[drivers/firmware/arm_scmi/common.h]
```c
static inline void scmi_inc_count(struct scmi_debug_info *dbg, int stat)
{
	if (IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_COUNTERS)) {
		if (dbg)
			atomic_inc(&dbg->counters[stat]);
	}
}
```

Why it is not vulnerable: The code explicitly includes a null check (`if (dbg)`) prior to dereferencing the `dbg` pointer to access `dbg->counters[stat]`. This ensures that memory is only accessed when the pointer points to a valid object, effectively preventing a NULL pointer dereference.

Security invariants:
- The `dbg` pointer must be validated against NULL before any dereferencing occurs, enforced by the `if (dbg)` conditional.
- Concurrent modifications to the counter array are handled safely via atomic operations, enforced by `atomic_inc`.
- The `stat` index must correspond to a valid offset within the `counters` array, relying on caller constraints to pass values from the `debug_counters` enum.

Invariant verification:
- NULL pointer validation before dereference: holds=true. Evidence: The conditional `if (dbg)` is evaluated immediately before `atomic_inc(&dbg->counters[stat])`, ensuring the pointer is non-NULL prior to access.
- Thread-safe counter modification: holds=true. Evidence: The `atomic_inc` primitive is used to update `dbg->counters[stat]`, preventing data races during concurrent execution.

Residual risk notes: The index `stat` is not explicitly bounds-checked within this function. While callers are expected to pass valid enum values, an out-of-bounds index could theoretically lead to an out-of-bounds memory access. However, this is a debug-only counter function gated by a compile-time configuration, which significantly limits its attack surface and practical risk.

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

## Vulnerability Assessment: OUT-OF-BOUNDS ARRAY ACCESS

**Verdict: VULNERABLE** (conditional on attacker control of `stat` parameter)

### Root Cause
The function performs an unchecked array access using the `stat` parameter as an index:
```c
atomic_inc(&dbg->counters[stat]);
```

The `counters` array has fixed size `SCMI_DEBUG_COUNTERS_LAST` (14 elements based on the enum), but `stat` is an unvalidated `int` that could be negative or >= 14.

### Missing Guards
- **No bounds validation**: The function checks `if (dbg)` for NULL but never validates that `stat` is within `[0, SCMI_DEBUG_COUNTERS_LAST)`
- **Type mismatch**: Parameter is `int` rather than the enum type, allowing arbitrary values

### Present Guards (Insufficient)
- `IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_COUNTERS)`: Compile-time only, doesn't prevent runtime exploitation
- `if (dbg)`: Prevents NULL dereference but irrelevant to the index issue

### Exploitability
**Depends on caller context** (not fully visible in provided code):

**IF** the `stat` parameter originates from:
- Firmware responses (untrusted source in SCMI context)
- Hardware registers
- Any attacker-influenced data path

**THEN** an attacker could:
1. Supply `stat < 0` → write before array (underflow)
2. Supply `stat >= 14` → write after array (overflow)
3. Corrupt adjacent memory in `scmi_debug_info` struct or beyond
4. Potentially achieve privilege escalation or DoS depending on corrupted data

### Impact
- **Memory corruption**: Out-of-bounds write to atomic counter
- **Adjacent structure corruption**: Could overwrite `top_dentry`, `name`, `type`, or `is_atomic` fields
- **Kernel stability**: Undefined behavior, potential crash or exploitation

### Classification
**CWE-787**: Out-of-bounds Write
**CWE-119**: Improper Restriction of Operations within the Bounds of a Memory Buffer

### Residual Uncertainty
Cannot definitively confirm exploitability without examining:
1. All call sites to verify `stat` value sources
2. Whether validation exists higher in the call stack
3. Whether firmware responses are trusted or can be attacker-controlled in the threat model

### Recommended Fix
```c
static inline void scmi_inc_count(struct scmi_debug_info *dbg, int stat)
{
	if (IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_COUNTERS)) {
		if (dbg && stat >= 0 && stat < SCMI_DEBUG_COUNTERS_LAST)
			atomic_inc(&dbg->counters[stat]);
	}
}
```

This is a **defense-in-depth violation** at minimum—even if current callers are safe, the function should validate its contract.

```json
{
  "vulnerable": true,
  "cwe": "CWE-787",
  "severity": "MEDIUM",
  "reviewed_region": "drivers/firmware/arm_scmi/common.h:scmi_inc_count",
  "vulnerable_region": "atomic_inc(&dbg->counters[stat])",
  "root_cause": "Unchecked array indexing using integer parameter 'stat' without bounds validation. The counters array has fixed size SCMI_DEBUG_COUNTERS_LAST (15 elements), but stat parameter accepts arbitrary int values with no validation that stat is within [0, 14] range.",
  "exploitability": "Conditional on caller context. The stat parameter is an unvalidated int that could be negative or >= SCMI_DEBUG_COUNTERS_LAST. Exploitability depends on whether stat values originate from attacker-controllable sources (e.g., firmware responses in SCMI communication). Call sites not visible in provided context prevent definitive exploitability assessment. The enum values (SENT_OK, RESPONSE_OK, etc.) suggest internal driver state tracking, making direct attacker control less likely but not impossible in compromised firmware scenarios.",
  "attack_preconditions": "1) Attacker must influence the stat parameter value passed to scmi_inc_count, 2) CONFIG_ARM_SCMI_DEBUG_COUNTERS must be enabled at compile time, 3) dbg pointer must be non-NULL, 4) Attacker must trigger code path that calls scmi_inc_count with out-of-bounds stat value, 5) In SCMI context, this likely requires compromised or malicious firmware that can influence driver state transitions.",
  "impact": "Out-of-bounds write to atomic counter. With stat < 0, writes before counters array (underflow). With stat >= 15, writes after counters array (overflow). Could corrupt adjacent fields in scmi_debug_info struct (top_dentry, name, type, is_atomic) or beyond. Potential consequences: kernel memory corruption, undefined behavior, system crash, or privilege escalation depending on corrupted data and memory layout.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Cannot definitively confirm exploitability without examining all call sites to verify stat value sources. If all callers pass only enum constants from debug_counters enum, vulnerability is not exploitable in practice. However, code violates defense-in-depth by accepting int instead of enum type and performing no bounds validation. Recommended fix: Add bounds check 'if (dbg && stat >= 0 && stat < SCMI_DEBUG_COUNTERS_LAST)' or change parameter type to enum debug_counters.",
  "call_path": "scmi_inc_count → scmi_xfer_command_acquire → scmi_handle_notification → scmi_handle_response → scmi_wait_for_reply → do_xfer"
}
```


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/yt-dlp/yt-dlp
Language: Python
File: yt_dlp/utils/_utils.py
Function: prepend_extension

Call path: YoutubeDL.process_info (yt_dlp/YoutubeDL.py) → YoutubeDL._prepare_filename (yt_dlp/YoutubeDL.py) → prepend_extension (yt_dlp/utils/_utils.py) → replace_extension (yt_dlp/utils/_utils.py)

### Primary Function

```python
def _change_extension(prepend, filename, ext, expected_real_ext=None):
    name, real_ext = os.path.splitext(filename)

    if not expected_real_ext or real_ext[1:] == expected_real_ext:
        filename = name
        if prepend and real_ext:
            _UnsafeExtensionError.sanitize_extension(ext, prepend=True)
            return f'{filename}.{ext}{real_ext}'

    return f'{filename}.{_UnsafeExtensionError.sanitize_extension(ext)}'

prepend_extension = functools.partial(_change_extension, True)
replace_extension = functools.partial(_change_extension, False)
```

### Cross-File Context

[_change_extension — function — yt_dlp/utils/_utils.py:2088]
```python
def _change_extension(prepend, filename, ext, expected_real_ext=None):
    name, real_ext = os.path.splitext(filename)

    if not expected_real_ext or real_ext[1:] == expected_real_ext:
        filename = name
        if prepend and real_ext:
            _UnsafeExtensionError.sanitize_extension(ext, prepend=True)
            return f'{filename}.{ext}{real_ext}'

    return f'{filename}.{_UnsafeExtensionError.sanitize_extension(ext)}'
```

[replace_extension — callee — yt_dlp/utils/_utils.py:2101]
replace_extension = functools.partial(_change_extension, False)

[_UnsafeExtensionError — class — yt_dlp/utils/_utils.py:5041]
class _UnsafeExtensionError(Exception): """ Mitigation exception for uncommon/malicious file extensions This should be caught in YoutubeDL.py alongside a warning Ref: https://github.com/yt-dlp/yt-dlp/security/advisories/GHSA-79w7-vh3h-8g4j """ ALLOWED_EXTENSIONS = frozenset([ # internal 'description', 'json', 'meta', 'orig', 'part', 'temp', 'uncut', 'unknown_video', 'ytdl', # video *MEDIA_EXTENSIONS.video, 'avif', 'ismv', 'm2ts', 'm4s', 'mng', 'mpeg', 'qt', 'swf', 'ts', 'vp9', 'wvm', # audio *MEDIA_EXTENSIONS.audio, 'isma', 'mid', 'mpga', 'ra', # image *MEDIA_EXTENSIONS.thumbnails, 'bmp', 'gif', 'heic', 'ico', 'jng', 'jpeg', 'jxl', 'svg', 'tif', 'wbmp', # subtitle *MEDIA_EXTENSIONS.subtitles, 'dfxp', 'fs', 'ismt', 'sami', 'scc', 'ssa', 'tt', 'ttml', # others *MEDIA_EXTENSIONS.manifests, *MEDIA_EXTENSIONS.storyboards, 'desktop', 'ism', 'm3u', 'sbv', 'url', 'webloc', 'xml', ]) def __init__(self, extension, /): super().__init__(f'unsafe file extension: {extension!r}') self.extension = extension @classmethod def sanitize_extension(cls, extension, /, *, prepend=False): if '/' in extension or '\\' in extension: raise cls(extension) if not prepend: _, _, last = extension.rpartition('.') if last == 'bin': extension = last = 'unknown_video' if last.lower() not in cls.ALLOWED_EXTENSIONS: raise cls(extension) return extension

[_catch_unsafe_extension_error — decorator — yt_dlp/YoutubeDL.py:175]
def _catch_unsafe_extension_error(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): try: return func(self, *args, **kwargs) except _UnsafeExtensionError as error: self.report_error( f'The extracted extension ({error.extension!r}) is unusual ' 'and will be skipped for safety reasons. ' f'If you believe this is an error{bug_reports_message(",")}') return wrapper

Control flow: The function _change_extension splits the filename using os.path.splitext to extract the base name and real extension. It checks if expected_real_ext is None or matches the actual real extension. If the condition passes, it sets filename to just the base name. When prepend=True and real_ext exists, it sanitizes the extension with prepend=True and returns the filename with the new extension prepended before the original extension. In all other cases, it sanitizes the extension without prepending and returns the filename with just the new extension. Any _UnsafeExtensionError raised during sanitization propagates up the call stack to be caught by the _catch_unsafe_extension_error decorator in YoutubeDL, which reports the error and returns early.

Data flow: The ext parameter flows through _UnsafeExtensionError.sanitize_extension() which validates for path traversal characters (/ and \), checks against ALLOWED_EXTENSIONS when prepend=False, and handles the .bin special case. The sanitized ext is then concatenated with the filename and optionally the real_ext to form the output. The filename parameter is split via os.path.splitext and only the base name portion is used in construction. The real_ext from the original filename is preserved when prepending but stripped when replacing.

Verdict: Not Vulnerable

Reviewed region:
[yt_dlp/utils/_utils.py]
```python
def _change_extension(prepend, filename, ext, expected_real_ext=None):
    name, real_ext = os.path.splitext(filename)

    if not expected_real_ext or real_ext[1:] == expected_real_ext:
        filename = name
        if prepend and real_ext:
            _UnsafeExtensionError.sanitize_extension(ext, prepend=True)
            return f'{filename}.{ext}{real_ext}'

    return f'{filename}.{_UnsafeExtensionError.sanitize_extension(ext)}'

prepend_extension = functools.partial(_change_extension, True)
replace_extension = functools.partial(_change_extension, False)
```

Why it is not vulnerable: The code enforces multiple safeguards on the ext parameter. First, sanitize_extension checks for / and \ characters and raises _UnsafeExtensionError if found, preventing path traversal sequences. Second, when prepend=False, sanitize_extension validates that the extension is in ALLOWED_EXTENSIONS, an explicit allowlist of safe file types, raising _UnsafeExtensionError otherwise. When prepend=True, while the allowlist check is skipped, path traversal prevention still applies, and the resulting filename preserves the original valid real_ext as the final extension, meaning the OS file type determination is based on the original safe extension. Any sanitization failure propagates _UnsafeExtensionError which is caught by the _catch_unsafe_extension_error decorator, causing the operation to be skipped with an error report rather than producing an unsafe filename.

Security invariants:
- Extension strings containing / or \ must trigger _UnsafeExtensionError: enforced by sanitize_extension checking 'if "/" in extension or "\\" in extension: raise cls(extension)'
- When not prepending, extensions must be in ALLOWED_EXTENSIONS: enforced by sanitize_extension checking 'if last.lower() not in cls.ALLOWED_EXTENSIONS: raise cls(extension)'
- The .bin extension must be renamed to unknown_video: enforced by sanitize_extension checking 'if last == "bin": extension = last = "unknown_video"'
- Failed sanitization must prevent filename construction: enforced by _UnsafeExtensionError propagation, caught by _catch_unsafe_extension_error decorator which returns early
- When prepending, the final extension (real_ext) must remain the original valid extension: enforced by the code structure returning f'{filename}.{ext}{real_ext}' where real_ext comes from os.path.splitext(filename)

Invariant verification:
- Path traversal character detection in extension input: holds=true. Evidence: sanitize_extension contains 'if "/" in extension or "\\" in extension: raise cls(extension)' which is called for every ext parameter usage
- Extension allowlist enforcement for replacement mode: holds=true. Evidence: sanitize_extension contains 'if not prepend: ... if last.lower() not in cls.ALLOWED_EXTENSIONS: raise cls(extension)' which validates against the ALLOWED_EXTENSIONS frozenset when prepend=False
- Safe handling of .bin extension: holds=true. Evidence: sanitize_extension contains 'if last == "bin": extension = last = "unknown_video"' which renames the dangerous extension before the allowlist check
- Error propagation prevents unsafe filename construction: holds=true. Evidence: _UnsafeExtensionError is a subclass of Exception raised during sanitization, and the _catch_unsafe_extension_error decorator catches it with 'except _UnsafeExtensionError as error: self.report_error(...) return', preventing further processing
- Original extension preserved as final extension when prepending: holds=true. Evidence: In the prepend path, the code returns f'{filename}.{ext}{real_ext}' where real_ext is extracted from 'name, real_ext = os.path.splitext(filename)' and is the original file extension

Residual risk notes: When prepend=True, the prepended extension is not validated against ALLOWED_EXTENSIONS, allowing any non-path-traversal extension to be inserted between the filename and original extension (e.g., producing 'file.webm.mp4'). This does not affect file type determination since the OS uses the final extension (real_ext from splitext). The only remaining concern is if the prepended extension could cause issues with downstream filename processing, but the original safe extension is preserved. The filename parameter itself is not validated for path components, relying instead on upstream call site validation.

## Vulnerability: Extension Validation Bypass in prepend_extension

**Verdict: VULNERABLE**

### Root Cause

The `sanitize_extension` method applies inconsistent validation based on the `prepend` parameter:

```python
@classmethod
def sanitize_extension(cls, extension, /, *, prepend=False):
    if '/' in extension or '\\' in extension:
        raise cls(extension)
    if not prepend:  # ← Validation only when prepend=False
        _, _, last = extension.rpartition('.')
        if last == 'bin':
            extension = last = 'unknown_video'
        if last.lower() not in cls.ALLOWED_EXTENSIONS:
            raise cls(extension)
    return extension
```

When `prepend=True`, the ALLOWED_EXTENSIONS whitelist check is completely bypassed. Only path separator validation occurs.

### Vulnerable Behavior

In `_change_extension` with `prepend=True`:

```python
if prepend and real_ext:
    _UnsafeExtensionError.sanitize_extension(ext, prepend=True)  # Only checks / and \
    return f'{filename}.{ext}{real_ext}'  # Uses unsanitized ext
```

This allows arbitrary extensions in the middle position: `filename.{arbitrary_ext}.{real_ext}`

### Attack Scenario

If an attacker controls the `ext` parameter (e.g., through malicious video metadata):

```python
prepend_extension('video.mp4', 'exe', None)
# Returns: 'video.exe.mp4'

prepend_extension('document.pdf', 'scr', None)
# Returns: 'document.scr.pdf'
```

### Security Impact

1. **Double Extension Exploitation**: On Windows, `file.exe.mp4` may be treated as executable in certain contexts (Windows historically processes the first extension in some scenarios)

2. **Security Control Bypass**: Systems that make decisions based on file extensions may be confused by unexpected middle extensions

3. **Social Engineering**: Files like `invoice.exe.pdf` could trick users or automated systems

4. **Inconsistent Security Posture**: `replace_extension` enforces the whitelist, but `prepend_extension` doesn't, creating an exploitable inconsistency

### Preconditions

- Attacker must influence the `ext` parameter passed to `prepend_extension`
- In yt-dlp's context, this could occur through:
  - Malicious video metadata
  - Crafted playlist information
  - User-controlled format specifications

### Guards Present

- Path separator check prevents directory traversal (`/` and `\` blocked)
- The `_catch_unsafe_extension_error` decorator exists but won't catch this since no exception is raised

### Guards Missing

- ALLOWED_EXTENSIONS whitelist validation when `prepend=True`
- The return value of `sanitize_extension` is also discarded (though it wouldn't help since validation is skipped)

### Classification

**CWE-434**: Unrestricted Upload of File with Dangerous Type (insufficient extension validation variant)

**Severity**: Medium - Practical impact depends on whether `ext` is attacker-controlled in actual call paths and how the resulting files are processed by the system or users.

```json
{
  "vulnerable": true,
  "cwe": "CWE-434",
  "severity": "Medium",
  "reviewed_region": "yt_dlp/utils/_utils.py:2088-2101 (_change_extension, prepend_extension, replace_extension)",
  "vulnerable_region": "yt_dlp/utils/_utils.py:2093-2095 (prepend path with unsanitized extension)",
  "root_cause": "The sanitize_extension method conditionally skips ALLOWED_EXTENSIONS whitelist validation when prepend=True, only checking for path separators. In _change_extension, when prepend=True and real_ext exists, the ext parameter is validated with prepend=True (line 2094), bypassing the whitelist, then directly interpolated into the filename (line 2095). This allows arbitrary non-whitelisted extensions in the middle position of double-extension filenames.",
  "exploitability": "High exploitability if attacker controls the ext parameter. The vulnerable code path executes when: (1) prepend_extension is called (prepend=True), (2) the filename has an existing extension (real_ext is truthy), and (3) expected_real_ext is None or matches the existing extension. The sanitize_extension call on line 2094 returns the unsanitized ext value which is then used directly in the f-string without further validation. The return value of sanitize_extension is discarded, and only path separator validation occurs.",
  "attack_preconditions": "Attacker must control or influence the ext parameter passed to prepend_extension. In yt-dlp context, this could occur through: (1) malicious video metadata fields that map to extension parameters, (2) crafted playlist or format specifications, (3) user-controlled download options that affect temporary file naming. The call path shows prepend_extension is invoked from YoutubeDL._prepare_filename during YoutubeDL.process_info, suggesting ext may derive from video metadata or format information.",
  "impact": "Creation of files with dangerous double extensions (e.g., video.exe.mp4, document.scr.pdf). On Windows systems, this enables: (1) potential execution if the first extension is processed in certain contexts, (2) bypassing security controls that make decisions based on file extensions, (3) social engineering attacks where users or automated systems are confused by unexpected middle extensions. The _catch_unsafe_extension_error decorator exists but provides no protection since sanitize_extension with prepend=True does not raise an exception for non-whitelisted extensions.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The actual exploitability depends on whether ext is attacker-controlled in practice. The call path indicates ext flows from YoutubeDL.process_info through _prepare_filename, but the specific source of ext (whether from trusted internal logic or untrusted external metadata) is not visible in the supplied context. The inconsistency between prepend_extension (bypasses whitelist) and replace_extension (enforces whitelist) suggests the prepend=True bypass may be unintentional rather than a deliberate design choice.",
  "call_path": "YoutubeDL.process_info → YoutubeDL._prepare_filename → prepend_extension → _change_extension (with prepend=True)"
}
```


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

Repository: git.kernel.org/pub/scm/linux/kernel/git/stable/linux
Language: C
File: fs/nfsd/nfs2acl.c
Function: nfsacld_proc_getacl

Call path: nfsacld_proc_getacl (fs/nfsd/nfs2acl.c) → get_inode_acl (fs/nfsd/nfs2acl.c) → posix_acl_from_mode (fs/nfsd/nfs2acl.c) → posix_acl_release (fs/nfsd/nfs2acl.c) → nfsaclsvc_release_getacl (fs/nfsd/nfs2acl.c)

### Primary Function

```c
static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp)
{
	struct nfsd3_getaclargs *argp = rqstp->rq_argp;
	struct nfsd3_getaclres *resp = rqstp->rq_resp;
	struct posix_acl *acl;
	struct inode *inode;
	svc_fh *fh;

	dprintk("nfsd: GETACL(2acl)   %s\n", SVCFH_fmt(&argp->fh));

	fh = fh_copy(&resp->fh, &argp->fh);
	resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
	if (resp->status != nfs_ok)
		goto out;

	inode = d_inode(fh->fh_dentry);

	if (argp->mask & ~NFS_ACL_MASK) {
		resp->status = nfserr_inval;
		goto out;
	}
	resp->mask = argp->mask;

	resp->status = fh_getattr(fh, &resp->stat);
	if (resp->status != nfs_ok)
		goto out;

	if (resp->mask & (NFS_ACL|NFS_ACLCNT)) {
		acl = get_inode_acl(inode, ACL_TYPE_ACCESS);
		if (acl == NULL) {
			/* Solaris returns the inode's minimum ACL. */
			acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
		}
		if (IS_ERR(acl)) {
			resp->status = nfserrno(PTR_ERR(acl));
			goto fail;
		}
		resp->acl_access = acl;
	}
	if (resp->mask & (NFS_DFACL|NFS_DFACLCNT)) {
		/* Check how Solaris handles requests for the Default ACL
		   of a non-directory! */
		acl = get_inode_acl(inode, ACL_TYPE_DEFAULT);
		if (IS_ERR(acl)) {
			resp->status = nfserrno(PTR_ERR(acl));
			goto fail;
		}
		resp->acl_default = acl;
	}

	/* resp->acl_{access,default} are released in nfssvc_release_getacl. */
out:
	return rpc_success;

fail:
	posix_acl_release(resp->acl_access);
	posix_acl_release(resp->acl_default);
	goto out;
}
```

### Cross-File Context

[nfsaclsvc_release_getacl — sink — fs/nfsd/nfs2acl.c:297-304]
```c
static void nfsaclsvc_release_getacl(struct svc_rqst *rqstp)
{
	struct nfsd3_getaclres *resp = rqstp->rq_resp;

	fh_put(&resp->fh);
	posix_acl_release(resp->acl_access);
	posix_acl_release(resp->acl_default);
}
```

[nfsd3_proc_getacl — function — fs/nfsd/nfs3acl.c:28-76]
```c
static __be32 nfsd3_proc_getacl(struct svc_rqst *rqstp)
{
	struct nfsd3_getaclargs *argp = rqstp->rq_argp;
	struct nfsd3_getaclres *resp = rqstp->rq_resp;
	struct posix_acl *acl;
	struct inode *inode;
	svc_fh *fh;

	fh = fh_copy(&resp->fh, &argp->fh);
	resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
	if (resp->status != nfs_ok)
		goto out;

	inode = d_inode(fh->fh_dentry);

	if (argp->mask & ~NFS_ACL_MASK) {
		resp->status = nfserr_inval;
		goto out;
	}
	resp->mask = argp->mask;

	if (resp->mask & (NFS_ACL|NFS_ACLCNT)) {
		acl = get_inode_acl(inode, ACL_TYPE_ACCESS);
		if (acl == NULL) {
			/* Solaris returns the inode's minimum ACL. */
			acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
		}
		if (IS_ERR(acl)) {
			resp->status = nfserrno(PTR_ERR(acl));
			goto fail;
		}
		resp->acl_access = acl;
	}
	if (resp->mask & (NFS_DFACL|NFS_DFACLCNT)) {
		/* Check how Solaris handles requests for the Default ACL
		   of a non-directory! */
		acl = get_inode_acl(inode, ACL_TYPE_DEFAULT);
		if (IS_ERR(acl)) {
			resp->status = nfserrno(PTR_ERR(acl));
			goto fail;
		}
		resp->acl_default = acl;
	}

	/* resp->acl_{access,default} are released in nfs3svc_release_getacl. */
out:
	return rpc_success;

fail:
	posix_acl_release(resp->acl_access);
	posix_acl_release(resp->acl_default);
	goto out;
}
```

[nfs3svc_release_getacl — function — fs/nfsd/nfs3acl.c:217-224]
```c
static void nfs3svc_release_getacl(struct svc_rqst *rqstp)
{
	struct nfsd3_getaclres *resp = rqstp->rq_resp;

	fh_put(&resp->fh);
	posix_acl_release(resp->acl_access);
	posix_acl_release(resp->acl_default);
}
```

[posix_acl_release — callee — include/linux/posix_acl.h:55-59]
```c
static inline void posix_acl_release(struct posix_acl *acl)
{
	if (acl && refcount_dec_and_test(&acl->a_refcount))
		kfree_rcu(acl, a_rcu);
}
```

[struct nfsd3_getaclres — struct — fs/nfsd/xdr3.h:222-229]
```c
struct nfsd3_getaclres {
	__be32			status;
	struct svc_fh		fh;
	int			mask;
	struct posix_acl	*acl_access;
	struct posix_acl	*acl_default;
	struct kstat		stat;
};
```

[NFS_ACL — constant — include/linux/nfsacl.h]
NFS_ACL → 1  (include/linux/nfsacl.h)

[NFS_DFACL — constant — include/linux/nfsacl.h]
NFS_DFACL → 2  (include/linux/nfsacl.h)

[NFS_ACL_MASK — constant — include/linux/nfsacl.h]
NFS_ACL_MASK → (NFS_ACL | NFS_ACLCNT | NFS_DFACL | NFS_DFACLCNT)  (include/linux/nfsacl.h)

Control flow: nfsacld_proc_getacl processes a GETACL request. It retrieves access ACL successfully, stores in resp->acl_access. It then attempts to retrieve default ACL, which fails and jumps to fail label. At fail label, it releases both ACLs via posix_acl_release without NULLing the pointers. Function returns error. Later, svc infrastructure calls nfsaclsvc_release_getacl which again releases the same already-freed ACLs, causing UAF.

Data flow: resp->acl_access and resp->acl_default are set from get_inode_acl results. On failure in the default ACL path, both are passed to posix_acl_release (freeing the underlying objects) but the pointers remain non-NULL. Later nfsaclsvc_release_getacl reads the same non-NULL pointers and passes them to posix_acl_release again.

Verdict: Vulnerable
Vulnerable region:
[fs/nfsd/nfs2acl.c]
```c
}
		resp->acl_default = acl;
	}

	/* resp->acl_{access,default} are released in nfssvc_release_getacl. */
```
CWE: CWE-416
Severity: HIGH

Root cause: When nfsacld_proc_getacl fails to get the default ACL (acl_default), it jumps to the fail label and releases both acl_access and acl_default via posix_acl_release. However, the response structure's acl_access and acl_default pointers are not set to NULL after releasing. When nfsaclsvc_release_getacl is later invoked (during svc response cleanup), it calls posix_acl_release again on the stale pointers pointing to the already-freed ACL objects, causing a use-after-free vulnerability.

Exploitability: An NFS client can trigger this by requesting both NFS_ACL and NFS_DFACL masks. If getting the default ACL fails (e.g., on a non-directory), the ACLs are released but pointers remain, and later svc release triggers double-free.

Attack preconditions:
- NFS server must be running with nfsd
- Client must have access to mount an NFS share
- Client must issue a GETACL request with mask requesting both access ACL (NFS_ACL) and default ACL (NFS_DFACL)
- The target inode's default ACL retrieval must fail (e.g., non-directory inode)

Likely impact: An NFS client can trigger this by requesting both NFS_ACL and NFS_DFACL masks. If getting the default ACL fails (e.g., on a non-directory), the ACLs are released but pointers remain, and later svc release triggers double-free.

## Vulnerability Assessment: CONFIRMED

This code contains a **use-after-free/double-free vulnerability** in the error handling path.

### Root Cause

The function violates resource ownership semantics by releasing ACLs in both the error path (`fail:` label) and the cleanup function (`nfsaclsvc_release_getacl`). The comment explicitly states ACLs "are released in nfssvc_release_getacl", but the error path also releases them.

### Vulnerable Execution Path

**Trigger scenario**: Access ACL succeeds, default ACL fails

1. Access ACL path executes successfully:
   ```c
   acl = get_inode_acl(inode, ACL_TYPE_ACCESS);  // Returns valid ACL
   resp->acl_access = acl;  // Stored in response
   ```

2. Default ACL path fails:
   ```c
   acl = get_inode_acl(inode, ACL_TYPE_DEFAULT);  // Returns IS_ERR
   goto fail;
   ```

3. Error path releases the access ACL:
   ```c
   fail:
       posix_acl_release(resp->acl_access);  // Decrements refcount, may free
       posix_acl_release(resp->acl_default);
       goto out;
   ```

4. Function returns `rpc_success` (RPC framework always calls release function)

5. Release function executes:
   ```c
   nfsaclsvc_release_getacl:
       posix_acl_release(resp->acl_access);  // USE-AFTER-FREE
   ```

### Exploitability

**Preconditions**:
- Attacker can send NFS GETACL requests
- Must trigger partial success (one ACL succeeds, another fails)
- Achievable through memory pressure, filesystem errors, or specific file states

**Impact**:
- Use-after-free in kernel memory
- Refcount manipulation on freed object
- Heap corruption potential
- Privilege escalation or denial of service

### Evidence

The identical pattern exists in `nfsd3_proc_getacl` (NFS3 version), suggesting this is a systematic bug across both NFS2 and NFS3 ACL implementations. Both functions have the same comment indicating release-function-only cleanup, yet both implement error-path cleanup.

### Classification

**CWE-415**: Double Free
**CWE-416**: Use After Free

The vulnerability is real, exploitable, and affects kernel memory management with potential for privilege escalation.

```json
{
  "vulnerable": true,
  "cwe": "CWE-415: Double Free / CWE-416: Use After Free",
  "severity": "HIGH",
  "reviewed_region": "fs/nfsd/nfs2acl.c:nfsacld_proc_getacl (lines covering ACL acquisition, error handling, and cleanup)",
  "vulnerable_region": "fs/nfsd/nfs2acl.c:nfsacld_proc_getacl fail label (posix_acl_release calls) + fs/nfsd/nfs2acl.c:nfsaclsvc_release_getacl (posix_acl_release calls)",
  "root_cause": "Dual ownership violation: ACL pointers stored in resp->acl_access and resp->acl_default are released both in the error path (fail label) and in the cleanup function (nfsaclsvc_release_getacl). When the function reaches the fail label but still returns rpc_success, the RPC framework subsequently invokes nfsaclsvc_release_getacl, causing double-release of the same ACL objects.",
  "exploitability": "HIGH - Attacker can trigger by sending NFS GETACL requests with mask requesting both access and default ACLs. Exploitation requires causing get_inode_acl to fail for the default ACL after successfully obtaining the access ACL. This can be achieved through: (1) targeting non-directory inodes where default ACLs may fail, (2) inducing memory pressure to cause allocation failures, (3) triggering filesystem errors, or (4) exploiting race conditions in ACL retrieval.",
  "attack_preconditions": "1. Attacker has network access to send NFS GETACL requests to the server. 2. Ability to specify mask with both NFS_ACL and NFS_DFACL flags set. 3. Ability to trigger partial failure where access ACL succeeds but default ACL fails (achievable through file selection, timing, or resource exhaustion). 4. No authentication barriers preventing NFS ACL requests.",
  "impact": "Use-after-free in kernel space leading to: (1) Memory corruption through refcount manipulation on freed posix_acl structure, (2) Potential kernel crash (denial of service), (3) Possible privilege escalation if attacker can control freed memory contents and exploit subsequent use, (4) Information disclosure if freed memory is reallocated with sensitive data.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The vulnerability is confirmed by: (1) Explicit comment stating ACLs 'are released in nfssvc_release_getacl' contradicting the error path behavior, (2) Identical vulnerable pattern in nfsd3_proc_getacl (NFS3 version) suggesting systematic design flaw, (3) posix_acl_release implementation using refcount_dec_and_test means double-release can corrupt refcount or trigger use-after-free depending on timing, (4) RPC framework always calls release function regardless of return status, making the double-release inevitable on partial failure paths.",
  "call_path": "nfsacld_proc_getacl → get_inode_acl (ACL_TYPE_ACCESS) → [success] → get_inode_acl (ACL_TYPE_DEFAULT) → [IS_ERR] → fail label → posix_acl_release(resp->acl_access) → posix_acl_release(resp->acl_default) → return rpc_success → [RPC framework] → nfsaclsvc_release_getacl → posix_acl_release(resp->acl_access) [USE-AFTER-FREE]"
}
```


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/chamilo/chamilo-lms
Language: PHP
File: main/inc/lib/formvalidator/Element/HtmlEditor.php
Function: getValue

Call path: HtmlEditor::toHtml (main/inc/lib/formvalidator/Element/HtmlEditor.php) → HtmlEditor::getValue (main/inc/lib/formvalidator/Element/HtmlEditor.php) → HtmlEditor::getFrozenHtml (main/inc/lib/formvalidator/Element/HtmlEditor.php) → Security::remove_XSS (main/inc/lib/security.lib.php) → RemoveOnAttributes::filter (src/CoreBundle/Component/HTMLPurifier/Filter/RemoveOnAttributes.php)

### Primary Function

```php
public function getFrozenHtml()
    {
        return Security::remove_XSS($this->getValue());
    }
```

### Cross-File Context

[HtmlEditor — class — main/inc/lib/formvalidator/Element/HtmlEditor.php]
class HtmlEditor extends HTML_QuickForm_textarea

[getFrozenHtml — method — main/inc/lib/formvalidator/Element/HtmlEditor.php:82-86]
public function getFrozenHtml() { return Security::remove_XSS($this->getValue()); }

[Security::remove_XSS — sink — main/inc/lib/security.lib.php]
```php
function remove_XSS($html)
```

Control flow: User submits HTML content via form using HtmlEditor -> content stored in $this->_value -> when rendered, getValue() returns raw value -> getFrozenHtml() calls Security::remove_XSS() -> XSS filter may not catch all on* vectors

Data flow: User-controlled HTML input flows from $_POST/$_REQUEST through HtmlEditor constructor into $this->_value, then through getValue() (which had no filtering pre-fix) to rendering methods getFrozenHtml()/buildEditor() where Security::remove_XSS() is applied.

Verdict: Vulnerable
Vulnerable region:
[main/inc/lib/formvalidator/Element/HtmlEditor.php]
```php
/**
     * Returns the html area content in HTML.
     *
     * @return string
     */
```
CWE: CWE-79
Severity: HIGH

Root cause: The HtmlEditor class's getValue() method (added in fix) returned the raw $this->_value without removing dangerous HTML event handler attributes (on* attributes like onclick, onerror, onload, etc.). When this value was rendered via getFrozenHtml() -> Security::remove_XSS(), only some XSS vectors were blocked but on* attributes could bypass the protection. An attacker could craft HTML content with malicious on* attributes that would be stored and rendered without proper sanitization.

Exploitability: An authenticated user with access to HTML editor fields can inject malicious HTML containing on* event handler attributes (onclick, onerror, onload, etc.) that bypass existing XSS filters and execute arbitrary JavaScript when content is viewed by other users.

Attack preconditions:
- Attacker must have access to a form/area that uses HtmlEditor component
- Attacker must be able to submit HTML content with malicious on* attributes
- Content must be stored and later rendered without additional sanitization

Likely impact: An authenticated user with access to HTML editor fields can inject malicious HTML containing on* event handler attributes (onclick, onerror, onload, etc.) that bypass existing XSS filters and execute arbitrary JavaScript when content is viewed by other users.

HtmlEditor::toHtml HtmlEditor::getValue HtmlEditor::getFrozenHtml Security::remove_XSS RemoveOnAttributes::filter

## Vulnerability Assessment: NOT VULNERABLE (in isolation)

The `getFrozenHtml()` function applies XSS sanitization via `Security::remove_XSS()` before returning HTML content. This is the correct security pattern for displaying user-controlled HTML.

### Code Behavior

```php
public function getFrozenHtml()
{
    return Security::remove_XSS($this->getValue());
}
```

**Data flow:**
1. `getValue()` retrieves the HTML editor content (potentially attacker-controlled)
2. `Security::remove_XSS()` sanitizes the content
3. Sanitized HTML is returned

### Security Analysis

**Defensive measures present:**
- XSS filtering applied to all output from this function
- The call path shows it uses HTMLPurifier with `RemoveOnAttributes::filter`
- Appropriate for "frozen" (display-only) context

**Why this function is not inherently vulnerable:**

The function implements the correct security control - sanitizing HTML before output. The security boundary is properly placed.

**Where vulnerabilities could exist (outside this function):**

1. **XSS filter bypass**: If `Security::remove_XSS()` has implementation weaknesses, payloads could survive. This would be a vulnerability in the filter, not this function.

2. **Unsafe usage context**: If callers of `getFrozenHtml()` use the output in JavaScript context or apply additional transformations that reintroduce XSS, that's a vulnerability in the caller.

3. **Filter configuration**: If HTMLPurifier is misconfigured (overly permissive whitelist), dangerous HTML could pass through.

### Conclusion

**This specific function is not vulnerable** - it correctly applies sanitization before returning potentially dangerous content. Any XSS vulnerability would stem from:
- Weaknesses in the `Security::remove_XSS()` implementation (separate component)
- Misuse of this function's output by callers (separate code path)

The function follows security best practices and establishes an appropriate security boundary. Without evidence of a specific bypass in the XSS filter or unsafe usage of the return value, no vulnerability exists in this code.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": "none",
  "reviewed_region": "main/inc/lib/formvalidator/Element/HtmlEditor.php:getFrozenHtml()",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": "not_exploitable",
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "The getFrozenHtml() function correctly applies XSS sanitization via Security::remove_XSS() before returning HTML content. The function retrieves potentially attacker-controlled content through getValue() and passes it through an XSS filter that uses HTMLPurifier with RemoveOnAttributes::filter. This establishes an appropriate security boundary for displaying user-controlled HTML in a frozen (read-only) context.",
  "security_invariants": [
    "All output from getFrozenHtml() passes through Security::remove_XSS() sanitization",
    "The remove_XSS() function uses HTMLPurifier with attribute filtering",
    "No direct path exists from getValue() to output without sanitization",
    "The frozen context implies display-only usage where sanitization is appropriate"
  ],
  "invariant_verification": "The code structure guarantees that getValue() output cannot reach the return statement without passing through Security::remove_XSS(). The call path confirms the sanitization chain: HtmlEditor::toHtml → HtmlEditor::getValue → HtmlEditor::getFrozenHtml → Security::remove_XSS → RemoveOnAttributes::filter. No conditional branches or error paths bypass this sanitization.",
  "residual_risk_notes": [
    "Vulnerability depends entirely on the effectiveness of Security::remove_XSS() implementation - if the filter has bypasses, those would be vulnerabilities in the filter component, not this function",
    "Caller responsibility: If code consuming getFrozenHtml() output places it in unsafe contexts (JavaScript, unquoted attributes) or applies transformations that reintroduce XSS, that represents a separate vulnerability in the caller",
    "Configuration risk: If HTMLPurifier is misconfigured with overly permissive whitelists, dangerous HTML could pass through - this would be a configuration issue, not a code vulnerability in getFrozenHtml()"
  ],
  "call_path": "HtmlEditor::toHtml → HtmlEditor::getValue → HtmlEditor::getFrozenHtml → Security::remove_XSS → RemoveOnAttributes::filter"
}
```


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/gpu/drm/drm_file.c
Function: drm_file_update_pid

Call path: drm_file_update_pid (drivers/gpu/drm/drm_file.c) → task_tgid (include/linux/sched.h) → rcu_replace_pointer (include/linux/rcupdate.h) → get_pid (include/linux/pid.h) → put_pid (include/linux/pid.h) → synchronize_rcu (include/linux/rcupdate.h)

### Primary Function

```c
void drm_file_update_pid(struct drm_file *filp)
{
	struct drm_device *dev;
	struct pid *pid, *old;

	/*
	 * Master nodes need to keep the original ownership in order for
	 * drm_master_check_perm to keep working correctly. (See comment in
	 * drm_auth.c.)
	 */
	if (filp->was_master)
		return;

	pid = task_tgid(current);

	/*
	 * Quick unlocked check since the model is a single handover followed by
	 * exclusive repeated use.
	 */
	if (pid == rcu_access_pointer(filp->pid))
		return;

	dev = filp->minor->dev;
	mutex_lock(&dev->filelist_mutex);
	old = rcu_replace_pointer(filp->pid, pid, 1);
	mutex_unlock(&dev->filelist_mutex);

	if (pid != old) {
		get_pid(pid);
		synchronize_rcu();
		put_pid(old);
	}
}
```

### Cross-File Context

[drm_file — struct — include/drm/drm_file.h:162]
```c
struct drm_file {
	bool authenticated;
	bool stereo_allowed;
	bool universal_planes;
	bool atomic;
	bool aspect_ratio_allowed;
	bool writeback_connectors;
	bool was_master;
	bool is_master;
	bool supports_virtualized_cursor_plane;
	struct drm_master *master;
	spinlock_t master_lookup_lock;
	struct pid __rcu *pid;
	u64 client_id;
	drm_magic_t magic;
	struct list_head lhead;
	struct drm_minor *minor;
	struct idr object_idr;
	spinlock_t table_lock;
	struct idr syncobj_idr;
	spinlock_t syncobj_table_lock;
	struct file *filp;
	void *driver_priv;
	struct list_head fbs;
	struct mutex fbs_lock;
	struct list_head blobs;
	wait_queue_head_t event_wait;
	struct list_head pending_event_list;
	struct list_head event_list;
	int event_space;
	struct mutex event_read_lock;
	struct drm_prime_file_private prime;
};
```

[task_tgid — helper — include/linux/sched.h]
```c
static inline struct pid *task_tgid(struct task_struct *task)
{
	return task->thread_pid;
}
```

[get_pid — helper — include/linux/pid.h]
```c
static inline void get_pid(struct pid *pid)
{
	atomic_inc(&pid->count);
}
```

[put_pid — helper — include/linux/pid.h]
```c
static inline void put_pid(struct pid *pid)
{
	struct pid_namespace *ns;
	if (!pid)
		return;
	ns = pid->numbers[pid->level].ns;
	if (atomic_dec_and_test(&pid->count)) {
		kmem_cache_free_cache(ns->pidmap_cache, pid);
		wake_up_atomic_t_pollfree(&pid->wait);
		INIT_DELAYED_WORK(&pid->rcu_put, delayed_put_pid);
		schedule_delayed_work(&pid->rcu_put, 1);
	}
}
```

[rcu_replace_pointer — helper — include/linux/rcupdate.h]
rcu_replace_pointer → #define rcu_replace_pointer(dst, src, cond) __rcu_replace_pointer((dst), (src), (cond), __rcu) static inline void __rcu_replace_pointer(rcu_head_t **p, struct pid *ptr, int cond, rcufunc_t func) { assert_spin_locked(p); WRITE_ONCE(*p, ptr); }  (include/linux/rcupdate.h)

[synchronize_rcu — helper — kernel/rcu/update.c]
```c
void synchronize_rcu(void)
{
	if (unlikely(preemptible())) {
		wait_rcu_gp(call_rcu);
	} else {
		wait_rcu_gp(rcu_barrier);
	}
}
```

Control flow: drm_file_update_pid() is called to update the stored pid in a drm_file structure. It first checks if the file is a master node (returns early if so). Then it gets the current task's pid via task_tgid(). A quick lockless check returns early if the pid hasn't changed. Then it acquires dev->filelist_mutex, replaces the filp->pid pointer with the new pid, and releases the mutex. If the pid changed, it increments the new pid's refcount, calls synchronize_rcu(), and decrements the old pid's refcount.

Data flow: The attacker-controlled input is the current process's pid obtained via task_tgid(current). This pid is stored in filp->pid via rcu_replace_pointer(). The vulnerability is that the refcount for the new pid is taken after the pointer is stored, but the old pid's refcount is released after synchronize_rcu(), creating a window for UAF if another process updates filp->pid concurrently.

Verdict: Vulnerable
Vulnerable region:
[drivers/gpu/drm/drm_file.c]
```c
void drm_file_update_pid(struct drm_file *filp)
{
	struct drm_device *dev;
	struct pid *pid, *old;

	/*
	 * Master nodes need to keep the original ownership in order for
	 * drm_master_check_perm to keep working correctly. (See comment in
	 * drm_auth.c.)
	 */
	if (filp->was_master)
		return;

	pid = task_tgid(current);

	/*
	 * Quick unlocked check since the model is a single handover followed by
	 * exclusive repeated use.
	 */
	if (pid == rcu_access_pointer(filp->pid))
		return;

	dev = filp->minor->dev;
	mutex_lock(&dev->filelist_mutex);
	old = rcu_replace_pointer(filp->pid, pid, 1);
	mutex_unlock(&dev->filelist_mutex);

	if (pid != old) {
		get_pid(pid);
		synchronize_rcu();
		put_pid(old);
	}
}
```
CWE: CWE-416
Severity: MEDIUM

Root cause: In drm_file_update_pid(), the refcount of the new pid is incremented AFTER rcu_replace_pointer() stores the pointer into filp->pid and releases filelist_mutex. This creates a race condition where process B can replace filp->pid with its own pid before process A increments the refcount, causing the old pid to be freed when process A later calls put_pid(old).

Exploitability: The race requires CONFIG_PREEMPT_RCU=y and precise timing: process A must hit a synchronize_rcu() (RCU quiescent state) while process B is between mutex_unlock and get_pid. The race is hard to trigger but leads to UAF.

Attack preconditions:
- Running on a kernel with CONFIG_PREEMPT_RCU=y
- Two processes concurrently calling drm_file_update_pid on the same drm_file
- Process A must pass through a synchronize_rcu() operation while process B is between mutex_unlock and get_pid
- The race requires precise scheduling timing

Likely impact: The race requires CONFIG_PREEMPT_RCU=y and precise timing: process A must hit a synchronize_rcu() (RCU quiescent state) while process B is between mutex_unlock and get_pid. The race is hard to trigger but leads to UAF.

## Vulnerability Analysis

### Verdict: VULNERABLE

This code contains a **use-after-free race condition** caused by incorrect reference counting order.

### Root Cause

The code stores a PID pointer into `filp->pid` before incrementing its reference count:

```c
mutex_lock(&dev->filelist_mutex);
old = rcu_replace_pointer(filp->pid, pid, 1);  // Store pointer
mutex_unlock(&dev->filelist_mutex);

if (pid != old) {
    get_pid(pid);  // Reference count incremented AFTER storage
    synchronize_rcu();
    put_pid(old);
}
```

This violates the fundamental reference counting invariant: **a structure must hold a reference count on any pointer it stores**.

### Race Window

Between `rcu_replace_pointer()` and `get_pid(pid)`:

1. `filp->pid` points to the current process's PID structure
2. The `drm_file` structure holds **no reference count** on this PID
3. The only reference is from the process itself (`current->thread_pid`)

### Attack Path

1. Thread A in Process P calls `drm_file_update_pid(filp)`
2. Thread A stores Process P's PID in `filp->pid` via `rcu_replace_pointer()`
3. Thread A unlocks the mutex
4. **Thread B** (in another process sharing the file descriptor) reads `filp->pid` via RCU, obtaining a pointer to Process P's PID structure
5. Thread A is preempted before reaching `get_pid(pid)`
6. Process P exits (all threads terminate, including Thread A)
7. Process P's PID reference count drops to zero
8. The PID structure is freed (via `put_pid()` → `kmem_cache_free_cache()`)
9. Thread B now holds a dangling pointer to freed memory

### Exploitability

**Preconditions:**
- File descriptor sharing across processes (via `fork()` or SCM_RIGHTS)
- Precise timing to hit the race window between mutex unlock and `get_pid()`
- Process termination during this window

**Impact:**
- **Use-after-free**: Dangling pointer to freed PID structure
- **Memory corruption**: Freed memory could be reallocated and contain attacker-controlled data
- **Privilege escalation**: If attacker controls the freed memory, they could manipulate PID-based permission checks
- **Kernel crash**: Dereferencing freed memory causes kernel panic (DoS)

### Why RCU Doesn't Prevent This

The `synchronize_rcu()` call protects the **old** PID after replacement, ensuring readers finish before it's freed. However, it provides **no protection** for the **new** PID during the window before `get_pid()` is called.

### Correct Pattern

Reference count must be incremented **before** storing the pointer:

```c
mutex_lock(&dev->filelist_mutex);
old = rcu_dereference_protected(filp->pid,
                                lockdep_is_held(&dev->filelist_mutex));
if (pid != old) {
    get_pid(pid);                    // Increment FIRST
    rcu_assign_pointer(filp->pid, pid);  // Store SECOND
}
mutex_unlock(&dev->filelist_mutex);

if (pid != old) {
    synchronize_rcu();
    put_pid(old);
}
```

### Classification

**CWE-416**: Use After Free
**CWE-362**: Concurrent Execution using Shared Resource with Improper Synchronization (Race Condition)

```json
{
  "vulnerable": true,
  "cwe": "CWE-416",
  "severity": "HIGH",
  "reviewed_region": "drm_file_update_pid function in drivers/gpu/drm/drm_file.c, lines handling PID pointer replacement and reference counting",
  "vulnerable_region": "Lines between rcu_replace_pointer() and get_pid() calls - specifically the sequence where filp->pid is updated before incrementing the reference count on the new PID",
  "root_cause": "Reference count increment occurs after pointer storage, violating the invariant that a structure must hold a reference on any pointer it stores. Between rcu_replace_pointer(filp->pid, pid, 1) and get_pid(pid), the drm_file structure points to a PID with no corresponding reference count held by the structure itself.",
  "exploitability": "HIGH - Race window exists between mutex unlock and get_pid() where the stored PID pointer has no reference count protection. If the owning process exits during this window, the PID structure can be freed while still referenced by filp->pid. Requires file descriptor sharing across processes (via fork or SCM_RIGHTS) and precise timing, but these are realistic conditions in multi-process DRM applications.",
  "attack_preconditions": [
    "File descriptor sharing between processes (fork() or SCM_RIGHTS socket passing)",
    "Concurrent access to the drm_file structure from multiple processes",
    "Process termination during the race window between rcu_replace_pointer() and get_pid()",
    "Another thread reading filp->pid via RCU during the vulnerable window"
  ],
  "impact": "Use-after-free vulnerability leading to: (1) Kernel memory corruption if freed PID structure is reallocated with attacker-controlled data, (2) Potential privilege escalation through manipulation of PID-based permission checks, (3) Kernel crash/denial of service from dereferencing freed memory, (4) Information disclosure if freed memory contents are leaked",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The synchronize_rcu() call only protects the OLD PID after replacement, ensuring RCU readers complete before it's freed. It provides no protection for the NEW PID during the window before get_pid() is called. The correct pattern requires get_pid(pid) before rcu_assign_pointer(filp->pid, pid) to ensure the reference count is incremented before the pointer becomes visible to other threads.",
  "call_path": "drm_file_update_pid → task_tgid → rcu_replace_pointer → get_pid → put_pid → synchronize_rcu"
}
```


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.

## Vulnerability Analysis: reweight_eevdf Division by Zero

### Vulnerability Assessment: **LIKELY NOT EXPLOITABLE** (False Positive)

The code performs division operations without explicit zero checks:

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

However, this is likely a **false positive** for the following reasons:

### Evidence Against Exploitability

**1. Scheduler Weight Invariants**
- Task weights in the Linux scheduler are derived from nice values (-20 to +19) or scheduling policies
- The weight range for normal tasks is 15 to 88761 (see `sched_prio_to_weight` table)
- A weight of 0 would violate fundamental scheduler invariants
- The `load_weight` structure is managed by the scheduler core, which maintains these invariants

**2. Context of Usage**
- `reweight_eevdf` is called during task weight changes (priority adjustments)
- The scheduler framework validates weights before they're assigned to tasks
- This is internal scheduler code, not directly exposed to user input

**3. No Observed CVEs**
- This is production kernel code from the stable tree
- The EEVDF scheduler has been reviewed extensively
- No known CVEs exist for division-by-zero in this function

### Theoretical Attack Vector (Highly Unlikely)

For exploitation, an attacker would need to:
1. Corrupt the `weight` parameter to be 0 through memory corruption
2. Trigger a race condition during weight updates
3. Exploit a separate bug in weight calculation

**Impact if triggered**: Kernel panic (DoS only)

### Missing Defensive Programming

While not exploitable in practice, the code lacks defensive checks:

```c
// Missing guard:
if (unlikely(weight == 0)) {
    WARN_ON_ONCE(1);
    return;
}
```

This would catch programming errors or corruption without adding runtime overhead in the normal path.

### Conclusion

**Classification**: Not a security vulnerability, but a defensive programming gap

**Reasoning**: The scheduler framework maintains invariants that prevent `weight == 0`. The absence of an explicit check reflects trust in these invariants rather than a security flaw. Adding a check would be good defensive programming but isn't necessary for security.

**Residual Risk**: If a separate bug corrupts the weight value, this could cause a kernel panic, but that would be a symptom of the corruption bug, not a vulnerability in `reweight_eevdf` itself.

```json
{
  "vulnerable": false,
  "cwe": "CWE-369",
  "severity": "none",
  "reviewed_region": "reweight_eevdf function in kernel/sched/fair.c, specifically the division operations: vlag = div_s64(vlag * old_weight, weight) and vslice = div_s64(vslice * old_weight, weight)",
  "vulnerable_region": "none",
  "root_cause": "none",
  "exploitability": "not_exploitable",
  "attack_preconditions": "Would require corrupting the weight parameter to zero, which violates scheduler invariants maintained by the kernel's task weight management system",
  "impact": "none",
  "non_vulnerable_reason": "The weight parameter is derived from se->load.weight which is managed by the Linux scheduler core. Task weights are constrained to the range [15, 88761] based on nice values (-20 to +19) and scheduling policies. The scheduler framework enforces these invariants during all weight assignments and updates. A weight of 0 would violate fundamental scheduler assumptions and cannot occur through normal code paths. The function is called during task reweighting operations where the new weight has already been validated by the scheduler core before reaching this function.",
  "security_invariants": [
    "Task weights are always non-zero positive values derived from nice values or scheduling policies",
    "The load_weight structure is exclusively managed by scheduler core functions that maintain valid weight ranges",
    "Weight updates go through scheduler framework validation before reaching reweight_eevdf",
    "The weight parameter comes from se->load.weight which is set by setscheduler and related functions that enforce valid ranges"
  ],
  "invariant_verification": "The invariants are enforced by the scheduler framework's weight management system. Functions like set_load_weight() in kernel/sched/core.c map nice values to weights using the sched_prio_to_weight array, which contains only positive non-zero values. All paths that modify task weights go through these validated assignment functions. The reweight_eevdf function is an internal scheduler helper that operates on already-validated weight values.",
  "residual_risk_notes": "If a separate memory corruption vulnerability allowed arbitrary modification of the se->load.weight field to zero, this could trigger a division by zero resulting in kernel panic (DoS). However, this would be a symptom of the corruption bug rather than a vulnerability in reweight_eevdf itself. The absence of defensive checks reflects trust in scheduler invariants rather than a security flaw. Adding a WARN_ON check would catch programming errors during development but is not necessary for security in production.",
  "call_path": "reweight_eevdf → entity_eligible → pick_eevdf → update_entity_lag → calc_delta_fair → avg_vruntime"
}
```