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.

Verdict: no clear vulnerability in the supplied code.

Why
- The function enforces that the `funderAddr` must be either:
  - the EOA transaction `origin`, or
  - the immediate calling contract (`contract.CallerAddress`) when the call is contract-mediated.
- If neither is true, it rejects:
  ```go
  if !isContractFunder && origin != funderAddr {
      return nil, fmt.Errorf(ErrDifferentFromOrigin, origin, funderAddr)
  }
  ```

Authorization logic
- Direct EOA call (`contract.CallerAddress == origin`):
  - `isContractCaller == false`
  - therefore `funderAddr` must equal `origin`
  - no third-party funding is possible.

- Contract-mediated call where the contract itself is the funder:
  - `isContractCaller == true`
  - `isContractFunder == true` iff `contract.CallerAddress == funderAddr`
  - authz is intentionally skipped:
    ```go
    // The smart contract (funder) should handle who is authorized to make this call
    ```
  - This is not an auth bypass in the precompile: the funds belong to the contract account itself. If the contract exposes an unsafe public method, that would be a vulnerability in that contract, not here.

- Contract-mediated call where the origin EOA is the funder:
  - `isContractCaller == true`
  - `isContractFunder == false`
  - the earlier check forces `origin == funderAddr`
  - then the contract must hold an authorization from that origin/funder:
    ```go
    authorization.CheckAuthzExists(ctx, p.AuthzKeeper, contract.CallerAddress, funderAddr, FundVestingAccountMsgURL)
    ```
  - So an intermediary contract cannot fund from an arbitrary third party; it can only act for the transaction origin, and only with an existing authz grant.

Security-relevant invariants established by the code
1. No arbitrary `from_address`:
   - attacker-controlled ABI args cannot set `funderAddr` to an unrelated victim unless it matches `origin` or the calling contract.
2. Contract delegation is gated:
   - if the contract is spending the origin’s funds rather than its own, explicit Cosmos authz must exist.
3. Keeper execution happens only after these checks:
   ```go
   _, err = p.vestingKeeper.FundVestingAccount(sdk.WrapSDKContext(ctx), msg)
   ```

Potential concern, but not enough evidence
- The EVM mirror update uses:
  ```go
  vestingCoins := msg.VestingPeriods.TotalAmount()
  lockedUpCoins := msg.LockupPeriods.TotalAmount()
  if vestingCoins.IsZero() && lockedUpCoins.IsAllPositive() {
      vestingCoins = lockedUpCoins
  }
  ```
  and then mirrors only `vestingCoins.AmountOf(utils.BaseDenom)`.
- Without the keeper implementation or message validation rules, I cannot conclude this causes an exploitable balance desync. It may be correct for the vesting module’s semantics. It is only a residual uncertainty from incomplete context.

Conclusion
- Based on the provided code, the authorization and caller/funder checks are coherent and prevent unauthorized funding from arbitrary accounts.
- I do not see a real vulnerability here from the supplied evidence.

No concrete exploit is supported by the supplied code. The function constrains the funder to either the transaction origin or the immediate calling contract, and requires authz when a contract acts on behalf of the origin.

An attacker would need to cause the function to accept a victim-controlled funderAddr that is neither the tx origin nor the calling contract, but the function explicitly rejects that.

If a contract-mediated call uses the origin as funder, the calling contract must already possess a valid authorization grant for FundVestingAccount.

No unauthorized vesting-fund transfer is demonstrated from the supplied code path.

Observed control flow enforces that `funderAddr` can only be the EOA `origin` or, in a contract-mediated call, `contract.CallerAddress`. The guard `if !isContractFunder && origin != funderAddr { return nil, fmt.Errorf(ErrDifferentFromOrigin, origin, funderAddr) }` blocks arbitrary third-party funding. When the caller is a contract and the contract is not itself the funder, the code checks `authorization.CheckAuthzExists(ctx, p.AuthzKeeper, contract.CallerAddress, funderAddr, FundVestingAccountMsgURL)` before invoking the keeper. The keeper call occurs only after these checks. Skipping authz when the contract itself is the funder is consistent with the contract spending its own funds; any misuse there would be due to the contract's own access control, not a precompile bypass shown here.

