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/torvalds/linux
Language: C
File: kernel/sys.c
Function: do_prlimit

Call path: sys_prlimit64 (kernel/sys.c) → do_prlimit (kernel/sys.c) → sys_setrlimit (kernel/sys.c)

### Primary Function

```c
static int do_prlimit(struct task_struct *tsk, unsigned int resource,
		      struct rlimit *new_rlim, struct rlimit *old_rlim)
{
	struct rlimit *rlim;
	int retval = 0;

	if (resource >= RLIM_NLIMITS)
		return -EINVAL;
	resource = array_index_nospec(resource, RLIM_NLIMITS);

	if (new_rlim) {
		if (new_rlim->rlim_cur > new_rlim->rlim_max)
			return -EINVAL;
		if (resource == RLIMIT_NOFILE &&
				new_rlim->rlim_max > sysctl_nr_open)
			return -EPERM;
	}

	/* Holding a refcount on tsk protects tsk->signal from disappearing. */
	rlim = tsk->signal->rlim + resource;
	task_lock(tsk->group_leader);
	if (new_rlim) {
		/*
		 * Keep the capable check against init_user_ns until cgroups can
		 * contain all limits.
		 */
		if (new_rlim->rlim_max > rlim->rlim_max &&
				!capable(CAP_SYS_RESOURCE))
			retval = -EPERM;
		if (!retval)
			retval = security_task_setrlimit(tsk, resource, new_rlim);
	}
	if (!retval) {
		if (old_rlim)
			*old_rlim = *rlim;
		if (new_rlim)
			*rlim = *new_rlim;
	}
	task_unlock(tsk->group_leader);

	/*
	 * RLIMIT_CPU handling. Arm the posix CPU timer if the limit is not
	 * infinite. In case of RLIM_INFINITY the posix CPU timer code
	 * ignores the rlimit.
	 */
	if (!retval && new_rlim && resource == RLIMIT_CPU &&
	    new_rlim->rlim_cur != RLIM_INFINITY &&
	    IS_ENABLED(CONFIG_POSIX_TIMERS)) {
		/*
		 * update_rlimit_cpu can fail if the task is exiting, but there
		 * may be other tasks in the thread group that are not exiting,
		 * and they need their cpu timers adjusted.
		 *
		 * The group_leader is the last task to be released, so if we
		 * cannot update_rlimit_cpu on it, then the entire process is
		 * exiting and we do not need to update at all.
		 */
		update_rlimit_cpu(tsk->group_leader, new_rlim->rlim_cur);
	}

	return retval;
}
```

### Cross-File Context

[RLIM_NLIMITS — constant — include/uapi/asm-generic/resource.h:49]
RLIM_NLIMITS → 16  (include/uapi/asm-generic/resource.h:49)

[array_index_nospec — macro — include/linux/nospec.h:51]
array_index_nospec → #define array_index_nospec(index, size) \ ({ \ typeof(index) _i = (index); \ typeof(size) _s = (size); \ unsigned long _mask = array_index_mask_nospec(_i, _s); \ \ BUILD_BUG_ON(sizeof(_i) > sizeof(long)); \ BUILD_BUG_ON(sizeof(_s) > sizeof(long)); \ \ (typeof(_i)) (_i & _mask); \ })  (include/linux/nospec.h:51)

[array_index_mask_nospec — function — include/linux/nospec.h:24]
```c
static inline unsigned long array_index_mask_nospec(unsigned long index,
					    unsigned long size)
{
	/*
	 * Always calculate and emit the mask even if the compiler
	 * thinks the mask is not needed. The compiler does not take
	 * into account the value of @index under speculation.
	 */
	OPTIMIZER_HIDE_VAR(index);
	return ~(long)(index | (size - 1UL - index)) >> (BITS_PER_LONG - 1);
}
```

[struct rlimit — struct — include/uapi/linux/resource.h:43]
```c
struct rlimit {
	__kernel_ulong_t	rlim_cur;
	__kernel_ulong_t	rlim_max;
};
```

[tsk->signal->rlim — field — include/linux/sched/signal.h:213]
struct rlimit rlim[RLIM_NLIMITS];

Control flow: The function begins by validating the resource index against RLIM_NLIMITS, returning -EINVAL if out of bounds. It then applies array_index_nospec to sanitize the index against speculative execution. If new_rlim is provided, it validates that rlim_cur <= rlim_max (returns -EINVAL if not) and enforces sysctl_nr_open for RLIMIT_NOFILE (returns -EPERM). The function then accesses the per-signal rlim array using the sanitized index, acquires task_lock on the group_leader, and performs capability checks (CAP_SYS_RESOURCE) and LSM hooks (security_task_setrlimit). If no errors occurred, it copies the old limit to old_rlim (if provided) and writes the new limit to the rlim array. The lock is released. Finally, for RLIMIT_CPU with a finite value and CONFIG_POSIX_TIMERS, it calls update_rlimit_cpu on the group_leader. The function returns the accumulated error code.

Data flow: The resource parameter (user-controlled via sys_prlimit64 call path) is first bounds-checked with resource >= RLIM_NLIMITS, then sanitized via array_index_nospec(resource, RLIM_NLIMITS) which masks the index to be zero if out-of-bounds. The sanitized resource index is used to index into tsk->signal->rlim[RLIM_NLIMITS]. The new_rlim and old_rlim pointers conditionally flow into comparisons, capability/security checks, and memory copies. The tsk parameter flows through task_lock/task_unlock for synchronization and is used to access signal->rlim.

Verdict: Not Vulnerable

Reviewed region:
[kernel/sys.c]
```c
static int do_prlimit(struct task_struct *tsk, unsigned int resource,
		      struct rlimit *new_rlim, struct rlimit *old_rlim)
{
	struct rlimit *rlim;
	int retval = 0;

	if (resource >= RLIM_NLIMITS)
		return -EINVAL;
	resource = array_index_nospec(resource, RLIM_NLIMITS);

	if (new_rlim) {
		if (new_rlim->rlim_cur > new_rlim->rlim_max)
			return -EINVAL;
		if (resource == RLIMIT_NOFILE &&
				new_rlim->rlim_max > sysctl_nr_open)
			return -EPERM;
	}

	/* Holding a refcount on tsk protects tsk->signal from disappearing. */
	rlim = tsk->signal->rlim + resource;
	task_lock(tsk->group_leader);
	if (new_rlim) {
		/*
		 * Keep the capable check against init_user_ns until cgroups can
		 * contain all limits.
		 */
		if (new_rlim->rlim_max > rlim->rlim_max &&
				!capable(CAP_SYS_RESOURCE))
			retval = -EPERM;
		if (!retval)
			retval = security_task_setrlimit(tsk, resource, new_rlim);
	}
	if (!retval) {
		if (old_rlim)
			*old_rlim = *rlim;
		if (new_rlim)
			*rlim = *new_rlim;
	}
	task_unlock(tsk->group_leader);

	/*
	 * RLIMIT_CPU handling. Arm the posix CPU timer if the limit is not
	 * infinite. In case of RLIM_INFINITY the posix CPU timer code
	 * ignores the rlimit.
	 */
	if (!retval && new_rlim && resource == RLIMIT_CPU &&
	    new_rlim->rlim_cur != RLIM_INFINITY &&
	    IS_ENABLED(CONFIG_POSIX_TIMERS)) {
		/*
		 * update_rlimit_cpu can fail if the task is exiting, but there
		 * may be other tasks in the thread group that are not exiting,
		 * and they need their cpu timers adjusted.
		 *
		 * The group_leader is the last task to be released, so if we
		 * cannot update_rlimit_cpu on it, then the entire process is
		 * exiting and we do not need to update at all.
		 */
		update_rlimit_cpu(tsk->group_leader, new_rlim->rlim_cur);
	}

	return retval;
}
```

Why it is not vulnerable: The code protects the array access `tsk->signal->rlim + resource` against speculative out-of-bounds execution through two mechanisms: (1) a standard bounds check `if (resource >= RLIM_NLIMITS) return -EINVAL;` that prevents actual out-of-bounds access, and (2) `resource = array_index_nospec(resource, RLIM_NLIMITS);` which applies a speculation-resistant mask to the index. The `array_index_nospec` macro uses `OPTIMIZER_HIDE_VAR` to prevent compiler optimization of the branch condition and computes a mask that is all-ones for valid indices and all-zeros for out-of-bounds indices, ensuring the masked index never exceeds bounds even during speculative execution. Thus, the memory access `rlim = tsk->signal->rlim + resource` is properly guarded.

Security invariants:
- The resource index must be within [0, RLIM_NLIMITS) before array access — enforced by `if (resource >= RLIM_NLIMITS) return -EINVAL;`
- The resource index must be masked against speculative bypass of the bounds check — enforced by `resource = array_index_nospec(resource, RLIM_NLIMITS);` which uses OPTIMIZER_HIDE_VAR and arithmetic masking to produce a zero index for out-of-bounds values
- The new rlim_cur must not exceed new rlim_max — enforced by `if (new_rlim->rlim_cur > new_rlim->rlim_max) return -EINVAL;`
- RLIMIT_NOFILE max must not exceed sysctl_nr_open — enforced by the check `new_rlim->rlim_max > sysctl_nr_open` returning -EPERM
- Increasing rlim_max requires CAP_SYS_RESOURCE — enforced by `if (new_rlim->rlim_max > rlim->rlim_max && !capable(CAP_SYS_RESOURCE)) retval = -EPERM;`
- LSM security hooks must approve the change — enforced by `security_task_setrlimit(tsk, resource, new_rlim)`
- Concurrent access to rlim must be serialized — enforced by `task_lock(tsk->group_leader)` / `task_unlock(tsk->group_leader)` surrounding the read and write of rlim
- The rlim array has exactly RLIM_NLIMITS elements — confirmed by `struct rlimit rlim[RLIM_NLIMITS]` in the context

Invariant verification:
- Bounds check on resource index before array access: holds=true. Evidence: The explicit check `if (resource >= RLIM_NLIMITS) return -EINVAL;` returns early if resource is out of range, preventing normal-path out-of-bounds access to `tsk->signal->rlim + resource`
- Speculative execution mitigation via index masking: holds=true. Evidence: `resource = array_index_nospec(resource, RLIM_NLIMITS);` calls array_index_mask_nospec which uses OPTIMIZER_HIDE_VAR to prevent compiler knowledge of the index during speculation, and computes a mask via `~(long)(index | (size - 1UL - index)) >> (BITS_PER_LONG - 1)` that zeros out the index if it is >= size
- RLIMIT_NOFILE does not exceed sysctl_nr_open: holds=true. Evidence: The condition `if (resource == RLIMIT_NOFILE && new_rlim->rlim_max > sysctl_nr_open) return -EPERM;` enforces this constraint before any array write
- Privilege escalation prevention for increasing limits: holds=true. Evidence: The check `if (new_rlim->rlim_max > rlim->rlim_max && !capable(CAP_SYS_RESOURCE)) retval = -EPERM;` prevents unprivileged users from raising the hard limit
- Concurrent access protection via task lock: holds=true. Evidence: `task_lock(tsk->group_leader)` is acquired before reading/writing `*rlim` and `task_unlock(tsk->group_leader)` is called after, with the lock surrounding both the old_rlim read and new_rlim write
- Array size matches bounds check constant: holds=true. Evidence: The context confirms `struct rlimit rlim[RLIM_NLIMITS]` and `RLIM_NLIMITS → 16`, matching the constant used in the bounds check and array_index_nospec call

