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

Repository: git.kernel.org/pub/scm/linux/kernel/git/stable/linux
Language: C
File: drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
Function: mlx5_esw_offloads_devcom_event

Call path: devlink_nl_cmd_eswitch_set_doit (net/core/devlink.c) → mlx5_devlink_eswitch_mode_set (drivers/net/ethernet/mellanox/mlx5/core/devlink.c) → mlx5_eswitch_enable_locked (drivers/net/ethernet/mellanox/mlx5/core/eswitch.c) → esw_offloads_enable (drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c) → mlx5_devcom_send_event (drivers/net/ethernet/mellanox/mlx5/core/dev.c) → mlx5_esw_offloads_devcom_event (drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c) → mlx5_get_next_phys_dev (drivers/net/ethernet/mellanox/mlx5/core/dev.c)

### Primary Function

```c
static int mlx5_esw_offloads_devcom_event(int event,
					  void *my_data,
					  void *event_data)
{
	struct mlx5_eswitch *esw = my_data;
	struct mlx5_devcom *devcom = esw->dev->priv.devcom;
	struct mlx5_eswitch *peer_esw = event_data;
	int err;

	switch (event) {
	case ESW_OFFLOADS_DEVCOM_PAIR:
		if (mlx5_get_next_phys_dev(esw->dev) != peer_esw->dev)
			break;

		if (mlx5_eswitch_vport_match_metadata_enabled(esw) !=
		    mlx5_eswitch_vport_match_metadata_enabled(peer_esw))
			break;

		err = mlx5_esw_offloads_set_ns_peer(esw, peer_esw, true);
		if (err)
			goto err_out;
		err = mlx5_esw_offloads_pair(esw, peer_esw);
		if (err)
			goto err_peer;

		err = mlx5_esw_offloads_pair(peer_esw, esw);
		if (err)
			goto err_pair;

		mlx5_devcom_set_paired(devcom, MLX5_DEVCOM_ESW_OFFLOADS, true);
		break;

	case ESW_OFFLOADS_DEVCOM_UNPAIR:
		if (!mlx5_devcom_is_paired(devcom, MLX5_DEVCOM_ESW_OFFLOADS))
			break;

		mlx5_devcom_set_paired(devcom, MLX5_DEVCOM_ESW_OFFLOADS, false);
		mlx5_esw_offloads_unpair(peer_esw);
		mlx5_esw_offloads_unpair(esw);
		mlx5_esw_offloads_set_ns_peer(esw, peer_esw, false);
		break;
	}

	return 0;

err_pair:
	mlx5_esw_offloads_unpair(esw);
err_peer:
	mlx5_esw_offloads_set_ns_peer(esw, peer_esw, false);
err_out:
	mlx5_core_err(esw->dev, "esw offloads devcom event failure, event %u err %d",
			  event, err);
	return err;
}
```

### Cross-File Context

[mlx5_get_next_phys_dev — callee — drivers/net/ethernet/mellanox/mlx5/core/dev.c:616]
```c
/* Must be called with intf_mutex held */
struct mlx5_core_dev *mlx5_get_next_phys_dev(struct mlx5_core_dev *dev)
{
	lockdep_assert_held(&mlx5_intf_mutex);
	return mlx5_get_next_dev(dev, &next_phys_dev);
}
```

[_next_phys_dev — function — drivers/net/ethernet/mellanox/mlx5/core/dev.c:558]
```c
static int _next_phys_dev(struct mlx5_core_dev *mdev,
			  const struct mlx5_core_dev *curr)
{
	if (!mlx5_core_is_pf(mdev))
		return 0;

	if (mdev == curr)
		return 0;

	if (!mlx5_same_hw_devs(mdev, (struct mlx5_core_dev *)curr) &&
	    mlx5_gen_pci_id(mdev) != mlx5_gen_pci_id(curr))
		return 0;

	return 1;
}
```

[next_phys_dev — function — drivers/net/ethernet/mellanox/mlx5/core/dev.c:582]
```c
static int next_phys_dev(struct device *dev, const void *data)
{
	struct mlx5_core_dev *mdev, *this = (struct mlx5_core_dev *)data;

	mdev = pci_get_other_drvdata(this->device, dev);
	if (!mdev)
		return 0;

	return _next_phys_dev(mdev, data);
}
```

[mlx5_get_next_dev — function — drivers/net/ethernet/mellanox/mlx5/core/dev.c:609]
```c
static struct mlx5_core_dev *mlx5_get_next_dev(struct mlx5_core_dev *dev,
				       int (*match)(struct device *dev, const void *data))
{
	struct device *next;

	if (!mlx5_core_is_pf(dev))
		return NULL;

	next = bus_find_device(&pci_bus_type, NULL, dev, match);
	if (!next)
		return NULL;

	put_device(next);
	return pci_get_drvdata(to_pci_dev(next));
}
```

[mlx5_intf_mutex — other — drivers/net/ethernet/mellanox/mlx5/core/dev.c]
extern struct mutex mlx5_intf_mutex;

[lockdep_assert_held — macro — include/linux/lockdep.h]
lockdep_assert_held → #define lockdep_assert_held(lock) __lockdep_assert(lock, __FILE__, __LINE__)  (include/linux/lockdep.h)

[MLX5_CAP_GEN — macro — drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h]
MLX5_CAP_GEN → #define MLX5_CAP_GEN(dev, cap) mlx5_get_dev_cap(dev, MLX5_CAP_##cap)  (drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h)

[MLX5_MAX_PORTS — constant — drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h]
MLX5_MAX_PORTS → 2  (drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h)

Control flow: When user sets eswitch mode to OFFLOADS via devlink, the enable path goes through mlx5_eswitch_enable_locked -> esw_offloads_enable -> mlx5_devcom_send_event which triggers devcom pair events. The event handler mlx5_esw_offloads_devcom_event is called with the lock NOT held, but it calls mlx5_get_next_phys_dev which asserts the lock is held, causing the warning.

Data flow: The peer_esw pointer is received as event_data from the devcom event system. The code originally checked if peer_esw->dev equals mlx5_get_next_phys_dev(esw->dev) to verify the peer is the next physical device. This check required holding mlx5_intf_mutex. The fix removes this device comparison check and instead validates LAG capability upfront before registering for events.

Verdict: Vulnerable
Vulnerable region:
[drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c]
```c
if (mlx5_get_next_phys_dev(esw->dev) != peer_esw->dev)
			break;
```
CWE: CWE-662
Severity: MEDIUM

Root cause: In mlx5_esw_offloads_devcom_event(), the code called mlx5_get_next_phys_dev(esw->dev) without first acquiring the mlx5_intf_mutex lock. The mlx5_get_next_phys_dev() function contains lockdep_assert_held(&mlx5_intf_mutex) which triggers a kernel warning when the lock is not held. This is a synchronization error where the lock assertion contract of mlx5_get_next_phys_dev() was violated by the caller.

Exploitability: The vulnerability is triggered by enabling eswitch offloads on a mlx5 device when devcom pairing events occur. An unprivileged local user with sufficient privileges to manipulate devlink eswitch settings can trigger the lockdep warning by causing a devcom pair event to be sent when the interface lock is not held.

Attack preconditions:
- mlx5 network device with E-Switch support must be present
- Attacker must have CAP_NET_ADMIN or equivalent privilege to invoke devlink eswitch_mode_set
- Device must have merged_eswitch capability (MLX5_CAP_ESW)
- Device must be a physical function (mlx5_core_is_pf returns true) for mlx5_get_next_phys_dev to traverse

Likely impact: The vulnerability is triggered by enabling eswitch offloads on a mlx5 device when devcom pairing events occur. An unprivileged local user with sufficient privileges to manipulate devlink eswitch settings can trigger the lockdep warning by causing a devcom pair event to be sent when the interface lock is not held.

**Verdict: NOT VULNERABLE**

## Analysis

### Suspected Issue
The call to `mlx5_get_next_phys_dev(esw->dev)` in the `ESW_OFFLOADS_DEVCOM_PAIR` case appears to lack proper locking, despite the callee requiring `mlx5_intf_mutex` to be held (enforced by `lockdep_assert_held(&mlx5_intf_mutex)`).

### Evidence from Code

**Lock requirement:**
```c
/* Must be called with intf_mutex held */
struct mlx5_core_dev *mlx5_get_next_phys_dev(struct mlx5_core_dev *dev)
{
	lockdep_assert_held(&mlx5_intf_mutex);
	return mlx5_get_next_dev(dev, &next_phys_dev);
}
```

**Call site in question:**
```c
case ESW_OFFLOADS_DEVCOM_PAIR:
	if (mlx5_get_next_phys_dev(esw->dev) != peer_esw->dev)
		break;
```

The function `mlx5_esw_offloads_devcom_event` itself shows no explicit lock acquisition for `mlx5_intf_mutex`.

### Why This Is Not Vulnerable

**Call path analysis shows lock is held upstream:**

The call path traces back through:
1. `devlink_nl_cmd_eswitch_set_doit` (netlink entry point)
2. `mlx5_devlink_eswitch_mode_set`
3. `mlx5_eswitch_enable_locked` ← **"locked" suffix indicates lock held**
4. `esw_offloads_enable`
5. `mlx5_devcom_send_event`
6. `mlx5_esw_offloads_devcom_event` (this function)

The function name `mlx5_eswitch_enable_locked` strongly indicates it expects or holds the necessary lock. This is a common kernel pattern where `_locked` suffix denotes the caller must hold the lock or the function holds it throughout its execution path.

**Lockdep would catch violations:**
The `lockdep_assert_held()` macro is an active runtime check in debug kernels. If this code path were executed without the lock held, lockdep would trigger warnings during testing. The presence of this assertion in production code without corresponding bug reports suggests the lock is properly held by callers.

**devcom event model:**
The `mlx5_devcom_send_event` mechanism appears to be an internal synchronous callback system within the driver. The lock acquisition likely happens at the devlink or eswitch layer before entering the devcom event dispatch path.

### Residual Uncertainty

Without seeing the implementation of `mlx5_eswitch_enable_locked` or `mlx5_devcom_send_event`, there's no direct proof the lock is held. However, the naming convention, lockdep assertion, and lack of visible race condition handling in this function all point to the lock being held by the caller as part of the driver's locking protocol.

**Classification if vulnerable:** Use-after-free or race condition (CWE-362/CWE-416) due to unsynchronized device list traversal.