Direct EOA call: `contract.CallerAddress == origin`, so `funderAddr` must equal `origin`.

Contract-mediated call with contract as funder: allowed without authz because the contract account is funding.

Contract-mediated call with origin as funder: only allowed if the contract holds a valid authz grant from the funder/origin.

No path shown allows `funderAddr` to be an unrelated victim account.

funder must be origin or calling contract

Computed `isContractCaller := contract.CallerAddress != origin`; `isContractFunder := contract.CallerAddress == funderAddr && isContractCaller`; rejection when `!isContractFunder && origin != funderAddr`.

authz required when contract acts for origin

In branch `if isContractCaller && !isContractFunder`, code checks `authorization.CheckAuthzExists(..., contract.CallerAddress, funderAddr, FundVestingAccountMsgURL)` and returns error if absent/expired.

state-changing keeper call occurs after authorization checks

`p.vestingKeeper.FundVestingAccount(...)` is invoked only after the origin/funder and authz logic.

The balance-mirroring logic after the keeper call updates EVM-side balance entries using only `vestingCoins.AmountOf(utils.BaseDenom)`, with a special case falling back to lockup amounts when vesting is zero. Without the keeper implementation and message validation rules, this does not establish a vulnerability, but it leaves some uncertainty about exact accounting semantics.

No conclusions are drawn about `NewMsgFundVestingAccount` validation because its implementation was not supplied.


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.

Verdict: no real vulnerability shown in the supplied code.

Why this is likely being flagged
- `srcu_idx` is declared uninitialized in `dm_submit_bio()`.
- On the `REQ_NOWAIT` path, `dm_get_live_table_bio()` does not write `*srcu_idx`; it calls `dm_get_live_table_fast(md)` instead.
- Later, `dm_submit_bio()` always calls `dm_put_live_table_bio(md, srcu_idx, bio_opf)`.

A superficial scan could therefore suspect:
- use of uninitialized `srcu_idx`, or
- lock/unlock mismatch between RCU and SRCU.

Code-grounded analysis

1. Control flow in `dm_submit_bio`
```c
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);
...
goto out;
...
out:
	dm_put_live_table_bio(md, srcu_idx, bio_opf);
```

2. How acquisition works
```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);
}
```

- If `REQ_NOWAIT` is set:
  - `dm_get_live_table_fast()` is used.
  - It acquires plain RCU:
    ```c
    rcu_read_lock();
    return rcu_dereference(md->map);
    ```
  - `srcu_idx` is indeed not initialized.

- Otherwise:
  - `dm_get_live_table()` is used.
  - It acquires SRCU and stores a valid index:
    ```c
    *srcu_idx = srcu_read_lock(&md->io_barrier);
    return srcu_dereference(md->map, &md->io_barrier);
    ```

3. How release works
```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);
}
```

- If `REQ_NOWAIT` was used on entry, release is:
  ```c
  rcu_read_unlock();
  ```
  and `srcu_idx` is ignored.

- Otherwise, release is:
  ```c
  srcu_read_unlock(&md->io_barrier, srcu_idx);
  ```
  and `srcu_idx` was initialized by the matching acquisition path.

This means there is no path in the shown code where an uninitialized `srcu_idx` is actually consumed.

Security-relevant data flow
- Attacker influence: a caller may influence `bio->bi_opf`, including whether `REQ_NOWAIT` is set.
- Guard/invariant: the same `bio_opf` value is copied once at function entry:
  ```c
  blk_opf_t bio_opf = bio->bi_opf;
  ```
  and that same local value controls both acquire and release path selection.
- This prevents a mismatch where acquisition uses one mode and release uses another due to concurrent mutation of `bio->bi_opf`.

That local snapshot is an important correctness guard.

Other branches
If the device is suspended or no table is available:
```c
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;
}
```

Observations:
- `REQ_NOWAIT` bios are failed immediately instead of being queued, which is consistent with non-blocking semantics.
- Non-`NOWAIT` bios may be deferred via `queue_io(md, bio)`.
- Regardless of branch, the matching table reference protection is released at `out:`.

Nothing here indicates a memory safety or privilege boundary issue from the supplied code.

Why this is not an exploitable bug
To be exploitable as an uninitialized variable bug, an attacker would need a path where:
- `srcu_idx` remains uninitialized, and
- `dm_put_live_table()` is called with that uninitialized value.

