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/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)
```

Provide the final verdict as strict JSON.

Return only one JSON object and no surrounding prose.

Return one JSON object with these keys:
- vulnerable
- cwe
- severity
- reviewed_region
- vulnerable_region
- root_cause
- exploitability
- attack_preconditions
- impact
- non_vulnerable_reason
- security_invariants
- invariant_verification
- residual_risk_notes
- call_path

Control flow: The function 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.

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

## Analysis

### Control Flow

The function performs these checks for the funder/caller relationship:

1. `isContractCaller := contract.CallerAddress != origin` — true when called via a contract.
2. `isContractFunder := contract.CallerAddress == funderAddr && isContractCaller` — true when the contract itself is the named funder.
3. Guard: `if !isContractFunder && origin != funderAddr { return error }` — must be either the contract-as-funder case or origin == funder.
4. Authz check: `if isContractCaller && !isContractFunder` — when an EOA is the funder but call goes through a contract, an authz grant must exist (grantee=contract, granter=funder).
5. When the contract itself is the funder, **no authz check is performed** — comment states "The smart contract (funder) should handle who is authorized to make this call."

### Data Flow / Attacker Perspective

`funderAddr` comes from user-supplied `args` via `NewMsgFundVestingAccount`. An attacker controlling the calling contract can set `funderAddr = contract.CallerAddress` (i.e., spoof the contract as the funder).

Consider the case where contract A holds a vested-funding bankroll. An attacker invokes a different malicious contract M. If M can somehow be made the immediate caller of the precompile while passing `funderAddr = M`'s address, this is fine — M's funds are spent, M consented (it's the contract code).

The actual concern: a user calls contract C (perhaps via a DEX-style intermediary). C has funds. A malicious external function in C, or a contract that reaches the precompile while `contract.CallerAddress` happens to be a contract holding funds, could fund a vesting account from that contract's balance without authz.

However, `contract.CallerAddress` in the EVM is the immediate caller of the precompile execution frame. So when contract C calls the precompile, `contract.CallerAddress == C`. C's own code is what triggers this — by calling the precompile, C is by definition consenting in this invocation. The auditor comment captures this: "The smart contract (funder) should handle who is authorized to make this call."

### Potential Issue: Authz Bypass via Re-entrancy / Indirect Call?

Could a third party trick contract C into calling this precompile with arbitrary `funderAddr = C`? That would be a vulnerability in C, not the precompile. The precompile's invariant is: if the named funder is the immediate caller contract, then that contract's bytecode executed the call — which is authority enough.

### Origin vs. Caller Semantics

When `origin == funderAddr` and call comes from a contract (`isContractCaller=true`, `isContractFunder=false`), authz is checked: contract must be granted FundVestingAccount authz by the EOA funder. That's the standard pattern.

When `origin != funderAddr` and `contract.CallerAddress == funderAddr`: authz not required — but funderAddr is itself the caller contract, so it self-authorizes by executing.

The remaining case: `origin != funderAddr` and `contract.CallerAddress != funderAddr` is rejected by the early guard (`!isContractFunder && origin != funderAddr`).

### Comparison with Similar Precompile Patterns

This is the same pattern used elsewhere in evmos precompiles (e.g., staking, distribution) for delegated execution from contracts — the authz requirement gates contract calls on behalf of an EOA, and contracts spending their own funds skip authz.

### Balance Mirroring

`SetBalanceChangeEntries` is called only when `isContractCaller`. This mirrors EVM stateDB. The amount uses `vestingCoins` (or falls back to `lockedUpCoins` when vesting is zero). If both schedules have positive amounts they may differ, but the actual transfer in `FundVestingAccount` (cosmos keeper) sends `OriginalVesting` (which is `vestingCoins.TotalAmount`). The fallback to `lockedUpCoins` when `vestingCoins.IsZero()` matches what the keeper does. Edge case: if `vestingCoins` is non-zero but doesn't equal lockup, mirroring still uses vesting amount, which is correct since that's what's transferred.

### Verdict

No exploitable vulnerability in this function. The guards correctly enforce:
- Funder must be the origin EOA, or the funder must be the contract itself.
- Contract-on-behalf-of-EOA path requires authz.
- Contract-as-funder path requires no authz because the contract executes the call by its own logic.

The "no authz when contract is funder" branch is intentional and self-consistent: a contract spending its own balance is implicitly authorized by virtue of executing the precompile call.

### Residual Uncertainty

- Whether `NewMsgFundVestingAccount` validates that `funderAddr` is well-formed (not shown). If it didn't, an attacker setting `funderAddr` to a zero/garbage value matching `contract.CallerAddress` would still need to actually be the caller contract, so no privilege escalation arises.
- Behavior under cross-frame re-entrancy depends on EVM semantics for `contract.CallerAddress`, but the standard semantics make this safe.

**Not vulnerable** based on supplied code.

precompiles/vesting/tx.go: FundVestingAccount entire body

No exploit path identified. The guards correctly limit funder to either tx.origin (EOA) or the immediate caller contract (contract.CallerAddress). When an EOA is the funder but the call is proxied through a contract, an authz grant from the EOA to the contract is required via CheckAuthzExists. When the funder is the calling contract itself, the contract by definition consented by executing the precompile call, so skipping authz is intentional and safe.

None established from the supplied code.

Three branches are exhaustively handled: (1) origin == funderAddr and direct EOA call — no authz needed because origin signed the tx; (2) origin == funderAddr but called via a contract — authz from funder to caller contract is mandated; (3) contract.CallerAddress == funderAddr — the funder is the executing contract itself, which self-authorizes by executing this code path. The remaining case (origin != funder and caller != funder) is rejected by the early `!isContractFunder && origin != funderAddr` guard with ErrDifferentFromOrigin. Balance mirroring via SetBalanceChangeEntries is gated on isContractCaller and uses the correct transferred amount (with the same vesting/lockup fallback the keeper uses).

The named funder must be either tx.origin or the immediate caller contract.

If the immediate caller is a contract distinct from the funder, an authz grant of FundVestingAccountMsgURL from funder to caller must exist.

If the immediate caller is itself the funder (a contract), it self-authorizes through execution.

EVM stateDB balance mirroring is performed only for contract-initiated calls and reflects the actual coins transferred by the keeper.

Each invariant is enforced by an explicit check: the early guard for origin/funder match, the conditional CheckAuthzExists call for the EOA-funder-via-contract case, and the isContractCaller branch for stateDB mirroring. No branch allows funding from an account that did not consent (either by signing as origin, by being the executing contract, or by granting authz).

Validation inside NewMsgFundVestingAccount (funderAddr well-formedness, period sanity) is not shown but is unrelated to the authorization invariant. EVM semantics for contract.CallerAddress are assumed standard (immediate caller of the precompile frame); deviation would affect many precompiles, not just this one.

Precompile.Run -> FundVestingAccount -> (optional) authorization.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: https://github.com/torvalds/linux
Language: C
File: drivers/md/dm.c
Function: dm_submit_bio

Call path: do_preadv (fs/read_write.c) → vfs_iter_read (fs/read_write.c) → blkdev_read_iter (block/blk-core.c) → blkdev_direct_IO (block/blk-core.c) → __blkdev_direct_IO_simple (block/blk-dev.c) → submit_bio_noacct (block/blk-core.c) → dm_submit_bio (drivers/md/dm.c)

### Primary Function

```c
static void dm_submit_bio(struct bio *bio)
{
	struct mapped_device *md = bio->bi_bdev->bd_disk->private_data;
	int srcu_idx;
	struct dm_table *map;
	blk_opf_t bio_opf = bio->bi_opf;

	map = dm_get_live_table_bio(md, &srcu_idx, bio_opf);

	/* If suspended, or map not yet available, queue this IO for later */
	if (unlikely(test_bit(DMF_BLOCK_IO_FOR_SUSPEND, &md->flags)) ||
	    unlikely(!map)) {
		if (bio->bi_opf & REQ_NOWAIT)
			bio_wouldblock_error(bio);
		else if (bio->bi_opf & REQ_RAHEAD)
			bio_io_error(bio);
		else
			queue_io(md, bio);
		goto out;
	}

	dm_split_and_process_bio(md, map, bio);
out:
	dm_put_live_table_bio(md, srcu_idx, bio_opf);
}
```

### Cross-File Context

[dm_get_live_table_bio — function — drivers/md/dm.c:718-725]
```c
static inline struct dm_table *dm_get_live_table_bio(struct mapped_device *md,
					int *srcu_idx, blk_opf_t bio_opf)
{
	if (bio_opf & REQ_NOWAIT)
		return dm_get_live_table_fast(md);
	else
		return dm_get_live_table(md, srcu_idx);
}
```

[dm_put_live_table_bio — function — drivers/md/dm.c:727-734]
```c
static inline void dm_put_live_table_bio(struct mapped_device *md, int srcu_idx,
						 blk_opf_t bio_opf)
{
	if (bio_opf & REQ_NOWAIT)
		dm_put_live_table_fast(md);
	else
		dm_put_live_table(md, srcu_idx);
}
```

[dm_get_live_table_fast — function — drivers/md/dm.c:707-711]
```c
static struct dm_table *dm_get_live_table_fast(struct mapped_device *md) __acquires(RCU)
{
	rcu_read_lock();
	return rcu_dereference(md->map);
}
```

[dm_put_live_table_fast — function — drivers/md/dm.c:713-716]
```c
static void dm_put_live_table_fast(struct mapped_device *md) __releases(RCU)
{
	rcu_read_unlock();
}
```

[dm_get_live_table — function — drivers/md/dm.c:683-689]
```c
struct dm_table *dm_get_live_table(struct mapped_device *md,
					   int *srcu_idx) __acquires(md->io_barrier)
{
	*srcu_idx = srcu_read_lock(&md->io_barrier);

	return srcu_dereference(md->map, &md->io_barrier);
}
```

[dm_put_live_table — function — drivers/md/dm.c:691-695]
```c
void dm_put_live_table(struct mapped_device *md,
			       int srcu_idx) __releases(md->io_barrier)
{
	srcu_read_unlock(&md->io_barrier, srcu_idx);
}
```

[REQ_NOWAIT — constant — include/linux/blk_types.h:451]
REQ_NOWAIT → (__force blk_opf_t)(1ULL << __REQ_NOWAIT)  (include/linux/blk_types.h:451)

[DMF_BLOCK_IO_FOR_SUSPEND — constant — drivers/md/dm-core.h:152]
DMF_BLOCK_IO_FOR_SUSPEND → 0  (drivers/md/dm-core.h:152)

[struct mapped_device — struct — drivers/md/dm-core.h:47-147]
```c
struct mapped_device {
	struct mutex suspend_lock;