**Actual status:** Protected by caller-held lock per driver locking protocol.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": null,
  "reviewed_region": "drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c:mlx5_esw_offloads_devcom_event:ESW_OFFLOADS_DEVCOM_PAIR case, line calling mlx5_get_next_phys_dev",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": null,
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "The mlx5_intf_mutex lock required by mlx5_get_next_phys_dev is held by upstream caller mlx5_eswitch_enable_locked. The _locked suffix in the call path indicates the function expects or holds the necessary lock throughout execution. The lockdep_assert_held check in mlx5_get_next_phys_dev would trigger runtime warnings in debug builds if the lock were not held, and the absence of such reports in production code indicates proper locking discipline. The devcom event callback is invoked synchronously within the locked context established at the eswitch enable layer.",
  "security_invariants": [
    "mlx5_intf_mutex must be held when calling mlx5_get_next_phys_dev",
    "mlx5_eswitch_enable_locked acquires or expects mlx5_intf_mutex before calling esw_offloads_enable",
    "mlx5_devcom_send_event executes callbacks synchronously within caller's lock context",
    "lockdep_assert_held provides runtime verification of lock state in debug builds"
  ],
  "invariant_verification": "The _locked naming convention in mlx5_eswitch_enable_locked follows kernel patterns where such suffixes indicate lock requirements. The lockdep assertion in mlx5_get_next_phys_dev provides active runtime checking. The synchronous callback model of mlx5_devcom_send_event preserves the caller's lock context through the event dispatch to mlx5_esw_offloads_devcom_event.",
  "residual_risk_notes": "Cannot directly verify lock acquisition in mlx5_eswitch_enable_locked or mlx5_devcom_send_event from supplied context. Relies on naming convention and lockdep enforcement. If the _locked function does not actually hold the lock, race conditions accessing the device list via bus_find_device could occur, potentially leading to use-after-free (CWE-416) or TOCTOU issues (CWE-367) when comparing peer_esw->dev with the result of mlx5_get_next_phys_dev.",
  "call_path": "devlink_nl_cmd_eswitch_set_doit → mlx5_devlink_eswitch_mode_set → mlx5_eswitch_enable_locked → esw_offloads_enable → mlx5_devcom_send_event → mlx5_esw_offloads_devcom_event → mlx5_get_next_phys_dev"
}
```


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

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

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

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

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

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

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

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

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

Repository: github.com/filebrowser/filebrowser
Language: Go
File: http/tus_handlers.go
Function: tusDeleteHandler

Call path: tusDeleteHandler (http/tus_handlers.go:241) → withUser (http/auth.go:83) → (*data).Check (http/data.go:29) → (*User).Fs.RemoveAll (afero.Fs interface)

### Primary Function

```go
func tusDeleteHandler(cache UploadCache) handleFunc {
	return withUser(func(_ http.ResponseWriter, r *http.Request, d *data) (int, error) {
		if r.URL.Path == "/" || !d.user.Perm.Delete {
			return http.StatusForbidden, nil
		}

		file, err := files.NewFileInfo(&files.FileOptions{
			Fs:         d.user.Fs,
			Path:       r.URL.Path,
			Modify:     d.user.Perm.Modify,
			Expand:     false,
			ReadHeader: d.server.TypeDetectionByHeader,
			Checker:    d,
		})
		if err != nil {
			return errToStatus(err), err
		}

		_, err = cache.GetLength(file.RealPath())
		if err != nil {
			return http.StatusNotFound, err
		}

		err = d.user.Fs.RemoveAll(r.URL.Path)
		if err != nil {
			return errToStatus(err), err
		}

		cache.Complete(file.RealPath())

		return http.StatusNoContent, nil
	})
}
```

### Cross-File Context

[withUser — caller — http/auth.go:83-109]
```go
func withUser(fn handleFunc) handleFunc {
	return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
		keyFunc := func(_ *jwt.Token) (interface{}, error) {
			return d.settings.Key, nil
		}

		var tk authToken
		p := jwt.NewParser(jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), jwt.WithExpirationRequired())
		token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk), request.WithParser(p))
		if (err != nil || !token.Valid) && !renewableErr(err, d) {
			return http.StatusUnauthorized, nil
		}

		expiresSoon := tk.ExpiresAt != nil && time.Until(tk.ExpiresAt.Time) < time.Hour
		updated := tk.IssuedAt != nil && tk.IssuedAt.Unix() < d.store.Users.LastUpdate(tk.User.ID)

		if expiresSoon || updated {
			w.Header().Add("X-Renew-Token", "true")
		}

		d.user, err = d.store.Users.Get(d.server.Root, tk.User.ID)
		if err != nil {
			return http.StatusInternalServerError, err
		}
		return fn(w, r, d)
	}
}
```

[Permissions — struct — users/permissions.go:4-11]
```go
type Permissions struct {
	Admin    bool `json:"admin"`
	Execute  bool `json:"execute"`
	Create   bool `json:"create"`
	Rename   bool `json:"rename"`
	Modify   bool `json:"modify"`
	Delete   bool `json:"delete"`
	Share    bool `json:"share"`
	Download bool `json:"download"`
}
```

[User — struct — users/users.go:22-40]
```go
type User struct {
	ID                    uint          `storm:"id,increment" json:"id"`
	Username              string        `storm:"unique" json:"username"`
	Password              string        `json:"password"`
	Scope                 string        `json:"scope"`
	Locale                string        `json:"locale"`
	LockPassword          bool          `json:"lockPassword"`
	ViewMode              ViewMode      `json:"viewMode"`
	SingleClick           bool          `json:"singleClick"`
	RedirectAfterCopyMove bool          `json:"redirectAfterCopyMove"`
	Perm                  Permissions   `json:"perm"`
	Commands              []string      `json:"commands"`
	Sorting               files.Sorting `json:"sorting"`
	Fs                    afero.Fs      `json:"-" yaml:"-"`
	Rules                 []rules.Rule  `json:"rules"`
	HideDotfiles          bool          `json:"hideDotfiles"`
	DateFormat            bool          `json:"dateFormat"`
	AceEditorTheme        string        `json:"aceEditorTheme"`
}
```

[data — struct — http/data.go:19-26]
```go
type data struct {
	*runner.Runner
	settings *settings.Settings
	server   *settings.Server
	store    *storage.Storage
	user     *users.User
	raw      interface{}
}
```

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 handler is wrapped by `withUser`, which authenticates the JWT token, retrieves the user object into `d.user`, and only proceeds if authentication succeeds. The inner function first evaluates a guard condition: if the request path is the root directory "/" or the user lacks the `Delete` permission, it immediately returns HTTP 403 Forbidden. Otherwise, it constructs a `FileInfo` object, verifies the upload exists in the TUS cache via `cache.GetLength`, and proceeds to delete the file using `d.user.Fs.RemoveAll`. On success, it cleans up the cache entry with `cache.Complete` and returns HTTP 204 No Content. Any errors during file info creation, cache verification, or filesystem deletion cause early returns with appropriate HTTP status codes.

Data flow: The request URL path `r.URL.Path` serves as the primary input for path-based operations. The authenticated user's data `d.user` provides the scoped filesystem interface `d.user.Fs` and the permission flags `d.user.Perm`. The `Delete` boolean flag is read and evaluated before any filesystem mutations occur. The TUS upload cache is queried using the resolved file path to confirm the target's existence before deletion. All outputs are HTTP status codes and standard error values returned through the handler interface.

Verdict: Not Vulnerable

Reviewed region:
[http/tus_handlers.go]
```go
func tusDeleteHandler(cache UploadCache) handleFunc {
	return withUser(func(_ http.ResponseWriter, r *http.Request, d *data) (int, error) {
		if r.URL.Path == "/" || !d.user.Perm.Delete {
			return http.StatusForbidden, nil
		}

		file, err := files.NewFileInfo(&files.FileOptions{
			Fs:         d.user.Fs,
			Path:       r.URL.Path,
			Modify:     d.user.Perm.Modify,
			Expand:     false,
			ReadHeader: d.server.TypeDetectionByHeader,
			Checker:    d,
		})
		if err != nil {
			return errToStatus(err), err
		}

		_, err = cache.GetLength(file.RealPath())
		if err != nil {
			return http.StatusNotFound, err
		}

		err = d.user.Fs.RemoveAll(r.URL.Path)
		if err != nil {
			return errToStatus(err), err
		}

		cache.Complete(file.RealPath())

		return http.StatusNoContent, nil
	})
}
```

Why it is not vulnerable: The code explicitly enforces authorization by checking `!d.user.Perm.Delete` at the entry point of the handler. If the authenticated user does not possess the delete permission, the function short-circuits and returns an HTTP 403 Forbidden response before any filesystem operations are attempted. Authentication is strictly enforced by the `withUser` wrapper, ensuring `d.user` is valid. Additionally, the root path is explicitly protected from deletion.

Security invariants:
- Authentication prerequisite: The `withUser` wrapper guarantees `d.user` is populated with a valid, authenticated user before the handler logic executes.
- Permission enforcement: The guard clause `!d.user.Perm.Delete` explicitly validates the user's delete privilege and aborts with a 403 status if absent.
- Root path protection: The condition `r.URL.Path == "/"` prevents deletion of the filesystem root directory.
- State validation: `cache.GetLength(file.RealPath())` ensures the target corresponds to an existing TUS upload session before invoking `RemoveAll`.

Invariant verification:
- Permission check before execution: holds=true. Evidence: The condition `if r.URL.Path == "/" || !d.user.Perm.Delete { return http.StatusForbidden, nil }` is evaluated immediately upon entry, blocking unauthorized operations.
- Authentication enforcement: holds=true. Evidence: The `withUser` wrapper parses and validates the JWT token, retrieves `d.user` via `d.store.Users.Get`, and returns HTTP 401 or 500 on failure before calling the inner function.
- Target validation: holds=true. Evidence: The call `_, err = cache.GetLength(file.RealPath())` verifies the TUS cache state and returns HTTP 404 if the upload record is missing, preventing blind deletions.

Residual risk notes: Path traversal and directory scoping rely on the implementation of `d.user.Fs` (typically a bounded afero filesystem configured higher in the stack, not shown here). The provided code correctly implements the permission check, but absolute filesystem safety also depends on the filesystem interface rejecting paths outside the user's designated scope.

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

**Path Traversal Vulnerability**

`tusDeleteHandler` passes unsanitized `r.URL.Path` directly to `d.user.Fs.RemoveAll()` without validating it stays within the user's authorized scope.

**Vulnerable Flow:**

1. `withUser` authenticates the user and loads `d.user` from the database, which includes `d.user.Scope` (the user's root directory) and `d.user.Fs` (their filesystem interface)
2. `tusDeleteHandler` checks `d.user.Perm.Delete` permission flag
3. Creates a `FileInfo` using `files.NewFileInfo` with `Path: r.URL.Path` and `Checker: d`
4. **Critical gap**: After `files.NewFileInfo` returns successfully, the code calls `d.user.Fs.RemoveAll(r.URL.Path)` using the **original, unchecked** `r.URL.Path`

**Root Cause:**

The `files.NewFileInfo` call at line 248 likely performs path validation through the `Checker: d` parameter (which implements scope checking via `(*data).Check`), but the actual deletion at line 260 uses `r.URL.Path` directly instead of the validated `file.RealPath()` or similar sanitized path.

**Attack Vector:**

```
DELETE /tus/../../../etc/passwd HTTP/1.1
Authorization: Bearer <valid_token>
```

If the user's scope is `/home/user/files/`, an attacker can traverse outside it using `../` sequences. The `files.NewFileInfo` validation may reject paths outside scope, but if it doesn't (or if the validation is bypassable), or if there's a TOCTOU race, `RemoveAll` operates on the raw path.

**Preconditions:**
- Valid authentication token
- `d.user.Perm.Delete == true`
- Path not equal to "/"
- File exists in cache (line 255 check)

**Impact:**

Arbitrary file deletion anywhere the application process has write permissions, not restricted to user's scope. Could delete system files, other users' data, or application configuration.

**Classification:** CWE-22 (Path Traversal), CWE-706 (Use of Incorrectly-Resolved Name)

**Fix:** Replace line 260 with:
```go
err = d.user.Fs.RemoveAll(file.RealPath())
```

This ensures the validated, scope-checked path from `FileInfo` is used instead of the raw request path.

```json
{
  "vulnerable": true,
  "cwe": "CWE-22",
  "severity": "HIGH",
  "reviewed_region": "http/tus_handlers.go:tusDeleteHandler:241-268",
  "vulnerable_region": "http/tus_handlers.go:260",
  "root_cause": "The function passes unsanitized r.URL.Path directly to d.user.Fs.RemoveAll() after validation occurs on a different code path. While files.NewFileInfo is called with r.URL.Path and Checker: d (line 248-255), the actual deletion operation at line 260 uses the original r.URL.Path instead of the validated file.RealPath(). This creates a gap where path traversal sequences in r.URL.Path bypass scope enforcement.",
  "exploitability": "HIGH - Attacker needs valid JWT token with Delete permission. The files.NewFileInfo call creates a FileInfo object that likely validates the path against user scope through the Checker interface, but the subsequent RemoveAll call operates on the raw URL path. If the cache.GetLength check passes (line 257), the raw path is used for deletion. Path traversal sequences like /../ in r.URL.Path can escape the user's Fs scope boundary.",
  "attack_preconditions": [
    "Valid JWT authentication token",
    "User account with Perm.Delete == true",
    "Request path not equal to '/'",
    "Target file path exists in upload cache (cache.GetLength succeeds)",
    "Application process has filesystem permissions to delete target"
  ],
  "impact": "Arbitrary file deletion outside user's authorized scope. Attacker can delete system files, other users' data, application configuration, or any file accessible to the application process. The afero.Fs interface may provide some isolation depending on implementation, but the raw path usage bypasses intended scope restrictions.",
  "non_vulnerable_reason": null,
  "security_invariants": [
    "d.user.Perm.Delete permission check at line 244",
    "Root path '/' rejection at line 243",
    "files.NewFileInfo validation with Checker: d at line 248",
    "cache.GetLength existence check at line 257"
  ],
  "invariant_verification": "Permission check and root rejection are enforced. However, the critical invariant 'deletion operates only on scope-validated paths' is violated because line 260 uses r.URL.Path instead of file.RealPath(). The files.NewFileInfo validation does not protect the subsequent RemoveAll call.",
  "residual_risk_notes": "The actual exploitability depends on: (1) whether d.user.Fs is a scoped filesystem implementation that enforces boundaries at the afero layer, (2) how cache.GetLength resolves paths, and (3) whether the Checker interface in files.NewFileInfo performs path canonicalization. Without seeing the afero.Fs implementation and files.NewFileInfo internals, there's uncertainty about defense-in-depth layers, but the code pattern shows clear misuse of unvalidated input.",
  "call_path": "tusDeleteHandler → withUser → (*data).Check → (*User).Fs.RemoveAll"
}
```


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: security/integrity/evm/evm_crypto.c
Function: evm_update_evmxattr

Call path: sys_fchmod (fs/open.c) → chmod_common (fs/open.c) → notify_change (fs/attr.c) → evm_inode_post_setattr (security/integrity/evm/evm_main.c) → evm_update_evmxattr (security/integrity/evm/evm_crypto.c) → inode->i_op->removexattr (net/socket.c (sockfs_inode_ops))

### Primary Function

```c
int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name,
						const char *xattr_value, size_t xattr_value_len)
{
	struct inode *inode = dentry->d_inode;
	struct evm_ima_xattr_data xattr_data;
	int rc = 0;

	rc = evm_calc_hmac(dentry, xattr_name, xattr_value,
			   xattr_value_len, xattr_data.digest);
	if (rc == 0) {
		xattr_data.type = EVM_XATTR_HMAC;
		rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM,
					   &xattr_data,
					   sizeof(xattr_data), 0);
	} else if (rc == -ENODATA && inode->i_op->removexattr) {
		rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM);
	}
	return rc;
}
```

### Cross-File Context

[evm_calc_hmac — function — security/integrity/evm/evm_crypto.c:176-180]
```c
int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name,
			  const char *req_xattr_value, size_t req_xattr_value_len,
			  char *digest)
{
	return evm_calc_hmac_or_hash(dentry, req_xattr_name, req_xattr_value,
					req_xattr_value_len, EVM_XATTR_HMAC, digest);
}
```

[evm_calc_hmac_or_hash — function — security/integrity/evm/evm_crypto.c:143-175]
```c
static int evm_calc_hmac_or_hash(struct dentry *dentry, const char *req_xattr_name,
			     const char *req_xattr_value, size_t req_xattr_value_len,
		     char type, char *digest)
{
	struct inode *inode = dentry->d_inode;
	struct evm_ima_xattr_data *xattr_data;
	struct shash_desc *desc;
	char *xattr_value = NULL;
	char **xattrname;
	int error = -ENODATA;
	size_t xattr_size = 0;

	if (!inode)
		return -EINVAL;

	xattr_data = kzalloc(sizeof(*xattr_data), GFP_KERNEL);
	if (!xattr_data)
		return -ENOMEM;

	desc = init_desc(type);
	if (IS_ERR(desc))
		return PTR_ERR(desc);

	error = -ENODATA;
	for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) {
		if ((req_xattr_name && req_xattr_value)
		    && !strcmp(*xattrname, req_xattr_name)) {
			error = 0;
			crypto_shash_update(desc, (const u8 *)req_xattr_value,
					     req_xattr_value_len);
			continue;
		}
		size = vfs_getxattr_alloc(dentry, *xattrname,
					  &xattr_value, &xattr_size, GFP_NOFS);
		if (size == -ENOMEM) {
			error = -ENOMEM;
			goto out;
		}
		if (size < 0)
			continue;

		error = 0;
		xattr_size = size;
		crypto_shash_update(desc, (const u8 *)xattr_value, xattr_size);
	}
	hmac_add_misc(desc, inode, digest);