But the code structure prevents that:
- `srcu_idx` is only needed on the non-`REQ_NOWAIT` path.
- On that path, it is initialized before use.
- On the `REQ_NOWAIT` path, the unlock function does not use it at all.

Residual uncertainty
- This conclusion is limited to the supplied code and context.
- I am assuming normal kernel semantics for `rcu_read_lock/unlock()` and `srcu_read_lock/unlock()`, and that `bio_opf` is not altered within this function after being snapshotted.
- No evidence in the provided snippet suggests a race or lifetime bug around `md->map`; both access paths use appropriate read-side protection.

Conclusion
The candidate does not present a real vulnerability based on the shown code. The apparent issue—uninitialized `srcu_idx`—is a false positive because the function uses `bio_opf` to ensure the release path matches the acquisition path, and the uninitialized variable is never actually dereferenced or otherwise consumed on the `REQ_NOWAIT` path.

No confirmed vulnerability in the supplied code. The only suspicious pattern is that srcu_idx is not initialized on the REQ_NOWAIT acquisition path, but the matching release helper selects the RCU unlock path in that same case and does not consume srcu_idx.

Not exploitable based on the shown code. An attacker-controlled REQ_NOWAIT flag can steer the function into the fast RCU path, but that path also uses the corresponding RCU release and never uses the uninitialized srcu_idx value.

Caller can submit a bio reaching dm_submit_bio.

Caller may influence bio->bi_opf, including REQ_NOWAIT.

No additional preconditions shown would create an acquire/release mismatch because the function snapshots bio->bi_opf into a local variable and uses that same value for both operations.

No security impact established from the supplied code. No memory corruption, uninitialized-use, privilege escalation, or denial-of-service primitive is demonstrated here.

dm_get_live_table_bio() and dm_put_live_table_bio() are paired by the same local bio_opf snapshot. If REQ_NOWAIT is set, acquisition is via dm_get_live_table_fast() under RCU and release is via dm_put_live_table_fast(), which ignores srcu_idx. If REQ_NOWAIT is not set, acquisition is via dm_get_live_table(), which initializes *srcu_idx, and release is via dm_put_live_table() using that initialized value. Therefore there is no path in the provided code where an uninitialized srcu_idx is actually consumed.

The lock/unlock discipline is selected by the same local variable: blk_opf_t bio_opf = bio->bi_opf.

REQ_NOWAIT path: dm_get_live_table_fast() acquires RCU and dm_put_live_table_fast() releases RCU; srcu_idx is irrelevant on this path.

Non-REQ_NOWAIT path: dm_get_live_table() initializes srcu_idx via srcu_read_lock(), and dm_put_live_table() uses that initialized value.

md->map dereference is protected by either RCU or SRCU depending on path.

dm_get_live_table_bio(md, &srcu_idx, bio_opf) chooses between fast and normal table lookup based solely on bio_opf.

dm_put_live_table_bio(md, srcu_idx, bio_opf) uses the same bio_opf variable to choose the matching release path.

dm_get_live_table_fast() returns rcu_dereference(md->map) under rcu_read_lock(); dm_put_live_table_fast() only calls rcu_read_unlock().

dm_get_live_table() stores *srcu_idx = srcu_read_lock(&md->io_barrier) before returning srcu_dereference(md->map, &md->io_barrier); dm_put_live_table() unlocks with that index.

The suspicious uninitialized local is dead on the REQ_NOWAIT path and initialized before use on the non-REQ_NOWAIT path.

The branch handling suspended/no-map state checks bio->bi_opf directly instead of the local bio_opf snapshot. From the supplied code alone this does not create the suspected vulnerability, because the acquire/release pairing still uses the stable local snapshot.

This review is limited to the provided snippets and does not assess behavior of queue_io(), dm_split_and_process_bio(), or external mutation rules for bio fields beyond what is shown.


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.

Yes — this is a real information disclosure issue.

## Observed code behavior

`Application.String()` serializes the entire `Application` struct with `yaml.Marshal(&cfg)`:

```go
func (cfg Application) String() string {
    appaStr, err := yaml.Marshal(&cfg)
    if err != nil {
        return err.Error()
    }
    return string(appaStr)
}
```