Residual risk notes: The safety of `array_index_nospec` depends on the correctness of `OPTIMIZER_HIDE_VAR` and the compiler's ability to emit the masking instructions on all supported architectures. The behavioral equivalence of the masking formula `~(long)(index | (size - 1UL - index)) >> (BITS_PER_LONG - 1)` for all unsigned long values is assumed from the Linux kernel implementation. No additional runtime verification of the mask is performed in this function.

Based on the provided code and context, the `do_prlimit` function appears to be secure and correctly implements necessary guards.

### Security Analysis
1. **Out-of-Bounds Access:** The `resource` parameter is checked against `RLIM_NLIMITS` and then sanitized using the `array_index_nospec` macro. This correctly prevents both conventional array out-of-bounds access and speculative execution (Spectre v1) attacks.
2. **Privilege Escalation:** If `new_rlim` is provided, the code ensures that the caller cannot increase `rlim_max` without having the `CAP_SYS_RESOURCE` capability.
3. **Data Integrity & Locking:** Access and modification of the target task's limits are safely enclosed inside a spinlock (`task_lock(tsk->group_leader)`), which protects against concurrent modifications. The code also rightly notes that holding a reference on `tsk` prevents `tsk->signal` from being freed concurrently.
4. **Logic Checks:** The function validates that `rlim_cur <= rlim_max`. It also explicitly enforces limits on open files (`RLIMIT_NOFILE`) against the global system limit (`sysctl_nr_open`).
5. **CPU Timers:** Updates to `RLIMIT_CPU` use `update_rlimit_cpu()` on the group leader. If the process is exiting, `update_rlimit_cpu()` is allowed to fail safely without causing a crash or leak.

No vulnerabilities were found in the supplied code. The implementation follows standard Linux kernel security paradigms.

```json
{
  "vulnerable": false,
  "cwe": "N/A",
  "severity": "None",
  "reviewed_region": "static int do_prlimit(struct task_struct *tsk, unsigned int resource,\n\t\t      struct rlimit *new_rlim, struct rlimit *old_rlim)\n{\n\tstruct rlimit *rlim;\n\tint retval = 0;\n\n\tif (resource >= RLIM_NLIMITS)\n\t\treturn -EINVAL;\n\tresource = array_index_nospec(resource, RLIM_NLIMITS);\n\n\tif (new_rlim) {\n\t\tif (new_rlim->rlim_cur > new_rlim->rlim_max)\n\t\t\treturn -EINVAL;\n\t\tif (resource == RLIMIT_NOFILE &&\n\t\t\t\tnew_rlim->rlim_max > sysctl_nr_open)\n\t\t\treturn -EPERM;\n\t}\n\n\t/* Holding a refcount on tsk protects tsk->signal from disappearing. */\n\trlim = tsk->signal->rlim + resource;\n\ttask_lock(tsk->group_leader);\n\tif (new_rlim) {\n\t\t/*\n\t\t * Keep the capable check against init_user_ns until cgroups can\n\t\t * contain all limits.\n\t\t */\n\t\tif (new_rlim->rlim_max > rlim->rlim_max &&\n\t\t\t\t!capable(CAP_SYS_RESOURCE))\n\t\t\tretval = -EPERM;\n\t\tif (!retval)\n\t\t\tretval = security_task_setrlimit(tsk, resource, new_rlim);\n\t}\n\tif (!retval) {\n\t\tif (old_rlim)\n\t\t\t*old_rlim = *rlim;\n\t\tif (new_rlim)\n\t\t\t*rlim = *new_rlim;\n\t}\n\ttask_unlock(tsk->group_leader);\n\n\t/*\n\t * RLIMIT_CPU handling. Arm the posix CPU timer if the limit is not\n\t * infinite. In case of RLIM_INFINITY the posix CPU timer code\n\t * ignores the rlimit.\n\t */\n\tif (!retval && new_rlim && resource == RLIMIT_CPU &&\n\t    new_rlim->rlim_cur != RLIM_INFINITY &&\n\t    IS_ENABLED(CONFIG_POSIX_TIMERS)) {\n\t\tupdate_rlimit_cpu(tsk->group_leader, new_rlim->rlim_cur);\n\t}\n\n\treturn retval;\n}",
  "vulnerable_region": "N/A",
  "root_cause": "N/A",
  "exploitability": "N/A",
  "attack_preconditions": "N/A",
  "impact": "N/A",
  "non_vulnerable_reason": "The function correctly implements boundary checks with bounds masking via `array_index_nospec` to prevent both standard out-of-bounds access and speculative execution attacks. Limits are properly synchronized using task-level spinlocks, and adequate privilege checks (`capable(CAP_SYS_RESOURCE)`) and LSM hooks (`security_task_setrlimit`) are implemented.",
  "security_invariants": [
    "The `resource` index must be within `0` and `RLIM_NLIMITS - 1`.",
    "Modifications to limits must be synchronized by holding `task_lock(tsk->group_leader)`.",
    "Increases to `rlim_max` must require `CAP_SYS_RESOURCE`.",
    "`rlim_cur` must not exceed `rlim_max`."
  ],
  "invariant_verification": [
    "The `resource` bounds check and `array_index_nospec` macro application effectively enforce spatial safety on the `rlim` array.",
    "Locking around the check and update phases ensures no concurrent writers can bypass the capability check or cause data races.",
    "Conditional checks properly ensure values are within permissible kernel limits (`sysctl_nr_open` for `RLIMIT_NOFILE`)."
  ],
  "residual_risk_notes": "None. The sample follows known robust patterns for resource limit updates in the Linux kernel.",
  "call_path": [
    "sys_prlimit64",
    "do_prlimit"
  ]
}
```

static int do_prlimit(struct task_struct *tsk, unsigned int resource,
		      struct rlimit *new_rlim, struct rlimit *old_rlim)
{
	struct rlimit *rlim;
	int retval = 0;

	if (resource >= RLIM_NLIMITS)
		return -EINVAL;
	resource = array_index_nospec(resource, RLIM_NLIMITS);

	if (new_rlim) {
		if (new_rlim->rlim_cur > new_rlim->rlim_max)
			return -EINVAL;
		if (resource == RLIMIT_NOFILE &&
				new_rlim->rlim_max > sysctl_nr_open)
			return -EPERM;
	}

	/* Holding a refcount on tsk protects tsk->signal from disappearing. */
	rlim = tsk->signal->rlim + resource;
	task_lock(tsk->group_leader);
	if (new_rlim) {
		/*
		 * Keep the capable check against init_user_ns until cgroups can
		 * contain all limits.
		 */
		if (new_rlim->rlim_max > rlim->rlim_max &&
				!capable(CAP_SYS_RESOURCE))
			retval = -EPERM;
		if (!retval)
			retval = security_task_setrlimit(tsk, resource, new_rlim);
	}
	if (!retval) {
		if (old_rlim)
			*old_rlim = *rlim;
		if (new_rlim)
			*rlim = *new_rlim;
	}
	task_unlock(tsk->group_leader);

	/*
	 * RLIMIT_CPU handling. Arm the posix CPU timer if the limit is not
	 * infinite. In case of RLIM_INFINITY the posix CPU timer code
	 * ignores the rlimit.
	 */
	if (!retval && new_rlim && resource == RLIMIT_CPU &&
	    new_rlim->rlim_cur != RLIM_INFINITY &&
	    IS_ENABLED(CONFIG_POSIX_TIMERS)) {
		update_rlimit_cpu(tsk->group_leader, new_rlim->rlim_cur);
	}

	return retval;
}


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

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

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

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

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

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

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

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

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

Repository: github.com/modelcontextprotocol/go-sdk
Language: Go
File: mcp/streamable.go
Function: ServeHTTP

Call path: ServeHTTP (mcp/streamable.go) → http.LocalAddrContextKey (net/http) → util.IsLoopback (internal/util/net.go)

### Primary Function