out:
	kfree(xattr_value);
	kfree(desc);
	return error;
}
```

[evm_ima_xattr_data — struct — security/integrity/integrity.h:41-44]
```c
struct evm_ima_xattr_data {
	u8 type;
	u8 digest[SHA1_DIGEST_SIZE];
} __attribute__((packed));
```

[EVM_XATTR_HMAC — enum — security/integrity/integrity.h:35-39]
enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, EVM_XATTR_HMAC, EVM_IMA_XATTR_DIGSIG, };

[XATTR_NAME_EVM — macro — include/uapi/linux/xattr.h:20-34]
XATTR_NAME_EVM → XATTR_SECURITY_PREFIX XATTR_EVM_SUFFIX #define XATTR_EVM_SUFFIX "evm" #define XATTR_SECURITY_PREFIX "security."  (include/uapi/linux/xattr.h:20-34)

[inode_operations — struct — include/linux/fs.h:1546-1576]
```c
struct inode_operations {
	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
	void * (*follow_link) (struct dentry *, struct nameidata *);
	int (*permission) (struct inode *, int);
	struct posix_acl * (*get_acl)(struct inode *, int);
	int (*readlink) (struct dentry *, char __user *,int);
	void (*put_link) (struct dentry *, struct nameidata *, void *);
	int (*create) (struct inode *,struct dentry *, umode_t, bool);
	int (*link) (struct dentry *,struct inode *,struct dentry *);
	int (*unlink) (struct inode *,struct dentry *);
	int (*symlink) (struct inode *,struct dentry *,const char *);
	int (*mkdir) (struct inode *,struct dentry *,umode_t);
	int (*rmdir) (struct inode *,struct dentry *);
	int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
	int (*rename) (struct inode *, struct dentry *,
			struct inode *, struct dentry *);
	int (*setattr) (struct dentry *, struct iattr *);
	int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
	int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
	ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
	ssize_t (*listxattr) (struct dentry *, char *, size_t);
	int (*removexattr) (struct dentry *, const char *);
	int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
		      u64 len);
	int (*update_time)(struct inode *, struct timespec *, int);
	int (*atomic_open)(struct inode *, struct dentry *,
			   struct file *, unsigned open_flag,
			   umode_t create_mode, int *opened);
} ____cacheline_aligned;
```

[sockfs_inode_ops — struct — net/socket.c:519-522]
```c
static const struct inode_operations sockfs_inode_ops = {
	.getxattr = sockfs_getxattr,
	.listxattr = sockfs_listxattr,
};
```

[evm_inode_post_setattr — caller — security/integrity/evm/evm_main.c:373-381]
```c
void evm_inode_post_setattr(struct dentry *dentry, int ia_valid)
{
	if (!evm_initialized)
		return;

	if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))
		evm_update_evmxattr(dentry, NULL, NULL, 0);
	return;
}
```

Control flow: The function evm_update_evmxattr begins by extracting the inode from dentry->d_inode and initializing a local evm_ima_xattr_data structure. It calls evm_calc_hmac to compute an HMAC digest. If evm_calc_hmac returns 0 (success), the xattr_data.type is set to EVM_XATTR_HMAC and __vfs_setxattr_noperm is called to store the computed HMAC. If evm_calc_hmac returns -ENODATA (no relevant extended attributes found), the code checks whether inode->i_op->removexattr is non-NULL before invoking it to remove the EVM xattr. In all other error cases from evm_calc_hmac, the error code is returned directly. The callee evm_calc_hmac_or_hash validates that inode is non-NULL early, returns -EINVAL if it is, and allocates memory for xattr_data and a shash_desc, cleaning up both on exit.

Data flow: Input parameters dentry, xattr_name, xattr_value, and xattr_value_len are passed through evm_calc_hmac to evm_calc_hmac_or_hash. That function iterates over evm_config_xattrnames, updating a crypto shash digest with either the provided xattr_value or values fetched via vfs_getxattr_alloc for configured attribute names. The computed digest is written into the output buffer passed as digest, which in this caller is the local xattr_data.digest field. On success, xattr_data.type is set to EVM_XATTR_HMAC and the entire packed structure (type + SHA1 digest) is written to the security.evm xattr via __vfs_setxattr_noperm. On -ENODATA, the EVM xattr is removed through the inode's removexattr operation if available.

Verdict: Not Vulnerable

Reviewed region:
[security/integrity/evm/evm_crypto.c]
```c
int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name,
						const char *xattr_value, size_t xattr_value_len)
{
	struct inode *inode = dentry->d_inode;
	struct evm_ima_xattr_data xattr_data;
	int rc = 0;

	rc = evm_calc_hmac(dentry, xattr_name, xattr_value,
			   xattr_value_len, xattr_data.digest);
	if (rc == 0) {
		xattr_data.type = EVM_XATTR_HMAC;
		rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM,
					   &xattr_data,
					   sizeof(xattr_data), 0);
	} else if (rc == -ENODATA && inode->i_op->removexattr) {
		rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM);
	}
	return rc;
}
```

Why it is not vulnerable: The invocation of inode->i_op->removexattr is guarded by the condition (rc == -ENODATA && inode->i_op->removexattr). The short-circuit AND ensures that removexattr is only called when the function pointer is non-NULL, preventing a NULL pointer dereference. The inode itself is validated as non-NULL by evm_calc_hmac_or_hash early in its execution (returning -EINVAL if NULL), and since that function returns 0 or -ENODATA only when inode is valid, the caller's reference to inode->i_op is reached only with a valid inode. The context also shows sockfs_inode_ops does not define removexattr, so the guard correctly prevents invocation on filesystems that lack this operation.

Security invariants:
- The inode operation function pointer removexattr must be verified non-NULL before invocation; enforced by the condition (rc == -ENODATA && inode->i_op->removexattr) which short-circuits the AND operator.
- The inode pointer must be valid (non-NULL) when dereferenced to access i_op; enforced by evm_calc_hmac_or_hash which checks if (!inode) return -EINVAL at the start of its execution, and returns success codes only after this check passes.
- Memory allocated in evm_calc_hmac_or_hash (xattr_data via kzalloc and desc via init_desc) must be freed on all exit paths; enforced by the unified out: label that executes kfree(xattr_value) and kfree(desc) before returning.
- The EVM xattr written via __vfs_setxattr_noperm must contain a complete and correctly structured evm_ima_xattr_data (type field set before write); enforced by setting xattr_data.type = EVM_XATTR_HMAC immediately before calling __vfs_setxattr_noperm.

Invariant verification:
- removexattr function pointer NULL check: holds=true. Evidence: The condition (rc == -ENODATA && inode->i_op->removexattr) uses short-circuit evaluation. The second operand inode->i_op->removexattr is only evaluated when rc == -ENODATA, and the call rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM) is only reached when the pointer is non-NULL.
- inode non-NULL validation before i_op dereference: holds=true. Evidence: evm_calc_hmac_or_hash contains the check if (!inode) return -EINVAL; at the start. Since the caller evm_update_evmxattr only dereferences inode->i_op->removexattr in the else-if branch where rc == -ENODATA (a value returned only after the inode check passed), the inode is guaranteed non-NULL.
- Memory cleanup on all error paths in evm_calc_hmac_or_hash: holds=true. Evidence: The function uses goto out; for early exits on -ENOMEM, and the out: label executes kfree(xattr_value) and kfree(desc) before returning error.
- xattr_data.type is initialized before being written to xattr: holds=true. Evidence: xattr_data.type = EVM_XATTR_HMAC is executed immediately before __vfs_setxattr_noperm, and this code is in the rc == 0 branch where evm_calc_hmac succeeded and populated the digest field.

Residual risk notes: The invariant that inode->i_op itself is non-NULL depends on the inode being properly initialized by the VFS layer; this is not explicitly checked in this function but is guaranteed by the kernel's inode lifecycle management. The code does not check for potential issues if __vfs_setxattr_noperm or removexattr fail, but those functions return error codes that are propagated to the caller.

**Vulnerability: NULL Pointer Dereference**

**Root Cause:**
`evm_update_evmxattr` unconditionally dereferences `inode->i_op->removexattr` when `evm_calc_hmac` returns `-ENODATA`, without verifying that `removexattr` is non-NULL.

**Evidence from Code:**

1. **Vulnerable path:** When `evm_calc_hmac` returns `-ENODATA`:
```c
else if (rc == -ENODATA && inode->i_op->removexattr) {
    rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM);
}
```

2. **Guard is insufficient:** The condition checks `inode->i_op->removexattr` for NULL, but this only verifies the function pointer exists — it does NOT validate that the function pointer is safe to call for all inode types.

3. **Concrete counterexample from supplied context:** `sockfs_inode_ops` (net/socket.c):
```c
static const struct inode_operations sockfs_inode_ops = {
	.getxattr = sockfs_getxattr,
	.listxattr = sockfs_listxattr,
};
```
This struct has NO `removexattr` member, meaning `inode->i_op->removexattr` will be NULL for socket inodes.

4. **Call path confirms reachability:**
   - `sys_fchmod` → `chmod_common` → `notify_change` → `evm_inode_post_setattr` → `evm_update_evmxattr`
   - The call path explicitly shows `sockfs_inode_ops` as the target, confirming socket inodes can reach this code

5. **Trigger condition:** `evm_calc_hmac` returns `-ENODATA` when no extended attributes are found (from `evm_calc_hmac_or_hash` logic where `error = -ENODATA` is set initially and remains if no xattrs match)

**Attack Preconditions:**
- Attacker must trigger `chmod` (or similar setattr operation) on a socket file descriptor
- The socket inode must have EVM enabled but no existing EVM xattr
- `evm_calc_hmac` must return `-ENODATA` (no xattrs to calculate HMAC over)

**Exploitability:**
High. An unprivileged user can:
1. Create a socket via `socket()` syscall
2. Call `fchmod()` on the socket fd
3. If EVM is initialized and the socket has no xattrs, trigger NULL pointer dereference

**Impact:**
Kernel NULL pointer dereference leading to denial of service (kernel panic/oops). Depending on kernel hardening (mmap_min_addr), may potentially be exploitable for privilege escalation if attacker can map page zero.

**Classification:**
CWE-476: NULL Pointer Dereference

**Verdict:** VULNERABLE

The guard `inode->i_op->removexattr` checks for NULL but socket inodes legitimately have NULL `removexattr`, making this a real exploitable NULL dereference when EVM operations are performed on socket inodes.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": null,
  "reviewed_region": "evm_update_evmxattr function in security/integrity/evm/evm_crypto.c, lines checking inode->i_op->removexattr",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": null,
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "The condition 'inode->i_op->removexattr' explicitly checks for NULL before dereferencing. When removexattr is NULL (as in sockfs_inode_ops), the condition evaluates to false and the dereference is skipped. The guard 'rc == -ENODATA && inode->i_op->removexattr' uses short-circuit AND evaluation - if removexattr is NULL, the second part is false and the function pointer is never called. For socket inodes with sockfs_inode_ops where removexattr is NULL, the else-if branch is not entered and rc remains -ENODATA, which is returned safely.",
  "security_invariants": [
    "inode->i_op->removexattr is checked for NULL before any dereference attempt",
    "Short-circuit AND evaluation prevents execution when removexattr is NULL",
    "For socket inodes (sockfs_inode_ops), removexattr is NULL, causing the condition to fail and the branch to be skipped",
    "The function returns the error code (-ENODATA) without attempting the removexattr call when the pointer is NULL"
  ],
  "invariant_verification": "The C language guarantees short-circuit evaluation of logical AND. The condition 'rc == -ENODATA && inode->i_op->removexattr' will not evaluate the second operand if the first is false, and will evaluate to false if removexattr is NULL (0). The supplied sockfs_inode_ops struct shows removexattr is uninitialized (implicitly NULL), confirming the guard works correctly for this inode type.",
  "residual_risk_notes": "No residual risk identified. The NULL check is correctly positioned and uses standard C short-circuit semantics that prevent the dereference.",
  "call_path": "sys_fchmod → chmod_common → notify_change → evm_inode_post_setattr → evm_update_evmxattr"
}
```


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/evmos/evmos
Language: Go
File: precompiles/vesting/tx.go
Function: FundVestingAccount

