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/PrestaShop/productcomments
Language: PHP
File: productcomments.php
Function: renderAuthorName

Call path: renderAuthorName (productcomments.php)

### Primary Function

```php
public function renderAuthorName($value, $row)
{
    if (!empty($row['customer_id'])) {
        $linkToCustomerProfile = $this->context->link->getAdminLink('AdminCustomers', false, [], [
            'id_customer' => $row['customer_id'],
            'viewcustomer' => 1,
        ]);

        return '<a href="' . $linkToCustomerProfile . '">' . $value . '</a>';
    }

    return $value;
}
```

### Cross-File Context

[ProductComments — class — productcomments.php:34]
class ProductComments extends Module implements WidgetInterface

Control flow: renderAuthorName is called as a callback by PrestaShop's AdminController grid rendering system. It receives a value (author name) and row data, then returns either a hyperlink to the customer profile with the author name embedded, or just the raw author name.

Data flow: The author name originates from user-submitted product comments (via $_POST when creating/editing comments), is stored in the database, and later retrieved and passed to renderAuthorName as the $value parameter without sanitization before being rendered into HTML.

Verdict: Vulnerable
Vulnerable region:
[productcomments.php]
```php
public function renderAuthorName($value, $row)
    {
        if (!empty($row['customer_id'])) {
            $linkToCustomerProfile = $this->context->link->getAdminLink('AdminCustomers', false, [], [
                'id_customer' => $row['customer_id'],
                'viewcustomer' => 1,
            ]);

            return '<a href="' . $linkToCustomerProfile . '">' . $value . '</a>';
        }

        return $value;
    }

    public function renderCriterionForm($id_criterion = 0)
    {
```
CWE: CWE-79
Severity: MEDIUM

Root cause: The renderAuthorName method directly concatenates the unescaped $value parameter into HTML anchor tags without applying any sanitization. The author name originates from user-submitted product reviews and is not sanitized before being rendered in the admin grid column.

Exploitability: An unauthenticated attacker can submit a product review with a malicious JavaScript payload in the author name field. The XSS will trigger when an administrator views the product comments admin grid, which calls renderAuthorName to display the author name.

Attack preconditions:
- Attacker can submit a product review with a crafted author name containing JavaScript
- Administrator views the product comments list in the PrestaShop admin panel
- No input validation sanitizes the author name before storage or rendering

Likely impact: An unauthenticated attacker can submit a product review with a malicious JavaScript payload in the author name field. The XSS will trigger when an administrator views the product comments admin grid, which calls renderAuthorName to display the author name.