```go
func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// DNS rebinding protection: auto-enabled for localhost servers.
	// See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
	if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" {
		if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil {
			if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) {
				http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden)
				return
			}
		}
	}

	// Allow multiple 'Accept' headers.
	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept#syntax
	accept := strings.Split(strings.Join(req.Header.Values("Accept"), ","), ",")
	var jsonOK, streamOK bool
	for _, c := range accept {
		switch strings.TrimSpace(c) {
		case "application/json", "application/*":
			jsonOK = true
		case "text/event-stream", "text/*":
			streamOK = true
		case "*/*":
			jsonOK = true
			streamOK = true
		}
	}

	if req.Method == http.MethodGet {
		if !streamOK {
			http.Error(w, "Accept must contain 'text/event-stream' for GET requests", http.StatusBadRequest)
			return
		}
	} else if (!jsonOK || !streamOK) && req.Method != http.MethodDelete { // TODO: consolidate with handling of http method below.
		http.Error(w, "Accept must contain both 'application/json' and 'text/event-stream'", http.StatusBadRequest)
		return
	}

	sessionID := req.Header.Get(sessionIDHeader)
	var sessInfo *sessionInfo
	if sessionID != "" {
		h.mu.Lock()
		sessInfo = h.sessions[sessionID]
		h.mu.Unlock()
		if sessInfo == nil && !h.opts.Stateless {
			// Unless we're in 'stateless' mode, which doesn't perform any Session-ID
			// validation, we require that the session ID matches a known session.
			//
			// In stateless mode, a temporary transport is be created below.
			http.Error(w, "session not found", http.StatusNotFound)
			return
		}
		// Prevent session hijacking: if the session was created with a user ID,
		// verify that subsequent requests come from the same user.
		if sessInfo != nil && sessInfo.userID != "" {
			tokenInfo := auth.TokenInfoFromContext(req.Context())
			if tokenInfo == nil || tokenInfo.UserID != sessInfo.userID {
				http.Error(w, "session user mismatch", http.StatusForbidden)
				return
			}
		}
	}

	if req.Method == http.MethodDelete {
		if sessionID == "" {
			http.Error(w, "Bad Request: DELETE requires an Mcp-Session-Id header", http.StatusBadRequest)
			return
		}
		if sessInfo != nil { // sessInfo may be nil in stateless mode
			// Closing the session also removes it from h.sessions, due to the
			// onClose callback.
			sessInfo.session.Close()
		}
		w.WriteHeader(http.StatusNoContent)
		return
	}

	switch req.Method {
	case http.MethodPost, http.MethodGet:
		if req.Method == http.MethodGet && (h.opts.Stateless || sessionID == "") {
			if h.opts.Stateless {
				// Per MCP spec: server MUST return 405 if it doesn't offer SSE stream.
				// In stateless mode, GET (SSE streaming) is not supported.
				// RFC 9110 §15.5.6: 405 responses MUST include Allow header.
				w.Header().Set("Allow", "POST")
				http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
			} else {
				// In stateful mode, GET is supported but requires a session ID.
				// This is a precondition error, similar to DELETE without session.
				http.Error(w, "Bad Request: GET requires an Mcp-Session-Id header", http.StatusBadRequest)
			}
			return
		}
	default:
		// RFC 9110 §15.5.6: 405 responses MUST include Allow header.
		if h.opts.Stateless {
			w.Header().Set("Allow", "POST")
		} else {
			w.Header().Set("Allow", "GET, POST, DELETE")
		}
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}

	// [§2.7] of the spec (2025-06-18) states:
	//
	// "If using HTTP, the client MUST include the MCP-Protocol-Version:
	// <protocol-version> HTTP header on all subsequent requests to the MCP
	// server, allowing the MCP server to respond based on the MCP protocol
	// version.
	//
	// For example: MCP-Protocol-Version: 2025-06-18
	// The protocol version sent by the client SHOULD be the one negotiated during
	// initialization.
	//
	// For backwards compatibility, if the server does not receive an
	// MCP-Protocol-Version header, and has no other way to identify the version -
	// for example, by relying on the protocol version negotiated during
	// initialization - the server SHOULD assume protocol version 2025-03-26.
	//
	// If the server receives a request with an invalid or unsupported
	// MCP-Protocol-Version, it MUST respond with 400 Bad Request."
	//
	// Since this wasn't present in the 2025-03-26 version of the spec, this
	// effectively means:
	//  1. IF the client provides a version header, it must be a supported
	//     version.
	//  2. In stateless mode, where we've lost the state of the initialize
	//     request, we assume that whatever the client tells us is the truth (or
	//     assume 2025-03-26 if the client doesn't say anything).
	//
	// This logic matches the typescript SDK.
	//
	// [§2.7]: https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#protocol-version-header
	protocolVersion := req.Header.Get(protocolVersionHeader)
	if protocolVersion == "" {
		protocolVersion = protocolVersion20250326
	}
	if !slices.Contains(supportedProtocolVersions, protocolVersion) {
		http.Error(w, fmt.Sprintf("Bad Request: Unsupported protocol version (supported versions: %s)", strings.Join(supportedProtocolVersions, ",")), http.StatusBadRequest)
		return
	}

	if sessInfo == nil {
		server := h.getServer(req)
		if server == nil {
			// The getServer argument to NewStreamableHTTPHandler returned nil.
			http.Error(w, "no server available", http.StatusBadRequest)
			return
		}
		if sessionID == "" {
			// In stateless mode, sessionID may be nonempty even if there's no
			// existing transport.
			sessionID = server.opts.GetSessionID()
		}
		transport := &StreamableServerTransport{
			SessionID:    sessionID,
			Stateless:    h.opts.Stateless,
			EventStore:   h.opts.EventStore,
			jsonResponse: h.opts.JSONResponse,
			logger:       h.opts.Logger,
		}

		// Sessions without a session ID are also stateless: there's no way to
		// address them.
		stateless := h.opts.Stateless || sessionID == ""
		// To support stateless mode, we initialize the session with a default
		// state, so that it doesn't reject subsequent requests.
		var connectOpts *ServerSessionOptions
		if stateless {
			// Peek at the body to see if it is initialize or initialized.
			// We want those to be handled as usual.
			var hasInitialize, hasInitialized bool
			{
				// TODO: verify that this allows protocol version negotiation for
				// stateless servers.
				body, err := io.ReadAll(req.Body)
				if err != nil {
					http.Error(w, "failed to read body", http.StatusInternalServerError)
					return
				}
				req.Body.Close()

				// Reset the body so that it can be read later.
				req.Body = io.NopCloser(bytes.NewBuffer(body))

				msgs, _, err := readBatch(body)
				if err == nil {
					for _, msg := range msgs {
						if req, ok := msg.(*jsonrpc.Request); ok {
							switch req.Method {
							case methodInitialize:
								hasInitialize = true
							case notificationInitialized:
								hasInitialized = true
							}
						}
					}
				}
			}

			// If we don't have InitializeParams or InitializedParams in the request,
			// set the initial state to a default value.
			state := new(ServerSessionState)
			if !hasInitialize {
				state.InitializeParams = &InitializeParams{
					ProtocolVersion: protocolVersion,
				}
			}
			if !hasInitialized {
				state.InitializedParams = new(InitializedParams)
			}
			state.LogLevel = "info"
			connectOpts = &ServerSessionOptions{
				State: state,
			}
		} else {
			// Cleanup is only required in stateful mode, as transportation is
			// not stored in the map otherwise.
			connectOpts = &ServerSessionOptions{
				onClose: func() {
					h.mu.Lock()
					defer h.mu.Unlock()
					if info, ok := h.sessions[transport.SessionID]; ok {
						info.stopTimer()
						delete(h.sessions, transport.SessionID)
						if h.onTransportDeletion != nil {
							h.onTransportDeletion(transport.SessionID)
						}
					}
				},
			}
		}

		// Pass req.Context() here, to allow middleware to add context values.
		// The context is detached in the jsonrpc2 library when handling the
		// long-running stream.
		session, err := server.Connect(req.Context(), transport, connectOpts)
		if err != nil {
			http.Error(w, "failed connection", http.StatusInternalServerError)
			return
		}
		// Capture the user ID from the token info to enable session hijacking
		// prevention on subsequent requests.
		var userID string
		if tokenInfo := auth.TokenInfoFromContext(req.Context()); tokenInfo != nil {
			userID = tokenInfo.UserID
		}
		sessInfo = &sessionInfo{
			session:   session,
			transport: transport,
			userID:    userID,
		}

		if stateless {
			// Stateless mode: close the session when the request exits.
			defer session.Close() // close the fake session after handling the request
		} else {
			// Otherwise, save the transport so that it can be reused

			// Clean up the session when it times out.
			//
			// Note that the timer here may fire multiple times, but
			// sessInfo.session.Close is idempotent.
			if h.opts.SessionTimeout > 0 {
				sessInfo.timeout = h.opts.SessionTimeout
				sessInfo.timer = time.AfterFunc(sessInfo.timeout, func() {
					sessInfo.session.Close()
				})
			}
			h.mu.Lock()
			h.sessions[transport.SessionID] = sessInfo
			h.mu.Unlock()
			defer func() {
				// If initialization failed, clean up the session (#578).
				if session.InitializeParams() == nil {
					// Initialization failed.
					session.Close()
				}
			}()
		}
	}

	if req.Method == http.MethodPost {
		sessInfo.startPOST()
		defer sessInfo.endPOST()
	}

	sessInfo.transport.ServeHTTP(w, req)
}
```

### Cross-File Context

[IsLoopback — function — internal/util/net.go:1-26]
```go
func IsLoopback(addr string) bool {
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		// If SplitHostPort fails, it might be just a host without a port.
		host = strings.Trim(addr, "[]")
	}
	if host == "localhost" {
		return true
	}
	ip, err := netip.ParseAddr(host)
	if err != nil {
		return false
	}
	return ip.IsLoopback()
}
```

[disablelocalhostprotection — var — mcp/streamable.go:228]
var disablelocalhostprotection = mcpgodebug.Value("disablelocalhostprotection")

[DisableLocalhostProtection — field — mcp/streamable.go:176]
DisableLocalhostProtection bool

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 ServeHTTP function processes incoming HTTP requests through a multi-stage pipeline. First, DNS rebinding protection checks if the request arrives on a loopback interface and validates that the Host header also resolves to a loopback address, returning 403 if mismatched. Next, Accept header parsing determines which content types the client supports. Method-specific validation then enforces Accept header requirements: GET requires text/event-stream, and non-GET/non-DELETE methods require both application/json and text/event-stream. Session ID handling looks up existing sessions under mutex protection and validates user ID consistency for session hijacking prevention. DELETE requests clean up sessions. POST and GET proceed to protocol version validation, followed by session creation if needed (with special stateless mode handling that peeks at the request body). Finally, the transport's ServeHTTP is called to handle the request. Early returns occur on 403 Forbidden, 400 Bad Request, 404 Not Found, and 405 Method Not Allowed responses throughout the pipeline.

Data flow: The local address is extracted from the request context via req.Context().Value(http.LocalAddrContextKey) as a net.Addr, then converted to string via localAddr.String() and passed to util.IsLoopback(). The Host header value from req.Host is passed directly to util.IsLoopback(). Both results feed into the DNS rebinding guard condition. The Accept header values are parsed by splitting on commas and trimming whitespace, setting jsonOK and streamOK boolean flags. Session ID flows from req.Header.Get(sessionIDHeader) through mutex-protected map lookup in h.sessions. The token info flows from auth.TokenInfoFromContext(req.Context()) and is compared against sessInfo.userID for session hijacking prevention. Protocol version flows from req.Header.Get(protocolVersionHeader) with a default fallback, validated against supportedProtocolVersions via slices.Contains.

Verdict: Not Vulnerable