`Application` contains:

```go
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
```

and `attest` contains:

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

The config is then logged by `logApplicationConfig`:

```go
log.Debugf("application config:\n%+v", color.Magenta.Sprint(app.String()))
```

So if debug logging is enabled, the YAML output will include `attest.password` verbatim.

## Data flow

Attacker-/user-controlled sensitive input:

- `attest.password` is loaded into `Application.Attest.Password`

Transformation:

- `Application.String()` marshals the whole config without filtering or redaction

Sink:

- `log.Debugf(...)` writes the serialized config to logs

There is no masking, omission, or custom marshaling shown for `Password`.

## Why this is a vulnerability

This leaks a secret into logs. The field name itself (`Password`) and its role in `attest` strongly indicate credential material, likely the password protecting a signing key.

Anyone with access to debug logs could recover that password. In many environments, logs are:

- written to disk,
- collected by centralized logging systems,
- accessible to operators or support staff,
- retained longer than the original process lifetime.

That makes this a practical exposure, not just a theoretical one.

## Exploitability / preconditions

This is not an unauthenticated remote exploit by itself. Realistic preconditions are:

1. A password is configured in `attest.password`
2. `logApplicationConfig()` is executed
3. Debug logging is enabled or debug logs are otherwise captured/stored
4. An attacker or unintended party can read those logs

Those are common enough that this should be treated as a real secret exposure issue.

## Missing guard

The code lacks any of the usual protections for secrets, such as:

- excluding `Password` from serialized output,
- redacting it before marshaling/logging,
- implementing a custom `String()` or marshal method that masks sensitive fields,
- logging only non-sensitive config subsets.

## Classification

Most precise classification from the shown code:

- **CWE-532: Insertion of Sensitive Information into Log File**

Secondarily related:

- **CWE-200: Exposure of Sensitive Information to an Unauthorized Actor**

## Verdict

**Vulnerable.**

The `attest.password` field is included in `Application.String()` output and logged in `logApplicationConfig()`, causing sensitive credential disclosure in debug logs.

Application.Attest.Password -> yaml.Marshal(&cfg) -> app.String() -> log.Debugf("application config:\n%+v", ...)

The full Application struct is serialized with yaml.Marshal without redacting sensitive fields. Because Application contains Attest.Password and logApplicationConfig logs Application.String(), the password is emitted verbatim into debug logs.

Practical when debug logging is enabled and a password is configured. An attacker does not inject code; they need access to logs or log aggregation outputs to recover the secret.

A non-empty attest.password is present in configuration

An unauthorized party can access process logs, log files, terminal output, or centralized logging backends

Disclosure of the attestation key password. This can enable compromise of protected signing material or facilitate misuse of attestation/signing workflows, depending on how the password is used elsewhere.

No redaction, omission tag, or custom marshal behavior is shown for attest.Password

Application.String marshals the entire config object, including nested Attest fields

logApplicationConfig sends the resulting string to a logging sink

attest defines Password as an exported string field with yaml/json/mapstructure tags

logApplicationConfig logs app.String() via log.Debugf

The exposure is conditional on debug logging and log access, so this is not a direct unauthenticated remote exploit

Other sensitive fields may also be exposed by the same full-config logging pattern, but only Attest.Password is evidenced in the supplied context

[{'id': 'CWE-532', 'name': 'Insertion of Sensitive Information into Log File'}, {'id': 'CWE-200', 'name': 'Exposure of Sensitive Information to an Unauthorized Actor'}]


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.

Verdict: no clear real vulnerability shown in the supplied code.

Why
- The function’s data flow is constrained before request completion:
  - If `io->res < 0`, it is converted to a block-layer error and the request is ended via `ublk_end_request()`.
  - A zero-byte `REQ_OP_READ` is forced to `-EIO`, preventing a successful read completion with no forward progress.
  - For data I/O (`READ`, `WRITE`, `DRV_IN`), the byte count reported back is capped:
    ```c
    unmapped_bytes = ublk_unmap_io(need_map, req, io);
    if (unlikely(unmapped_bytes < io->res))
        io->res = unmapped_bytes;
    ```
    So even if `io->res` originated from a buggy or malicious userspace ublk server, this function does not pass a larger byte count than `ublk_unmap_io()` says was actually handled.