public function renderAuthorName($value, $row)
    {
        if (!empty($row['customer_id'])) {
            $linkToCustomerProfile = $this->context->link->getAdminLink('AdminCustomers', false, [], [
                'id_customer' => $row['customer_id'],
                'viewcustomer' => 1,
            ]);

            return '' . $value . '';
        }

        return $value;
    }

    public function renderCriterionForm($id_criterion = 0)
    {

## Vulnerability: Stored Cross-Site Scripting (XSS) in Admin Panel

**VERDICT: VULNERABLE**

### Root Cause
The function directly concatenates user-controlled data (`$value`) into HTML output without encoding:

```php
return '<a href="' . $linkToCustomerProfile . '">' . $value . '</a>';
```

The `$value` parameter (author name) is inserted into the HTML anchor tag with no sanitization or HTML entity encoding.

### Attack Vector
1. Attacker submits a product comment with malicious name: `<script>alert(document.cookie)</script>` or `<img src=x onerror=alert(1)>`
2. Name is stored in database
3. Admin views the product comments list in back-office
4. This function renders the malicious payload directly into HTML
5. JavaScript executes in admin session context

### Exploitability
- **Preconditions:** Attacker can submit product comments (typically public functionality), admin must view comments list
- **Complexity:** Low - straightforward injection
- **Impact:** Critical - admin session hijacking, account takeover, privilege escalation, data theft

### Missing Guard
No HTML encoding applied to `$value`. Should use:
- `htmlspecialchars($value, ENT_QUOTES, 'UTF-8')`, or
- PrestaShop's `Tools::safeOutput($value)`

### Why `$linkToCustomerProfile` is Safe
Generated by `getAdminLink()` framework method which handles URL encoding of parameters including `$row['customer_id']`.

### Classification
**CWE-79: Stored XSS** - Admin panel context makes this high severity due to elevated privilege level of victims.

```json
{
  "vulnerable": true,
  "cwe": "CWE-79",
  "severity": "HIGH",
  "reviewed_region": "productcomments.php:renderAuthorName",
  "vulnerable_region": "return '<a href=\"' . $linkToCustomerProfile . '\">' . $value . '</a>'; and return $value;",
  "root_cause": "The $value parameter (author name) is directly concatenated into HTML output without HTML entity encoding, allowing injection of arbitrary HTML/JavaScript tags",
  "exploitability": "HIGH - Attacker submits product comment with malicious author name (e.g., <script>alert(document.cookie)</script>), which is stored and rendered unencoded when admin views comments list",
  "attack_preconditions": "Attacker can submit product comments (typically public functionality), admin must view the product comments list in back-office",
  "impact": "CRITICAL - JavaScript execution in admin session context enables session hijacking, privilege escalation, unauthorized actions as admin, data theft, and potential backdoor installation",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The $linkToCustomerProfile is generated by framework method getAdminLink() which should handle URL encoding properly, so the href attribute is likely safe. The vulnerability is specifically in the anchor text content ($value) which lacks encoding",
  "call_path": "renderAuthorName (productcomments.php)"
}
```

return '' . $value . ''; and return $value;


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: net/wireless/nl80211.c
Function: nl80211_set_wiphy

Call path: nl80211_set_wiphy (net/wireless/nl80211.c) → nla_get_u32 (lib/nlattr.c) → rdev_set_wiphy_params (net/wireless/rdev-ops.h) → fq_tin_dequeue (include/net/fq_impl.h)

### Primary Function

```c
static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = NULL;
	struct net_device *netdev = NULL;
	struct wireless_dev *wdev;
	int result = 0, rem_txq_params = 0;
	struct nlattr *nl_txq_params;
	u32 changed;
	u8 retry_short = 0, retry_long = 0;
	u32 frag_threshold = 0, rts_threshold = 0;
	u8 coverage_class = 0;
	u32 txq_limit = 0, txq_memory_limit = 0, txq_quantum = 0;

	rtnl_lock();
	/* ... wiphy/device lookup ... */

	wiphy_lock(&rdev->wiphy);

	/* ... various attribute handling ... */

	if (info->attrs[NL80211_ATTR_TXQ_QUANTUM]) {
		if (!wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_TXQS)) {
			result = -EOPNOTSUPP;
			goto out;
		}
		txq_quantum = nla_get_u32(
			info->attrs[NL80211_ATTR_TXQ_QUANTUM]);
		changed |= WIPHY_PARAM_TXQ_QUANTUM;
	}

	if (changed) {
		/* ... old value backup ... */
		if (changed & WIPHY_PARAM_TXQ_QUANTUM)
			rdev->wiphy.txq_quantum = txq_quantum;

		result = rdev_set_wiphy_params(rdev, changed);
		if (result) {
			/* ... restore old values ... */
			goto out;
		}
	}

	result = 0;

out:
	wiphy_unlock(&rdev->wiphy);
	return result;
}
```

### Cross-File Context

[q_range — struct — net/wireless/nl80211.c:466-468]
```c
static struct netlink_range_validation q_range = {
	.max = INT_MAX,
};
```

[nl80211_policy — struct — net/wireless/nl80211.c:470-752]
```c
static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
	/* ... many entries ... */
	[NL80211_ATTR_TXQ_QUANTUM] = { .type = NLA_U32 }, /* pre-fix */
	[NL80211_ATTR_TXQ_QUANTUM] = NLA_POLICY_FULL_RANGE(NLA_U32, &q_range), /* post-fix */
	/* ... many entries ... */
};
```

[NL80211_ATTR_TXQ_QUANTUM — constant — include/uapi/linux/nl80211.h]
NL80211_ATTR_TXQ_QUANTUM → 323  (include/uapi/linux/nl80211.h)

[NLA_POLICY_FULL_RANGE — macro — include/net/netlink.h]
NLA_POLICY_FULL_RANGE → #define NLA_POLICY_FULL_RANGE(type, range) \ NLA_POLICY_RANGE(type, (range)->min, (range)->max)  (include/net/netlink.h)

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 nl80211_set_wiphy begins by acquiring rtnl_lock() and performing wiphy/device lookup. It then acquires wiphy_lock(&rdev->wiphy) before processing netlink attributes. For the TXQ_QUANTUM attribute, it first checks if the NL80211_EXT_FEATURE_TXQS extension feature is enabled; if not, it sets result to -EOPNOTSUPP and jumps to the out label. If the feature is present, it extracts the u32 value via nla_get_u32() and marks WIPHY_PARAM_TXQ_QUANTUM as changed. When changed flags are set, old values are backed up, rdev->wiphy.txq_quantum is updated, and rdev_set_wiphy_params() is called. On error from rdev_set_wiphy_params(), old values are restored before jumping to out. The out label releases wiphy_unlock(&rdev->wiphy) and returns the result.

Data flow: User-supplied input arrives via info->attrs[NL80211_ATTR_TXQ_QUANTUM] from a netlink message. The nl80211_policy array governs validation during netlink parsing: the entry for NL80211_ATTR_TXQ_QUANTUM uses NLA_POLICY_FULL_RANGE(NLA_U32, &q_range), which constrains the value against the q_range structure (with max = INT_MAX). After validation, nla_get_u32() extracts the bounded value into the local variable txq_quantum. This value is conditionally stored into rdev->wiphy.txq_quantum and passed through rdev_set_wiphy_params(rdev, changed). The call path eventually reaches fq_tin_dequeue via rdev-ops.h.

Verdict: Not Vulnerable

Reviewed region:
[net/wireless/nl80211.c]
```c
static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *rdev = NULL;
	struct net_device *netdev = NULL;
	struct wireless_dev *wdev;
	int result = 0, rem_txq_params = 0;
	struct nlattr *nl_txq_params;
	u32 changed;
	u8 retry_short = 0, retry_long = 0;
	u32 frag_threshold = 0, rts_threshold = 0;
	u8 coverage_class = 0;
	u32 txq_limit = 0, txq_memory_limit = 0, txq_quantum = 0;

	rtnl_lock();
	/* ... wiphy/device lookup ... */

	wiphy_lock(&rdev->wiphy);

	/* ... various attribute handling ... */

	if (info->attrs[NL80211_ATTR_TXQ_QUANTUM]) {
		if (!wiphy_ext_feature_isset(&rdev->wiphy,
					     NL80211_EXT_FEATURE_TXQS)) {
			result = -EOPNOTSUPP;
			goto out;
		}
		txq_quantum = nla_get_u32(
			info->attrs[NL80211_ATTR_TXQ_QUANTUM]);
		changed |= WIPHY_PARAM_TXQ_QUANTUM;
	}

	if (changed) {
		/* ... old value backup ... */
		if (changed & WIPHY_PARAM_TXQ_QUANTUM)
			rdev->wiphy.txq_quantum = txq_quantum;

		result = rdev_set_wiphy_params(rdev, changed);
		if (result) {
			/* ... restore old values ... */
			goto out;
		}
	}

	result = 0;

out:
	wiphy_unlock(&rdev->wiphy);
	return result;
}
```

Why it is not vulnerable: The user-supplied txq_quantum value is validated by the netlink policy infrastructure before it is processed. The nl80211_policy entry for NL80211_ATTR_TXQ_QUANTUM specifies NLA_POLICY_FULL_RANGE(NLA_U32, &q_range), which bounds the accepted value against q_range.max (INT_MAX). This prevents unbounded user-controlled values from being accepted. Additionally, the code checks wiphy_ext_feature_isset(&rdev->wiphy, NL80211_EXT_FEATURE_TXQS) before processing the attribute, rejecting it with -EOPNOTSUPP if the hardware/driver does not support TXQs. The value is then stored into rdev->wiphy.txq_quantum and passed to rdev_set_wiphy_params under proper locking (wiphy_lock), ensuring consistent access.

Security invariants:
- The txq_quantum input must be validated against a defined maximum range before use — enforced by nl80211_policy[NL80211_ATTR_TXQ_QUANTUM] = NLA_POLICY_FULL_RANGE(NLA_U32, &q_range) with q_range.max = INT_MAX
- The txq_quantum attribute must only be processed when the device supports TXQ features — enforced by the wiphy_ext_feature_isset(&rdev->wiphy, NL80211_EXT_FEATURE_TXQS) check that returns -EOPNOTSUPP if the feature is absent
- Modifications to wiphy parameters must occur under proper serialization — enforced by wiphy_lock(&rdev->wiphy) acquired before reading/writing rdev->wiphy.txq_quantum and calling rdev_set_wiphy_params, with wiphy_unlock at the out label
- On failure of rdev_set_wiphy_params, previously modified values must be restored — enforced by the error path that backs up old values before modification and restores them in the error branch before jumping to out
- The rtnl_lock must be held during device lookup and parameter setting — enforced by rtnl_lock() at the start of the function

Invariant verification:
- Range validation of NL80211_ATTR_TXQ_QUANTUM against defined maximum: holds=true. Evidence: nl80211_policy[NL80211_ATTR_TXQ_QUANTUM] = NLA_POLICY_FULL_RANGE(NLA_U32, &q_range) expands to NLA_POLICY_RANGE(NLA_U32, q_range.min, q_range.max) with q_range.max = INT_MAX, ensuring the netlink parser rejects values exceeding this bound
- Feature capability check before processing txq_quantum: holds=true. Evidence: The guard if (!wiphy_ext_feature_isset(&rdev->wiphy, NL80211_EXT_FEATURE_TXQS)) { result = -EOPNOTSUPP; goto out; } prevents processing when the driver does not support TXQs
- Mutual exclusion during wiphy parameter modification: holds=true. Evidence: wiphy_lock(&rdev->wiphy) is acquired before the attribute processing section, and wiphy_unlock(&rdev->wiphy) is called at the out label on all exit paths
- Atomicity of parameter changes with rollback on error: holds=true. Evidence: Old values are backed up before modification, and the error path after rdev_set_wiphy_params failure includes restoration logic before goto out

Residual risk notes: The range validation caps txq_quantum at INT_MAX (2^31 - 1), which is a large value. Whether this upper bound is sufficient to prevent excessive resource allocation or loop iteration in downstream code (rdev_set_wiphy_params and fq_tin_dequeue) depends on the semantics of how txq_quantum is consumed; the current context does not expose those callees' internal behavior. Additionally, the interaction between the netlink policy validation and nla_get_u32() relies on the netlink infrastructure correctly applying the policy before get functions are called.

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

## Vulnerability Assessment: CONFIRMED

**Classification:** Improper Input Validation (CWE-20) / Integer Constraint Violation (CWE-1284)

### Vulnerable Behavior

The pre-fix code accepts `txq_quantum = 0` through netlink without validation:

```c
txq_quantum = nla_get_u32(info->attrs[NL80211_ATTR_TXQ_QUANTUM]);
// No validation - any u32 value accepted including 0
rdev->wiphy.txq_quantum = txq_quantum;
result = rdev_set_wiphy_params(rdev, changed);
```

The policy shows the vulnerability was patched:
```c
[NL80211_ATTR_TXQ_QUANTUM] = { .type = NLA_U32 }, /* pre-fix - vulnerable */
[NL80211_ATTR_TXQ_QUANTUM] = NLA_POLICY_FULL_RANGE(NLA_U32, &q_range), /* post-fix */
```

### Root Cause

The "quantum" value represents bytes dequeued per round in fair queuing. Zero is semantically invalid and causes:
- **Division by zero** when calculating queue shares
- **Infinite loops** in dequeue logic
- **Complete queue starvation** preventing any packet transmission

The call path to `fq_tin_dequeue` confirms this value is used in queue management where zero breaks fundamental assumptions.

### Attack Preconditions

1. **CAP_NET_ADMIN** capability (or user namespace with network admin)
2. Wireless device with `NL80211_EXT_FEATURE_TXQS` support
3. Ability to send netlink messages to nl80211

### Exploitation

```c
// Attacker sends netlink message:
NL80211_CMD_SET_WIPHY with NL80211_ATTR_TXQ_QUANTUM = 0

// Kernel accepts and propagates:
rdev->wiphy.txq_quantum = 0
// Later in fq_tin_dequeue or related code:
bytes_allowed = quantum; // 0
while (bytes_sent < bytes_allowed) // never executes, or
share = total / quantum; // division by zero → panic
```

### Impact

- **Kernel panic** (division by zero)
- **System hang** (infinite loop in queue processing)
- **Denial of Service** (wireless networking completely disabled)
- Local attacker with network admin can crash the system

### Fix Analysis

The post-fix adds range validation with `max = INT_MAX`. However, the provided context doesn't show an explicit `.min` field in `q_range`. If the framework defaults to `.min = 0`, the fix is incomplete. A proper fix requires:

```c
static struct netlink_range_validation q_range = {
	.min = 1,  // or reasonable minimum quantum size
	.max = INT_MAX,
};
```

**Verdict:** Real vulnerability in pre-fix state. Privileged local attacker can trigger kernel crash/hang by setting invalid quantum value.

```json
{
  "vulnerable": true,
  "cwe": "CWE-1284",
  "severity": "high",
  "reviewed_region": "net/wireless/nl80211.c:nl80211_set_wiphy",
  "vulnerable_region": "net/wireless/nl80211.c:nl80211_set_wiphy - txq_quantum assignment without validation",
  "root_cause": "The pre-fix code accepts txq_quantum=0 from userspace via nla_get_u32() without validation. The netlink policy shows { .type = NLA_U32 } which accepts any u32 value including 0. This zero value is then assigned to rdev->wiphy.txq_quantum and propagated to queue management code where it causes division by zero or infinite loops in fq_tin_dequeue.",
  "exploitability": "HIGH - Attacker with CAP_NET_ADMIN can send NL80211_CMD_SET_WIPHY netlink message with NL80211_ATTR_TXQ_QUANTUM=0. The code path directly assigns this value without bounds checking. The call path to fq_tin_dequeue indicates this value is used in fair queuing calculations where zero breaks fundamental assumptions (quantum represents bytes per dequeue round).",
  "attack_preconditions": [
    "CAP_NET_ADMIN capability or equivalent network admin privileges",
    "Wireless device supporting NL80211_EXT_FEATURE_TXQS feature",
    "Ability to send netlink messages to nl80211 subsystem"
  ],
  "impact": "Kernel panic via division by zero in queue management, system hang from infinite loops in dequeue logic, or complete denial of service with wireless networking disabled. Local privileged attacker can crash the system.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The post-fix shows NLA_POLICY_FULL_RANGE(NLA_U32, &q_range) with q_range.max=INT_MAX, but the supplied context does not show an explicit .min field. If the netlink framework defaults .min to 0, the fix may be incomplete and should explicitly set .min=1 or a reasonable minimum quantum size.",
  "call_path": "nl80211_set_wiphy → nla_get_u32 → rdev_set_wiphy_params → fq_tin_dequeue"
}
```


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.

## Vulnerability: Out-of-Bounds Read (CWE-125)

**Verdict: VULNERABLE**

### Root Cause

The function calls `ceph_decode_32(p)` without first verifying that 4 bytes are available in the input buffer:

```c
len = ceph_decode_32(p);  // Line 1: NO bounds check
if (*p + len > end)       // Line 2: bounds check happens AFTER the read
    return -EINVAL;
```

The `ceph_decode_32` implementation reads 4 bytes unconditionally:
```c
u32 v = get_unaligned_le32(*p);  // reads 4 bytes without checking *p + 4 <= end
*p += sizeof(u32);
```

### Attack Vector

An attacker controlling the Ceph authentication server (or performing MitM) can send a malformed packet where the encrypted data section has fewer than 4 bytes. When `ceph_x_decrypt` is called:

1. `*p` points to the start of encrypted data
2. `end - *p < 4` (less than 4 bytes available)
3. `ceph_decode_32(p)` reads 4 bytes anyway, accessing memory beyond `end`
4. The bounds check on line 2 executes too late

### Impact

- **Information Disclosure**: Leaks up to 4 bytes of kernel memory beyond the buffer
- **Potential Crash**: If the out-of-bounds read crosses into unmapped memory

### Preconditions

- Attacker can send crafted authentication packets to the victim
- No prior authentication required (this code runs during authentication handshake)
- Reliably triggerable with any packet containing < 4 bytes in the encrypted section

### Evidence from Codebase

The codebase provides safe alternatives that this code should use:

```c
#define ceph_decode_32_safe(p, end, v, bad)
    do {
        ceph_decode_need(p, end, sizeof(u32), bad);  // checks bounds FIRST
        v = ceph_decode_32(p);
    } while (0)
```

The presence of these safe macros confirms that bounds checking before decode operations is the expected pattern.

### Fix

Add bounds validation before the decode:
```c
if (*p + sizeof(u32) > end)
    return -EINVAL;
len = ceph_decode_32(p);
```

Or use the safe macro pattern already established in the codebase.

```json
{
  "vulnerable": true,
  "cwe": "CWE-125",
  "severity": "MEDIUM-HIGH",
  "reviewed_region": "net/ceph/auth_x.c:ceph_x_decrypt lines 1-20",
  "vulnerable_region": "net/ceph/auth_x.c:ceph_x_decrypt line 8 (len = ceph_decode_32(p))",
  "root_cause": "ceph_decode_32(p) unconditionally reads 4 bytes and advances pointer before any bounds validation occurs. The subsequent check 'if (*p + len > end)' validates the encrypted data region but executes after *p has already been advanced 4 bytes, failing to protect the initial length field read.",
  "exploitability": "HIGH - Attacker controls packet format and can craft authentication packets with encrypted data sections containing fewer than 4 bytes. No prior authentication required as this code executes during authentication handshake. Vulnerability is deterministic and reliably triggerable.",
  "attack_preconditions": [
    "Attacker can send crafted Ceph authentication packets to victim",
    "Packet contains encrypted data section with < 4 bytes",
    "No authentication required (vulnerability in authentication path)",
    "Network connectivity to Ceph client/server"
  ],
  "impact": "Out-of-bounds read of up to 4 bytes of kernel memory beyond buffer boundary. Primary impact is information disclosure of kernel memory contents. Secondary impact is potential kernel crash if read crosses into unmapped memory region. Limited to 4-byte leak per invocation but can be repeated across multiple packets.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "The codebase provides ceph_decode_32_safe macro that performs bounds checking via ceph_decode_need before calling ceph_decode_32. The existence of these safe variants confirms that bounds checking before decode operations is the expected security pattern, and this function violates that pattern.",
  "call_path": "ceph_x_handle_reply → ceph_x_proc_ticket_reply → process_one_ticket → ceph_x_decrypt → ceph_decode_32 (out-of-bounds read occurs here)"
}
```


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

Repository: git.kernel.org/pub/scm/linux/kernel/git/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.

## Vulnerability Assessment: OUT-OF-BOUNDS READ

### Vulnerable Behavior

The function calculates a data pointer beyond the `iscsi_uevent` structure without verifying the containing buffer is large enough:

```c
char *data = (char*)ev + sizeof(*ev);  // Assumes data follows ev
if (ev->u.set_param.len > PAGE_SIZE)   // Only validates claimed length
    return -EINVAL;
```

The code then uses this potentially out-of-bounds pointer:
- `sscanf(data, "%d", &value)` - reads until null/non-digit (unbounded)
- `transport->set_param(conn, ..., data, ev->u.set_param.len)` - passes OOB pointer with attacker-controlled length

### Root Cause

**Missing buffer boundary validation**. The code validates `len <= PAGE_SIZE` but never confirms the message buffer is actually `sizeof(*ev) + len` bytes. It trusts the user-controlled `len` field to describe available data.

### Attack Preconditions

1. Attacker can send netlink messages to iSCSI transport interface (requires CAP_NET_ADMIN or equivalent)
2. Caller (`iscsi_if_recv_msg`) does not validate message size >= `sizeof(iscsi_uevent) + ev->u.set_param.len`
3. Attacker sends message where:
   - Actual buffer size = `sizeof(iscsi_uevent)` (or slightly larger)
   - `ev->u.set_param.len` = large value (up to PAGE_SIZE = 4096)

### Exploitability

**Likely exploitable** if caller lacks proper size validation. The pattern in `iscsi_set_host_param` (identical vulnerability) suggests systematic missing checks.

Attack vector:
```
Send netlink message:
  - Total size: sizeof(iscsi_uevent) bytes
  - ev->u.set_param.len: 4096
  - Result: data pointer reads 4096 bytes beyond allocated buffer
```

### Impact

- **Information Disclosure**: Leaks up to 4KB of kernel memory through:
  - `sscanf` reading beyond buffer until delimiter
  - `transport->set_param` processing OOB data
- **Denial of Service**: Potential crash if accessing unmapped pages

### Residual Uncertainty

Cannot definitively confirm exploitability without seeing `iscsi_if_recv_msg` implementation. If that function validates `nlmsg_len >= sizeof(*ev) + ev->u.set_param.len`, the vulnerability is mitigated at the caller level.

However, the identical pattern in `iscsi_set_host_param` and lack of local validation strongly suggests this is a real vulnerability.

### Classification

**CWE-805: Buffer Access with Incorrect Length Value**
Secondary: **CWE-125: Out-of-bounds Read**

The code uses an attacker-controlled length field to access memory without validating the length against actual buffer boundaries.

```json
{
  "vulnerable": true,
  "cwe": "CWE-805",
  "severity": "HIGH",
  "reviewed_region": "drivers/scsi/scsi_transport_iscsi.c:iscsi_set_param",
  "vulnerable_region": "Line calculating data pointer and subsequent usage without buffer size validation: char *data = (char*)ev + sizeof(*ev); followed by sscanf(data, ...) and transport->set_param(..., data, ev->u.set_param.len)",
  "root_cause": "The function calculates a data pointer beyond the iscsi_uevent structure and uses an attacker-controlled length field (ev->u.set_param.len) to describe available data, but never validates that the containing buffer is actually sizeof(*ev) + len bytes. Only validates len <= PAGE_SIZE, not that the buffer contains that much data after the structure.",
  "exploitability": "Likely exploitable if caller (iscsi_if_recv_msg) lacks proper message size validation. Attacker with CAP_NET_ADMIN can send netlink message with actual size = sizeof(iscsi_uevent) but ev->u.set_param.len = large value (up to 4096), causing OOB read when sscanf or transport->set_param accesses the data pointer. The identical vulnerability pattern in iscsi_set_host_param suggests systematic missing validation.",
  "attack_preconditions": [
    "Attacker has CAP_NET_ADMIN or equivalent netlink access to iSCSI transport interface",
    "Caller function does not validate message size >= sizeof(iscsi_uevent) + ev->u.set_param.len",
    "Attacker can craft netlink message with small actual size but large len field"
  ],
  "impact": "Information disclosure of up to 4KB of kernel memory through sscanf reading beyond buffer or transport->set_param processing OOB data. Potential denial of service if accessing unmapped pages causes kernel panic. Severity elevated by direct kernel memory access without bounds checking.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Cannot definitively confirm exploitability without examining iscsi_if_recv_msg implementation. If that caller validates nlmsg_len >= sizeof(*ev) + ev->u.set_param.len, vulnerability is mitigated at caller level. However, the identical pattern in iscsi_set_host_param and complete absence of local buffer size validation strongly indicates this is a real vulnerability requiring caller-level validation that likely doesn't exist.",
  "call_path": "iscsi_if_rx → iscsi_if_recv_msg → iscsi_set_param → [sscanf on OOB pointer OR transport->set_param with OOB pointer]"
}
```


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

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.

The DNS rebinding protection mechanism is **not vulnerable** to bypass attacks in its current implementation.

## Protection Mechanism Analysis

The code implements DNS rebinding protection by:
1. Checking if the server is listening on a loopback address (via `http.LocalAddrContextKey`)
2. Validating that the `Host` header also references a loopback address
3. Rejecting requests where server is on loopback but Host header is not

## IsLoopback Function Correctness

The `IsLoopback` helper correctly identifies loopback addresses:
- Handles "localhost" literal (case-sensitive)
- Parses IPv4 addresses (127.0.0.1)
- Parses IPv6 addresses (::1) with or without brackets
- Properly extracts host from "host:port" format via `net.SplitHostPort`
- Falls back to bracket trimming when port parsing fails

Tested edge cases:
- `IsLoopback("127.0.0.1:8080")` → true (splits to "127.0.0.1")
- `IsLoopback("[::1]:8080")` → true (splits to "::1")
- `IsLoopback("evil.com")` → false (not an IP, not "localhost")
- `IsLoopback("evil.com:8080")` → false (splits to "evil.com")

## Attack Vector Analysis

**DNS Rebinding Attack (Blocked):**
1. Attacker controls evil.com, initially resolving to attacker IP
2. DNS changes to resolve to 127.0.0.1
3. Browser sends request to 127.0.0.1 with `Host: evil.com`
4. Protection triggers: `IsLoopback(localAddr) && !IsLoopback("evil.com")`
5. Request rejected with 403 Forbidden ✓

**Case Sensitivity (False Positive, Not Bypass):**
- `IsLoopback("LOCALHOST")` returns false (case-sensitive check)
- This causes legitimate requests with `Host: LOCALHOST` to be rejected
- This is overly strict but not a security bypass

**Malformed Inputs (Handled):**
- Missing brackets, invalid ports, etc. are handled gracefully
- No bypass identified through malformed Host headers

## Environmental Dependency

The protection has one critical dependency:
```go
if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil
```

If `http.LocalAddrContextKey` is not set in the request context, the entire protection block is skipped. This occurs when:
- Running behind reverse proxies that don't preserve local address context
- Using custom HTTP server implementations
- Testing with mocked requests

However, this is a **deployment configuration issue**, not a code vulnerability. The standard Go `net/http` server sets this value correctly.

## Verdict

**Not Vulnerable.** The DNS rebinding protection correctly validates that requests to loopback servers include loopback Host headers. The implementation properly handles various address formats and cannot be bypassed through malformed inputs or case manipulation. The only concern is environmental - the protection depends on `http.LocalAddrContextKey` being set by the HTTP server, which is guaranteed by Go's standard library but may not hold in non-standard deployments.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": "none",
  "reviewed_region": "mcp/streamable.go:ServeHTTP:DNS rebinding protection block (lines ~230-238)",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": "not_exploitable",
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "The DNS rebinding protection correctly validates that requests to loopback-bound servers include loopback Host headers. The IsLoopback helper properly parses host:port formats, handles IPv4/IPv6 addresses with netip.ParseAddr, and checks the 'localhost' literal. Attack attempts with non-loopback Host headers (e.g., 'evil.com') are rejected with 403 Forbidden when localAddr is loopback, preventing DNS rebinding attacks where an attacker-controlled domain resolves to 127.0.0.1.",
  "security_invariants": [
    "When server listens on loopback address AND DisableLocalhostProtection=false AND disablelocalhostprotection!=1, requests MUST have loopback Host header",
    "IsLoopback correctly identifies 127.0.0.0/8, ::1, and 'localhost' literal as loopback",
    "IsLoopback handles host:port format via net.SplitHostPort before IP parsing",
    "Non-loopback Host headers to loopback servers trigger 403 Forbidden response"
  ],
  "invariant_verification": "Verified through code inspection: (1) http.LocalAddrContextKey provides server bind address, (2) IsLoopback(localAddr.String()) checks if server is on loopback, (3) IsLoopback(req.Host) checks if Host header is loopback, (4) condition '!IsLoopback(req.Host)' when server is loopback triggers rejection. The IsLoopback implementation correctly uses net.SplitHostPort to extract host from 'host:port', then netip.ParseAddr for IP validation, with explicit 'localhost' string check.",
  "residual_risk_notes": "Protection depends on http.LocalAddrContextKey being set by HTTP server - standard Go net/http sets this, but custom server implementations or reverse proxies may not preserve it, causing protection bypass through environmental misconfiguration rather than code vulnerability. Case-sensitive 'localhost' check rejects 'LOCALHOST' (overly strict, not a bypass). Protection can be disabled via DisableLocalhostProtection option or disablelocalhostprotection debug flag.",
  "call_path": "ServeHTTP (mcp/streamable.go) → http.LocalAddrContextKey (net/http) → util.IsLoopback (internal/util/net.go)"
}
```


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

Repository: git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
Language: C
File: net/ipv4/tcp_input.c
Function: tcp_cwnd_reduction

Call path: tcp_enter_cwr (net/ipv4/tcp_input.c) → tcp_init_cwnd_reduction (net/ipv4/tcp_input.c) → tcp_cwnd_reduction (net/ipv4/tcp_input.c) → tcp_try_keep_open (net/ipv4/tcp_input.c) → tcp_process_loss (net/ipv4/tcp_input.c) → tcp_fastretrans_alert (net/ipv4/tcp_input.c)

### Primary Function

```c
static void tcp_cwnd_reduction(struct sock *sk, const int prior_unsacked,
				       int fast_rexmit, int flag)
{
	struct tcp_sock *tp = tcp_sk(sk);
	int sndcnt = 0;
	int delta = tp->snd_ssthresh - tcp_packets_in_flight(tp);
	int newly_acked_sacked = prior_unsacked -
				 (tp->packets_out - tp->sacked_out);

	if (newly_acked_sacked <= 0 || WARN_ON_ONCE(!tp->prior_cwnd))
		return;

	tp->prr_delivered += newly_acked_sacked;
	if (delta < 0) {
		u64 dividend = (u64)tp->snd_ssthresh * tp->prr_delivered +
			       tp->prior_cwnd - 1;
		sndcnt = div_u64(dividend, tp->prior_cwnd) - tp->prr_out;
	} else if ((flag & FLAG_RETRANS_DATA_ACKED) &&
		   !(flag & FLAG_LOST_RETRANS)) {
		sndcnt = min_t(int, delta,
			       max_t(int, tp->prr_delivered - tp->prr_out,
				     newly_acked_sacked) + 1);
	} else {
		sndcnt = min(delta, newly_acked_sacked);
	}
	sndcnt = max(sndcnt, (fast_rexmit ? 1 : 0));
	tp->snd_cwnd = tcp_packets_in_flight(tp) + sndcnt;
}
```

### Cross-File Context

[tcp_init_cwnd_reduction — helper — net/ipv4/tcp_input.c:2458-2470]
```c
static void tcp_init_cwnd_reduction(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	tp->high_seq = tp->snd_nxt;
	tp->tlp_high_seq = 0;
	tp->snd_cwnd_cnt = 0;
	tp->prior_cwnd = tp->snd_cwnd;
	tp->prr_delivered = 0;
	tp->prr_out = 0;
	tp->snd_ssthresh = inet_csk(sk)->icsk_ca_ops->ssthresh(sk);
	tcp_ecn_queue_cwr(tp);
}
```

[tcp_packets_in_flight — function — include/net/tcp.h:997-1001]
```c
static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
	return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}