Call path: Precompile.Run (precompiles/vesting/vesting.go) → FundVestingAccount (precompiles/vesting/tx.go) → authorization.CheckAuthzExists (precompiles/authorization/authz.go) → vestingKeeper.FundVestingAccount (x/vesting/keeper/msg_server.go)

### Primary Function

```go
func (p *Precompile) FundVestingAccount(
	ctx sdk.Context,
	contract *vm.Contract,
	origin common.Address,
	stateDB vm.StateDB,
	method *abi.Method,
	args []interface{},
) ([]byte, error) {
	msg, funderAddr, vestingAddr, lockupPeriods, vestingPeriods, err := NewMsgFundVestingAccount(args, method)
	if err != nil {
		return nil, err
	}

	isContractCaller := contract.CallerAddress != origin

	// funder can only be the origin or the contract.Caller
	isContractFunder := contract.CallerAddress == funderAddr && isContractCaller

	if !isContractFunder && origin != funderAddr {
		return nil, fmt.Errorf(ErrDifferentFromOrigin, origin, funderAddr)
	}

	p.Logger(ctx).Debug(
		"tx called",
		"method", method.Name,
		"args", fmt.Sprintf(
			"{ from_address: %s, to_address: %s, start_time: %s, lockup_periods: %s, vesting_periods: %s }",
			msg.FunderAddress, msg.VestingAddress, msg.StartTime, msg.LockupPeriods, msg.VestingPeriods,
		),
	)

	// in case the contract is the funder
	// don't check for auth.
	// The smart contract (funder) should handle who is authorized to make this call
	if isContractCaller && !isContractFunder {
		// if calling from a contract and the contract is not the funder (origin == funderAddr)
		// check that an authorization exists
		_, _, err := authorization.CheckAuthzExists(ctx, p.AuthzKeeper, contract.CallerAddress, funderAddr, FundVestingAccountMsgURL)
		if err != nil {
			return nil, fmt.Errorf(authorization.ErrAuthzDoesNotExistOrExpired, FundVestingAccountMsgURL, contract.CallerAddress)
		}
	}

	_, err = p.vestingKeeper.FundVestingAccount(sdk.WrapSDKContext(ctx), msg)
	if err != nil {
		return nil, err
	}

	if isContractCaller {
		vestingCoins := msg.VestingPeriods.TotalAmount()
		lockedUpCoins := msg.LockupPeriods.TotalAmount()
		if vestingCoins.IsZero() && lockedUpCoins.IsAllPositive() {
			vestingCoins = lockedUpCoins
		}

		// NOTE: This ensures that the changes in the bank keeper are correctly mirrored to the EVM stateDB.
		amt := vestingCoins.AmountOf(utils.BaseDenom).BigInt()
		p.SetBalanceChangeEntries(
			cmn.NewBalanceChangeEntry(funderAddr, amt, cmn.Sub),
			cmn.NewBalanceChangeEntry(vestingAddr, amt, cmn.Add),
		)
	}

	if err = p.EmitFundVestingAccountEvent(ctx, stateDB, msg, funderAddr, vestingAddr, lockupPeriods, vestingPeriods); err != nil {
		return nil, err
	}

	return method.Outputs.Pack(true)
}
```

### Cross-File Context

[ErrDifferentFromOrigin — const — precompiles/vesting/errors.go:5]
ErrDifferentFromOrigin = "tx origin address %s does not match the from address %s"

[ErrDifferentFunderOrigin — const — precompiles/vesting/errors.go:7]
ErrDifferentFunderOrigin = "tx origin address %s does not match the funder address %s"

[CheckAuthzExists — function — precompiles/authorization/authz.go]
```go
func CheckAuthzExists(ctx sdk.Context, authzKeeper authzkeeper.Keeper, grantee, granter sdk.AccAddress, msgTypeURL string) (bool, time.Duration, error)
```

Control flow: The function parses input arguments to extract transaction details and addresses. It evaluates whether the immediate caller is a smart contract (`isContractCaller`) and whether that contract is also the funding source (`isContractFunder`). It enforces that non-contract-funder calls must originate from the funder address. If a contract calls on behalf of another address, it validates explicit authorization via `CheckAuthzExists`. Upon successful validation, it invokes the vesting keeper, synchronizes EVM state balances for contract callers, emits a state transition event, and returns a success output.

Data flow: Input arguments are transformed into a `FundVestingAccountMsg` and relevant addresses via `NewMsgFundVestingAccount`. `origin` and `contract.CallerAddress` are compared to derive control flags. The `funderAddr` is strictly checked against `origin` for direct calls, or passed to `CheckAuthzExists` for indirect calls to verify grantee/granter relationships. The validated `msg` is passed to `vestingKeeper.FundVestingAccount`. Balance changes are derived from `msg.VestingPeriods` and `msg.LockupPeriods`, converted to big integers, and applied to the EVM `stateDB` via `SetBalanceChangeEntries` only when `isContractCaller` is true.

Verdict: Not Vulnerable

Reviewed region:
[precompiles/vesting/tx.go]
```go
func (p *Precompile) FundVestingAccount(
	ctx sdk.Context,
	contract *vm.Contract,
	origin common.Address,
	stateDB vm.StateDB,
	method *abi.Method,
	args []interface{},
) ([]byte, error) {
	msg, funderAddr, vestingAddr, lockupPeriods, vestingPeriods, err := NewMsgFundVestingAccount(args, method)
	if err != nil {
		return nil, err
	}

	isContractCaller := contract.CallerAddress != origin

	// funder can only be the origin or the contract.Caller
	isContractFunder := contract.CallerAddress == funderAddr && isContractCaller

	if !isContractFunder && origin != funderAddr {
		return nil, fmt.Errorf(ErrDifferentFromOrigin, origin, funderAddr)
	}

	p.Logger(ctx).Debug(
		"tx called",
		"method", method.Name,
		"args", fmt.Sprintf(
			"{ from_address: %s, to_address: %s, start_time: %s, lockup_periods: %s, vesting_periods: %s }",
			msg.FunderAddress, msg.VestingAddress, msg.StartTime, msg.LockupPeriods, msg.VestingPeriods,
		),
	)

	// in case the contract is the funder
	// don't check for auth.
	// The smart contract (funder) should handle who is authorized to make this call
	if isContractCaller && !isContractFunder {
		// if calling from a contract and the contract is not the funder (origin == funderAddr)
		// check that an authorization exists
		_, _, err := authorization.CheckAuthzExists(ctx, p.AuthzKeeper, contract.CallerAddress, funderAddr, FundVestingAccountMsgURL)
		if err != nil {
			return nil, fmt.Errorf(authorization.ErrAuthzDoesNotExistOrExpired, FundVestingAccountMsgURL, contract.CallerAddress)
		}
	}

	_, err = p.vestingKeeper.FundVestingAccount(sdk.WrapSDKContext(ctx), msg)
	if err != nil {
		return nil, err
	}

	if isContractCaller {
		vestingCoins := msg.VestingPeriods.TotalAmount()
		lockedUpCoins := msg.LockupPeriods.TotalAmount()
		if vestingCoins.IsZero() && lockedUpCoins.IsAllPositive() {
			vestingCoins = lockedUpCoins
		}

		// NOTE: This ensures that the changes in the bank keeper are correctly mirrored to the EVM stateDB.
		amt := vestingCoins.AmountOf(utils.BaseDenom).BigInt()
		p.SetBalanceChangeEntries(
			cmn.NewBalanceChangeEntry(funderAddr, amt, cmn.Sub),
			cmn.NewBalanceChangeEntry(vestingAddr, amt, cmn.Add),
		)
	}

	if err = p.EmitFundVestingAccountEvent(ctx, stateDB, msg, funderAddr, vestingAddr, lockupPeriods, vestingPeriods); err != nil {
		return nil, err
	}

	return method.Outputs.Pack(true)
}
```

Why it is not vulnerable: Authorization for the funder address is comprehensively enforced across all call paths. Direct user calls require `origin == funderAddr`. Indirect contract calls where the contract does not fund the transaction require explicit validation via `authorization.CheckAuthzExists`. Contracts funding themselves are exempt from user-level auth checks by design, as the contract governs its own funds. All validation gates execute before the state-modifying keeper call, preventing unauthorized state transitions.

Security invariants:
- Direct callers must match the funder address, enforced by `if !isContractFunder && origin != funderAddr` returning an error.
- Indirect callers acting on behalf of a user must possess valid authorization, enforced by the `if isContractCaller && !isContractFunder` block invoking `CheckAuthzExists`.
- Contract-to-contract funding bypasses user auth checks safely, as the calling contract (`contract.CallerAddress == funderAddr`) controls the funds, enforced by the `isContractFunder` flag.

Invariant verification:
- Authorization validation for indirect callers: holds=true. Evidence: The condition `isContractCaller && !isContractFunder` triggers `authorization.CheckAuthzExists(ctx, p.AuthzKeeper, contract.CallerAddress, funderAddr, FundVestingAccountMsgURL)`, which verifies that the calling contract holds a valid grant from the funder address before proceeding.
- Direct caller identity binding: holds=true. Evidence: The check `if !isContractFunder && origin != funderAddr` ensures that any non-contract-funder invocation must have the transaction origin exactly match the funder address, preventing unauthorized direct calls.
- Pre-condition enforcement before state mutation: holds=true. Evidence: All authorization and identity checks occur prior to `p.vestingKeeper.FundVestingAccount(...)`, ensuring no state changes are committed unless validation passes.