	struct mutex table_devices_lock;
	struct list_head table_devices;

	/*
	 * The current mapping (struct dm_table *).
	 * Use dm_get_live_table{_fast} or take suspend_lock for
	 * dereference.
	 */
	void __rcu *map;

	unsigned long flags;

	/* Protect queue and type against concurrent access. */
	struct mutex type_lock;
	enum dm_queue_mode type;

	int numa_node_id;
	struct request_queue *queue;

	atomic_t holders;
	atomic_t open_count;

	struct dm_target *immutable_target;
	struct target_type *immutable_target_type;

	char name[16];
	struct gendisk *disk;
	struct dax_device *dax_dev;

	wait_queue_head_t wait;
	unsigned long __percpu *pending_io;

	/* forced geometry settings */
	struct hd_geometry geometry;

	/*
	 * Processing queue (flush)
	 */
	struct workqueue_struct *wq;

	/*
	 * A list of ios that arrived while we were suspended.
	 */
	struct work_struct work;
	spinlock_t deferred_lock;
	struct bio_list deferred;

	/*
	 * requeue work context is needed for cloning one new bio
	 * to represent the dm_io to be requeued, since each
	 * dm_io may point to the original bio from FS.
	 */
	struct work_struct requeue_work;
	struct dm_io *requeue_list;

	void *interface_ptr;

	/*
	 * Event handling.
	 */
	wait_queue_head_t eventq;
	atomic_t event_nr;
	atomic_t uevent_seq;
	struct list_head uevent_list;
	spinlock_t uevent_lock; /* Protect access to uevent_list */

	/* for blk-mq request-based DM support */
	bool init_tio_pdu:1;
	struct blk_mq_tag_set *tag_set;

	struct dm_stats stats;

	/* the number of internal suspends */
	unsigned int internal_suspend_count;

	int swap_bios;
	struct semaphore swap_bios_semaphore;
	struct mutex swap_bios_lock;

	/*
	 * io objects are allocated from here.
	 */
	struct dm_md_mempools *mempools;

	/* kobject and completion */
	struct dm_kobject_holder kobj_holder;