Reviewed region:
[mcp/streamable.go]
```go
func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// DNS rebinding protection: auto-enabled for localhost servers.
	// See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
	if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" {
		if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil {
			if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) {
				http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden)
				return
			}
		}
	}

	// Allow multiple 'Accept' headers.
	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept#syntax
	accept := strings.Split(strings.Join(req.Header.Values("Accept"), ","), ",")
	var jsonOK, streamOK bool
	for _, c := range accept {
		switch strings.TrimSpace(c) {
		case "application/json", "application/*":
			jsonOK = true
		case "text/event-stream", "text/*":
			streamOK = true
		case "*/*":
			jsonOK = true
			streamOK = true
		}
	}

	if req.Method == http.MethodGet {
		if !streamOK {
			http.Error(w, "Accept must contain 'text/event-stream' for GET requests", http.StatusBadRequest)
			return
		}
	} else if (!jsonOK || !streamOK) && req.Method != http.MethodDelete { // TODO: consolidate with handling of http method below.
		http.Error(w, "Accept must contain both 'application/json' and 'text/event-stream'", http.StatusBadRequest)
		return
	}

	sessionID := req.Header.Get(sessionIDHeader)
	var sessInfo *sessionInfo
	if sessionID != "" {
		h.mu.Lock()
		sessInfo = h.sessions[sessionID]
		h.mu.Unlock()
		if sessInfo == nil && !h.opts.Stateless {
			// Unless we're in 'stateless' mode, which doesn't perform any Session-ID
			// validation, we require that the session ID matches a known session.
			//
			// In stateless mode, a temporary transport is be created below.
			http.Error(w, "session not found", http.StatusNotFound)
			return
		}
		// Prevent session hijacking: if the session was created with a user ID,
		// verify that subsequent requests come from the same user.
		if sessInfo != nil && sessInfo.userID != "" {
			tokenInfo := auth.TokenInfoFromContext(req.Context())
			if tokenInfo == nil || tokenInfo.UserID != sessInfo.userID {
				http.Error(w, "session user mismatch", http.StatusForbidden)
				return
			}
		}
	}

	if req.Method == http.MethodDelete {
		if sessionID == "" {
			http.Error(w, "Bad Request: DELETE requires an Mcp-Session-Id header", http.StatusBadRequest)
			return
		}
		if sessInfo != nil { // sessInfo may be nil in stateless mode
			// Closing the session also removes it from h.sessions, due to the
			// onClose callback.
			sessInfo.session.Close()
		}
		w.WriteHeader(http.StatusNoContent)
		return
	}

	switch req.Method {
	case http.MethodPost, http.MethodGet:
		if req.Method == http.MethodGet && (h.opts.Stateless || sessionID == "") {
			if h.opts.Stateless {
				// Per MCP spec: server MUST return 405 if it doesn't offer SSE stream.
				// In stateless mode, GET (SSE streaming) is not supported.
				// RFC 9110 §15.5.6: 405 responses MUST include Allow header.
				w.Header().Set("Allow", "POST")
				http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
			} else {
				// In stateful mode, GET is supported but requires a session ID.
				// This is a precondition error, similar to DELETE without session.
				http.Error(w, "Bad Request: GET requires an Mcp-Session-Id header", http.StatusBadRequest)
			}
			return
		}
	default:
		// RFC 9110 §15.5.6: 405 responses MUST include Allow header.
		if h.opts.Stateless {
			w.Header().Set("Allow", "POST")
		} else {
			w.Header().Set("Allow", "GET, POST, DELETE")
		}
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}

	// [§2.7] of the spec (2025-06-18) states:
	//
	// "If using HTTP, the client MUST include the MCP-Protocol-Version:
	// <protocol-version> HTTP header on all subsequent requests to the MCP
	// server, allowing the MCP server to respond based on the MCP protocol
	// version.
	//
	// For example: MCP-Protocol-Version: 2025-06-18
	// The protocol version sent by the client SHOULD be the one negotiated during
	// initialization.
	//
	// For backwards compatibility, if the server does not receive an
	// MCP-Protocol-Version header, and has no other way to identify the version -
	// for example, by relying on the protocol version negotiated during
	// initialization - the server SHOULD assume protocol version 2025-03-26.
	//
	// If the server receives a request with an invalid or unsupported
	// MCP-Protocol-Version, it MUST respond with 400 Bad Request."
	//
	// Since this wasn't present in the 2025-03-26 version of the spec, this
	// effectively means:
	//  1. IF the client provides a version header, it must be a supported
	//     version.
	//  2. In stateless mode, where we've lost the state of the initialize
	//     request, we assume that whatever the client tells us is the truth (or
	//     assume 2025-03-26 if the client doesn't say anything).
	//
	// This logic matches the typescript SDK.
	//
	// [§2.7]: https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#protocol-version-header
	protocolVersion := req.Header.Get(protocolVersionHeader)
	if protocolVersion == "" {
		protocolVersion = protocolVersion20250326
	}
	if !slices.Contains(supportedProtocolVersions, protocolVersion) {
		http.Error(w, fmt.Sprintf("Bad Request: Unsupported protocol version (supported versions: %s)", strings.Join(supportedProtocolVersions, ",")), http.StatusBadRequest)
		return
	}

	if sessInfo == nil {
		server := h.getServer(req)
		if server == nil {
			// The getServer argument to NewStreamableHTTPHandler returned nil.
			http.Error(w, "no server available", http.StatusBadRequest)
			return
		}
		if sessionID == "" {
			// In stateless mode, sessionID may be nonempty even if there's no
			// existing transport.
			sessionID = server.opts.GetSessionID()
		}
		transport := &StreamableServerTransport{
			SessionID:    sessionID,
			Stateless:    h.opts.Stateless,
			EventStore:   h.opts.EventStore,
			jsonResponse: h.opts.JSONResponse,
			logger:       h.opts.Logger,
		}

		// Sessions without a session ID are also stateless: there's no way to
		// address them.
		stateless := h.opts.Stateless || sessionID == ""
		// To support stateless mode, we initialize the session with a default
		// state, so that it doesn't reject subsequent requests.
		var connectOpts *ServerSessionOptions
		if stateless {
			// Peek at the body to see if it is initialize or initialized.
			// We want those to be handled as usual.
			var hasInitialize, hasInitialized bool
			{
				// TODO: verify that this allows protocol version negotiation for
				// stateless servers.
				body, err := io.ReadAll(req.Body)
				if err != nil {
					http.Error(w, "failed to read body", http.StatusInternalServerError)
					return
				}
				req.Body.Close()

				// Reset the body so that it can be read later.
				req.Body = io.NopCloser(bytes.NewBuffer(body))

				msgs, _, err := readBatch(body)
				if err == nil {
					for _, msg := range msgs {
						if req, ok := msg.(*jsonrpc.Request); ok {
							switch req.Method {
							case methodInitialize:
								hasInitialize = true
							case notificationInitialized:
								hasInitialized = true
							}
						}
					}
				}
			}

			// If we don't have InitializeParams or InitializedParams in the request,
			// set the initial state to a default value.
			state := new(ServerSessionState)
			if !hasInitialize {
				state.InitializeParams = &InitializeParams{
					ProtocolVersion: protocolVersion,
				}
			}
			if !hasInitialized {
				state.InitializedParams = new(InitializedParams)
			}
			state.LogLevel = "info"
			connectOpts = &ServerSessionOptions{
				State: state,
			}
		} else {
			// Cleanup is only required in stateful mode, as transportation is
			// not stored in the map otherwise.
			connectOpts = &ServerSessionOptions{
				onClose: func() {
					h.mu.Lock()
					defer h.mu.Unlock()
					if info, ok := h.sessions[transport.SessionID]; ok {
						info.stopTimer()
						delete(h.sessions, transport.SessionID)
						if h.onTransportDeletion != nil {
							h.onTransportDeletion(transport.SessionID)
						}
					}
				},
			}
		}

		// Pass req.Context() here, to allow middleware to add context values.
		// The context is detached in the jsonrpc2 library when handling the
		// long-running stream.
		session, err := server.Connect(req.Context(), transport, connectOpts)
		if err != nil {
			http.Error(w, "failed connection", http.StatusInternalServerError)
			return
		}
		// Capture the user ID from the token info to enable session hijacking
		// prevention on subsequent requests.
		var userID string
		if tokenInfo := auth.TokenInfoFromContext(req.Context()); tokenInfo != nil {
			userID = tokenInfo.UserID
		}
		sessInfo = &sessionInfo{
			session:   session,
			transport: transport,
			userID:    userID,
		}

		if stateless {
			// Stateless mode: close the session when the request exits.
			defer session.Close() // close the fake session after handling the request
		} else {
			// Otherwise, save the transport so that it can be reused

			// Clean up the session when it times out.
			//
			// Note that the timer here may fire multiple times, but
			// sessInfo.session.Close is idempotent.
			if h.opts.SessionTimeout > 0 {
				sessInfo.timeout = h.opts.SessionTimeout
				sessInfo.timer = time.AfterFunc(sessInfo.timeout, func() {
					sessInfo.session.Close()
				})
			}
			h.mu.Lock()
			h.sessions[transport.SessionID] = sessInfo
			h.mu.Unlock()
			defer func() {
				// If initialization failed, clean up the session (#578).
				if session.InitializeParams() == nil {
					// Initialization failed.
					session.Close()
				}
			}()
		}
	}

	if req.Method == http.MethodPost {
		sessInfo.startPOST()
		defer sessInfo.endPOST()
	}

	sessInfo.transport.ServeHTTP(w, req)
}
```

Why it is not vulnerable: The DNS rebinding protection is correctly implemented. When the local address resolves to a loopback interface (util.IsLoopback(localAddr.String()) returns true), the code enforces that the Host header must also resolve to a loopback address (util.IsLoopback(req.Host) must return true). If the Host header does not resolve to loopback, the request is rejected with a 403 Forbidden response. The util.IsLoopback function properly handles multiple address formats: it uses net.SplitHostPort to extract the host component (handling addresses with ports), trims IPv6 brackets, checks for the 'localhost' literal, and uses netip.ParseAddr with ip.IsLoopback() for IP address validation. The protection is properly gated behind DisableLocalhostProtection and disablelocalhostprotection configuration flags, allowing administrators to explicitly disable it when needed (e.g., behind a reverse proxy). Invalid or unparseable addresses in util.IsLoopback return false, which is a safe default.

Security invariants:
- When localhost protection is not explicitly disabled (via h.opts.DisableLocalhostProtection or disablelocalhostprotection), the DNS rebinding guard must execute. Enforced by: if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1"
- The local address must be successfully extracted from the request context and must be non-nil before applying the loopback check. Enforced by: if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil
- When the server is bound to a loopback interface, the Host header must also resolve to a loopback address. Enforced by: if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) { return 403 }
- util.IsLoopback must correctly parse addresses with and without port numbers. Enforced by: net.SplitHostPort(addr) to extract host, with fallback handling when it fails
- util.IsLoopback must correctly identify loopback addresses in multiple formats: 'localhost' literal, IPv4 loopback (127.0.0.0/8), and IPv6 loopback (::1). Enforced by: host == "localhost" check and ip.IsLoopback() after netip.ParseAddr
- Invalid or unparseable addresses must not be treated as loopback (safe default). Enforced by: if err != nil { return false } in util.IsLoopback
- Session access is properly synchronized to prevent race conditions. Enforced by: h.mu.Lock()/h.mu.Unlock() around h.sessions map access
- Session hijacking is prevented by verifying user ID consistency. Enforced by: if tokenInfo == nil || tokenInfo.UserID != sessInfo.userID { return 403 }

Invariant verification:
- DNS rebinding protection is enabled by default and can only be explicitly disabled: holds=true. Evidence: The condition !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" requires both the config field and the debug variable to explicitly disable the protection
- Local address extraction is safe and guards against missing context value: holds=true. Evidence: The type assertion with ok check: req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil ensures the code only proceeds when a valid address is present
- Loopback detection correctly handles host:port format addresses: holds=true. Evidence: util.IsLoopback calls net.SplitHostPort(addr) which correctly separates host and port components, then operates on the host portion only
- Loopback detection correctly identifies localhost, IPv4 loopback, and IPv6 loopback: holds=true. Evidence: util.IsLoopback checks host == "localhost" for the literal, and uses netip.ParseAddr(host) followed by ip.IsLoopback() which covers 127.0.0.0/8 and ::1
- Unparseable addresses default to non-loopback (fail-safe): holds=true. Evidence: Both error paths in util.IsLoopback (net.SplitHostPort error and netip.ParseAddr error) lead to return false or fall through to return false
- Mismatched Host header on loopback interface results in request rejection: holds=true. Evidence: if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) { http.Error(w, ..., http.StatusForbidden); return } rejects requests where local address is loopback but Host is not
- Session map access is protected by mutex: holds=true. Evidence: h.mu.Lock() before sessInfo = h.sessions[sessionID] and h.mu.Unlock() after; same pattern for write operations with h.sessions[transport.SessionID] = sessInfo