```

[div_u64 — function — include/linux/math64.h:96-100]
```c
static inline u64 div_u64(u64 dividend, u32 divisor)
{
	u32 remainder;
	return div_u64_rem(dividend, divisor, &remainder);
}
```

[WARN_ON_ONCE — macro — include/asm-generic/bug.h:109-118]
WARN_ON_ONCE → #define WARN_ON_ONCE(condition) ({ \ static bool __section(.data.unlikely) __warned; \ int __ret_warn_once = !!(condition); \ \ if (unlikely(__ret_warn_once)) \ if (WARN_ON(!__warned)) \ __warned = true; \ unlikely(__ret_warn_once); \ })  (include/asm-generic/bug.h:109-118)

[struct tcp_sock — struct — include/linux/tcp.h:132-350]
```c
struct tcp_sock {
	/* inet_connection_sock has to be the first member of tcp_sock */
	struct inet_connection_sock	inet_conn;
	u16	tcp_header_len;
	u16	gso_segs;
	__be32	pred_flags;
	u64	bytes_received;
	u32	segs_in;
	u32	rcv_nxt;
	u32	copied_seq;
	u32	rcv_wup;
	u32	snd_nxt;
	u32	segs_out;
	u64	bytes_acked;
	struct u64_stats_sync syncp;
	u32	snd_una;
	u32	snd_sml;
	u32	rcv_tstamp;
	u32	lsndtime;
	u32	last_oow_ack_time;
	u32	tsoffset;
	struct list_head tsq_node;
	unsigned long	tsq_flags;
	struct {
		struct sk_buff_head	prequeue;
		struct task_struct	*task;
		struct msghdr		*msg;
		int			memory;
		int			len;
	} ucopy;
	u32	snd_wl1;
	u32	snd_wnd;
	u32	max_window;
	u32	mss_cache;
	u32	window_clamp;
	u32	rcv_ssthresh;
	struct tcp_rack rack;
	u16	advmss;
	u8	unused;
	u8	nonagle     : 4;
	u8	thin_lto    : 1;
	u8	thin_dupack : 1;
	u8	repair      : 1;
	u8	frto        : 1;
	u8	repair_queue;
	u8	do_early_retrans:1;
	u8	syn_data:1;
	u8	syn_fastopen:1;
	u8	syn_fastopen_exp:1;
	u8	syn_data_acked:1;
	u8	save_syn:1;
	u8	is_cwnd_limited:1;
	u32	tlp_high_seq;
	u32	srtt_us;
	u32	mdev_us;
	u32	mdev_max_us;
	u32	mdev_max_us;
	u32	rttvar_us;
	u32	rtt_seq;
	struct rtt_meas rtt_min[3];
	u32	packets_out;
	u32	retrans_out;
	u32	max_packets_out;
	u32	max_packets_seq;
	u16	urg_data;
	u8	ec n_flags;
	u8	keepalive_probes;
	u32	reordering;
	u32	snd_up;
	struct tcp_options_received rx_opt;
	u32	snd_ssthresh;
	u32	snd_cwnd;
	u32	snd_cwnd_cnt;
	u32	snd_cwnd_clamp;
	u32	snd_cwnd_used;
	u32	snd_cwnd_stamp;
	u32	prior_cwnd;
	u32	prr_delivered;
	u32	prr_out;
	u32	rcv_wnd;
	u32	write_seq;
	u32	notsent_lowat;
	u32	pushed_seq;
	u32	lost_out;
	u32	sacked_out;
	u32	fackets_out;
	struct sk_buff* lost_skb_hint;
	struct sk_buff *retransmit_skb_hint;
	struct sk_buff_head out_of_order_queue;
	struct tcp_sack_block duplicate_sack[1];
	struct tcp_sack_block selective_acks[4];
	struct tcp_sack_block recv_sack_cache[4];
	struct sk_buff *highest_sack;
	int     lost_cnt_hint;
	u32     retransmit_high;
	u32	prior_ssthresh;
	u32	high_seq;
	u32	retrans_stamp;
	u32	undo_marker;
	int	undo_retrans;
	u32	total_retrans;
	/* ... more fields ... */
}
```

[FLAG_RETRANS_DATA_ACKED — constant — net/ipv4/tcp_input.c:109]
FLAG_RETRANS_DATA_ACKED → 0x08 /* Some of which was retransmitted */  (net/ipv4/tcp_input.c:109)

[FLAG_LOST_RETRANS — constant — net/ipv4/tcp_input.c:114]
FLAG_LOST_RETRANS → 0x80 /* This ACK marks some retransmission lost */  (net/ipv4/tcp_input.c:114)

Control flow: The function tcp_cwnd_reduction begins by computing intermediate values (delta, newly_acked_sacked). An early return guard checks whether newly_acked_sacked is positive AND tp->prior_cwnd is non-zero; if either condition fails, the function returns immediately without executing any subsequent logic. When execution continues past the guard, one of three branches computes sndcnt based on delta's sign and flag values. In the delta < 0 branch, div_u64 is called with tp->prior_cwnd as divisor. All three branches flow to updating tp->snd_cwnd as the final operation.

Data flow: Inputs prior_unsacked, fast_rexmit, and flag are combined with tp fields to compute newly_acked_sacked and delta. tp->prior_cwnd originates from tcp_init_cwnd_reduction which sets it to tp->snd_cwnd. The newly_acked_sacked value is added to tp->prr_delivered. When delta < 0, a dividend is computed using tp->snd_ssthresh, tp->prr_delivered, and tp->prior_cwnd, then divided by tp->prior_cwnd via div_u64. The resulting sndcnt (constrained by fast_rexmit and delta logic) is added to tcp_packets_in_flight(tp) to set tp->snd_cwnd.

Verdict: Not Vulnerable

Reviewed region:
[net/ipv4/tcp_input.c]
```c
static void tcp_cwnd_reduction(struct sock *sk, const int prior_unsacked,
				       int fast_rexmit, int flag)
{
	struct tcp_sock *tp = tcp_sk(sk);
	int sndcnt = 0;
	int delta = tp->snd_ssthresh - tcp_packets_in_flight(tp);
	int newly_acked_sacked = prior_unsacked -
				 (tp->packets_out - tp->sacked_out);

	if (newly_acked_sacked <= 0 || WARN_ON_ONCE(!tp->prior_cwnd))
		return;

	tp->prr_delivered += newly_acked_sacked;
	if (delta < 0) {
		u64 dividend = (u64)tp->snd_ssthresh * tp->prr_delivered +
			       tp->prior_cwnd - 1;
		sndcnt = div_u64(dividend, tp->prior_cwnd) - tp->prr_out;
	} else if ((flag & FLAG_RETRANS_DATA_ACKED) &&
		   !(flag & FLAG_LOST_RETRANS)) {
		sndcnt = min_t(int, delta,
			       max_t(int, tp->prr_delivered - tp->prr_out,
				     newly_acked_sacked) + 1);
	} else {
		sndcnt = min(delta, newly_acked_sacked);
	}
	sndcnt = max(sndcnt, (fast_rexmit ? 1 : 0));
	tp->snd_cwnd = tcp_packets_in_flight(tp) + sndcnt;
}
```

Why it is not vulnerable: The division by tp->prior_cwnd in the delta < 0 branch (div_u64(dividend, tp->prior_cwnd)) is protected by an early return guard at the function entry: 'if (newly_acked_sacked <= 0 || WARN_ON_ONCE(!tp->prior_cwnd)) return;'. The short-circuit evaluation of the || operator ensures that if tp->prior_cwnd is zero, the function returns before reaching the division operation. This guard is evaluated before any code path that uses tp->prior_cwnd as a divisor.

Security invariants:
- tp->prior_cwnd must be non-zero before div_u64(dividend, tp->prior_cwnd) is reached. This is enforced by the guard 'if (newly_acked_sacked <= 0 || WARN_ON_ONCE(!tp->prior_cwnd)) return;' which uses short-circuit || evaluation, guaranteeing early return when tp->prior_cwnd is zero.
- The short-circuit evaluation of || ensures the WARN_ON_ONCE(!tp->prior_cwnd) check is always evaluated before any division by tp->prior_cwnd occurs, since the return statement immediately follows the guard.
- tcp_init_cwnd_reduction sets tp->prior_cwnd = tp->snd_cwnd, establishing that prior_cwnd reflects the congestion window value at the time congestion control state is initialized.

Invariant verification:
- Division by zero prevention for tp->prior_cwnd: holds=true. Evidence: The guard 'if (newly_acked_sacked <= 0 || WARN_ON_ONCE(!tp->prior_cwnd)) return;' at the function entry ensures tp->prior_cwnd != 0 before any subsequent code executes, including the div_u64(dividend, tp->prior_cwnd) call in the delta < 0 branch. The || operator's short-circuit semantics guarantee the check is evaluated and the return is taken if prior_cwnd is zero.
- All code paths reaching division pass the non-zero guard: holds=true. Evidence: The function has a single entry point and the guard is placed before all computational branches. The delta < 0 branch containing div_u64(dividend, tp->prior_cwnd) can only be reached after the guard has confirmed tp->prior_cwnd is non-zero.

Residual risk notes: The safety of the division relies on tp->prior_cwnd remaining non-zero between the guard check and the div_u64 call. If concurrent modification of tp->prior_cwnd were possible, a TOCTOU issue could arise. However, the surrounding TCP input processing context typically holds the socket lock (sk_lock), serializing access to these fields. Without visibility into the locking discipline in the callers, this assumption cannot be fully verified from the supplied context alone.

The code is **not vulnerable**.

## Analysis

This function implements TCP Proportional Rate Reduction (PRR) for congestion control. I traced potential integer overflow/underflow paths and verified guards:

### Protected Scenarios

**1. Integer underflow in `newly_acked_sacked`:**
```c
int newly_acked_sacked = prior_unsacked - (tp->packets_out - tp->sacked_out);
```
If `tp->sacked_out > tp->packets_out`, the inner subtraction underflows to a large u32 value, making `newly_acked_sacked` very negative. However:
```c
if (newly_acked_sacked <= 0 || WARN_ON_ONCE(!tp->prior_cwnd))
    return;