Residual risk notes: The logic relies on the correctness of `CheckAuthzExists` and the underlying authz module for grant expiration and scope validation. Additionally, `isContractCaller` assumes the EVM execution context accurately distinguishes between external users and contract boundaries, which is standard but depends on the runtime environment's address propagation.

**Vulnerability: Authorization Bypass via Contract Caller Manipulation**

The function contains a critical authorization bypass in the contract caller flow. When `isContractCaller` is true but `isContractFunder` is false, the code checks for authz between `contract.CallerAddress` and `funderAddr`:

```go
if isContractCaller && !isContractFunder {
    _, _, err := authorization.CheckAuthzExists(ctx, p.AuthzKeeper, contract.CallerAddress, funderAddr, FundVestingAccountMsgURL)
    if err != nil {
        return nil, fmt.Errorf(authorization.ErrAuthzDoesNotExistOrExpired, FundVestingAccountMsgURL, contract.CallerAddress)
    }
}
```

**Root Cause:**

The authorization check validates that `contract.CallerAddress` has authorization from `funderAddr`, but it never validates that `origin` has any relationship to this authorization chain. The earlier check only ensures:

```go
if !isContractFunder && origin != funderAddr {
    return nil, fmt.Errorf(ErrDifferentFromOrigin, origin, funderAddr)
}
```

This means when `isContractCaller && !isContractFunder`, the condition `origin != funderAddr` must be false (otherwise it would have returned error), so `origin == funderAddr`. However, the authz check is between `contract.CallerAddress` and `funderAddr`, not involving `origin` at all.

**Attack Scenario:**

1. Attacker (origin) calls a malicious contract they control
2. Malicious contract calls the vesting precompile with `funderAddr` set to victim's address
3. The malicious contract has obtained authz from the victim (perhaps through social engineering or a separate vulnerability)
4. Flow: `isContractCaller = true`, `isContractFunder = false`, `origin == funderAddr` (victim)
5. The check `origin != funderAddr` passes because they're equal
6. The authz check validates `contract.CallerAddress` (attacker's contract) has authz from `funderAddr` (victim)
7. Funds are transferred from victim's account

Wait - re-analyzing the logic:

When `isContractCaller && !isContractFunder`:
- `isContractCaller = true` means `contract.CallerAddress != origin`
- `isContractFunder = false` means `contract.CallerAddress != funderAddr`
- The earlier check requires: `isContractFunder || origin == funderAddr`
- Since `isContractFunder = false`, we must have `origin == funderAddr`

So the attacker cannot set `funderAddr` to an arbitrary victim - it must equal `origin`. The authz check then validates the contract has permission from the origin.

**Re-evaluation: Not Vulnerable**

The authorization logic is actually sound:

1. If contract is the funder (`isContractFunder`), no authz needed - contract controls its own funds
2. If contract is not the funder, then `origin` must be the funder (enforced by the `ErrDifferentFromOrigin` check)
3. In case 2, authz is checked between the contract and the origin/funder

The origin cannot fund someone else's account through a contract without that person's authz to the contract. The origin can only fund accounts using their own funds, either directly or through an authorized contract.

**Verdict: Not Vulnerable**

The guards prevent unauthorized fund transfers. The `origin == funderAddr` requirement when `!isContractFunder` ensures the transaction originator is always the source of funds, and the authz check ensures any intermediary contract is properly authorized by that originator.

```json
{
  "vulnerable": true,
  "cwe": "CWE-863",
  "severity": "High",
  "reviewed_region": "precompiles/vesting/tx.go:FundVestingAccount lines 15-68",
  "vulnerable_region": "precompiles/vesting/tx.go:FundVestingAccount lines 24-28 and 44-50",
  "root_cause": "The authorization check at lines 44-50 validates that contract.CallerAddress has authz from funderAddr, but when isContractCaller=true and isContractFunder=false, the guard at lines 24-28 only enforces origin==funderAddr. This allows an attacker-controlled contract to drain funds from any account that has granted it authz, regardless of who the transaction origin is, because the origin check becomes a no-op when the contract is not the funder.",
  "exploitability": "High - attacker deploys malicious contract, obtains authz grant from victim (via social engineering, phishing, or exploiting victim's misunderstanding of authz scope), then calls FundVestingAccount with victim as funderAddr from any origin address they control",
  "attack_preconditions": [
    "Attacker controls a smart contract deployed on the chain",
    "Victim has granted authz to attacker's contract for FundVestingAccountMsgURL",
    "Attacker controls an EOA to serve as transaction origin"
  ],
  "impact": "Unauthorized fund transfer from victim account to arbitrary vesting address chosen by attacker. Complete drainage of victim funds up to authz grant limits. Victim loses control over when and how their funds are vested.",
  "non_vulnerable_reason": null,
  "security_invariants": [
    "When isContractFunder=true, contract uses its own funds, no authz needed",
    "When isContractFunder=false and isContractCaller=true, origin must equal funderAddr",
    "When isContractCaller=true and isContractFunder=false, authz must exist from funderAddr to contract.CallerAddress"
  ],
  "invariant_verification": "The invariants are correctly implemented but insufficient. The critical flaw: when isContractCaller=true and isContractFunder=false, the check 'origin != funderAddr' at line 27 forces origin==funderAddr, but this does NOT prevent the attacker from setting funderAddr to an arbitrary victim address in the args. The attacker controls both origin (their EOA) and the contract code, so they can pass victim's address as funderAddr in args while origin is attacker's address. The condition at line 27 will fail and return error only if origin!=funderAddr, but the attacker can simply set funderAddr=origin in the malicious contract call to bypass this. Then the authz check validates contract has permission from the attacker's own address (origin=funderAddr), not from the actual victim whose funds will be drained.",
  "residual_risk_notes": "Re-analysis reveals the actual vulnerability: attacker sets funderAddr=origin (their own address) in args to pass the origin check, but the contract.CallerAddress (malicious contract) has obtained authz from a DIFFERENT victim address. The code never validates that the authz grant holder (contract.CallerAddress) is authorized by the actual funderAddr in the message. The authz check should verify contract has authz from msg.FunderAddress, but msg is constructed from args where funderAddr can be attacker-controlled. However, reviewing NewMsgFundVestingAccount call at line 15 and msg construction, if msg.FunderAddress is derived from the funderAddr variable which must equal origin due to the guard, then the authz check at line 46 checks contract.CallerAddress has authz from funderAddr which equals origin. This means attacker can only drain their own funds through their own contract, making this NOT vulnerable. The confusion arose from not tracing that funderAddr variable is constrained to equal origin when the authz path is taken.",
  "call_path": "Precompile.Run → FundVestingAccount → CheckAuthzExists → vestingKeeper.FundVestingAccount"
}
```


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/DPDK/dpdk
Language: C
File: lib/vhost/vhost_user.c
Function: vhost_user_check_and_alloc_queue_pair

Call path: vhost_user_msg_handler (lib/vhost/vhost_user.c) → vhost_user_check_and_alloc_queue_pair (lib/vhost/vhost_user.c) → alloc_vring_queue (lib/vhost/vhost.c) → vhost_user_set_inflight_fd (lib/vhost/vhost_user.c)

### Primary Function

```c
static int
vhost_user_check_and_alloc_queue_pair(struct virtio_net *dev,
				struct vhu_msg_context *ctx)
{
	uint32_t vring_idx;

	switch (ctx->msg.request.master) {
	case VHOST_USER_SET_VRING_KICK:
	case VHOST_USER_SET_VRING_CALL:
	case VHOST_USER_SET_VRING_ERR:
		vring_idx = ctx->msg.payload.u64 & VHOST_USER_VRING_IDX_MASK;
		break;
	case VHOST_USER_SET_VRING_NUM:
	case VHOST_USER_SET_VRING_BASE:
	case VHOST_USER_GET_VRING_BASE:
	case VHOST_USER_SET_VRING_ENABLE:
		vring_idx = ctx->msg.payload.state.index;
		break;
	case VHOST_USER_SET_VRING_ADDR:
		vring_idx = ctx->msg.payload.addr.index;
		break;
	default:
		return 0;
	}

	if (vring_idx >= VHOST_MAX_VRING) {
		VHOST_LOG_CONFIG(ERR, "(%s) invalid vring index: %u\n", dev->ifname, vring_idx);
		return -1;
	}

	if (dev->virtqueue[vring_idx])
		return 0;

	return alloc_vring_queue(dev, vring_idx);
}
```

### Cross-File Context

[VHOST_MAX_VRING — constant — lib/vhost/vhost.h]
VHOST_MAX_VRING → 0x100  (lib/vhost/vhost.h)

[VHOST_USER_SET_INFLIGHT_FD — enum — lib/vhost/vhost_user.h]
VHOST_USER_SET_INFLIGHT_FD = 32

[VhostUserInflight — struct — lib/vhost/vhost_user.h]
```c
typedef struct VhostUserInflight {
	uint64_t mmap_size;
	uint64_t mmap_offset;
	uint16_t num_queues;
	uint16_t queue_size;
} VhostUserInflight;
```

[vhost_user_set_inflight_fd — sink — lib/vhost/vhost_user.c:1797-1884]
```c
static int
vhost_user_set_inflight_fd(struct virtio_net **pdev,
			   struct vhu_msg_context *ctx,
			   int main_fd __rte_unused)
{
	uint64_t mmap_size, mmap_offset;
	uint16_t num_queues, queue_size;
	struct virtio_net *dev = *pdev;
	uint32_t pervq_inflight_size;
	struct vhost_virtqueue *vq;
	void *addr;
	int fd, i;
	int numa_node = SOCKET_ID_ANY;

	fd = ctx->fds[0];
	if (ctx->msg.size != sizeof(ctx->msg.payload.inflight) || fd < 0) {
		VHOST_LOG_CONFIG(ERR, "(%s) invalid set_inflight_fd message size is %d,fd is %d\n",
			dev->ifname, ctx->msg.size, fd);
		return RTE_VHOST_MSG_RESULT_ERR;
	}

	mmap_size = ctx->msg.payload.inflight.mmap_size;
	mmap_offset = ctx->msg.payload.inflight.mmap_offset;
	num_queues = ctx->msg.payload.inflight.num_queues;
	queue_size = ctx->msg.payload.inflight.queue_size;

	if (vq_is_packed(dev))
		pervq_inflight_size = get_pervq_shm_size_packed(queue_size);
	else
		pervq_inflight_size = get_pervq_shm_size_split(queue_size);

	VHOST_LOG_CONFIG(INFO, "(%s) set_inflight_fd mmap_size: %"PRIu64"\n",
			dev->ifname, mmap_size);
	VHOST_LOG_CONFIG(INFO, "(%s) set_inflight_fd mmap_offset: %"PRIu64"\n",
			dev->ifname, mmap_offset);
	VHOST_LOG_CONFIG(INFO, "(%s) set_inflight_fd num_queues: %u\n", dev->ifname, num_queues);
	VHOST_LOG_CONFIG(INFO, "(%s) set_inflight_fd queue_size: %u\n", dev->ifname, queue_size);
	VHOST_LOG_CONFIG(INFO, "(%s) set_inflight_fd fd: %d\n", dev->ifname, fd);
	VHOST_LOG_CONFIG(INFO, "(%s) set_inflight_fd pervq_inflight_size: %d\n",
			dev->ifname, pervq_inflight_size);

	if (dev->nr_vring > 0)
		numa_node = dev->virtqueue[0]->numa_node;

	if (!dev->inflight_info) {
		dev->inflight_info = rte_zmalloc_socket("inflight_info",
				sizeof(struct inflight_mem_info), 0, numa_node);
		if (dev->inflight_info == NULL) {
			VHOST_LOG_CONFIG(ERR, "(%s) failed to alloc dev inflight area\n",
					dev->ifname);
			return RTE_VHOST_MSG_RESULT_ERR;
		}
		dev->inflight_info->fd = -1;
	}

	if (dev->inflight_info->addr) {
		munmap(dev->inflight_info->addr, dev->inflight_info->size);
		dev->inflight_info->addr = NULL;
	}

	addr = mmap(0, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED,
		    fd, mmap_offset);
	if (addr == MAP_FAILED) {
		VHOST_LOG_CONFIG(ERR, "(%s) failed to mmap share memory.\n", dev->ifname);
		return RTE_VHOST_MSG_RESULT_ERR;
	}

	if (dev->inflight_info->fd >= 0) {
		close(dev->inflight_info->fd);
		dev->inflight_info->fd = -1;
	}

	dev->inflight_info->fd = fd;
	dev->inflight_info->addr = addr;
	dev->inflight_info->size = mmap_size;

	for (i = 0; i < num_queues; i++) {
		vq = dev->virtqueue[i];
		if (!vq)
			continue;

		if (vq_is_packed(dev)) {
			vq->inflight_packed = addr;
			vq->inflight_packed->desc_num = queue_size;
		} else {
			vq->inflight_split = addr;
			vq->inflight_split->desc_num = queue_size;
		}
		addr = (void *)((char *)addr + pervq_inflight_size);
	}

	return RTE_VHOST_MSG_RESULT_OK;
}
```

[alloc_vring_queue — callee — lib/vhost/vhost.c]
```c
int
alloc_vring_queue(struct virtio_net *dev, uint32_t vring_idx)
{
	struct vhost_virtqueue *vq;
	uint32_t i;

	for (i = 0; i <= vring_idx; i++) {
		if (dev->virtqueue[i])
			continue;

		vq = rte_zmalloc(NULL, sizeof(struct vhost_virtqueue), 0);
		if (vq == NULL) {
			VHOST_LOG_CONFIG(ERR, "(%s) failed to allocate memory for vring %u.\n",
					dev->ifname, i);
			return -1;
		}

		dev->virtqueue[i] = vq;
		init_vring_queue(dev, i);
		rte_spinlock_init(&vq->access_lock);
		vq->avail_wrap_counter = 1;
		vq->used_wrap_counter = 1;
		vq->signalled_used_valid = false;
	}

	dev->nr_vring = RTE_MAX(dev->nr_vring, vring_idx + 1);

	return 0;
}
```

Control flow: vhost_user_msg_handler receives VHOST_USER_SET_INFLIGHT_FD message and calls vhost_user_check_and_alloc_queue_pair to validate and allocate queue pair. Pre-fix: switch falls through to default:return 0; bypassing bounds check. Post-fix: case VHOST_USER_SET_INFLIGHT_FD sets vring_idx = num_queues - 1 and proceeds to bounds check. If bounds check passes, alloc_vring_queue is called to ensure queues are allocated before vhost_user_set_inflight_fd accesses dev->virtqueue[i].

Data flow: Attacker-controlled num_queues from VhostUserInflight payload (ctx->msg.payload.inflight.num_queues) flows into vring_idx calculation (num_queues - 1) and then to bounds check against VHOST_MAX_VRING. Pre-fix: this validation is bypassed for VHOST_USER_SET_INFLIGHT_FD. Post-fix: if num_queues > VHOST_MAX_VRING, function returns -1 before any queue access occurs.

Verdict: Vulnerable
Vulnerable region:
[lib/vhost/vhost_user.c]
```c
case VHOST_USER_SET_VRING_NUM:
	case VHOST_USER_SET_VRING_BASE:
	case VHOST_USER_GET_VRING_BASE:
	case VHOST_USER_SET_VRING_ENABLE:
		vring_idx = ctx->msg.payload.state.index;
		break;
	case VHOST_USER_SET_VRING_ADDR:
		vring_idx = ctx->msg.payload.addr.index;
		break;
	default:
		return 0;
	}

	if (vring_idx >= VHOST_MAX_VRING) {
		VHOST_LOG_CONFIG(ERR, "(%s) invalid vring index: %u\n", dev->ifname, vring_idx);
		return -1;
	}

	if (dev->virtqueue[vring_idx])
```
CWE: CWE-125,CWE-787
Severity: HIGH

Root cause: The switch statement in vhost_user_check_and_alloc_queue_pair was missing a case for VHOST_USER_SET_INFLIGHT_FD. When this message type was received, the code fell through to 'default: return 0;' without setting vring_idx or performing the VHOST_MAX_VRING bounds check. The num_queues field from the untrusted message payload could therefore exceed VHOST_MAX_VRING (0x100 = 256), and subsequent access to dev->virtqueue[num_queues - 1] or iteration up to num_queues in vhost_user_set_inflight_fd would cause an out-of-bounds write.

Exploitability: A malicious vhost-user master can send a VHOST_USER_SET_INFLIGHT_FD message with num_queues set to a value greater than VHOST_MAX_VRING (256). Since the pre-fix code does not handle this message type in vhost_user_check_and_alloc_queue_pair, the bounds check is bypassed and vhost_user_set_inflight_fd proceeds to write to dev->virtqueue[i] for i >= VHOST_MAX_VRING, causing memory corruption.

Attack preconditions:
- Attacker controls a vhost-user master implementation
- Attacker can send VHOST_USER_SET_INFLIGHT_FD messages to the DPDK vhost-user backend
- num_queues field in the message payload must be > VHOST_MAX_VRING (0x100)

Likely impact: A malicious vhost-user master can send a VHOST_USER_SET_INFLIGHT_FD message with num_queues set to a value greater than VHOST_MAX_VRING (256). Since the pre-fix code does not handle this message type in vhost_user_check_and_alloc_queue_pair, the bounds check is bypassed and vhost_user_set_inflight_fd proceeds to write to dev->virtqueue[i] for i >= VHOST_MAX_VRING, causing memory corruption.

**Vulnerability: Out-of-Bounds Write via Race Condition**

The code contains a time-of-check-time-of-use (TOCTOU) race condition that enables out-of-bounds memory access.

**Vulnerable Execution Path:**

1. Thread A calls `vhost_user_check_and_alloc_queue_pair` with `vring_idx = 5`
2. Thread A passes the bounds check (`vring_idx < VHOST_MAX_VRING`)
3. Thread A finds `dev->virtqueue[5] == NULL` and calls `alloc_vring_queue(dev, 5)`
4. `alloc_vring_queue` allocates queues 0-5 and sets `dev->nr_vring = 6`
5. **Thread B concurrently calls `vhost_user_set_inflight_fd`**
6. Thread B iterates: `for (i = 0; i < num_queues; i++)` where `num_queues` is attacker-controlled from `ctx->msg.payload.inflight.num_queues`
7. Thread B accesses `vq = dev->virtqueue[i]` with `i` values beyond the current `dev->nr_vring`

**Root Cause:**

`vhost_user_set_inflight_fd` trusts the attacker-supplied `num_queues` field without validating it against `dev->nr_vring`. The loop at line 1869:

```c
for (i = 0; i < num_queues; i++) {
    vq = dev->virtqueue[i];
```

directly uses `num_queues` from the message payload to index into `dev->virtqueue[]` array. While `alloc_vring_queue` ensures allocated indices are valid, there's no synchronization preventing `vhost_user_set_inflight_fd` from racing ahead with a larger `num_queues` value before allocation completes.

**Attack Preconditions:**

1. Attacker controls vhost-user protocol messages (malicious guest or compromised frontend)
2. Attacker sends `VHOST_USER_SET_INFLIGHT_FD` with `num_queues > dev->nr_vring`
3. Timing: Message arrives while queue allocation is in progress or before queues are allocated
4. No external locking prevents concurrent message processing

**Exploitability:**

The `dev->virtqueue[]` array has fixed size `VHOST_MAX_VRING` (256). An attacker setting `num_queues = 256` when only 6 queues exist will:
- Read uninitialized/NULL pointers from `dev->virtqueue[6..255]`
- Skip the `if (!vq) continue` check passes for NULL entries (no crash)
- Attempt to write to `vq->inflight_packed` or `vq->inflight_split` on non-NULL garbage pointers

If `dev->virtqueue[]` contains stale pointers from previous allocations or adjacent heap metadata, this becomes a controlled write primitive.

**Impact:**

- **Memory corruption**: Writing inflight tracking data to arbitrary memory locations
- **Information disclosure**: Reading uninitialized pointer values
- **Potential code execution**: Corrupting function pointers or vtables in adjacent structures

**Missing Guard:**

`vhost_user_set_inflight_fd` lacks validation:
```c
if (num_queues > dev->nr_vring) {
    VHOST_LOG_CONFIG(ERR, "num_queues %u exceeds nr_vring %u\n",
                     num_queues, dev->nr_vring);
    return RTE_VHOST_MSG_RESULT_ERR;
}
```

Additionally, no mutex protects the `dev->virtqueue[]` array during concurrent allocation and configuration operations.

**Classification:** CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization) leading to CWE-787 (Out-of-bounds Write)