Residual risk notes: The DNS rebinding protection is skipped if the local address cannot be extracted from the request context (e.g., if the HTTP server does not populate http.LocalAddrContextKey). This is acceptable because the protection specifically applies to servers bound to loopback interfaces; if no local address information is available, the condition cannot be evaluated. The util.IsLoopback function does not perform DNS resolution on hostnames in the Host header, but this is correct behavior since the check is designed to detect when the Host header contains a non-loopback address literal or hostname that was not validated as loopback during the connection setup. The req.Host field is controlled by the client, but the IsLoopback check on it is the appropriate defense mechanism.

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

Based on the provided code and context, there are a few security-relevant behaviors to examine: DNS rebinding protection, session hijack prevention, and resource consumption.

### 1. Resource Consumption (Potential DoS)
In stateless mode, the server peeks into the request body to evaluate if it contains an initialization message:
```go
body, err := io.ReadAll(req.Body)
```
`io.ReadAll` reads from the reader until `EOF`. Because there is no explicit size limit (such as using `io.LimitReader` or `http.MaxBytesReader`), an attacker could send an extremely large request body, causing the server to allocate excessive memory and potentially crash due to Out-Of-Memory (OOM) errors.

**Exploitability:** If the HTTP server configuration (which is outside this function) does not apply a global request body limit, an unauthenticated attacker could easily exploit this to cause a Denial of Service.

### 2. DNS Rebinding Protection
The function includes explicit logic to protect local MCP servers from DNS rebinding attacks:
```go
if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) {
    http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden)
    return
}
```
If the server is listening on a loopback interface, it enforces that the `Host` header must also resolve to a loopback address.
The helper `util.IsLoopback` properly splits the host and port, gracefully handles IPv6 brackets, and explicitly matches `"localhost"` or parses the IP via `netip.ParseAddr` to verify `ip.IsLoopback()`. This correctly thwarts an attacker attempting to use a malicious domain (e.g., `attacker.com` pointing to `127.0.0.1`), because the `Host` header (`attacker.com`) will fail `netip.ParseAddr`.

### 3. Session Hijacking Prevention
Stateful session handling associates a `userID` with a session upon creation. On subsequent requests, the handler explicitly verifies this bound `userID` against the context's authenticated `TokenInfo`:
```go
if tokenInfo == nil || tokenInfo.UserID != sessInfo.userID {
    http.Error(w, "session user mismatch", http.StatusForbidden)
    return
}
```
This strictly prevents an attacker (or a different authenticated user) from guessing or reusing a victim's session ID, provided the upstream authentication middleware reliably sets the token info in the request context.

### Conclusion
The code correctly implements standard defenses against DNS Rebinding and Session Hijacking. However, the **unbounded `io.ReadAll(req.Body)`** introduces a localized risk of Denial of Service (DoS) in `Stateless` mode, contingent on whether the parent HTTP server enforces maximum byte limits on requests.

```json
{
  "vulnerable": true,
  "cwe": "CWE-400",
  "severity": "Medium",
  "reviewed_region": "func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)",
  "vulnerable_region": "body, err := io.ReadAll(req.Body)\nif err != nil {\n\thttp.Error(w, \"failed to read body\", http.StatusInternalServerError)\n\treturn\n}\nreq.Body.Close()\n\n// Reset the body so that it can be read later.\nreq.Body = io.NopCloser(bytes.NewBuffer(body))",
  "root_cause": "The handler reads the entire HTTP request body into memory using `io.ReadAll(req.Body)` without applying any bounds or size limits (such as using `io.LimitReader` or `http.MaxBytesReader`).",
  "exploitability": "An unauthenticated attacker can send a single HTTP request with a massive payload (e.g., streaming gigabytes of data) when the handler is operating in stateless mode or handling a new session. Since `io.ReadAll` allocates a byte slice to fit the entire stream, this will quickly exhaust the server's memory.",
  "attack_preconditions": "The server must be configured in `Stateless` mode (or process requests without a session ID), and the upstream HTTP server or middleware must not proactively restrict the request body size.",
  "impact": "Denial of Service (DoS) due to Out-Of-Memory (OOM) crashes, affecting all users and services running on the same host/process.",
  "non_vulnerable_reason": "",
  "security_invariants": [],
  "invariant_verification": [],
  "residual_risk_notes": "",
  "call_path": "ServeHTTP (mcp/streamable.go) \u2192 io.ReadAll"
}
```

ServeHTTP (mcp/streamable.go) → io.ReadAll


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/document/document.php
Function: document_listing_display

Call path: document.php (main/document/document.php) → DocumentManager::get_document_data_by_id (main/document/document.php) → document_listing_display (main/document/document.php)

### Primary Function

```php
<?php
// Document title with link and comment
$titleWithLink = $link.$session_img.'<br />'.$invisibility_span_open;
$commentText = nl2br(htmlspecialchars($document_data['comment'], ENT_QUOTES, $charset));
if (!empty($commentText)) {
    $titleWithLink .= '<em>'.$commentText.'</em>';
}
$titleWithLink .= $invisibility_span_close.$user_link;
$row[] = $titleWithLink;
```

### Cross-File Context

[Security::remove_XSS — method — main/inc/lib/security.lib.php:203]
public static function remove_XSS($var, $user_status = null, $filter_terms = false) { if ($filter_terms) { $var = self::filter_terms($var); } if (empty($user_status)) { if (api_is_anonymous()) { $user_status = ANONYMOUS; } else { if (api_is_allowed_to_edit()) { $user_status = COURSEMANAGER; } else { $user_status = STUDENT; } } } if ($user_status == COURSEMANAGERLOWSECURITY) { return $var; } static $purifier = []; if (!isset($purifier[$user_status])) { $cache_dir = api_get_path(SYS_ARCHIVE_PATH).'Serializer'; if (!file_exists($cache_dir)) { $mode = api_get_permissions_for_new_directories(); mkdir($cache_dir, $mode); } $config = HTMLPurifier_Config::createDefault(); $config->set('Cache.SerializerPath', $cache_dir); $config->set('Core.Encoding', api_get_system_encoding()); $config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); $config->set('HTML.MaxImgLength', '2560'); $config->set('HTML.TidyLevel', 'light'); $config->set('Core.ConvertDocumentToFragment', false); $config->set('Core.RemoveProcessingInstructions', true); if (api_get_setting('enable_iframe_inclusion') == 'true') { $config->set('Filter.Custom', [new AllowIframes()]); } $config->set('Attr.AllowedFrameTargets', ['_blank', '_top', '_self', '_parent']); if ($user_status == STUDENT) { global $allowed_html_student; $config->set('HTML.SafeEmbed', true); $config->set('HTML.SafeObject', true); $config->set('Filter.YouTube', true); $config->set('HTML.FlashAllowFullScreen', true); $config->set('HTML.Allowed', $allowed_html_student); } elseif ($user_status == COURSEMANAGER) { global $allowed_html_teacher; $config->set('HTML.SafeEmbed', true); $config->set('HTML.SafeObject', true); $config->set('Filter.YouTube', true); $config->set('HTML.FlashAllowFullScreen', true); $config->set('HTML.Allowed', $allowed_html_teacher); } else { global $allowed_html_anonymous; $config->set('HTML.Allowed', $allowed_html_anonymous); } $config->set('Attr.EnableID', true); $config->set('CSS.AllowImportant', true); $config->set('CSS.AllowTricky', true); $config->set('CSS.Proprietary', true); $config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true, 'ftp' => true, 'nntp' => true, 'news' => true, 'data' => true]); $config->set('HTML.SafeIframe', true); $config->set('HTML.DefinitionID', 'html5-definitions'); $config->set('HTML.DefinitionRev', 1); if ($def = $config->maybeGetRawHTMLDefinition()) { $def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', ['src' => 'URI', 'type' => 'Text', 'width' => 'Length', 'height' => 'Length', 'poster' => 'URI', 'preload' => 'Enum#auto,metadata,none', 'controls' => 'Bool']); $def->addElement('audio', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', ['autoplay' => 'Bool', 'src' => 'URI', 'loop' => 'Bool', 'preload' => 'Enum#auto,metadata,none', 'controls' => 'Bool', 'muted' => 'Bool']); $def->addElement('source', 'Block', 'Flow', 'Common', ['src' => 'URI', 'type' => 'Text']); } $purifier[$user_status] = new HTMLPurifier($config); } if (is_array($var)) { return $purifier[$user_status]->purifyArray($var); } else { return $purifier[$user_status]->purify($var); } }

Control flow: User requests document listing -> document.php retrieves document data via DocumentManager -> builds HTML title with link and comment -> outputs to browser without sanitization -> XSS executes in victim's browser

Data flow: Document title (user-controlled) -> $link variable -> $titleWithLink concatenation -> $row[] array -> HTML output -> Browser execution

Verdict: Vulnerable
Vulnerable region:
[main/document/document.php]
```php
);

            // Document title with link and comment
            $titleWithLink = $link.$session_img.'<br />'.$invisibility_span_open;
            $commentText = nl2br(htmlspecialchars($document_data['comment'], ENT_QUOTES, $charset));
            if (!empty($commentText)) {
                $titleWithLink .= '<em>'.$commentText.'</em>';
            }
```
CWE: CWE-79
Severity: MEDIUM

Root cause: The $link variable containing the document title HTML is assigned directly to $titleWithLink without passing through Security::remove_XSS() sanitization. The $link variable is constructed from document data including the title which can be set/modified by users with document creation/editing permissions, allowing injection of malicious HTML/script tags that execute when the document list is displayed.

Exploitability: An attacker with course document creation/edit permissions creates or renames a document with a malicious JavaScript payload in the title. When a user views the course document listing, the JavaScript executes in their browser.

Attack preconditions:
- Attacker must have ability to create or rename documents in a course (authenticated user with document creation/edit permissions)
- Attacker crafts document title containing malicious HTML/JavaScript
- Victim user views the affected document listing page

Likely impact: An attacker with course document creation/edit permissions creates or renames a document with a malicious JavaScript payload in the title. When a user views the course document listing, the JavaScript executes in their browser.

);

            // Document title with link and comment
            $titleWithLink = $link.$session_img.'
'.$invisibility_span_open;
            $commentText = nl2br(htmlspecialchars($document_data['comment'], ENT_QUOTES, $charset));
            if (!empty($commentText)) {
                $titleWithLink .= ''.$commentText.'';
            }

Based on the provided code and context, there is no evidence of a vulnerability in this specific snippet.

### Analysis
The code is responsible for processing and formatting a document's comment for display. The input data, `$document_data['comment']`, is passed through `htmlspecialchars` with the `ENT_QUOTES` flag.

```php
$commentText = nl2br(htmlspecialchars($document_data['comment'], ENT_QUOTES, $charset));
```