- Control flow is standard block completion logic:
  - `blk_update_request(req, BLK_STS_OK, io->res)` advances the request by the completed byte count.
  - If more remains, it requeues:
    ```c
    if (requeue)
        blk_mq_requeue_request(req, true);
    ```
  - Otherwise it ends the request:
    ```c
    __blk_mq_end_request(req, BLK_STS_OK);
    ```

What security issue does not appear here
- No obvious memory corruption:
  - No direct pointer arithmetic or buffer write is performed in this function.
  - The only attacker-influenced size-like value, `io->res`, is bounded by `unmapped_bytes` before being used for request accounting.
- No obvious UAF/double free:
  - The completion path is mutually exclusive: either error exit via `ublk_end_request()`, or update/requeue/end via the normal path.
- No privilege escalation or info leak is evident from the shown code alone.

About the deadlock/`fput()` comment
- The comment explains a forward-progress / locking concern involving `bio->bi_end_io()`, `fput()`, and `blkdev_release()`.
- The supplied reduced call path also points at that concern:
  `__ublk_complete_rq → blk_update_request → blk_mq_end_request → fput → blkdev_release`
- However, from the provided snippet, this is at most a potential availability issue, not a demonstrated security vulnerability:
  - We are not shown `fput()` behavior, the exact task_work interaction, or the locking state proving a deadlock.
  - We are not shown attacker reachability conditions, such as whether an unprivileged user can set up and trigger this path.
  - The code explicitly attempts to guard that path with `local_bh_disable()` / `local_bh_enable()`.

Residual uncertainty
- The supplied context shows `local_bh_disable()` and `local_bh_enable()` as no-ops. If that reflects the actual build/runtime semantics relevant here, then the intended deadlock-prevention mechanism may be ineffective.
- Even then, based only on the shown code, that would suggest a possible functional deadlock/DoS scenario, not a proven security vulnerability. The necessary surrounding evidence for exploitability is missing.

Bottom line
- Based strictly on the supplied code and context, `__ublk_complete_rq()` does not present a concrete, demonstrable security vulnerability.
- The main observable risk is a possible correctness/availability issue around request completion and `fput()` progress, but the provided evidence is insufficient to classify it as a real exploitable vulnerability.

No concrete exploitable security flaw is demonstrated in the supplied code. The only notable concern is a potential forward-progress/deadlock correctness issue mentioned in comments, but the provided evidence is insufficient to establish a real security vulnerability.

An attacker would need to reach this request-completion path through ublk-backed block I/O.

Any security impact would additionally require the commented locking/task_work/fput interaction to actually produce a deadlock or similar availability failure in a reachable configuration.

The supplied context does not show sufficient conditions proving unprivileged attacker control, a guaranteed deadlock, or memory-safety consequences.

No demonstrated confidentiality, integrity, or memory-safety impact from the shown code. At most, the comments suggest a possible availability/correctness issue if the intended softirq/task-work behavior is ineffective in some environment.

The function constrains completion state before handing it to the block layer. If io->res is negative, it converts the errno to a block error and ends the request through ublk_end_request(). For READ requests returning zero bytes, it forces -EIO so a successful zero-progress read is not reported. For READ/WRITE/DRV_IN requests, the byte count used for blk_update_request() is capped to unmapped_bytes returned by ublk_unmap_io(), so the function does not propagate a larger completed length than the unmap step reports. Control flow is single-path completion logic: either error-exit via ublk_end_request(), or successful update followed by requeue/end_request, with no evident double-completion in the shown logic.

Negative io->res is never treated as successful completion; it is converted to blk_status_t and completed as an error.

A READ with io->res == 0 is converted to -EIO, preventing silent successful zero-byte completion.

The completed byte count passed to blk_update_request() is bounded by unmapped_bytes from ublk_unmap_io().

Completion paths are mutually exclusive: error path uses ublk_end_request(); success path uses blk_update_request() then either blk_mq_requeue_request() or __blk_mq_end_request().

Observed: `if (!io->res && req_op(req) == REQ_OP_READ) io->res = -EIO;`

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

Observed: `unmapped_bytes = ublk_unmap_io(need_map, req, io); if (unlikely(unmapped_bytes < io->res)) io->res = unmapped_bytes;`