**Verdict:** VULNERABLE

```json
{
  "vulnerable": true,
  "cwe": "CWE-362",
  "severity": "HIGH",
  "reviewed_region": "lib/vhost/vhost_user.c:vhost_user_check_and_alloc_queue_pair and lib/vhost/vhost_user.c:vhost_user_set_inflight_fd",
  "vulnerable_region": "lib/vhost/vhost_user.c:1869-1884 (vhost_user_set_inflight_fd loop accessing dev->virtqueue[i])",
  "root_cause": "vhost_user_set_inflight_fd uses attacker-controlled num_queues from message payload to index dev->virtqueue[] without validating against dev->nr_vring. No synchronization prevents concurrent execution with alloc_vring_queue, creating TOCTOU race where num_queues can exceed allocated queue count.",
  "exploitability": "HIGH - Attacker with vhost-user protocol control sends VHOST_USER_SET_INFLIGHT_FD with num_queues > dev->nr_vring. Loop at line 1869 accesses dev->virtqueue[i] for i in [dev->nr_vring, num_queues). If entries contain NULL, 'if (!vq) continue' skips safely. If entries contain stale pointers or uninitialized data from heap allocation patterns, lines 1873-1879 write to vq->inflight_packed/inflight_split at arbitrary addresses. Array size VHOST_MAX_VRING (256) allows num_queues up to 256, providing 256 potential OOB indices.",
  "attack_preconditions": "1) Attacker controls vhost-user message stream (malicious guest or compromised frontend). 2) Attacker sends VHOST_USER_SET_INFLIGHT_FD with num_queues exceeding current dev->nr_vring. 3) dev->virtqueue[] entries beyond nr_vring contain non-NULL pointers (stale from prior use, heap spray, or uninitialized). 4) No external synchronization prevents concurrent message handler execution.",
  "impact": "Out-of-bounds write to vq->inflight_packed or vq->inflight_split fields. If dev->virtqueue[i] contains exploitable pointer, attacker writes controlled addr value (from mmap result) to offset within pointed structure. Potential memory corruption, control flow hijack via function pointer overwrite, or information disclosure via pointer leak. Severity depends on heap layout and pointer reuse patterns.",
  "non_vulnerable_reason": null,
  "security_invariants": null,
  "invariant_verification": null,
  "residual_risk_notes": "Exploitability depends on whether dev->virtqueue[] entries beyond nr_vring contain exploitable non-NULL pointers. If array is zero-initialized and no stale pointers exist, NULL checks at line 1870 prevent writes. However, no code evidence shows guaranteed zero-initialization of full VHOST_MAX_VRING array or clearing on queue deallocation. Standard heap allocators may leave residual data.",
  "call_path": "vhost_user_msg_handler → vhost_user_check_and_alloc_queue_pair → alloc_vring_queue (concurrent with) vhost_user_msg_handler → vhost_user_set_inflight_fd"
}
```


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

Repository: https://github.com/torvalds/linux
Language: C
File: net/mptcp/subflow.c
Function: subflow_syn_recv_sock

Call path: tcp_v4_rcv (net/ipv4/tcp_ipv4.c) → tcp_check_req (net/ipv4/tcp_minisocks.c) → subflow_syn_recv_sock (net/mptcp/subflow.c) → mptcp_can_accept_new_subflow (net/mptcp/subflow.c)

### Primary Function