This ensures that any HTML-special characters (such as `<`, `>`, `&`, `"`, and `'`) are properly escaped before being concatenated into the HTML string, effectively neutralizing Cross-Site Scripting (XSS) payloads within the comment field. `nl2br` is then applied safely *after* `htmlspecialchars`, which is the correct order of operations to preserve intended line breaks without opening up HTML injection vectors.

While the variables `$link`, `$session_img`, `$invisibility_span_open`, and `$user_link` are also concatenated into the output, their definitions are not provided in this snippet. Assuming they are generated safely elsewhere, the explicit handling of the comment data is secure.

### Verdict
The code is **not vulnerable** to XSS through `$document_data['comment']` because it appropriately sanitizes the input using `htmlspecialchars` with `ENT_QUOTES`.

```json
{
  "vulnerable": false,
  "cwe": "CWE-79",
  "severity": "None",
  "reviewed_region": "<?php\n// Document title with link and comment\n$titleWithLink = $link.$session_img.'<br />'.$invisibility_span_open;\n$commentText = nl2br(htmlspecialchars($document_data['comment'], ENT_QUOTES, $charset));\nif (!empty($commentText)) {\n    $titleWithLink .= '<em>'.$commentText.'</em>';\n}\n$titleWithLink .= $invisibility_span_close.$user_link;\n$row[] = $titleWithLink;",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": null,
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "The code uses htmlspecialchars with the ENT_QUOTES flag to properly escape the comment data before displaying it, and appropriately calls it before nl2br. This effectively mitigates any XSS attacks from the comment field.",
  "security_invariants": [
    "Data from `$document_data['comment']` is passed through `htmlspecialchars` with `ENT_QUOTES` before being concatenated into the HTML output."
  ],
  "invariant_verification": "The call to `htmlspecialchars($document_data['comment'], ENT_QUOTES, $charset)` is clearly visible in the provided code.",
  "residual_risk_notes": "The safety of other variables concatenated into `$titleWithLink` (e.g., `$link`, `$session_img`, `$user_link`) cannot be verified from the given snippet, but the specific processing of the comment is secure.",
  "call_path": "document.php (main/document/document.php) → DocumentManager::get_document_data_by_id (main/document/document.php) → document_listing_display (main/document/document.php)"
}
```

<?php
// Document title with link and comment
$titleWithLink = $link.$session_img.'
'.$invisibility_span_open;
$commentText = nl2br(htmlspecialchars($document_data['comment'], ENT_QUOTES, $charset));
if (!empty($commentText)) {
    $titleWithLink .= ''.$commentText.'';
}
$titleWithLink .= $invisibility_span_close.$user_link;
$row[] = $titleWithLink;


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: net/ceph/auth_x.c
Function: ceph_x_decrypt

Call path: ceph_x_handle_reply (net/ceph/auth_x.c) → ceph_x_proc_ticket_reply (net/ceph/auth_x.c) → process_one_ticket (net/ceph/auth_x.c) → ceph_x_decrypt (net/ceph/auth_x.c) → ceph_decode_copy (include/linux/ceph/decode.h)

### Primary Function

```c
static int ceph_x_decrypt(struct ceph_crypto_key *secret,
			  void **p, void *end, void *obuf, size_t olen)
{
	struct ceph_x_encrypt_header head;
	size_t head_len = sizeof(head);
	int len, ret;

	len = ceph_decode_32(p);
	if (*p + len > end)
		return -EINVAL;

	dout("ceph_x_decrypt len %d\n", len);
	ret = ceph_decrypt2(secret, &head, &head_len, obuf, &olen,
			    *p, len);
	if (ret)
		return ret;
	if (head.struct_v != 1 || le64_to_cpu(head.magic) != CEPHX_ENC_MAGIC)
		return -EPERM;
	*p += len;
	return olen;
}
```

### Cross-File Context

[TEMP_TICKET_BUF_LEN — macro — net/ceph/auth_x.c:16]
TEMP_TICKET_BUF_LEN → 256  (net/ceph/auth_x.c:16)

[CEPHX_ENC_MAGIC — constant — net/ceph/auth_x_protocol.h:83]
CEPHX_ENC_MAGIC → 0xff009cad8826aa55ull  (net/ceph/auth_x_protocol.h:83)

[ceph_x_encrypt_header — struct — net/ceph/auth_x_protocol.h:85-88]
```c
struct ceph_x_encrypt_header {\n\t__u8 struct_v;\n\t__le64 magic;\n} __attribute__ ((packed));
```

[ceph_decode_copy — sink — include/linux/ceph/decode.h:41-45]
ceph_decode_copy → static inline void ceph_decode_copy(void **p, void *pv, size_t n)\n{\n\tmemcpy(pv, *p, n);\n\t*p += n;\n}  (include/linux/ceph/decode.h:41-45)

[ceph_decode_32 — function — include/linux/ceph/decode.h:23-28]
```c
static inline u32 ceph_decode_32(void **p)\n{\n\tu32 v = get_unaligned_le32(*p);\n\t*p += sizeof(u32);\n\treturn v;\n}
```

[ceph_decode_8_safe — macro — include/linux/ceph/decode.h:76-80]
ceph_decode_8_safe → #define ceph_decode_8_safe(p, end, v, bad)\t \t\n\tdo {\t\t\t\t\t\t\t\t\t\n\t\tceph_decode_need(p, end, sizeof(u8), bad);\t\n\t\tv = ceph_decode_8(p);\t\t\t\t\n\t} while (0)  (include/linux/ceph/decode.h:76-80)

[ceph_decode_32_safe — macro — include/linux/ceph/decode.h:66-70]
ceph_decode_32_safe → #define ceph_decode_32_safe(p, end, v, bad)\t \t\n\tdo {\t\t\t\t\t\t\t\t\t\n\t\tceph_decode_need(p, end, sizeof(u32), bad);\t\n\t\tv = ceph_decode_32(p);\t\t\t\t\n\t} while (0)  (include/linux/ceph/decode.h:66-70)

[ceph_decode_need — macro — include/linux/ceph/decode.h:55-59]
ceph_decode_need → #define ceph_decode_need(p, end, n, bad)\t\t\t\n\tdo {\t\t\t\t\t\t\t\t\n\t\tif (!likely(ceph_has_room(p, end, n)))\t\t\n\t\t\tgoto bad;\t\t\t\t\t\n\t} while (0)  (include/linux/ceph/decode.h:55-59)

Control flow: ceph_x_handle_reply dispatches to ceph_x_proc_ticket_reply which iterates over ticket replies calling process_one_ticket for each. process_one_ticket calls ceph_x_decrypt to decrypt ticket data. The pre-fix code passes a fixed 256-byte buffer to ceph_x_decrypt which is insufficient for large tickets. ceph_decrypt2 internally calls ceph_decode_copy which performs the unbounded memcpy causing overflow.

Data flow: The ticket data originates from the Ceph Monitor in the network protocol. It is decoded from wire format via ceph_decode_32() to determine length 'dlen'. In the pre-fix code, this dlen value controls a memcpy into a 256-byte fixed buffer without validation against TEMP_TICKET_BUF_LEN. The attacker controls the ticket length in the protocol, enabling arbitrary overflow up to available memory.

Verdict: Vulnerable
Vulnerable region:
[net/ceph/auth_x.c]
```c
static int ceph_x_decrypt(struct ceph_crypto_key *secret,
			  void **p, void *end, void *obuf, size_t olen)
{
	struct ceph_x_encrypt_header head;
	size_t head_len = sizeof(head);
	int len, ret;

	len = ceph_decode_32(p);
	if (*p + len > end)
		return -EINVAL;

	dout("ceph_x_decrypt len %d\n", len);
	ret = ceph_decrypt2(secret, &head, &head_len, obuf, &olen,
			    *p, len);
	if (ret)
		return ret;
	if (head.struct_v != 1 || le64_to_cpu(head.magic) != CEPHX_ENC_MAGIC)
		return -EPERM;
	*p += len;
	return olen;
}
```
CWE: CWE-399
Severity: HIGH

Root cause: The TEMP_TICKET_BUF_LEN macro hardcodes a 256-byte buffer for cephx auth ticket decryption. The function ceph_x_decrypt receives obuf as a caller-allocated fixed-size buffer. When the actual encrypted/decrypted data length exceeds 256 bytes, ceph_decode_copy() in ceph_decrypt2 writes beyond the buffer boundary. This occurs in process_one_ticket when handling unencrypted tickets (is_enc=0) at line 213: ceph_decode_copy(p, ticket_buf, dlen) copies dlen bytes into ticket_buf which is allocated as TEMP_TICKET_BUF_LEN=256.

Exploitability: An attacker controlling a Ceph Monitor sends a crafted CEPHX auth ticket reply containing an unencrypted ticket blob with size > 256 bytes. The ceph_x_decrypt function uses a fixed 256-byte stack buffer, and the underlying ceph_decode_copy() performs an unbounded memcpy into this buffer, resulting in buffer overflow.

Attack preconditions:
- Attacker must control or compromise a Ceph Monitor to send crafted auth replies
- Target must be a Linux kernel Ceph client attempting authentication with the attacker-controlled monitor
- The crafted ticket must have is_enc=0 (unencrypted) with dlen > TEMP_TICKET_BUF_LEN (256 bytes)

Likely impact: An attacker controlling a Ceph Monitor sends a crafted CEPHX auth ticket reply containing an unencrypted ticket blob with size > 256 bytes. The ceph_x_decrypt function uses a fixed 256-byte stack buffer, and the underlying ceph_decode_copy() performs an unbounded memcpy into this buffer, resulting in buffer overflow.

### Analysis

The function `ceph_x_decrypt` is responsible for decoding and decrypting encrypted data from a network buffer. It starts by reading a 32-bit length prefix from the input buffer pointer `p` using `ceph_decode_32`.

```c
	int len, ret;

	len = ceph_decode_32(p);
	if (*p + len > end)
		return -EINVAL;
```

The issue arises because `len` is declared as a signed integer (`int len`). `ceph_decode_32` returns an unsigned 32-bit integer (`u32`). If a malicious server or man-in-the-middle attacker crafts a payload where the length field is `0x80000000` or greater, the returned unsigned value will be implicitly cast to a negative signed `int`.

When evaluating the bounds check `if (*p + len > end)`, `len` acts as a negative offset. Consequently, `*p + len` results in a pointer that is earlier in memory than `*p`, rather than past it. As long as this does not result in an extreme underflow that wraps around past `end`, the bounds check will erroneously pass.

The negative `len` is then passed to `ceph_decrypt2()`. If `ceph_decrypt2` expects an `int` for the length, it might fail or behave unexpectedly; if it internally casts the length to a `size_t` or unsigned type, the negative `int` will be converted to an enormously large positive number. This typically causes `ceph_decrypt2` to perform massively out-of-bounds reads from `*p` and out-of-bounds writes into `obuf`, leading to severe memory corruption (heap overflow) or a kernel crash.

### Verdict

**Vulnerable**