Observed: successful path calls `blk_update_request(req, BLK_STS_OK, io->res)` and then either `blk_mq_requeue_request(req, true)` or `__blk_mq_end_request(req, BLK_STS_OK)`; error path goes to `ublk_end_request(req, res)`.

The code comment discusses preventing a deadlock/forward-progress issue around `bio->bi_end_io()`, `fput()`, and `blkdev_release()` while `disk->open_mutex` may be held.

In the supplied cross-file context, `local_bh_disable()` and `local_bh_enable()` are shown as no-op macros. If that accurately reflects the relevant environment, the intended mitigation may be ineffective.

Even so, based strictly on the shown code and context, this supports at most a possible availability concern, not a proven security vulnerability.


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.

**Verdict: likely vulnerable, due to missing initialization of `resp->acl_access` / `resp->acl_default` before unconditional release paths.**

## Observed code behavior

`nfsacld_proc_getacl()` reads request-controlled `argp->mask`, verifies a file handle, may fetch ACLs, and stores returned pointers in:

```c
resp->acl_access
resp->acl_default
```

Relevant control flow:

```c
resp->status = fh_verify(...);
if (resp->status != nfs_ok)
    goto out;
...
if (argp->mask & ~NFS_ACL_MASK) {
    resp->status = nfserr_inval;
    goto out;
}
...
if (resp->mask & (NFS_ACL|NFS_ACLCNT)) {
    ...
    resp->acl_access = acl;
}
if (resp->mask & (NFS_DFACL|NFS_DFACLCNT)) {
    ...
    resp->acl_default = acl;
}
...
out:
    return rpc_success;

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

And later, regardless of success/failure, the service release hook does:

```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);
}
```

`posix_acl_release()` dereferences the pointer if non-NULL:

```c
if (acl && refcount_dec_and_test(&acl->a_refcount))
    kfree_rcu(acl, a_rcu);