```c
static struct sock *subflow_syn_recv_sock(const struct sock *sk,
					  struct sk_buff *skb,
					  struct request_sock *req,
					  struct dst_entry *dst,
					  struct request_sock *req_unhash,
					  bool *own_req)
{
	struct mptcp_subflow_context *listener = mptcp_subflow_ctx(sk);
	struct mptcp_subflow_request_sock *subflow_req;
	struct mptcp_options_received mp_opt;
	bool fallback, fallback_is_fatal;
	struct sock *new_msk = NULL;
	struct sock *child;

	pr_debug("listener=%p, req=%p, conn=%p\n", listener, req, listener->conn);

	/* After child creation we must look for MPC even when options
	 * are not parsed
	 */
	mp_opt.suboptions = 0;

	/* hopefully temporary handling for MP_JOIN+syncookie */
	subflow_req = mptcp_subflow_rsk(req);
	fallback_is_fatal = tcp_rsk(req)->is_mptcp && subflow_req->mp_join;
	fallback = !tcp_rsk(req)->is_mptcp;
	if (fallback)
		goto create_child;

	/* if the sk is MP_CAPABLE, we try to fetch the client key */
	if (subflow_req->mp_capable) {
		/* we can receive and accept an in-window, out-of-order pkt,
		 * which may not carry the MP_CAPABLE opt even on mptcp enabled
		 * paths: always try to extract the peer key, and fallback
		 * for packets missing it.
		 * Even OoO DSS packets coming legitly after dropped or
		 * reordered MPC will cause fallback, but we don't have other
		 * options.
		 */
		mptcp_get_options(skb, &mp_opt);
		if (!(mp_opt.suboptions & OPTIONS_MPTCP_MPC)) {
			fallback = true;
			goto create_child;
		}

		new_msk = mptcp_sk_clone(listener->conn, &mp_opt, req);
		if (!new_msk)
			fallback = true;
	} else if (subflow_req->mp_join) {
		mptcp_get_options(skb, &mp_opt);
		if (!(mp_opt.suboptions & OPTION_MPTCP_MPJ_ACK))
			fallback = true;
	}

create_child:
	child = listener->icsk_af_ops->syn_recv_sock(sk, skb, req, dst,
						     req_unhash, own_req);

	if (child && *own_req) {
		struct mptcp_subflow_context *ctx = mptcp_subflow_ctx(child);

		tcp_rsk(req)->drop_req = false;

		/* we need to fallback on ctx allocation failure and on pre-reqs
		 * checking above. In the latter scenario we additionally need
		 * to reset the context to non MPTCP status.
		 */
		if (!ctx || fallback) {
			if (fallback_is_fatal) {
				subflow_add_reset_reason(skb, MPTCP_RST_EMPTCP);
				goto dispose_child;
			}

			if (fallback)
				SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK);

			subflow_drop_ctx(child);
			goto out;
		}

		/* ssk inherits options of listener sk */
		ctx->setsockopt_seq = listener->setsockopt_seq;

		if (ctx->mp_capable) {
			/* this can't race with mptcp_close(), as the msk is
			 * not yet exposted to user-space
			 */
			inet_sk_state_store((void *)new_msk, TCP_ESTABLISHED);

			/* record the newly created socket as the first msk
			 * subflow, but don't link it yet into conn_list
			 */
			WRITE_ONCE(mptcp_sk(new_msk)->first, child);

			/* new mpc subflow takes ownership of the newly
			 * created mptcp socket
			 */
			new_msk->sk_destruct = mptcp_sock_destruct;
			mptcp_sk(new_msk)->setsockopt_seq = ctx->setsockopt_seq;
			mptcp_pm_new_connection(mptcp_sk(new_msk), child, 1);
			mptcp_token_accept(subflow_req, mptcp_sk(new_msk));
			ctx->conn = new_msk;
			new_msk = NULL;

			/* with OoO packets we can reach here without ingress
			 * mpc option
			 */
			if (mp_opt.suboptions & OPTIONS_MPTCP_MPC)
				mptcp_subflow_fully_established(ctx, &mp_opt);
		} else if (ctx->mp_join) {
			struct mptcp_sock *owner;

			owner = subflow_req->msk;
			if (!owner) {
				subflow_add_reset_reason(skb, MPTCP_RST_EPROHIBIT);
				goto dispose_child;
			}

			if (!subflow_hmac_valid(req, &mp_opt) ||
			    !mptcp_can_accept_new_subflow(subflow_req->msk)) {
				SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_JOINACKMAC);
				subflow_add_reset_reason(skb, MPTCP_RST_EPROHIBIT);
				goto dispose_child;
			}

			/* move the msk reference ownership to the subflow */
			subflow_req->msk = NULL;
			ctx->conn = (struct sock *)owner;

			if (subflow_use_different_sport(owner, sk)) {
				pr_debug("ack inet_sport=%d %d\n",
					 ntohs(inet_sk(sk)->inet_sport),
					 ntohs(inet_sk((struct sock *)owner)->inet_sport));
				if (!mptcp_pm_sport_in_anno_list(owner, sk)) {
					SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MISMATCHPORTACKRX);
					goto dispose_child;
				}
				SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_JOINPORTACKRX);
			}

			if (!mptcp_finish_join(child))
				goto dispose_child;

			SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_JOINACKRX);
			tcp_rsk(req)->drop_req = true;
		}
	}

out:
	/* dispose of the left over mptcp master, if any */
	if (unlikely(new_msk))
		mptcp_force_close(new_msk);

	/* check for expected invariant - should never trigger, just help
	 * catching eariler subtle bugs
	 */
	if (ctx && ctx->mp_join && ctx->conn)
		 WARN_ON_ONCE(ctx->conn != (struct sock *)subflow_req->msk);

	return child;

dispose_child:
	tcp_rsk(req)->drop_req = true;
	if (child)
		tcp_close(child, 0);
	return NULL;
}
```

### Cross-File Context

[mptcp_can_accept_new_subflow — sink — net/mptcp/subflow.c:64]
```c
static bool mptcp_can_accept_new_subflow(const struct mptcp_sock *msk)
{
	return mptcp_is_fully_established((void *)msk) &&
	       READ_ONCE(msk->pm.accept_subflow);
}
```

[subflow_hmac_valid — function — net/mptcp/subflow.c:583]
```c
static bool subflow_hmac_valid(const struct request_sock *req,
				       const struct mptcp_options_received *mp_opt)
{
	const struct mptcp_subflow_request_sock *subflow_req;
	u8 hmac[SHA256_DIGEST_SIZE];
	struct mptcp_sock *msk;

	subflow_req = mptcp_subflow_rsk(req);
	msk = subflow_req->msk;

	subflow_generate_hmac(msk->remote_key, msk->local_key,
			      subflow_req->remote_nonce,
			      subflow_req->local_nonce, hmac);

	return !crypto_memneq(hmac, mp_opt->hmac, MPTCPOPT_HMAC_LEN);
}
```

[mptcp_subflow_request_sock — struct — include/net/mptcp.h]
```c
struct mptcp_subflow_request_sock {
	struct request_sock sk;
	unsigned int msk_accessed:1;
	unsigned int mp_capable:1;
	unsigned int mp_join:1;
	unsigned int csum_reqd:1;
	unsigned int allow_join_id0:1;
	unsigned int request_bkup:1;
	u32 remote_nonce;
	u32 local_nonce;
	u64 thmac;
	u32 token;
	u16 remote_id;
	u8 local_id;
	struct mptcp_sock *msk;
};
```

[SUBFLOW_REQ_INC_STATS — macro — net/mptcp/subflow.c:28]
SUBFLOW_REQ_INC_STATS → #define SUBFLOW_REQ_INC_STATS(req, field) \ MPTCP_INC_STATS(sock_net(req_to_sk(req)), field)  (net/mptcp/subflow.c:28)

[subflow_add_reset_reason — function — net/mptcp/subflow.c:118]
```c
static void subflow_add_reset_reason(struct sk_buff *skb, u8 reason)
{
	struct mptcp_ext *mpext = skb_ext_add(skb, SKB_EXT_MPTCP);

	if (mpext) {
		memset(mpext, 0, sizeof(*mpext));
		mpext->reset_reason = reason;
	}
}
```

Control flow: The function processes MP-TCP SYN-ACK for either a new MP_CAPABLE connection or an MP_JOIN subflow. Path 1 (MP_CAPABLE): extracts MPC options from skb, clones a new msk via mptcp_sk_clone(), then delegates to the transport's syn_recv_sock(). If the cloned msk fails or MPC option is missing, it falls back to standard TCP. After child creation, if ctx and !fallback, it links the new msk to the subflow and calls mptcp_pm_new_connection() and mptcp_token_accept(). Path 2 (MP_JOIN): validates HMAC and checks mptcp_can_accept_new_subflow(), verifies the owner msk exists, optionally checks source port annotation list, then calls mptcp_finish_join(). Error paths via 'dispose_child' set drop_req=true, close the child socket, and return NULL. Cleanup at 'out' disposes any leftover new_msk.

Data flow: Inputs include the listener socket (sk), incoming skb, and request_sock (req). subflow_req is derived from req via mptcp_subflow_rsk(). MP options are extracted via mptcp_get_options(). For MP_CAPABLE, new_msk is cloned from listener->conn using mp_opt data. For MP_JOIN, owner msk comes from subflow_req->msk. The child socket is created by the transport layer's syn_recv_sock(). ctx is derived from child. In MP_JOIN flow, owner reference is transferred to ctx->conn after validation. hmac validation uses keys and nonces from msk and subflow_req.

Verdict: Not Vulnerable

Reviewed region:
[net/mptcp/subflow.c]
```c
static struct sock *subflow_syn_recv_sock(const struct sock *sk,
					  struct sk_buff *skb,
					  struct request_sock *req,
					  struct dst_entry *dst,
					  struct request_sock *req_unhash,
					  bool *own_req)
{
	struct mptcp_subflow_context *listener = mptcp_subflow_ctx(sk);
	struct mptcp_subflow_request_sock *subflow_req;
	struct mptcp_options_received mp_opt;
	bool fallback, fallback_is_fatal;
	struct sock *new_msk = NULL;
	struct sock *child;

	pr_debug("listener=%p, req=%p, conn=%p\n", listener, req, listener->conn);

	/* After child creation we must look for MPC even when options
	 * are not parsed
	 */
	mp_opt.suboptions = 0;

	/* hopefully temporary handling for MP_JOIN+syncookie */
	subflow_req = mptcp_subflow_rsk(req);
	fallback_is_fatal = tcp_rsk(req)->is_mptcp && subflow_req->mp_join;
	fallback = !tcp_rsk(req)->is_mptcp;
	if (fallback)
		goto create_child;

	/* if the sk is MP_CAPABLE, we try to fetch the client key */
	if (subflow_req->mp_capable) {
		/* we can receive and accept an in-window, out-of-order pkt,
		 * which may not carry the MP_CAPABLE opt even on mptcp enabled
		 * paths: always try to extract the peer key, and fallback
		 * for packets missing it.
		 * Even OoO DSS packets coming legitly after dropped or
		 * reordered MPC will cause fallback, but we don't have other
		 * options.
		 */
		mptcp_get_options(skb, &mp_opt);
		if (!(mp_opt.suboptions & OPTIONS_MPTCP_MPC)) {
			fallback = true;
			goto create_child;
		}

		new_msk = mptcp_sk_clone(listener->conn, &mp_opt, req);
		if (!new_msk)
			fallback = true;
	} else if (subflow_req->mp_join) {
		mptcp_get_options(skb, &mp_opt);
		if (!(mp_opt.suboptions & OPTION_MPTCP_MPJ_ACK))
			fallback = true;
	}

create_child:
	child = listener->icsk_af_ops->syn_recv_sock(sk, skb, req, dst,
						     req_unhash, own_req);

	if (child && *own_req) {
		struct mptcp_subflow_context *ctx = mptcp_subflow_ctx(child);

		tcp_rsk(req)->drop_req = false;

		/* we need to fallback on ctx allocation failure and on pre-reqs
		 * checking above. In the latter scenario we additionally need
		 * to reset the context to non MPTCP status.
		 */
		if (!ctx || fallback) {
			if (fallback_is_fatal) {
				subflow_add_reset_reason(skb, MPTCP_RST_EMPTCP);
				goto dispose_child;
			}

			if (fallback)
				SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK);

			subflow_drop_ctx(child);
			goto out;
		}

		/* ssk inherits options of listener sk */
		ctx->setsockopt_seq = listener->setsockopt_seq;

		if (ctx->mp_capable) {
			/* this can't race with mptcp_close(), as the msk is
			 * not yet exposted to user-space
			 */
			inet_sk_state_store((void *)new_msk, TCP_ESTABLISHED);

			/* record the newly created socket as the first msk
			 * subflow, but don't link it yet into conn_list
			 */
			WRITE_ONCE(mptcp_sk(new_msk)->first, child);

			/* new mpc subflow takes ownership of the newly
			 * created mptcp socket
			 */
			new_msk->sk_destruct = mptcp_sock_destruct;
			mptcp_sk(new_msk)->setsockopt_seq = ctx->setsockopt_seq;
			mptcp_pm_new_connection(mptcp_sk(new_msk), child, 1);
			mptcp_token_accept(subflow_req, mptcp_sk(new_msk));
			ctx->conn = new_msk;
			new_msk = NULL;

			/* with OoO packets we can reach here without ingress
			 * mpc option
			 */
			if (mp_opt.suboptions & OPTIONS_MPTCP_MPC)
				mptcp_subflow_fully_established(ctx, &mp_opt);
		} else if (ctx->mp_join) {
			struct mptcp_sock *owner;

			owner = subflow_req->msk;
			if (!owner) {
				subflow_add_reset_reason(skb, MPTCP_RST_EPROHIBIT);
				goto dispose_child;
			}

			if (!subflow_hmac_valid(req, &mp_opt) ||
			    !mptcp_can_accept_new_subflow(subflow_req->msk)) {
				SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_JOINACKMAC);
				subflow_add_reset_reason(skb, MPTCP_RST_EPROHIBIT);
				goto dispose_child;
			}

			/* move the msk reference ownership to the subflow */
			subflow_req->msk = NULL;
			ctx->conn = (struct sock *)owner;

			if (subflow_use_different_sport(owner, sk)) {
				pr_debug("ack inet_sport=%d %d\n",
					 ntohs(inet_sk(sk)->inet_sport),
					 ntohs(inet_sk((struct sock *)owner)->inet_sport));
				if (!mptcp_pm_sport_in_anno_list(owner, sk)) {
					SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MISMATCHPORTACKRX);
					goto dispose_child;
				}
				SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_JOINPORTACKRX);
			}

			if (!mptcp_finish_join(child))
				goto dispose_child;

			SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_JOINACKRX);
			tcp_rsk(req)->drop_req = true;
		}
	}

out:
	/* dispose of the left over mptcp master, if any */
	if (unlikely(new_msk))
		mptcp_force_close(new_msk);

	/* check for expected invariant - should never trigger, just help
	 * catching eariler subtle bugs
	 */
	if (ctx && ctx->mp_join && ctx->conn)
		 WARN_ON_ONCE(ctx->conn != (struct sock *)subflow_req->msk);

	return child;

dispose_child:
	tcp_rsk(req)->drop_req = true;
	if (child)
		tcp_close(child, 0);
	return NULL;
}
```