**Root Cause**: A signedness error (CWE-839/CWE-128) where a network-provided 32-bit unsigned length is stored in a signed `int` and validated using pointer arithmetic, allowing a negative value to bypass the `> end` bounds check.
**Impact**: Out-of-bounds memory read/write during the subsequent call to `ceph_decrypt2`, likely leading to a kernel panic (Denial of Service) or potentially remote code execution.

```json
{
  "vulnerable": true,
  "cwe": "CWE-839",
  "severity": "High",
  "reviewed_region": "static int ceph_x_decrypt(struct ceph_crypto_key *secret,\n\t\t\t  void **p, void *end, void *obuf, size_t olen)\n{\n\tstruct ceph_x_encrypt_header head;\n\tsize_t head_len = sizeof(head);\n\tint len, ret;\n\n\tlen = ceph_decode_32(p);\n\tif (*p + len > end)\n\t\treturn -EINVAL;\n\n\tdout(\"ceph_x_decrypt len %d\\n\", len);\n\tret = ceph_decrypt2(secret, &head, &head_len, obuf, &olen,\n\t\t\t    *p, len);\n\tif (ret)\n\t\treturn ret;\n\tif (head.struct_v != 1 || le64_to_cpu(head.magic) != CEPHX_ENC_MAGIC)\n\t\treturn -EPERM;\n\t*p += len;\n\treturn olen;\n}",
  "vulnerable_region": "\tint len, ret;\n\n\tlen = ceph_decode_32(p);\n\tif (*p + len > end)\n\t\treturn -EINVAL;",
  "root_cause": "The 32-bit unsigned length value from the network buffer is stored in a signed `int` variable (`len`). A sufficiently large length value (>= 0x80000000) becomes negative, passing the `*p + len > end` upper-bounds check because the resulting pointer is strictly less than `*p`.",
  "exploitability": "Easily exploitable by a malicious server or a Man-in-the-Middle (MitM) attacker who sends a crafted ticket reply containing a length field >= 0x80000000.",
  "attack_preconditions": "The client must connect to an attacker-controlled server or have its traffic intercepted by a MitM attacker capable of forging Ceph auth responses.",
  "impact": "Out-of-bounds memory read and write operations inside `ceph_decrypt2`, which can lead to a kernel panic (Denial of Service) or potentially Remote Code Execution (RCE).",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": null,
  "call_path": "ceph_x_handle_reply (net/ceph/auth_x.c) \u2192 ceph_x_proc_ticket_reply (net/ceph/auth_x.c) \u2192 process_one_ticket (net/ceph/auth_x.c) \u2192 ceph_x_decrypt (net/ceph/auth_x.c) \u2192 ceph_decode_copy (include/linux/ceph/decode.h)"
}
```


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

Repository: git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
Language: C
File: drivers/scsi/scsi_transport_iscsi.c
Function: iscsi_set_param

Call path: iscsi_if_rx (drivers/scsi/scsi_transport_iscsi.c) → iscsi_if_recv_msg (drivers/scsi/scsi_transport_iscsi.c) → iscsi_set_param (drivers/scsi/scsi_transport_iscsi.c) → transport->set_param (drivers/scsi/libiscsi.c) → iscsi_session_get_param (drivers/scsi/libiscsi.c) → iscsi_conn_get_param (drivers/scsi/libiscsi.c) → iscsi_host_get_param (drivers/scsi/libiscsi.c)

### Primary Function

```c
static int
iscsi_set_param(struct iscsi_transport *transport, struct iscsi_uevent *ev)
{
	char *data = (char*)ev + sizeof(*ev);
	struct iscsi_cls_conn *conn;
	struct iscsi_cls_session *session;
	int err = 0, value = 0;

	if (ev->u.set_param.len > PAGE_SIZE)
		return -EINVAL;

	session = iscsi_session_lookup(ev->u.set_param.sid);
	conn = iscsi_conn_lookup(ev->u.set_param.sid, ev->u.set_param.cid);
	if (!conn || !session)
		return -EINVAL;

	switch (ev->u.set_param.param) {
	case ISCSI_PARAM_SESS_RECOVERY_TMO:
		sscanf(data, "%d", &value);
		if (!session->recovery_tmo_sysfs_override)
			session->recovery_tmo = value;
		break;
	default:
		err = transport->set_param(conn, ev->u.set_param.param,
					   data, ev->u.set_param.len);
	}

	return err;
}
```

### Cross-File Context

[iscsi_set_host_param — function — drivers/scsi/scsi_transport_iscsi.c:3026]
```c
static int
iscsi_set_host_param(struct iscsi_transport *transport,
		     struct iscsi_uevent *ev)
{
	char *data = (char*)ev + sizeof(*ev);
	struct Scsi_Host *shost;
	int err;

	if (!transport->set_host_param)
		return -ENOSYS;

	if (ev->u.set_host_param.len > PAGE_SIZE)
		return -EINVAL;

	shost = scsi_host_lookup(ev->u.set_host_param.host_no);
	if (!shost) {
		printk(KERN_ERR "set_host_param could not find host no %u\n",
		       ev->u.set_host_param.host_no);
		return -ENODEV;
	}

	err = transport->set_host_param(shost, ev->u.set_host_param.param,
					data, ev->u.set_host_param.len);
	scsi_host_put(shost);
	return err;
}
```

[sysfs_emit — function — fs/sysfs/file.c:732]
```c
int sysfs_emit(char *buf, const char *fmt, ...)
{
	va_list args;
	int len;

	if (WARN(!buf || offset_in_page(buf),
		 "invalid sysfs_emit: buf:%p\n", buf))
		return 0;

	va_start(args, fmt);
	len = vscnprintf(buf, PAGE_SIZE, fmt, args);
	va_end(args);

	return len;
}
```

[PAGE_SIZE — constant — arch/x86/include/asm/page_size.h:4]
PAGE_SIZE → 4096  (arch/x86/include/asm/page_size.h:4)

[struct iscsi_uevent — struct — include/scsi/iscsi_if.h:93]
```c
struct iscsi_uevent {
	uint32_t type;
	uint32_t iferror;
	uint64_t transport_handle;

	union {
		struct msg_set_param {
			uint32_t sid;
			uint32_t cid;
			uint32_t param;
			uint32_t len;
		} set_param;
		/* ... other fields ... */
	} u;
}
```

[enum iscsi_param — enum — include/scsi/iscsi_if.h:526]
enum iscsi_param { ISCSI_PARAM_MAX_RECV_DLENGTH, ISCSI_PARAM_MAX_XMIT_DLENGTH, /* ... */ ISCSI_PARAM_USERNAME, ISCSI_PARAM_PASSWORD, ISCSI_PARAM_TARGET_NAME, /* ... */ ISCSI_PARAM_MAX, };

Control flow: The function extracts a data pointer from the uevent structure and immediately validates the user-controlled length field `ev->u.set_param.len` against `PAGE_SIZE`, returning `-EINVAL` if the limit is exceeded. It then performs kernel object lookups for the session and connection, returning `-EINVAL` if either lookup fails. Execution branches based on `ev->u.set_param.param`: for `ISCSI_PARAM_SESS_RECOVERY_TMO`, it parses an integer via `sscanf` and conditionally updates a session timeout field; for all other parameters, it delegates to `transport->set_param`. The function concludes by returning the accumulated error code.

Data flow: The input `ev` structure provides the payload length (`ev->u.set_param.len`), parameter identifier, and session/connection IDs. `data` is derived as a pointer to the memory region immediately following the `ev` header. The length field is validated early. In the recovery timeout case, `data` is parsed into a local integer `value`. In the default case, `data` and the validated `len` are forwarded to the transport callback. The only state modifications occur on the `session` struct or are passed through to the callee, with all external inputs bounded before consumption.

Verdict: Not Vulnerable

Reviewed region:
[drivers/scsi/scsi_transport_iscsi.c]
```c
static int
iscsi_set_param(struct iscsi_transport *transport, struct iscsi_uevent *ev)
{
	char *data = (char*)ev + sizeof(*ev);
	struct iscsi_cls_conn *conn;
	struct iscsi_cls_session *session;
	int err = 0, value = 0;

	if (ev->u.set_param.len > PAGE_SIZE)
		return -EINVAL;

	session = iscsi_session_lookup(ev->u.set_param.sid);
	conn = iscsi_conn_lookup(ev->u.set_param.sid, ev->u.set_param.cid);
	if (!conn || !session)
		return -EINVAL;

	switch (ev->u.set_param.param) {
	case ISCSI_PARAM_SESS_RECOVERY_TMO:
		sscanf(data, "%d", &value);
		if (!session->recovery_tmo_sysfs_override)
			session->recovery_tmo = value;
		break;
	default:
		err = transport->set_param(conn, ev->u.set_param.param,
					   data, ev->u.set_param.len);
	}

	return err;
}
```

Why it is not vulnerable: The code explicitly validates the user-controlled `ev->u.set_param.len` against `PAGE_SIZE` before any parsing, state modification, or callback delegation occurs. This early bounds check prevents oversized payloads from triggering buffer overflows or memory corruption. Additionally, mandatory null checks on `session` and `conn` prevent invalid pointer dereferences, and the format string used for integer parsing is strict and safe.

Security invariants:
- User-controlled payload length must not exceed `PAGE_SIZE` before any processing. Enforced by `if (ev->u.set_param.len > PAGE_SIZE) return -EINVAL;`.
- Kernel object pointers must be valid before access or delegation. Enforced by `if (!conn || !session) return -EINVAL;`.
- Downstream callbacks must receive a validated length to prevent unbounded reads/writes. Enforced by passing `ev->u.set_param.len` to `transport->set_param` only after the `PAGE_SIZE` gate.
- String-to-integer parsing must be bounded and use safe formats. Enforced by the `PAGE_SIZE` upper limit on `data` and the strict `"%d"` format specifier in `sscanf`.

Invariant verification:
- Length bounds validation: holds=true. Evidence: The condition `if (ev->u.set_param.len > PAGE_SIZE) return -EINVAL;` executes immediately after data extraction, rejecting oversized inputs before any usage.
- Null pointer safety: holds=true. Evidence: The lookup results are checked via `if (!conn || !session) return -EINVAL;` prior to dereferencing `session->recovery_tmo_sysfs_override` or passing `conn` to the callback.
- Safe payload delegation: holds=true. Evidence: The validated `len` is explicitly passed to `transport->set_param(conn, ev->u.set_param.param, data, ev->u.set_param.len)`, ensuring downstream handlers operate on a strictly bounded size.

Residual risk notes: The `sscanf` call for `ISCSI_PARAM_SESS_RECOVERY_TMO` does not explicitly use the `len` field to limit parsing, relying instead on the payload's implicit null-termination and the `PAGE_SIZE` upper bound. While acceptable given the strict length gate, using size-aware parsing would be marginally more robust. Additionally, the safety of the `default` branch inherently relies on `transport->set_param` correctly respecting the `len` parameter it receives.

An analysis of the `iscsi_set_param` function reveals an out-of-bounds (OOB) read vulnerability due to improper handling of user-supplied data from a Netlink message.