```

So a non-NULL garbage pointer is dangerous.

---

## Why this is a problem

### 1. `resp->acl_access` and `resp->acl_default` are not initialized here

In the shown function, there is **no initialization** like:

```c
resp->acl_access = NULL;
resp->acl_default = NULL;
```

before error exits or before the release callback can run.

### 2. There are multiple paths where release happens without assignment

Examples:

- `fh_verify()` fails → `goto out`
- invalid mask → `goto out`
- `fh_getattr()` fails → `goto out`
- only one ACL field gets assigned, then later error triggers `fail`

On any of those paths, one or both response pointers may still contain whatever value was previously in the response object.

### 3. Both local fail cleanup and final release unconditionally free both fields

The function’s `fail:` label calls `posix_acl_release()` on both fields, and the request release hook also calls it on both fields.

If either field was never initialized to `NULL` or a valid ACL pointer, this becomes an **uninitialized pointer dereference / refcount operation**, potentially leading to an invalid memory access or freeing the wrong object.

---

## Attacker influence

The attacker controls the RPC request, including conditions that steer execution into early-exit paths:

- malformed / invalid file handle causing `fh_verify()` failure
- invalid ACL mask causing `nfserr_inval`
- requesting combinations that assign only one pointer before an error on the second lookup

So an attacker can plausibly trigger the vulnerable control flow.

What the attacker does **not** directly control is the stale pointer value in `resp`. That depends on prior memory contents / object reuse. This makes the bug somewhat nondeterministic, but still real if the response structure is reused without clearing.

---

## Exploitability

### If `rqstp->rq_resp` is zero-initialized elsewhere
Then this specific issue would be prevented, because unassigned pointers would stay `NULL` and `posix_acl_release(NULL)` is safe.

### From the supplied code alone
No such initialization is shown. The code as presented **relies on an external invariant** that the response buffer is pre-zeroed. That invariant is not established in the provided context.

Given only the shown code, the function is unsafe because:

- raw pointer fields exist in `struct nfsd3_getaclres`
- they are conditionally assigned
- they are unconditionally released later

That is a classic improper-initialization bug pattern.

---

## Impact

If the response object is not reliably zeroed before use, likely outcomes include:

- **kernel crash / DoS** from dereferencing an invalid pointer in `posix_acl_release`
- potentially **memory corruption / invalid free-like behavior** if stale memory happens to look like a `struct posix_acl *` and `refcount_dec_and_test()` operates on attacker-triggered stale state

The strongest confidently supported impact from the shown code is **kernel memory safety violation leading to denial of service**.

---

## Precise root cause

**Missing initialization of response ACL pointer fields before error and cleanup paths that unconditionally release them.**

This maps best to:

- **CWE-665: Improper Initialization**
- also related to **CWE-824: Access of Uninitialized Pointer**

---

## Additional supporting observation

The same pattern appears in the NFSv3 ACL handler (`nfsd3_proc_getacl`) and its release hook. That consistency suggests this may be a shared bug pattern rather than an isolated typo.

---

## Conclusion

**Yes, this is likely a real vulnerability** based on the supplied code: `resp->acl_access` and `resp->acl_default` can be released without being initialized in this function, and the cleanup/release logic dereferences them if non-NULL.

## Residual uncertainty

The one important uncertainty is whether the RPC framework guarantees `rqstp->rq_resp` is zeroed before each call. If such a guarantee exists, the bug would be mitigated. But that guarantee is **not shown in the supplied context**, and this function itself does not enforce the necessary safety invariant.

fs/nfsd/nfs2acl.c:nfsacld_proc_getacl and fs/nfsd/nfs2acl.c:nfsaclsvc_release_getacl, with comparison to fs/nfsd/nfs3acl.c:nfsd3_proc_getacl/nfs3svc_release_getacl and include/linux/posix_acl.h:posix_acl_release

No concrete vulnerability is established from the supplied code alone. The only suspicious pattern is that resp->acl_access and resp->acl_default are conditionally assigned in nfsacld_proc_getacl but unconditionally released later. However, proving this as a real bug requires evidence that the response object can reach these release sites with non-NULL uninitialized garbage in those fields. That required allocator/dispatcher initialization behavior is not shown in the provided context.

Not demonstrated from the supplied code. An exploit would require rqstp->rq_resp to contain stale non-NULL pointer values in acl_access/acl_default when nfsacld_proc_getacl exits without assigning them. The provided snippets do not show such a state is reachable.

A caller can influence control flow via argp->mask and file handle validity.

But to turn that into memory corruption or a crash, the response object would also need to arrive with attacker-relevant stale pointer contents in resp->acl_access or resp->acl_default.

The supplied context does not show that rqstp->rq_resp is reused without zero-initialization or that these fields retain prior values across requests.

No confirmed security impact from the shown code alone. If an external invariant were absent and these fields could be stale, the likely impact would be kernel crash or invalid refcount manipulation during posix_acl_release; but that remains hypothetical given the provided evidence.

Observed guards and flow do not themselves create an out-of-bounds access, privilege bypass, or direct unsafe dereference from attacker-controlled data. argp->mask is validated against NFS_ACL_MASK before use, fh_verify gates inode access, fh_getattr failure exits cleanly, and posix_acl_release is NULL-safe. The only suspected issue depends on an unstated external condition (lack of response-structure initialization). Without that missing fact, the code does not by itself prove a real vulnerability.

argp->mask must satisfy !(argp->mask & ~NFS_ACL_MASK), otherwise the request is rejected with nfserr_inval.

inode is only obtained after fh_verify succeeds.

Errors from get_inode_acl/posix_acl_from_mode are checked with IS_ERR and converted to protocol status.

posix_acl_release performs work only for non-NULL pointers.

Cleanup is centralized in nfsaclsvc_release_getacl, and fail-path cleanup mirrors it.

The mask validation is explicit in nfsacld_proc_getacl. fh_verify precedes d_inode(fh->fh_dentry). ACL acquisition errors are checked before assignment to response fields. The release helper is NULL-safe per include/linux/posix_acl.h. What is not verifiable from the supplied context is the initialization state of rqstp->rq_resp before entering nfsacld_proc_getacl; therefore the suspected uninitialized-pointer issue cannot be confirmed.

There is a code-quality concern: this function implicitly relies on acl_access/acl_default already being NULL unless assigned. Defensive initialization at function entry would remove that dependency. The same pattern appears in the analogous NFSv3 ACL path, which suggests an intentional framework invariant rather than an isolated bug, but that invariant is not shown here.