```
This guard catches negative/zero values and returns early, preventing further processing.

**2. Division by zero:**
```c
sndcnt = div_u64(dividend, tp->prior_cwnd) - tp->prr_out;
```
The same guard (`WARN_ON_ONCE(!tp->prior_cwnd)`) prevents division by zero.

**3. Overflow in dividend calculation:**
```c
u64 dividend = (u64)tp->snd_ssthresh * tp->prr_delivered + tp->prior_cwnd - 1;
```
The explicit cast to `u64` before multiplication prevents 32-bit overflow. All subsequent additions are safe within u64 range.

**4. Negative `sndcnt` clamping:**
```c
sndcnt = max(sndcnt, (fast_rexmit ? 1 : 0));
```
Even if `sndcnt` becomes negative (e.g., when `tp->prr_out` exceeds the division result), this clamps it to non-negative values before the final cwnd calculation.

**5. Underflow in `max_t` expression:**
```c
max_t(int, tp->prr_delivered - tp->prr_out, newly_acked_sacked)
```
If `tp->prr_out > tp->prr_delivered`, the subtraction underflows. When cast to `int`, this produces a negative value. The `max_t` then correctly selects `newly_acked_sacked` (positive), making the result bounded and safe.

### Intentional Design

The signed `delta` calculation:
```c
int delta = tp->snd_ssthresh - tcp_packets_in_flight(tp);
```
Intentionally allows negative values to signal that packets in flight exceed the slow start threshold. The code explicitly handles `if (delta < 0)` as a distinct congestion case.

### Theoretical but Infeasible

Overflow in `tp->prr_delivered` accumulation would require ~43 million ACKs in a single congestion event without reset. This is bounded by network physics and the congestion control state machine that resets `prr_delivered` via `tcp_init_cwnd_reduction` at each new congestion event.

## Verdict

No exploitable vulnerability. The function has appropriate guards for edge cases, intentional signed/unsigned handling for the congestion control algorithm, and bounds that prevent realistic overflow scenarios.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": "none",
  "reviewed_region": "tcp_cwnd_reduction function in net/ipv4/tcp_input.c, lines implementing PRR congestion control algorithm",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": null,
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "All potential integer overflow/underflow paths are protected by explicit guards. The early return guard 'if (newly_acked_sacked <= 0 || WARN_ON_ONCE(!tp->prior_cwnd))' prevents: (1) processing when newly_acked_sacked underflows to negative values due to inconsistent packet counters, (2) division by zero in the PRR calculation. The u64 cast in dividend calculation prevents 32-bit multiplication overflow. The max() clamp ensures sndcnt is non-negative before final cwnd calculation. Negative delta values are intentional design for the congestion control algorithm, not a bug.",
  "security_invariants": [
    "newly_acked_sacked must be positive to proceed past early return guard",
    "tp->prior_cwnd must be non-zero (enforced by WARN_ON_ONCE) to prevent division by zero",
    "dividend calculation uses u64 to prevent multiplication overflow of u32 values",
    "sndcnt is clamped to non-negative via max(sndcnt, 0 or 1) before use in cwnd calculation",
    "tcp_init_cwnd_reduction initializes prr_delivered=0 and prr_out=0 at congestion event start",
    "tp->prr_delivered accumulates monotonically within a congestion window reduction episode"
  ],
  "invariant_verification": "Guard at line 'if (newly_acked_sacked <= 0 || WARN_ON_ONCE(!tp->prior_cwnd))' explicitly checks both conditions before any arithmetic operations that depend on them. The u64 cast is visible in 'u64 dividend = (u64)tp->snd_ssthresh * tp->prr_delivered'. The max() clamp is explicit in 'sndcnt = max(sndcnt, (fast_rexmit ? 1 : 0))'. tcp_init_cwnd_reduction shows initialization of prr_delivered and prr_out to 0.",
  "residual_risk_notes": "The code assumes tcp_packets_in_flight() returns reasonable values bounded by network constraints. Extreme packet counter corruption from memory corruption elsewhere could theoretically cause issues, but this is outside the scope of this function's control flow. The WARN_ON_ONCE for !tp->prior_cwnd suggests the kernel developers consider zero prior_cwnd an abnormal state that should not occur in normal operation.",
  "call_path": "tcp_enter_cwr → tcp_init_cwnd_reduction → tcp_cwnd_reduction → tcp_try_keep_open → tcp_process_loss → tcp_fastretrans_alert"
}
```