### Vulnerability Analysis
The `iscsi_set_param` function is invoked to handle Netlink messages from userspace. It calculates a pointer to the payload data:
```c
char *data = (char*)ev + sizeof(*ev);
```
It subsequently validates that the user-provided length field (`ev->u.set_param.len`) does not exceed `PAGE_SIZE`:
```c
if (ev->u.set_param.len > PAGE_SIZE)
    return -EINVAL;
```

However, when handling the `ISCSI_PARAM_SESS_RECOVERY_TMO` parameter, the function directly passes this `data` pointer to `sscanf`:
```c
sscanf(data, "%d", &value);
```

There are two primary issues here:
1. **Missing Null-Termination**: Netlink message payloads supplied by userspace are raw byte buffers and are not guaranteed to be null-terminated. The `sscanf` function expects a valid C-string. If an attacker sends a payload consisting entirely of numeric digits without a null terminator, `sscanf` will read past the end of the provided payload into adjacent memory until it happens to encounter a non-digit character or a null byte.
2. **Missing Length Validation**: The function relies on the `len` attribute within the event structure but does not verify that the underlying socket buffer (`skb`) actually contains that much data. If the Netlink message is crafted to be smaller than `sizeof(*ev) + 1`, the `data` pointer may immediately point out of bounds of the allocated skb data buffer.

### Impact
This results in an Out-of-Bounds Read (CWE-125). In the context of `sscanf("%d")`, the kernel will over-read memory searching for digits. While this typically results in relatively benign behavior (reading adjacent slab memory until a non-digit is hit), it can potentially trigger a page fault and kernel panic (Denial of Service) if the data resides at the boundary of a mapped page and the adjacent page is unmapped.

### Verdict
**Vulnerable**. The function improperly handles unvalidated, non-null-terminated user data, leading to an out-of-bounds read.

```json
{
  "vulnerable": true,
  "cwe": "CWE-125",
  "severity": "Low",
  "reviewed_region": "static int\niscsi_set_param(struct iscsi_transport *transport, struct iscsi_uevent *ev)\n{\n\tchar *data = (char*)ev + sizeof(*ev);\n\tstruct iscsi_cls_conn *conn;\n\tstruct iscsi_cls_session *session;\n\tint err = 0, value = 0;\n\n\tif (ev->u.set_param.len > PAGE_SIZE)\n\t\treturn -EINVAL;\n\n\tsession = iscsi_session_lookup(ev->u.set_param.sid);\n\tconn = iscsi_conn_lookup(ev->u.set_param.sid, ev->u.set_param.cid);\n\tif (!conn || !session)\n\t\treturn -EINVAL;\n\n\tswitch (ev->u.set_param.param) {\n\tcase ISCSI_PARAM_SESS_RECOVERY_TMO:\n\t\tsscanf(data, \"%d\", &value);\n\t\tif (!session->recovery_tmo_sysfs_override)\n\t\t\tsession->recovery_tmo = value;\n\t\tbreak;\n\tdefault:\n\t\terr = transport->set_param(conn, ev->u.set_param.param,\n\t\t\t\t\t   data, ev->u.set_param.len);\n\t}\n\n\treturn err;\n}",
  "vulnerable_region": "case ISCSI_PARAM_SESS_RECOVERY_TMO:\n\t\tsscanf(data, \"%d\", &value);\n\t\tif (!session->recovery_tmo_sysfs_override)",
  "root_cause": "The function passes a raw payload buffer from a Netlink message directly to `sscanf` without ensuring it is null-terminated or properly sized, leading to a potential out-of-bounds read.",
  "exploitability": "An attacker with access to the iSCSI netlink socket can craft a message where the payload contains no null terminator, causing `sscanf` to read past the end of the buffer until it encounters a non-digit character or null byte.",
  "attack_preconditions": "The attacker must have sufficient privileges to send messages over the iSCSI Netlink interface.",
  "impact": "Denial of Service (DoS) due to a kernel panic if the out-of-bounds read accesses an unmapped memory page.",
  "non_vulnerable_reason": "",
  "security_invariants": "",
  "invariant_verification": "",
  "residual_risk_notes": "",
  "call_path": "iscsi_if_rx (drivers/scsi/scsi_transport_iscsi.c) \u2192 iscsi_if_recv_msg (drivers/scsi/scsi_transport_iscsi.c) \u2192 iscsi_set_param (drivers/scsi/scsi_transport_iscsi.c) \u2192 transport->set_param (drivers/scsi/libiscsi.c) \u2192 iscsi_session_get_param (drivers/scsi/libiscsi.c) \u2192 iscsi_conn_get_param (drivers/scsi/libiscsi.c) \u2192 iscsi_host_get_param (drivers/scsi/libiscsi.c)"
}
```


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/privacyidea/privacyidea
Language: Python
File: privacyidea/lib/decorators.py
Function: check_user_or_serial_in_request_wrapper

Call path: offlinerefill (privacyidea/api/validate.py) → check_user_or_serial_in_request_wrapper (privacyidea/lib/decorators.py) → get_tokens (privacyidea/lib/token.py)

### Primary Function

```python
def check_user_or_serial_in_request_wrapper(*args, **kwds):
    user = self.request.all_data.get("user")
    serial = self.request.all_data.get("serial")
    if not serial and not user:
        raise ParameterError(_("You need to specify a serial or a user."))
    f_result = func(*args, **kwds)
    return f_result
```

### Cross-File Context

[check_user_or_serial_in_request — class — privacyidea/lib/decorators.py:75-94]
class check_user_or_serial_in_request(object): """ Decorator to check user and serial in a request. If the request does not contain a serial number (serial) or a user (user) it will throw a ParameterError. """ def __init__(self, request): self.request = request def __call__(self, func): @functools.wraps(func) def check_user_or_serial_in_request_wrapper(*args, **kwds): user = self.request.all_data.get("user") serial = self.request.all_data.get("serial") if not serial and not user: raise ParameterError(_("You need to specify a serial or a user.")) f_result = func(*args, **kwds) return f_result return check_user_or_serial_in_request_wrapper

[ParameterError — exception — privacyidea/lib/error.py]
class ParameterError(Exception)

Control flow: HTTP request enters at offlinerefill endpoint, passes through @check_user_or_serial_in_request decorator which validates user/serial parameters, then calls get_tokens(serial=serial) which performs LDAP query using unsanitized serial parameter in pre-fix version.

Data flow: Attacker-controlled user/serial HTTP parameters flow through request.all_data.get() to get_tokens() LDAP query without validation or sanitization for LDAP wildcard characters in pre-fix version.

Verdict: Vulnerable
Vulnerable region:
[privacyidea/lib/decorators.py]
```python
def check_user_or_serial_in_request_wrapper(*args, **kwds):
            user = self.request.all_data.get("user")
            serial = self.request.all_data.get("serial")
            if not serial and not user:
                raise ParameterError(_("You need to specify a serial or a user."))
            f_result = func(*args, **kwds)
            return f_result
```
CWE: CWE-20
Severity: HIGH

Root cause: The check_user_or_serial_in_request decorator's __call__ method retrieves user and serial from request.all_data using .get() without a default value and without stripping whitespace or validating for LDAP wildcard characters. When these unsanitized values are passed to downstream LDAP query functions, an attacker can inject LDAP wildcards (% for any character sequence, * for any single character) to manipulate query results or bypass authentication checks.

Exploitability: The vulnerability is exploitable via HTTP POST requests to /validate/offlinerefill (missing decorator) or via the existing /validate/check endpoint (decorator present but insufficient validation). An attacker can inject LDAP wildcards (% or *) in the user or serial parameters to manipulate LDAP queries.

Attack preconditions:
- Attacker can send HTTP requests to the privacyIDEA /validate/offlinerefill or /validate/check endpoints
- Attacker provides user parameter containing '%' character or serial parameter containing '*' character
- The vulnerable endpoint processes these parameters in LDAP queries without sanitization

Likely impact: The vulnerability is exploitable via HTTP POST requests to /validate/offlinerefill (missing decorator) or via the existing /validate/check endpoint (decorator present but insufficient validation). An attacker can inject LDAP wildcards (% or *) in the user or serial parameters to manipulate LDAP queries.

Based on the provided code and context, there are no indications of a vulnerability within the `check_user_or_serial_in_request_wrapper` function.

### Analysis
The function acts as a decorator wrapper designed to enforce a specific precondition: an incoming API request must contain either a `user` or a `serial` parameter.

1. **Control Flow**: The wrapper correctly retrieves the `user` and `serial` parameters from the request's data. If both evaluate to false (e.g., they are `None` or empty strings), it interrupts the execution flow and raises a `ParameterError` to properly reject the malformed request. If at least one is present, it passes control to the decorated function.
2. **State and Concurrency**: The decorator captures a `request` object. In the context of the underlying web framework (Flask, which `privacyidea` uses), the imported `request` object is a thread-local proxy (`werkzeug.local.LocalProxy`). Storing this proxy in the decorator instance `self.request` at module load time is safe, because evaluating `self.request.all_data` during the wrapper's execution will correctly dispatch to the thread-local request of the current active thread/context.
3. **Data Validation**: The function only checks for parameter *presence*, which aligns exactly with its stated purpose. Any further sanitization, validation of data types, or authorization checks are the responsibility of downstream handlers (such as `get_tokens` or the API endpoint itself).

### Conclusion
The code correctly implements a mandatory parameter check and fails securely (by raising a `ParameterError`) when the required inputs are missing. No security boundaries are violated, and no unsafe operations are performed on the attacker-controlled input within this function.

```json
{
  "vulnerable": false,
  "cwe": "N/A",
  "severity": "None",
  "reviewed_region": "privacyidea/lib/decorators.py",
  "vulnerable_region": "N/A",
  "root_cause": "N/A",
  "exploitability": "N/A",
  "attack_preconditions": "N/A",
  "impact": "N/A",
  "non_vulnerable_reason": "The function correctly enforces the presence of either a 'user' or 'serial' parameter by checking truthiness (which guards against None and empty strings) and raising a ParameterError if both are missing. State management of the request proxy is safe because evaluating properties on the thread-local proxy inside the wrapper executes within the current request context.",
  "security_invariants": [
    "At least one of 'serial' or 'user' must be truthy for the decorated function to execute.",
    "Requests lacking both parameters are safely rejected via ParameterError."
  ],
  "invariant_verification": "The condition `if not serial and not user:` accurately verifies truthiness, catching `None`, empty strings, and other falsy values. The subsequent `raise ParameterError` halts control flow before reaching the decorated function.",
  "residual_risk_notes": "The function only validates presence, not content or authorization. The downstream handlers (like `get_tokens`) must still properly validate and sanitize the input values to prevent injection or unauthorized access.",
  "call_path": "offlinerefill (privacyidea/api/validate.py) \u2192 check_user_or_serial_in_request_wrapper (privacyidea/lib/decorators.py) \u2192 get_tokens (privacyidea/lib/token.py)"
}
```