Why it is not vulnerable: Dynamically allocated or returned pointers are explicitly checked before dereference: new_msk is checked with 'if (!new_msk) fallback = true' at line 50; ctx is checked with 'if (!ctx || fallback)' at line 66; owner (subflow_req->msk) is checked with 'if (!owner)' at line 94; child is checked with 'if (child && *own_req)' at line 61 and 'if (child)' at the dispose_child label. The subflow_hmac_valid() and mptcp_can_accept_new_subflow() callees receive their msk argument only after the caller verifies 'if (!owner)' at line 94, ensuring the callee's dereferences of msk->remote_key, msk->local_key, and msk->pm.accept_subflow operate on valid pointers. Protocol-stack-derived pointers (listener, subflow_req, tcp_rsk(req)) are assumed valid by the call context (tcp_check_req), and the function relies on this stack-level invariant rather than adding redundant defensive checks.

Security invariants:
- new_msk must be non-NULL before use in MP_CAPABLE branch: enforced by 'if (!new_msk) fallback = true' at line 50, which diverts control to the fallback path where new_msk is never dereferenced.
- ctx must be non-NULL before accessing ctx->setsockopt_seq, ctx->mp_capable, ctx->mp_join: enforced by 'if (!ctx || fallback)' at line 66, which either jumps to 'dispose_child' or 'out' if ctx is NULL.
- owner (subflow_req->msk) must be non-NULL before HMAC validation and mptcp_can_accept_new_subflow(): enforced by 'owner = subflow_req->msk; if (!owner) { ... goto dispose_child; }' at lines 94-97.
- child must be non-NULL before closing in dispose_child: enforced by 'if (child) tcp_close(child, 0)' at line 121.
- subflow_req must be non-NULL before accessing subflow_req->mp_join and subflow_req->mp_capable: enforced implicitly by the call path (tcp_check_req) which only invokes this function for properly initialized MPTCP request sockets.
- msk pointer passed to subflow_hmac_valid() and mptcp_can_accept_new_subflow() must be non-NULL: enforced by the caller's 'if (!owner)' check at line 94, where owner is assigned from subflow_req->msk before the calls at lines 100-101.

Invariant verification:
- NULL check on dynamically cloned msk before use: holds=true. Evidence: Line 50: 'if (!new_msk) fallback = true;' ensures new_msk is either valid or fallback path is taken where new_msk is not dereferenced.
- NULL check on child socket's MPTCP context before field access: holds=true. Evidence: Line 66: 'if (!ctx || fallback)' guards all subsequent accesses to ctx->setsockopt_seq, ctx->mp_capable, and ctx->mp_join.
- NULL check on MP_JOIN owner msk before HMAC validation and subflow acceptance check: holds=true. Evidence: Lines 94-97: 'owner = subflow_req->msk; if (!owner) { ... goto dispose_child; }' ensures owner is non-NULL before the calls to subflow_hmac_valid() and mptcp_can_accept_new_subflow() at lines 100-101.
- Caller-guaranteed safety for callee pointer dereferences in subflow_hmac_valid and mptcp_can_accept_new_subflow: holds=true. Evidence: Both callees dereference their msk parameter (msk->remote_key, msk->local_key, msk->pm.accept_subflow). The caller at line 94 checks 'if (!owner)' where owner equals subflow_req->msk, and passes the same subflow_req->msk value to both callees. This ensures the msk argument is non-NULL.
- Safe disposal of child socket on error path: holds=true. Evidence: Line 121: 'if (child) tcp_close(child, 0)' ensures child is checked before close, and line 119 'tcp_rsk(req)->drop_req = true;' is reached only after child validity is confirmed.
- Cleanup of leftover new_msk at function exit: holds=true. Evidence: Lines 113-114: 'if (unlikely(new_msk)) mptcp_force_close(new_msk);' disposes any orphaned msk before returning.

Residual risk notes: The function does not perform explicit NULL checks on listener (mptcp_subflow_ctx(sk)) or subflow_req (mptcp_subflow_rsk(req)), relying instead on the protocol stack invariant that tcp_check_req only invokes this function for properly initialized MPTCP sockets. This is consistent with kernel network stack design where upper-layer handlers assume lower-layer validation. Additionally, subflow_hmac_valid() and mptcp_can_accept_new_subflow() do not defensively check their msk parameter internally, relying on caller-side validation. If an unrelated code path were to call these functions with a NULL argument, a dereference would occur, but no such call path exists in the supplied context.

The code contains a **use-after-free vulnerability** in the MP_JOIN path.

## Vulnerability Analysis

### Root Cause
In the `ctx->mp_join` branch (line ~147), ownership of `subflow_req->msk` is transferred to `ctx->conn`:

```c
owner = subflow_req->msk;
// ... validation checks ...
subflow_req->msk = NULL;  // ownership transferred
ctx->conn = (struct sock *)owner;
```

However, the validation check at line ~153 calls `mptcp_can_accept_new_subflow(subflow_req->msk)` **after** checking `subflow_hmac_valid` but **before** the ownership transfer. If `subflow_hmac_valid` passes but `mptcp_can_accept_new_subflow` fails, execution jumps to `dispose_child`, which calls `tcp_close(child, 0)`.

The critical issue: `mptcp_can_accept_new_subflow` dereferences `subflow_req->msk`:

```c
static bool mptcp_can_accept_new_subflow(const struct mptcp_sock *msk)
{
	return mptcp_is_fully_established((void *)msk) &&
	       READ_ONCE(msk->pm.accept_subflow);
}
```

### Attack Scenario

**Preconditions:**
1. Attacker controls MP_JOIN handshake timing and can craft valid HMAC (requires prior successful MPTCP connection establishment to obtain keys)
2. Target MPTCP socket exists but `pm.accept_subflow` is false OR socket is not fully established
3. Race condition: `subflow_req->msk` reference becomes stale between HMAC validation and accept check

**Exploitation Path:**
1. Attacker sends MP_JOIN SYN with valid HMAC
2. `subflow_hmac_valid(req, &mp_opt)` passes (line ~153)
3. Between HMAC check and `mptcp_can_accept_new_subflow` call, the referenced `mptcp_sock` is freed by another thread (e.g., main connection closes)
4. `mptcp_can_accept_new_subflow(subflow_req->msk)` dereferences freed memory
5. Depending on heap state, this causes:
   - Kernel crash (denial of service)
   - Information disclosure if freed memory contains sensitive data
   - Potential control flow hijack if attacker can control freed memory contents

### Data Flow Trace
```
subflow_req->msk (stale pointer)
  ↓
mptcp_can_accept_new_subflow(subflow_req->msk)
  ↓
mptcp_is_fully_established((void *)msk)  // dereference
READ_ONCE(msk->pm.accept_subflow)        // dereference
```

### Missing Guard
No reference counting or locking protects `subflow_req->msk` between its initial use in `subflow_hmac_valid` and the later `mptcp_can_accept_new_subflow` check. The code assumes the pointer remains valid throughout the validation sequence, but concurrent connection teardown can invalidate this assumption.

### Impact
- **Severity:** High
- **Type:** Use-after-free (CWE-416)
- **Impact:** Kernel memory corruption, denial of service, potential privilege escalation
- **Exploitability:** Requires valid MPTCP connection context and precise timing, but feasible for local or network attackers with MPTCP capability

### Correct Fix
The validation order should be reversed, or `subflow_req->msk` should be protected with proper reference counting/locking throughout the validation sequence. The check should occur before HMAC validation or with explicit lifetime guarantees on the `msk` pointer.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": "none",
  "reviewed_region": "net/mptcp/subflow.c:subflow_syn_recv_sock, lines 147-165 (MP_JOIN validation path)",
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": null,
  "attack_preconditions": null,
  "impact": null,
  "non_vulnerable_reason": "The pointer subflow_req->msk is protected by the request_sock lifecycle and cannot be freed during validation. The request_sock (req) holds a reference to the mptcp_sock through subflow_req->msk, and this reference remains valid throughout syn_recv_sock execution because: (1) req is passed as a parameter and remains live for the entire function, (2) the msk reference in subflow_req is established during the initial SYN processing and persists until explicitly cleared at line 158 after validation completes, (3) the ownership transfer pattern (subflow_req->msk = NULL; ctx->conn = owner) occurs AFTER all validation checks pass, meaning validation always operates on the original valid reference. The validation sequence at lines 153-156 checks subflow_hmac_valid and mptcp_can_accept_new_subflow in order, both dereferencing subflow_req->msk, but this pointer cannot become stale mid-function because the request_sock lifecycle guarantees it. The mptcp_sock can only be freed when all references are dropped, and subflow_req holds one such reference that isn't released until line 158. Concurrent connection teardown on the main socket would need to wait for this subflow processing to complete or would be blocked by the reference count.",
  "security_invariants": [
    "request_sock (req) parameter remains valid throughout syn_recv_sock execution",
    "subflow_req->msk reference is valid from function entry until explicitly cleared at line 158",
    "mptcp_sock reference counting prevents premature deallocation while subflow_req holds reference",
    "validation checks (lines 153-156) complete atomically with respect to the request_sock lifecycle",
    "ownership transfer (line 158) only occurs after all validation passes"
  ],
  "invariant_verification": "The request_sock is allocated and populated during SYN processing before syn_recv_sock is called. The subflow_req->msk pointer is set during that initial processing and the reference is held by the request_sock structure. The kernel's request_sock infrastructure ensures the req parameter and its embedded subflow_req remain valid for the duration of syn_recv_sock. The mptcp_sock cannot be freed while subflow_req->msk points to it because proper reference counting is maintained. The code pattern shows ownership transfer (subflow_req->msk = NULL) only after validation succeeds, confirming the reference remains valid during all validation checks.",
  "residual_risk_notes": "Without seeing the complete reference counting implementation for mptcp_sock and the exact lifecycle management in the MP_JOIN SYN processing path, there remains theoretical uncertainty about whether subflow_req->msk could be invalidated by an extremely rare race condition. However, the standard kernel request_sock pattern and the code structure strongly indicate proper lifetime management. The validation-then-transfer pattern is a common idiom for safe ownership transfer in kernel networking code.",
  "call_path": "tcp_v4_rcv → tcp_check_req → subflow_syn_recv_sock → mptcp_can_accept_new_subflow"
}
```