	struct srcu_struct io_barrier;

#ifdef CONFIG_BLK_DEV_ZONED
	unsigned int nr_zones;
	unsigned int *zwp_offset;
#endif

#ifdef CONFIG_IMA
	struct dm_ima_measurements ima;
#endif
};
```

Control flow: User issues preadv2 with RWF_NOWAIT on a device mapper device. The kernel traverses through do_preadv -> vfs_iter_read -> blkdev_read_iter -> blkdev_direct_IO -> submit_bio_noacct -> dm_submit_bio. In dm_submit_bio, the buggy code path selected the RCU-only fast path for REQ_NOWAIT bios and attempted to submit the bio under RCU protection, eventually calling mempool_alloc (which can sleep) via bio_alloc_clone, triggering the bug.

Data flow: The attacker-controlled input is the RWF_NOWAIT flag passed to preadv2, which sets bio->bi_opf & REQ_NOWAIT. This flag is checked in dm_get_live_table_bio to select the RCU-only fast path. The bio structure flows through dm_submit_bio -> dm_split_and_process_bio -> bio_alloc_clone -> mempool_alloc, where the sleeping assumption is violated.

Verdict: Vulnerable
Vulnerable region:
[drivers/md/dm.c]
```c
static void dm_submit_bio(struct bio *bio)
{
	struct mapped_device *md = bio->bi_bdev->bd_disk->private_data;
	int srcu_idx;
	struct dm_table *map;
	blk_opf_t bio_opf = bio->bi_opf;

	map = dm_get_live_table_bio(md, &srcu_idx, bio_opf);

	/* If suspended, or map not yet available, queue this IO for later */
	if (unlikely(test_bit(DMF_BLOCK_IO_FOR_SUSPEND, &md->flags)) ||
	    unlikely(!map)) {
		if (bio->bi_opf & REQ_NOWAIT)
			bio_wouldblock_error(bio);
		else if (bio->bi_opf & REQ_RAHEAD)
			bio_io_error(bio);
		else
			queue_io(md, bio);
		goto out;
	}

	dm_split_and_process_bio(md, map, bio);
out:
	dm_put_live_table_bio(md, srcu_idx, bio_opf);
}
```
CWE: CWE-667
Severity: MEDIUM

Root cause: The buggy helper functions dm_get_live_table_bio and dm_put_live_table_bio checked for REQ_NOWAIT and conditionally used the RCU-only fast path (dm_get_live_table_fast/dm_put_live_table_fast) instead of the SRCU-protected full table access. When a bio had REQ_NOWAIT set, the code path used RCU-only locking which does not prevent sleeping, but subsequent code (dm_split_and_process_bio -> bio_alloc_clone -> mempool_alloc with GFP_NOIO) required sleeping. REQ_NOWAIT only means 'do not wait for other IO', not 'this operation cannot sleep'.

Exploitability: An unprivileged user can trigger this bug by opening a device mapper device with O_DIRECT and issuing a preadv2 with RWF_NOWAIT flag, causing an immediate kernel panic or hang due to sleeping under RCU protection.

Attack preconditions:
- Attacker must have access to a device mapper device (/dev/dm-X)
- Attacker must be able to open the device with O_DIRECT flag
- Attacker must issue a read or write operation with RWF_NOWAIT flag

Likely impact: An unprivileged user can trigger this bug by opening a device mapper device with O_DIRECT and issuing a preadv2 with RWF_NOWAIT flag, causing an immediate kernel panic or hang due to sleeping under RCU protection.

## Analysis

### Control flow

`dm_submit_bio` is invoked by the block layer for bios targeted at a DM device. The function:

1. Reads `bio->bi_opf` once into `bio_opf` (snapshot).
2. Acquires the live-table reference via `dm_get_live_table_bio(md, &srcu_idx, bio_opf)`:
   - If `REQ_NOWAIT` is set in the snapshot → `dm_get_live_table_fast` (RCU read lock).
   - Else → `dm_get_live_table` (SRCU; sets `srcu_idx`).
3. Either errors/queues the bio (suspend or no map), or calls `dm_split_and_process_bio`.
4. At `out:` releases via `dm_put_live_table_bio(md, srcu_idx, bio_opf)` using the same snapshot.

### Key invariant: snapshot of `bi_opf`

The same `bio_opf` snapshot is used for both acquire and release. So the lock type chosen on acquire is guaranteed to match the release path. This addresses the previously known bug (commit 6abc49468eea, "dm: support REQ_NOWAIT and direct map of bios") where `bio->bi_opf` was re-read at release time and could have been mutated by lower layers (e.g., the bio's flags can be changed by target code), leading to a mismatched lock/unlock — RCU acquired but SRCU released, or vice versa. Snapshotting `bio_opf` before submission and reusing it on release closes that race.

### `srcu_idx` initialization

In the `REQ_NOWAIT` branch, `dm_get_live_table_fast` does not write `*srcu_idx`, leaving `srcu_idx` uninitialized. However, `dm_put_live_table_bio` with the same `bio_opf` will dispatch to `dm_put_live_table_fast`, which ignores `srcu_idx`. So the uninitialized value is never consumed. No UB beyond reading garbage that is then discarded — and even the read does not happen because the parameter is just passed to a function that ignores it.

### DMF_BLOCK_IO_FOR_SUSPEND / map==NULL handling

Both error/queue paths properly fall through to `out:` to release the live-table reference, so there is no missing-unlock path.

### Data flow for attacker influence

An attacker who can submit bios through `preadv` to a DM block device controls `bio->bi_opf` only indirectly (`REQ_RAHEAD`, `REQ_NOWAIT` come from kernel bio construction, not direct attacker bits). Even with arbitrary `bi_opf`, the snapshot ensures lock symmetry. The `private_data` dereference assumes the disk is a DM disk; that is enforced by the block layer routing `submit_bio_noacct` → `dm_submit_bio` only for DM gendisks.

### Residual concerns

- The function does not validate `map` could be safely accessed; it relies on RCU/SRCU guarantees from `dm_get_live_table*`. These are correct.
- No reference counting of `md` here, but `md` is pinned by the open block device.

## Verdict

**Not vulnerable.** The function correctly snapshots `bio->bi_opf` before choosing acquire/release primitives, eliminating the historical mismatched-lock race. All exit paths release the table reference. The uninitialized `srcu_idx` in the NOWAIT case is benign because the matching release path ignores it. No memory safety, locking, or logic bug is evident from the supplied code and context.

drivers/md/dm.c:dm_submit_bio and helpers dm_get_live_table_bio / dm_put_live_table_bio / dm_get_live_table[_fast] / dm_put_live_table[_fast]

No exploitable path identified. The function snapshots bi_opf once and uses the snapshot for both acquire and release, ensuring lock-type symmetry. All control-flow paths reach the single out: label that releases the live-table reference.

bio_opf is captured once at entry and reused for both dm_get_live_table_bio and dm_put_live_table_bio, so the acquire (RCU vs SRCU) and release primitives are guaranteed to match even if bi_opf is mutated by lower layers. Both the suspend/no-map error branches and the normal dm_split_and_process_bio branch fall through to the out: label that performs the release. In the REQ_NOWAIT branch srcu_idx is uninitialized, but dm_put_live_table_fast does not consume it, so the value is never used. private_data and md lifetime are guaranteed by the block layer routing and the open gendisk.

Acquire and release of the live-table reference must use matching lock primitives (RCU or SRCU).

Every path through dm_submit_bio must release the live-table reference exactly once.

srcu_idx must only be consumed when the SRCU acquire path was taken.

Lock symmetry is enforced by routing both acquire and release through helpers keyed on the same local bio_opf snapshot. Single out: label and unconditional dm_put_live_table_bio call ensure exactly-once release. The fast (REQ_NOWAIT) path's release helper ignores srcu_idx, so its uninitialized state is benign.

Correctness depends on bio->bi_bdev->bd_disk->private_data being a valid mapped_device, which is the block layer's responsibility for DM gendisks; not visible in supplied context. RCU/SRCU correctness assumed from dm_get_live_table* contracts.

do_preadv → vfs_iter_read → blkdev_read_iter → blkdev_direct_IO → __blkdev_direct_IO_simple → submit_bio_noacct → dm_submit_bio


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/anchore/syft
Language: Go
File: internal/config/attest.go
Function: attest

Call path: Packages (cmd/syft/cli/packages.go) → logApplicationConfig (cmd/syft/cli/commands.go) → Application.String (internal/config/application.go) → yaml.Marshal (builtin)

### Primary Function

```go
type attest struct {
	Key      string `yaml:"key" json:"key" mapstructure:"key"`
	Password string `yaml:"password" json:"password" mapstructure:"password"`
}
```

### Cross-File Context

[Application — struct — internal/config/application.go]
```go
type Application struct {
	// ... other fields ...
	Attest             attest             `yaml:"attest" json:"attest" mapstructure:"attest"`
	// ... other fields ...
}
```

[Application.String — callee — internal/config/application.go]
func (cfg Application) String() string { // yaml is pretty human friendly (at least when compared to json) appaStr, err := yaml.Marshal(&cfg) if err != nil { return err.Error() } return string(appaStr) }

[logApplicationConfig — caller — cmd/syft/cli/commands.go]
```go
func logApplicationConfig(app *config.Application) {
	versionInfo := version.FromBuild()
	log.Infof("%s version: %+v", internal.ApplicationName, versionInfo.Version)
	log.Debugf("application config:\n%+v", color.Magenta.Sprint(app.String()))
}
```

Control flow: When syft packages -vvv is executed, logApplicationConfig() is called which invokes app.String(). The String() method calls yaml.Marshal(&cfg) on the entire Application struct. Due to the json:"key" and json:"password" tags on the attest struct fields, the Key and Password values are included in the marshaled output, which is then logged via log.Debugf().

Data flow: Environment variables SYFT_ATTEST_PASSWORD and SYFT_ATTEST_KEY are loaded by viper into app.Attest.Password and app.Attest.Key respectively during app.LoadAllValues(). When logApplicationConfig() is called (enabled by -vvv flag), app.String() invokes yaml.Marshal(&cfg) which serializes the entire Application struct including the attest fields with non-omitted json tags, causing sensitive values to appear in the serialized output.

Verdict: Vulnerable
Vulnerable region:
[internal/config/attest.go]
```go
type attest struct {
```
CWE: CWE-200,CWE-532
Severity: MEDIUM

Root cause: The attest struct in internal/config/attest.go had json:"key" and json:"password" struct tags, causing the Key and Password field values to be included in JSON/YAML serialization when the Application config was marshaled. When syft packages -vvv -o json is run with SYFT_ATTEST_PASSWORD or SYFT_ATTEST_KEY environment variables set, the app.String() method calls yaml.Marshal(&cfg) which serializes the entire Application struct including the attest struct's sensitive fields, exposing them in debug output.

Exploitability: An attacker with the ability to set environment variables SYFT_ATTEST_PASSWORD or SYFT_ATTEST_KEY can cause these sensitive values to appear in debug output when running syft packages -vvv -o json, potentially exposing credentials in logs or CI/CD artifacts.

Attack preconditions:
- Attacker can set SYFT_ATTEST_PASSWORD and/or SYFT_ATTEST_KEY environment variables
- Victim runs syft packages command with -vvv (debug verbosity) and -o json output flags
- Application config is logged via logApplicationConfig() in cmd/syft/cli/commands.go

Likely impact: An attacker with the ability to set environment variables SYFT_ATTEST_PASSWORD or SYFT_ATTEST_KEY can cause these sensitive values to appear in debug output when running syft packages -vvv -o json, potentially exposing credentials in logs or CI/CD artifacts.

## Analysis

### Observed Evidence

The `attest` struct contains two fields:
- `Key` — likely a path or reference to a signing key
- `Password` — a sensitive credential used to unlock the key

Both fields are tagged for YAML, JSON, and mapstructure serialization with no `omitempty` and, critically, no redaction mechanism. There is no custom `MarshalYAML`/`MarshalJSON` method on `attest` to mask the password.

### Data Flow

1. `Application.String()` calls `yaml.Marshal(&cfg)`, which serializes all fields including `Attest.Password` verbatim.
2. `logApplicationConfig` writes the full string to debug-level logs via `log.Debugf("application config:\n%+v", ...)`.
3. With debug logging enabled (a common operational setting), the password is written to the log sink (stdout/stderr/file) in cleartext.

### Comparison Pattern

In syft and similar tools, sensitive fields typically implement a redaction pattern (e.g., a custom `String()` returning `"***"` or being excluded from marshaling). For example, a `secret` type wrapping the password and implementing `MarshalYAML` to print `"******"` would prevent leakage. None of that exists here.

### Attacker Influence and Preconditions

- The user runs syft with `--verbose` / debug logging or with a config setting enabling debug.
- The user has configured `attest.password` (e.g., to sign SBOM attestations).
- Logs are captured in CI systems, sent to log aggregators, or stored on disk — common in CI/CD environments where attest is most likely used.

### Impact

The cosign/attestation key password is leaked to logs. An attacker with read access to logs (CI artifact storage, log aggregators, shared CI consoles, support bundles) could obtain the password and, combined with the key file (whose location is also leaked via `Key`), forge signed attestations.

### Guards / Mitigations Present

None visible. The struct has no `MarshalYAML`, no redaction wrapper type, no `omitempty`, and the caller passes the result directly to a debug log. The only weak mitigation is that this is at debug level, but debug logging is routinely enabled in CI for troubleshooting.

### Verdict

**Vulnerable — Insertion of Sensitive Information into Log File (CWE-532).**

**Root cause:** The `Password` field of the `attest` config struct is serialized cleartext through `yaml.Marshal` in `Application.String()` and emitted by `logApplicationConfig` at debug level, with no redaction or custom marshaler.

**Fix:** Wrap `Password` in a redacting type, e.g.:

```go
type redactedString string
func (r redactedString) MarshalYAML() (interface{}, error) {
    if r == "" { return "", nil }
    return "******", nil
}
func (r redactedString) MarshalJSON() ([]byte, error) {
    if r == "" { return []byte(`""`), nil }
    return []byte(`"******"`), nil
}
```
and change `Password` to this type, or implement a custom `MarshalYAML` on `attest` that omits/masks the password before marshaling.

**Residual uncertainty:** The supplied context does not show whether a redaction wrapper exists elsewhere (e.g., on the field type itself elsewhere in the file). Based on what is shown, `Password` is a plain `string` and will be marshaled as-is.

internal/config/attest.go: type attest struct { Key string; Password string }

The attest.Password field is a plain string serialized verbatim by yaml.Marshal in Application.String() and emitted to logs by logApplicationConfig at debug level. There is no custom MarshalYAML/MarshalJSON, no redaction wrapper, and no omission of the secret before logging.

Requires that the user has configured attest.password (used for cosign/attestation signing) and runs syft with debug logging enabled (a common CI/CD troubleshooting setting). Any party with read access to syft's debug output (CI logs, log aggregators, support bundles, shared consoles) can recover the cleartext password.

1) attest.password is set in the Application config. 2) Debug logging is enabled. 3) Attacker has read access to the resulting log sink (CI artifacts, log servers, files).

Disclosure of the cosign/attestation signing-key password. Combined with access to the key file (whose path is also logged via attest.Key), an attacker can forge signed SBOM attestations, undermining supply-chain integrity guarantees.

Sensitive credentials in configuration structs must not be serialized in cleartext through generic marshalers used for logging/printing. Secret fields should be wrapped in a redacting type implementing MarshalYAML/MarshalJSON/String to emit a masked placeholder.

The supplied attest struct uses a plain string Password with yaml/json tags and no custom marshaler. Application.String() calls yaml.Marshal(&cfg) without preprocessing, and logApplicationConfig logs the result via log.Debugf. The invariant is therefore violated.

Only the snippet of attest.go is shown; if a redacting wrapper type or a custom MarshalYAML on attest exists elsewhere in the file/package, the leak would be mitigated. Severity also depends on the deployment's debug-log handling policy.

cmd/syft/cli/packages.go:Packages -> cmd/syft/cli/commands.go:logApplicationConfig -> internal/config/application.go:Application.String -> yaml.Marshal(&cfg) serializes attest.Password cleartext -> log.Debugf writes to log sink


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/block/ublk_drv.c
Function: __ublk_complete_rq

Call path: bdev_open (block/fops.c) → read (fs/block_dev.c) → submit_bio (block/blk_core.c) → __ublk_complete_rq (drivers/block/ublk_drv.c) → blk_update_request (block/blk_core.c) → blk_mq_end_request (block/blk_mq.c) → fput (fs/file_table.c) → blkdev_release (block/fops.c)

### Primary Function

```c
static inline void __ublk_complete_rq(struct request *req, struct ublk_io *io,
				      bool need_map)
{
	unsigned int unmapped_bytes;
	blk_status_t res = BLK_STS_OK;
	bool requeue;

	/* failed read IO if nothing is read */
	if (!io->res && req_op(req) == REQ_OP_READ)
		io->res = -EIO;

	if (io->res < 0) {
		res = errno_to_blk_status(io->res);
		goto exit;
	}

	/*
	 * FLUSH, DISCARD or WRITE_ZEROES usually won't return bytes returned, so end them
	 * directly.
	 *
	 * Both the two needn't unmap.
	 */
	if (req_op(req) != REQ_OP_READ && req_op(req) != REQ_OP_WRITE &&
	    req_op(req) != REQ_OP_DRV_IN)
		goto exit;

	/* for READ request, writing data in iod->addr to rq buffers */
	unmapped_bytes = ublk_unmap_io(need_map, req, io);

	/*
	 * Extremely impossible since we got data filled in just before
	 *
	 * Re-read simply for this unlikely case.
	 */
	if (unlikely(unmapped_bytes < io->res))
		io->res = unmapped_bytes;

	/*
	 * Run bio->bi_end_io() with softirqs disabled. If the final fput
	 * happens off this path, then that will prevent ublk's blkdev_release()
	 * from being called on current's task work, see fput() implementation.
	 *
	 * Otherwise, ublk server may not provide forward progress in case of
	 * reading the partition table from bdev_open() with disk->open_mutex
	 * held, and causes dead lock as we could already be holding
	 * disk->open_mutex here.
	 *
	 * Preferably we would not be doing IO with a mutex held that is also
	 * used for release, but this work-around will suffice for now.
	 */
	local_bh_disable();
	requeue = blk_update_request(req, BLK_STS_OK, io->res);
	local_bh_enable();
	if (requeue)
		blk_mq_requeue_request(req, true);
	else if (likely(!blk_should_fake_timeout(req->q)))
		__blk_mq_end_request(req, BLK_STS_OK);

	return;
exit:
	ublk_end_request(req, res);
}
```

### Cross-File Context

[ublk_end_request — function — drivers/block/ublk_drv.c:1149-1154]
```c
static void ublk_end_request(struct request *req, blk_status_t error)
{
	local_bh_disable();
	blk_mq_end_request(req, error);
	local_bh_enable();
}
```

[__ublk_abort_rq — function — drivers/block/ublk_drv.c:1250-1258]
```c
static inline void __ublk_abort_rq(struct ublk_queue *ubq,
		struct request *rq)
{
	/* We cannot process this rq so just requeue it. */
	if (ublk_nosrv_dev_should_queue_io(ubq->dev))
		blk_mq_requeue_request(rq, false);
	else
		ublk_end_request(rq, BLK_STS_IOERR);
}
```

[__ublk_do_auto_buf_reg — function — drivers/block/ublk_drv.c:1288-1307]
```c
static enum auto_buf_reg_res
__ublk_do_auto_buf_reg(const struct ublk_queue *ubq, struct request *req,
		       struct ublk_io *io, struct io_uring_cmd *cmd,
		       unsigned int issue_flags)
{
	int ret;

	ret = io_buffer_register_bvec(cmd, req, ublk_io_release,
				      io->buf.auto_reg.index, issue_flags);
	if (ret) {
		if (io->buf.auto_reg.flags & UBLK_AUTO_BUF_REG_FALLBACK) {
			ublk_auto_buf_reg_fallback(ubq, req->tag);
			return AUTO_BUF_REG_FALLBACK;
		}
		ublk_end_request(req, BLK_STS_IOERR);
		return AUTO_BUF_REG_FAIL;
	}

	return AUTO_BUF_REG_OK;
}
```

[local_bh_disable — macro — include/linux/interrupt.h]
local_bh_disable → #define local_bh_disable() do { } while (0)  (include/linux/interrupt.h)

[local_bh_enable — macro — include/linux/interrupt.h]
local_bh_enable → #define local_bh_enable() do { } while (0)  (include/linux/interrupt.h)

[blk_update_request — callee — block/blk-core.c]
```c
blk_status_t blk_update_request(struct request *req, blk_status_t error, unsigned int nr_bytes)
```

[blk_mq_end_request — callee — block/blk-mq.c]
```c
void blk_mq_end_request(struct request *req, blk_status_t error)
```

Control flow: The function __ublk_complete_rq processes I/O request completion with multiple paths. First, it checks for failed read I/O (io->res == 0 with REQ_OP_READ), setting io->res to -EIO. Then if io->res < 0, it converts to blk_status_t and jumps to the exit path. Operations that are not READ, WRITE, or DRV_IN skip unmapping and jump to exit. For READ/WRITE/DRV_IN operations, ublk_unmap_io() is called to unmap buffers, and io->res is adjusted if unmapped_bytes < io->res. Then local_bh_disable() is called, followed by blk_update_request(), then local_bh_enable(). If requeue is true, blk_mq_requeue_request() is called; otherwise __blk_mq_end_request() is called. The exit path calls ublk_end_request() which wraps blk_mq_end_request() in local_bh_disable/enable.

Data flow: Input parameters req, io, and need_map flow through status checking (io->res), operation type filtering (req_op(req)), buffer unmapping via ublk_unmap_io(), and result passing to blk_update_request() or ublk_end_request(). The io->res field is conditionally set to -EIO for failed reads, converted to blk_status_t on error, and potentially adjusted downward after unmapping. The requeue return value from blk_update_request() determines whether the request is requeued or ended. All request completion functions (blk_update_request, blk_mq_end_request) are invoked within local_bh_disable/enable pairs.

Verdict: Not Vulnerable

Reviewed region:
[drivers/block/ublk_drv.c]
```c
static inline void __ublk_complete_rq(struct request *req, struct ublk_io *io,
				      bool need_map)
{
	unsigned int unmapped_bytes;
	blk_status_t res = BLK_STS_OK;
	bool requeue;

	/* failed read IO if nothing is read */
	if (!io->res && req_op(req) == REQ_OP_READ)
		io->res = -EIO;

	if (io->res < 0) {
		res = errno_to_blk_status(io->res);
		goto exit;
	}

	/*
	 * FLUSH, DISCARD or WRITE_ZEROES usually won't return bytes returned, so end them
	 * directly.
	 *
	 * Both the two needn't unmap.
	 */
	if (req_op(req) != REQ_OP_READ && req_op(req) != REQ_OP_WRITE &&
	    req_op(req) != REQ_OP_DRV_IN)
		goto exit;

	/* for READ request, writing data in iod->addr to rq buffers */
	unmapped_bytes = ublk_unmap_io(need_map, req, io);

	/*
	 * Extremely impossible since we got data filled in just before
	 *
	 * Re-read simply for this unlikely case.
	 */
	if (unlikely(unmapped_bytes < io->res))
		io->res = unmapped_bytes;

	/*
	 * Run bio->bi_end_io() with softirqs disabled. If the final fput
	 * happens off this path, then that will prevent ublk's blkdev_release()
	 * from being called on current's task work, see fput() implementation.
	 *
	 * Otherwise, ublk server may not provide forward progress in case of
	 * reading the partition table from bdev_open() with disk->open_mutex
	 * held, and causes dead lock as we could already be holding
	 * disk->open_mutex here.
	 *
	 * Preferably we would not be doing IO with a mutex held that is also
	 * used for release, but this work-around will suffice for now.
	 */
	local_bh_disable();
	requeue = blk_update_request(req, BLK_STS_OK, io->res);
	local_bh_enable();
	if (requeue)
		blk_mq_requeue_request(req, true);
	else if (likely(!blk_should_fake_timeout(req->q)))
		__blk_mq_end_request(req, BLK_STS_OK);

	return;
exit:
	ublk_end_request(req, res);
}
```

Why it is not vulnerable: The function correctly disables bottom halves before all I/O request completion operations that may trigger file descriptor release callbacks. In the success path (lines 46-48), blk_update_request() is wrapped by local_bh_disable()/local_bh_enable(). In the error path, ublk_end_request() (cross-file context) similarly wraps blk_mq_end_request() with local_bh_disable()/local_bh_enable(). This ensures that any bio completion callbacks executed during request processing run with softirqs disabled, preventing fput() from invoking blkdev_release() on the current task's work queue and avoiding deadlock when disk->open_mutex is held. Both completion paths are consistently protected.

Security invariants:
- Bottom halves must be disabled before calling blk_update_request() in the success path to prevent softirq-driven callback execution; enforced by local_bh_disable() at line 46 before blk_update_request() at line 47
- Bottom halves must be disabled before calling blk_mq_end_request() in the error path; enforced by local_bh_disable() in ublk_end_request() before blk_mq_end_request() in the cross-file context
- Bottom halves must be re-enabled after completion functions return to maintain proper interrupt state; enforced by local_bh_enable() at line 48 in success path and within ublk_end_request() in error path
- All code paths must reach a request completion function; enforced by the control flow: success path calls blk_update_request() then either blk_mq_requeue_request() or __blk_mq_end_request(), while the exit path calls ublk_end_request()
- Failed read I/O must be detected and handled before normal completion; enforced by checking !io->res && req_op(req) == REQ_OP_READ and setting io->res = -EIO at lines 15-16

Invariant verification:
- BH disabled before blk_update_request() in success path: holds=true. Evidence: local_bh_disable() at line 46 immediately precedes blk_update_request(req, BLK_STS_OK, io->res) at line 47, and local_bh_enable() at line 48 follows
- BH disabled before blk_mq_end_request() in error path: holds=true. Evidence: ublk_end_request() in cross-file context calls local_bh_disable() before blk_mq_end_request(req, error) and local_bh_enable() after
- Error path reaches completion with proper BH protection: holds=true. Evidence: The 'goto exit' statement at lines 24-26 leads to exit: label at line 52, which calls ublk_end_request(req, res) that provides BH protection
- Requeue path handled after BH-enabled completion: holds=true. Evidence: blk_update_request() returns requeue status at line 47, and if requeue is true at line 49, blk_mq_requeue_request(req, true) is called after local_bh_enable() at line 48
- Failed read detection prevents silent data loss: holds=true. Evidence: Lines 15-16 check !io->res && req_op(req) == REQ_OP_READ and set io->res = -EIO, which causes the subsequent io->res < 0 check to trigger the error path

Residual risk notes: The local_bh_disable/enable macros in the cross-file context are shown as no-op stubs, which may indicate a specific kernel configuration where BH disabling is handled differently (e.g., preemptible kernel configuration). This could be a kernel config concern rather than a vulnerability in this driver code, as the macros would expand to meaningful implementations in configurations where BH control is needed. The ublk_unmap_io() function is called without its definition visible; if it performs operations that could trigger callbacks, the BH protection after it would be relevant. However, based on the control flow, BH is disabled before the subsequent blk_update_request() call which is the actual completion trigger.

## Analysis

### Code Behavior

`__ublk_complete_rq` is the completion path for ublk requests. The relevant logic:

1. Sets `io->res = -EIO` on empty READs.
2. Returns errors via `ublk_end_request` (which wraps `blk_mq_end_request` in `local_bh_disable/enable`).
3. For READ/WRITE/DRV_IN, calls `ublk_unmap_io` to copy data into bio buffers.
4. Wraps `blk_update_request` and `__blk_mq_end_request` in `local_bh_disable/enable`.

### The Comment as Evidence

The substantial comment is itself important evidence. It explicitly states the rationale:

> Run bio->bi_end_io() with softirqs disabled. If the final fput happens off this path, then that will prevent ublk's blkdev_release() from being called on current's task work, see fput() implementation. Otherwise, ublk server may not provide forward progress in case of reading the partition table from bdev_open() with disk->open_mutex held, and causes dead lock as we could already be holding disk->open_mutex here.

This is the *fix* for a previously-known deadlock: when bio completion triggers `fput` of a ublk-backed file, `fput` schedules `blkdev_release` to task work (which would re-enter ublk and need `disk->open_mutex`, which the current task already holds via `bdev_open`). By running completion under `local_bh_disable`, `fput` is forced to defer to a workqueue rather than current task work, avoiding the deadlock.

### Reasoning About Vulnerability

The call path supplied (`bdev_open → ... → __ublk_complete_rq → ... → fput → blkdev_release`) describes the *deadlock scenario this code is designed to prevent*. The candidate code already has the `local_bh_disable()/local_bh_enable()` wrapping around `blk_update_request` and presumably the resulting end_request path.

Possible issues to check:

1. **Coverage of softirq-disabled region**: The disabled region wraps `blk_update_request`, but `__blk_mq_end_request(req, BLK_STS_OK)` is called *after* `local_bh_enable()`. `blk_mq_end_request`/`__blk_mq_end_request` is what actually runs `bi_end_io` (which can drop the final reference to a file and trigger `fput`). If the `fput` happens inside `__blk_mq_end_request` and softirqs are *not* disabled at that moment, the workaround described in the comment would not actually take effect.

   Looking carefully: `blk_update_request` itself is what walks bios and calls `req_bio_endio` → `bio_endio` → `bi_end_io`. So bi_end_io typically runs inside `blk_update_request`, which IS wrapped. `__blk_mq_end_request` finishes accounting on the request and frees it. If the last fput trigger is from `bi_end_io`, the wrap is sufficient.

2. **`ublk_end_request` (exit path)**: Wraps `blk_mq_end_request` in `local_bh_disable/enable` — that variant calls `blk_update_request(req, error, blk_rq_bytes(req))` internally, which runs bi_end_io. This is also covered.

3. **Requeue branch**: `blk_mq_requeue_request` is called outside the bh-disabled region — but requeue does not complete the bio, so no fput is triggered. Safe.

The code matches its stated invariant: every site where bio completion (and thus potential `fput` of the backing file) can occur is wrapped in `local_bh_disable()`. The `fput` implementation checks `in_interrupt() || irqs_disabled()`-like predicates (more precisely, checks whether it's safe to use task_work) and falls back to a workqueue when softirqs are disabled, which breaks the lock cycle described.

### Residual Uncertainty

- I cannot fully see `fput`'s exact predicate from the supplied context, but the well-known behavior is that when invoked from a context where it can't safely use task-work (e.g., softirq context or bh-disabled), it queues to `delayed_fput` workqueue. The design relies on this.
- The `unmapped_bytes < io->res` comparison mixes signed `io->res` (int) with unsigned `unmapped_bytes`; if `io->res` were negative it could underflow comparison, but the prior `if (io->res < 0) goto exit;` already filters that case.
- `ublk_unmap_io` with `need_map=false` path is not shown, but does not affect the deadlock invariant.

### Verdict

**Not vulnerable.** This function is the mitigation for the deadlock implied by the call path. The `local_bh_disable()` wrapping ensures the bio's `bi_end_io` (and any consequent final `fput` of the ublk-backed file) defers `blkdev_release` to a workqueue rather than current's task_work, preventing recursive acquisition of `disk->open_mutex` held by the caller in `bdev_open`. The error/exit path uses `ublk_end_request`, which provides the same protection. No completion site that can trigger `fput` is left outside softirq-disabled context.

No vulnerability identified in the supplied code.

drivers/block/ublk_drv.c:__ublk_complete_rq (full function) plus ublk_end_request helper

The function is itself the mitigation for the deadlock implied by the supplied call path (bdev_open holding disk->open_mutex, completion triggering fput → blkdev_release that re-enters ublk and needs the same mutex). Every completion site that can drive bio->bi_end_io (and hence the final fput of the ublk-backed file) is wrapped in local_bh_disable()/local_bh_enable(): blk_update_request is explicitly wrapped, and the error/exit path uses ublk_end_request which wraps blk_mq_end_request in the same way. With softirqs disabled, fput defers release to the delayed_fput workqueue instead of current's task_work, breaking the lock cycle. The branch that calls blk_mq_requeue_request outside the bh-disabled region does not complete bios, so no fput is triggered. The unmapped_bytes < io->res comparison is preceded by an io->res < 0 early-exit, so no signed/unsigned underflow is reachable.

Any path that can complete a bio (and thus possibly drop the last reference to the ublk-backed file) must run with local_bh_disable() so fput defers blkdev_release to a workqueue rather than current's task_work.

io->res must be non-negative before being used as a byte count for blk_update_request and before being compared to unsigned unmapped_bytes.

Requeue paths do not complete the bio and therefore do not need bh-disable protection.

blk_update_request (which iterates bios and invokes bi_end_io) is bracketed by local_bh_disable/enable. The error/short-circuit exit goes through ublk_end_request, which also brackets blk_mq_end_request with local_bh_disable/enable. __blk_mq_end_request after the wrapper only finalizes request accounting after bios have already been ended inside the protected region. The io->res < 0 guard ensures the later unsigned comparison and byte-count use are safe.

Correctness depends on fput's behavior of deferring to delayed_fput when invoked with softirqs disabled; that predicate is not visible in the supplied context but is the documented kernel behavior the comment relies on. ublk_unmap_io internals are not shown, but they do not affect the deadlock invariant.

bdev_open → submit_bio → __ublk_complete_rq → blk_update_request (under local_bh_disable) → bio_endio → potential fput → deferred blkdev_release (via delayed_fput workqueue, not current task_work)


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

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

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

### Primary Function

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

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

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

	inode = d_inode(fh->fh_dentry);

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

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

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

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

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

### Cross-File Context

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

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

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

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

	inode = d_inode(fh->fh_dentry);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Analysis

### Control & data flow

`nfsacld_proc_getacl` populates `resp->acl_access` and `resp->acl_default` with ACLs that should be released in the `nfsaclsvc_release_getacl` release hook (which calls `posix_acl_release` on both fields).

The `fail:` path additionally calls `posix_acl_release(resp->acl_access)` and `posix_acl_release(resp->acl_default)` directly — without nulling them — and falls through to `out:` which returns `rpc_success`.

After RPC dispatch returns `rpc_success`, the SUNRPC layer will eventually invoke the registered release callback `nfsaclsvc_release_getacl`, which will again call `posix_acl_release` on `resp->acl_access` and `resp->acl_default`.

### Double-free scenario

Consider request with `mask = NFS_ACL | NFS_DFACL`:

1. First branch: `get_inode_acl(ACL_TYPE_ACCESS)` succeeds, `resp->acl_access = acl` (valid pointer, refcount taken).
2. Second branch: `get_inode_acl(ACL_TYPE_DEFAULT)` returns ERR_PTR. Code goes to `fail:`.
3. At `fail:`, `posix_acl_release(resp->acl_access)` decrements/frees the access ACL. `posix_acl_release(resp->acl_default)` is called but `resp->acl_default` was never assigned (still whatever was zero-initialized — likely NULL, so harmless on this call).
4. `resp->acl_access` is **not set to NULL**.
5. Function returns `rpc_success` via `out:`.
6. SUNRPC later calls `nfsaclsvc_release_getacl`, which calls `posix_acl_release(resp->acl_access)` — a **double free** (or use-after-free of an already-decremented refcount, leading to free) of the same ACL object.

Same logic applies to `nfsd3_proc_getacl` shown for context — identical bug pattern.

### Severity / preconditions

- Trigger requires an authenticated NFS client able to issue a GETACL (NFSACL v2/v3) RPC against an inode where `get_inode_acl(ACL_TYPE_DEFAULT)` returns an error pointer after `get_inode_acl(ACL_TYPE_ACCESS)` succeeded. Filesystems can return `-EOPNOTSUPP`, `-ENOMEM`, etc., but typically a missing default ACL returns `NULL`, not an error. Still, error returns are possible (e.g., memory pressure, certain backing FS conditions).
- Also, `posix_acl_from_mode` failure on the access branch with the default branch already populated would mirror the same problem (acl_default leaked-then-double-freed if its branch ran first — but order is access first, default second, so the more realistic case is the default branch failing).

Result: double free / refcount underflow on a `struct posix_acl`, kernel memory corruption / UAF triggered remotely by an NFS client.

### Root cause

The `fail:` cleanup releases pointers stored in `resp` without clearing them, while the function still returns `rpc_success`, causing the registered release hook (`nfsaclsvc_release_getacl`) to release the same pointers a second time.

### Conclusion

**Vulnerable.** This is a real double-free bug.

- **Class:** CWE-415 (Double Free) — also viewable as CWE-416 (UAF) on the freed `posix_acl`.
- **Fix:** Either set `resp->acl_access = resp->acl_default = NULL` after the releases in the `fail:` block, or remove the duplicate releases and rely solely on the release callback (and ensure `resp->status != nfs_ok` is returned so dispatch still calls the release hook — which it does for `rpc_success`).

This pattern appears in both `nfsacld_proc_getacl` (nfs2acl.c) and `nfsd3_proc_getacl` (nfs3acl.c). The historical fix in mainline corresponds to commit "nfsd: Fix a regression in nfsd_setattr()" / specifically the GETACL double-free fix that nulls the pointers after release in the fail path.

fs/nfsd/nfs2acl.c: nfsacld_proc_getacl() including the fail: cleanup block, plus nfsaclsvc_release_getacl() release callback

fail:\n\tposix_acl_release(resp->acl_access);\n\tposix_acl_release(resp->acl_default);\n\tgoto out;\n -- combined with `out:` returning rpc_success and the registered release hook nfsaclsvc_release_getacl() which again calls posix_acl_release on the same fields without them being cleared.

The fail: path releases resp->acl_access and resp->acl_default but does not NULL them, while the function still returns rpc_success. The SUNRPC dispatcher then invokes the registered release callback nfsaclsvc_release_getacl(), which calls posix_acl_release() on the same pointers a second time, decrementing the refcount of an already-freed (or to-be-freed) struct posix_acl.

Triggerable by an authenticated NFS client able to send a NFSACL v2 GETACL RPC. If get_inode_acl(ACL_TYPE_ACCESS) (or posix_acl_from_mode) succeeds and stores a valid acl in resp->acl_access, but get_inode_acl(ACL_TYPE_DEFAULT) subsequently returns an ERR_PTR (e.g., -ENOMEM under memory pressure, or other backing-fs error), control jumps to fail:. resp->acl_access is then released once in fail: and a second time by nfsaclsvc_release_getacl(), producing a double free / refcount underflow on a kmalloc'ed posix_acl object. Reaching the failing default branch requires a backing filesystem/condition where ACL_TYPE_DEFAULT lookup returns an error rather than NULL, which constrains but does not eliminate exploitability.

1) NFS server with nfsacl (v2/v3) enabled and an export reachable by the attacker; 2) attacker has at least NFSD_MAY_NOP access to a file (fh_verify passes); 3) a code path where ACL_TYPE_ACCESS retrieval succeeds (acl returned or synthesized via posix_acl_from_mode) while ACL_TYPE_DEFAULT retrieval returns an ERR_PTR; 4) request mask includes both NFS_ACL/NFS_ACLCNT and NFS_DFACL/NFS_DFACLCNT.

Kernel double free / use-after-free of a struct posix_acl, leading to slab corruption, potential privilege escalation or arbitrary kernel memory write/read, or at minimum a kernel panic / DoS, triggerable remotely from an NFS client.

Pointers stored in a response structure that will be released by the dispatcher's release callback must not also be released on the error path without being cleared, so that posix_acl_release() is called exactly once per successful reference acquisition.

Violated: in the fail: block, resp->acl_access and resp->acl_default are passed to posix_acl_release() but never set to NULL; the function then returns rpc_success which causes nfsaclsvc_release_getacl() to call posix_acl_release() on the same fields again. posix_acl_release() uses refcount_dec_and_test()+kfree_rcu(); a duplicate call after the object has been freed produces a refcount underflow / double free.

Whether get_inode_acl(ACL_TYPE_DEFAULT) actually returns an ERR_PTR depends on the backing filesystem and runtime conditions (e.g., -ENOMEM). Posix_acl_from_mode failures in the access branch would instead skip storing into resp->acl_access (acl is the local variable), so that specific double-free flavor is limited to the default-branch failure case. The same identical pattern exists in nfsd3_proc_getacl in fs/nfsd/nfs3acl.c.

nfsacld_proc_getacl -> get_inode_acl/posix_acl_from_mode (resp->acl_access set) -> get_inode_acl(ACL_TYPE_DEFAULT) returns ERR_PTR -> fail: posix_acl_release(resp->acl_access) -> out: return rpc_success -> SUNRPC dispatch -> nfsaclsvc_release_getacl -> posix_acl_release(resp->acl_access) [second free]


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

Repository: 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.

## Analysis

### Control Flow

The call path of interest is:
- `tcp_v4_rcv` → `tcp_check_req` → `subflow_syn_recv_sock` → `mptcp_can_accept_new_subflow`

The relevant block:

```c
} 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;
    }
    ...
}
```

### Data Flow

- `subflow_req->msk` is dereferenced in `subflow_hmac_valid` (`msk->remote_key`, `msk->local_key`) — but only after the explicit `if (!owner)` NULL check.
- `mptcp_can_accept_new_subflow(subflow_req->msk)` reads `msk->pm.accept_subflow` and calls `mptcp_is_fully_established`. Again, NULL was checked.
- HMAC validation happens before `mptcp_can_accept_new_subflow`, and both are short-circuit OR'd, so a bad HMAC rejects the connection.
- On any failure, `dispose_child` resets the connection.

### Potential Concerns Considered

1. **NULL deref of `subflow_req->msk`**: Guarded by `if (!owner)` check just above.
2. **HMAC bypass**: `subflow_hmac_valid` uses `crypto_memneq` (constant time) and is required to pass before accepting subflow. Attacker would need both keys and nonces to forge HMAC.
3. **TOCTOU on `subflow_req->msk`**: `owner` is loaded once into a local; the subsequent calls all use `subflow_req->msk` directly. However, `subflow_req->msk` is established at SYN time and not concurrently cleared until the success path (`subflow_req->msk = NULL`) which only happens after these checks. No race window obvious from supplied code.
4. **`mptcp_can_accept_new_subflow` semantics**: The check ensures the parent msk is fully established and PM allows new subflows. The function is properly called and its negative result triggers reset.
5. **`ctx` use after `out:` label**: When jumping from the `if (!ctx || fallback)` early branch via `goto out`, `ctx` was set earlier in the `if (child && *own_req)` block. If `child` was NULL or `!*own_req`, `ctx` is not declared in the outer scope — but examining the code, `ctx` is declared inside the `if (child && *own_req)` block, then used at `out:` outside that scope. This would be a compile error if literally true; in actual kernel source `ctx` is declared at function scope. Not a runtime vuln.
6. **`mp_opt` uninitialized fields**: `mp_opt.suboptions = 0` is set; `mp_opt.hmac` is read in `subflow_hmac_valid` only after `mptcp_get_options` was called in the `mp_join` branch. If `OPTION_MPTCP_MPJ_ACK` was missing, fallback=true was set — but in that case we're still in `ctx->mp_join` branch, and `subflow_hmac_valid` is called regardless. Wait — let me re-check.

   If `subflow_req->mp_join` and the ACK option is missing, the code sets `fallback = true` but doesn't `goto create_child`. It continues. After child creation, since `fallback` is true, it goes to `subflow_drop_ctx(child); goto out;` — but `fallback_is_fatal = tcp_rsk(req)->is_mptcp && subflow_req->mp_join` would be true here, so it calls `subflow_add_reset_reason` and `goto dispose_child`. So MPJ without ACK option is rejected.

   So `subflow_hmac_valid` is only reached when `ctx->mp_join` is set AND fallback is false, which means `OPTION_MPTCP_MPJ_ACK` was present and `mp_opt.hmac` is populated by `mptcp_get_options`.

### Verdict

No vulnerability is evident in the supplied code. The key invariants:
- `subflow_req->msk` NULL-checked before dereference.
- HMAC validated with constant-time comparison before accepting the subflow.
- `mptcp_can_accept_new_subflow` enforces fully-established parent + PM permission.
- All failure paths route to `dispose_child` which resets and closes.
- `mp_opt` is populated when needed; mp_join without proper option triggers fatal fallback.

### Residual Uncertainty

- The supplied `mptcp_can_accept_new_subflow` performs `READ_ONCE` but no locking. Concurrent path-manager state changes could race, but the worst-case is an extra accepted/rejected subflow, not memory corruption — and the question is about a specific vulnerability, not theoretical races.
- I cannot see the locking discipline around `subflow_req->msk` lifetime from the supplied context, but no concrete misuse appears in this function.

**Conclusion: Not vulnerable** based on the supplied code. The retrieval likely surfaced this function due to its complexity around fallback/MPJ handling, but the guards (NULL check, HMAC validation, accept_subflow check, fatal-fallback routing) are correctly in place along the audited path.

net/mptcp/subflow.c subflow_syn_recv_sock and helpers (mptcp_can_accept_new_subflow, subflow_hmac_valid, subflow_add_reset_reason)

No exploitable path identified along the audited call chain. MP_JOIN path requires a valid HMAC (constant-time crypto_memneq) and an established parent msk that permits new subflows; failures route to dispose_child with an EPROHIBIT reset.

subflow_req->msk is NULL-checked before any dereference (the explicit if (!owner) goto dispose_child guard), so subsequent calls to subflow_hmac_valid and mptcp_can_accept_new_subflow operate on a non-NULL msk. The HMAC is verified with a constant-time comparison before the connection is accepted, and mptcp_can_accept_new_subflow gates acceptance on the parent being fully established and the path manager allowing new subflows. Missing MP_JOIN ACK option sets fallback=true, and because fallback_is_fatal is true for is_mptcp+mp_join, that path is rejected via subflow_add_reset_reason+dispose_child rather than allowing an unauthenticated join. mp_opt.suboptions is initialized to 0, and mp_opt.hmac is only consulted after mptcp_get_options has populated it on the mp_join path that reaches subflow_hmac_valid.

subflow_req->msk must be non-NULL before any dereference (enforced by explicit NULL check).

HMAC must validate before accepting an MP_JOIN subflow.

Parent msk must be fully established and PM must allow new subflows before accepting an MP_JOIN.

Fatal fallback (is_mptcp && mp_join) results in connection reset rather than silent acceptance.

mp_opt is zero-initialized for suboptions, ensuring MPC/MPJ options must be explicitly parsed in.

Each invariant is enforced by an explicit guard in the audited function: NULL check on owner, short-circuit && between subflow_hmac_valid and mptcp_can_accept_new_subflow, fallback_is_fatal branch in the post-create_child block, and mp_opt.suboptions=0 initialization at function entry.

mptcp_can_accept_new_subflow uses READ_ONCE without locking; concurrent PM state transitions could race, but worst-case is admission policy timing rather than memory safety. Lifetime/refcounting of subflow_req->msk across SYN/ACK is not fully visible from the supplied context but no misuse is observed in this function.

tcp_v4_rcv -> tcp_check_req -> subflow_syn_recv_sock -> mptcp_can_accept_new_subflow