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, exploitable vulnerability shown (authorization logic appears intentional and guarded)

Based strictly on the provided code and call path, `FundVestingAccount` does **not** expose an obvious authorization bypass or fund-stealing path. The function enforces that the `funderAddr` in the message is either:

1) the transaction `origin` (EOA that initiated the EVM transaction), or
2) the calling contract (`contract.CallerAddress`) **when the caller is a contract**.

If neither is true, it returns `ErrDifferentFromOrigin`.

---

## What an attacker might try (and why it doesn’t work)

### 1) Spoofing `funderAddr` to steal from someone else
An attacker-controlled EOA calls the precompile and sets `funderAddr = victim`.

- If the caller is an EOA, `contract.CallerAddress == origin`, so:
  - `isContractCaller` is false
  - `isContractFunder` is false
  - The check becomes: `if !false && origin != funderAddr { ... }`
  - Since `origin != victim`, it fails.

So an EOA cannot name an arbitrary third-party funder.

### 2) Calling via a contract to bypass the origin check
Attacker EOA calls attacker contract, which calls the precompile, passing `funderAddr = victim`.

- Now `isContractCaller` is true (`contract.CallerAddress` is the attacker contract, `origin` is attacker EOA).
- `isContractFunder` is false unless the attacker contract address equals `funderAddr` (it doesn’t).
- The same “funder must be origin or (caller contract when contract caller)” rule triggers:
  - `if !isContractFunder && origin != funderAddr` → `origin != victim` → fails.

So a contract can’t set `funderAddr` to a third party either, unless the origin is that third party.

---

## The “authz check is skipped” behavior is limited and consistent

This block is security-relevant:

```go
// in case the contract is the funder don't check for auth.
if isContractCaller && !isContractFunder {
    _, _, err := authorization.CheckAuthzExists(..., contract.CallerAddress, funderAddr, ...)
    ...
}
```

Interpretation based on variables:

- **If a contract calls the precompile and the contract itself is also the funder** (`isContractFunder == true`), the code **skips** `CheckAuthzExists`.
- That’s consistent with the comment: if the contract is spending its own funds, the contract’s internal logic is expected to enforce who can trigger spending.

This is not an authorization bypass against *other accounts*, because the earlier invariant still holds: if `isContractFunder` is true, then `funderAddr == contract.CallerAddress` and `isContractCaller == true`, i.e., the contract is spending **its own** account.

If a contract calls but the funder is the **origin EOA** (common “EOA initiates through a contract” case), then authz **is checked**, requiring an existing authorization from the funder to the contract caller. That prevents a contract from pulling funds from the origin without explicit grant.

---

## Balance mirroring / stateDB note (not a direct vuln from shown code)
The function mirrors balance changes into EVM stateDB only when `isContractCaller` is true. That’s likely an EVM/Cosmos integration detail. From the snippet alone, it doesn’t create an obvious exploit, but correctness depends on the rest of the precompile framework (e.g., whether EOA-initiated calls need mirroring too, and how `SetBalanceChangeEntries` is applied/validated). Nothing in the shown code suggests an attacker can leverage this to mint funds or bypass keeper accounting.

---

## Residual uncertainties (need more code to fully eliminate risk)
Not vulnerabilities proven here, but worth checking elsewhere in the repo:

- What exactly `NewMsgFundVestingAccount` accepts/validates (e.g., negative/overflow times, amounts, denom enforcement, period ordering).
- How `authorization.CheckAuthzExists` interprets grantee/granter and whether addresses are correctly converted (here it’s passed `contract.CallerAddress` and `funderAddr`; types in signature are `sdk.AccAddress`, but call site uses EVM `common.Address`—there must be conversion in omitted code; if that conversion is wrong, it could be a bug).
- Whether `vestingKeeper.FundVestingAccount` has any side-effects that could be abused even when funds cannot be stolen (e.g., DoS via large period arrays), but that’s outside shown evidence.

---

## Conclusion
With the provided context, the function’s key security property holds: **a caller cannot set `funderAddr` to an unrelated third party**, and **contract-mediated spending from an EOA requires an authz grant**. The “skip authz when contract is funder” case appears intentional and does not, by itself, enable theft from other accounts.

Not exploitable for unauthorized third-party funding based on the shown logic; caller cannot arbitrarily select a different funder without failing the origin/contract-funder checks or the authz check.

Attacker can submit EVM transactions invoking the vesting precompile (directly as EOA or via a contract).

Attacker attempts to set msg.FunderAddress (funderAddr) to an address they do not control.

No demonstrated ability to steal funds or bypass required authorization from the provided code. The function enforces funder identity constraints and requires authz when a contract attempts to act on behalf of an EOA funder.

The function enforces that funderAddr must be either (a) tx origin, or (b) the calling contract address when the call originates from a contract (contract funder). If a contract calls and the funder is not the contract itself (i.e., origin == funderAddr case), it additionally requires a valid authz grant from the funder to the contract caller via CheckAuthzExists. The only case where authz is skipped is when the contract itself is the funder, which is consistent with spending the contract’s own funds.

A third-party address cannot be set as funderAddr unless it equals tx origin or the calling contract (when isContractCaller).

If isContractCaller is true and funderAddr == origin (i.e., the contract is attempting to move origin’s funds), a valid authorization (authz) must exist for contract.CallerAddress as grantee and funderAddr as granter for the FundVestingAccountMsgURL.

Guard 1: if !isContractFunder && origin != funderAddr => error, so funderAddr must equal origin unless isContractFunder is true.

isContractFunder implies (contract.CallerAddress == funderAddr) and isContractCaller, so only the calling contract can be funder in that branch.

Guard 2: if isContractCaller && !isContractFunder => CheckAuthzExists(contract.CallerAddress, funderAddr, FundVestingAccountMsgURL) required; failure aborts.

Correctness depends on unseen conversions/types: CheckAuthzExists signature expects sdk.AccAddress for grantee/granter; the call site passes EVM addresses (common.Address). If conversion exists elsewhere, it should be reviewed for mismatch/encoding issues, but this is not evidenced in the provided snippet.

NewMsgFundVestingAccount input validation (amount bounds, denom restrictions, period sizes) is not shown; potential DoS or validation bugs would be there, not proven here.

EVM stateDB mirroring via SetBalanceChangeEntries occurs only when isContractCaller; this appears to be integration logic, but any accounting mismatch would require more surrounding code to assess.


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** (the suspected issue—use of an uninitialized `srcu_idx` leading to bad unlock—does not materialize in this code as written)

What looks suspicious at first glance is that `srcu_idx` is declared uninitialized in `dm_submit_bio()`, and `dm_put_live_table_bio(md, srcu_idx, bio_opf)` is always called on the `out:` path—even when `dm_get_live_table_bio()` took the `REQ_NOWAIT` fast path, which does **not** initialize `*srcu_idx`.

However, the `bio_opf` value (captured before any branching) controls **both** the get and put behavior, and for `REQ_NOWAIT` the put path does not use `srcu_idx` at all. So there is no invalid unlock.

---

## Concrete control/data-flow review

### Relevant code paths

#### 1) `REQ_NOWAIT` set
- `bio_opf = bio->bi_opf`
- `map = dm_get_live_table_bio(md, &srcu_idx, bio_opf);`

`dm_get_live_table_bio()`:

```c
if (bio_opf & REQ_NOWAIT)
    return dm_get_live_table_fast(md);
```

`dm_get_live_table_fast()`:

```c
rcu_read_lock();
return rcu_dereference(md->map);
```

So for NOWAIT:
- lock acquired: **RCU read lock**
- `srcu_idx` is **not written** (remains uninitialized)
- `map` is read via RCU

Later, regardless of whether `map` is NULL / suspended / processed, execution goes to `out:`:

```c
dm_put_live_table_bio(md, srcu_idx, bio_opf);
```

`dm_put_live_table_bio()`:

```c
if (bio_opf & REQ_NOWAIT)
    dm_put_live_table_fast(md);
else
    dm_put_live_table(md, srcu_idx);
```

So for NOWAIT:
- unlock performed: `rcu_read_unlock()`
- **`srcu_idx` is not accessed**
- therefore, the uninitialized local does not affect behavior

This is safe as long as `bio_opf` remains consistent between get and put. In this function, `bio_opf` is copied once from `bio->bi_opf` before `map` is acquired and is not modified afterward, so the pairing is stable.

#### 2) `REQ_NOWAIT` not set
`dm_get_live_table()` is used:

```c
*srcu_idx = srcu_read_lock(&md->io_barrier);
return srcu_dereference(md->map, &md->io_barrier);
```

Here `srcu_idx` is initialized, and `dm_put_live_table()` uses it:

```c
srcu_read_unlock(&md->io_barrier, srcu_idx);
```

So the SRCU lock/unlock pairing is also correct.

---

## Suspension / NULL map behavior

This block:

```c
if (unlikely(test_bit(DMF_BLOCK_IO_FOR_SUSPEND, &md->flags)) ||
    unlikely(!map)) {
    ...
    goto out;
}
```

does not create a vulnerability on its own:

- It may cause queueing (`queue_io(md, bio)`) or immediate error completion depending on flags (NOWAIT, RAHEAD).
- Importantly, it still drops the read-side protection (RCU or SRCU) via `dm_put_live_table_bio()` on all paths.

No use-after-free is evident here: `map` is only used after being fetched under the appropriate read-side primitive, and the release happens after `dm_split_and_process_bio()` returns (which is the typical pattern for “read-side critical section covers use of dereferenced pointer”).

---

## Security impact assessment

- **Uninitialized variable?** Present syntactically (`srcu_idx`), but **not used** in the NOWAIT path due to the conditional put routine. This is not an information disclosure or memory corruption vector in the shown logic.
- **Lock mismatch?** Not shown. The same `bio_opf` governs both acquisition and release, ensuring RCU↔RCU and SRCU↔SRCU pairing.
- **Attacker influence?** Even if an attacker can cause `REQ_NOWAIT` to be set via user-triggered I/O patterns, that only changes which locking scheme is used; it does not create a path where `srcu_idx` is consumed uninitialized.

---

## Residual uncertainty (not enough context to call a vuln)
- If some *callee* (e.g., `dm_split_and_process_bio()` or `queue_io()`) were to mutate `bio->bi_opf` and the code used `bio->bi_opf` at `out:` instead of the local copy, that could risk mismatched put. But this function explicitly uses the saved `bio_opf`, so it is robust against such mutation.
- Nothing else in the provided snippet indicates memory lifetime violations around `md->map`.

---

### Conclusion
Based strictly on the provided code and context, `dm_submit_bio()` does **not** present a real vulnerability. The apparent uninitialized `srcu_idx` is benign because it is never read on the `REQ_NOWAIT` fast path, and on the non-NOWAIT path it is properly initialized before use.

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(), dm_get_live_table(), dm_put_live_table()

Not exploitable from shown code. The only suspicious element (uninitialized srcu_idx) is not dereferenced on the REQ_NOWAIT path and is initialized before use on the non-NOWAIT path.

Although srcu_idx is declared without initialization in dm_submit_bio(), it is only consumed by dm_put_live_table() when REQ_NOWAIT is not set. When REQ_NOWAIT is set, dm_get_live_table_bio() takes the RCU fast path (does not set srcu_idx) and dm_put_live_table_bio() correspondingly uses dm_put_live_table_fast(), which ignores srcu_idx. The lock/unlock primitive pairing (RCU vs SRCU) is controlled by the same saved bio_opf value, preventing mismatched unlock or uninitialized index use.

The same decision bit (REQ_NOWAIT in bio_opf) governs both table acquisition and release, ensuring correct RCU/SRCU pairing.

On the non-NOWAIT path, srcu_idx is initialized by dm_get_live_table() before any possible call to dm_put_live_table().

On the NOWAIT path, srcu_idx is never read because dm_put_live_table_fast() does not take an index parameter.

All early exits (suspend/map NULL, NOWAIT/RAHEAD handling) still go through dm_put_live_table_bio(), so the read-side critical section is always exited.

Verified by direct control-flow tracing: dm_submit_bio() stores bio_opf once from bio->bi_opf and passes it unchanged to both dm_get_live_table_bio() and dm_put_live_table_bio(). The helper implementations show srcu_idx is only set/used in the non-NOWAIT branch; the NOWAIT branch uses rcu_read_lock/unlock and never references srcu_idx.

This review does not assess correctness/safety inside dm_split_and_process_bio() or queue_io(); it only establishes that the shown lock/index handling is not an uninitialized-use or lock-mismatch vulnerability.

If future code changes were to base dm_put_live_table_bio() on bio->bi_opf instead of the saved bio_opf, then mutations to bio->bi_opf by callees could create a lock/unlock mismatch; the current code avoids this by using a local copy.

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.

### Verdict: **Real vulnerability (sensitive information exposure via logging/serialization)**

#### Observed behavior (code-grounded)
- The `attest` config struct contains two sensitive fields:

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

- `Application` embeds this as `Attest attest`.
- `Application.String()` marshals the **entire** `Application` struct to YAML with `yaml.Marshal(&cfg)` and returns it as a string.
- `logApplicationConfig()` logs `app.String()` at debug level:

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

Because there are no `yaml:"-"` tags, redaction logic, or custom marshaling, the YAML output will include `attest.key` and `attest.password` in plaintext whenever debug logging is enabled.

#### Why this is a vulnerability
This is a classic **information disclosure / leakage of secrets through logs**:
- Secrets placed in configuration (passwords, private keys, tokens) are printed to logs verbatim.
- Debug logs often end up in:
  - CI logs / build artifacts
  - centralized log aggregation (e.g., CloudWatch, ELK, Splunk)
  - support bundles, bug reports, terminal scrollback
  - container logs accessible to other principals in the cluster

Even though it’s “debug” level, it’s still a realistic exposure path—many deployments enable debug temporarily or accidentally, and logs are frequently accessible beyond the intended audience.

#### Exploitability / attacker influence
- Preconditions:
  - The application is run with debug logging enabled (or logs are otherwise collected).
  - The config contains real secrets in `Attest.Key` and/or `Attest.Password`.
- An attacker does not need to control the input to exploit; they only need read access to logs (which is commonly broader than access to the original secret source).
- Impact:
  - Disclosure of attestation password and key material, enabling unauthorized signing/attestation actions or impersonation, depending on how those values are used elsewhere.

#### Classification
- **CWE-532: Insertion of Sensitive Information into Log File**
- Also aligns with **CWE-359: Exposure of Private Personal Information to an Unauthorized Actor** (broader category) and common “secrets in logs” findings.

#### Concrete fix directions
Any of the following would address the issue:

1. **Redact sensitive fields in String()** before marshaling (most robust):
   - Create a sanitized copy of `Application` where `Attest.Password` and possibly `Attest.Key` are replaced with `"***"`/empty.
2. **Custom marshalers** for `attest` or `Application` that omit/redact secrets when marshaling for display.
3. **Tag-based omission** (less ideal if the values are still needed in file output):
   - `yaml:"-"` would prevent serialization entirely, but then you can’t emit them to config files via YAML. Redaction is usually preferred for “String for logging”.

As written, the code will log secrets, so this is a real vulnerability.

yaml.Marshal(&cfg) serializes entire Application including Attest.Key and Attest.Password without redaction

log.Debugf("application config:\n%+v", ... app.String()) logs serialized config

The Application.String() method marshals the full configuration struct to YAML for display/logging, and the Attest fields (key, password) are included due to default yaml tags and lack of redaction/omission; logApplicationConfig emits this string to logs.

Passive; any principal with access to debug logs can obtain secrets. No need for attacker-controlled input—only log read access and presence of configured secrets.

Debug logging enabled or debug logs collected/retained

Application configured with non-empty Attest.Key and/or Attest.Password

Attacker can read logs (local user, CI logs, centralized log store, container logs, support bundle, etc.)

Disclosure of attestation key material and/or password via logs, potentially enabling unauthorized signing/attestation or impersonation depending on downstream use of these secrets.

None shown that prevent serialization or logging of Attest.Key/Password (no yaml:"-", no custom MarshalYAML, no redaction prior to logging, no conditional suppression based on field sensitivity).

From provided code, Application.String() always marshals &cfg; logApplicationConfig always logs app.String() at Debugf. The attest struct tags explicitly include both key and password for YAML/JSON/mapstructure, so they will appear in marshaled output.

Exposure is gated by debug logging level, but debug logs are frequently enabled temporarily or accidentally and are often broadly accessible in CI/observability systems.

Other fields in Application (not shown) may also contain secrets and would be similarly leaked by full-struct marshaling.


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 vulnerability in the shown code

From the code and provided context, `__ublk_complete_rq()` does not present a concrete, exploitable vulnerability by itself. The function’s behavior is consistent with normal Linux block-layer request completion patterns, and the potentially “scary” pieces (bytes accounting, requeue/end, BH disabling, error mapping) have visible guards that prevent obvious memory corruption, double-completion, or attacker-controlled pointer use—at least within what’s shown.

That said, there are a couple of *risk areas* that depend on invariants outside the snippet (notably `ublk_unmap_io()` and the trustworthiness of `io->res`), but they are not demonstrably vulnerable from the supplied code.

---

## What the function does (security-relevant flow)

### Inputs
- `struct request *req`: kernel block request; may correspond to user-triggered I/O (e.g., `read()`).
- `struct ublk_io *io`: completion/result object produced by ublk userspace server interaction.
- `need_map`: controls mapping/unmapping behavior in `ublk_unmap_io()`.

### Control/data flow
1. **Special-case failed reads returning 0 bytes**
   ```c
   if (!io->res && req_op(req) == REQ_OP_READ)
       io->res = -EIO;
   ```
   This prevents silently completing a READ with 0 bytes as success. This is not a vulnerability; it’s a correctness/robustness guard.

2. **Negative `io->res` treated as error**
   ```c
   if (io->res < 0) {
       res = errno_to_blk_status(io->res);
       goto exit;
   }
   ```
   Ensures errors go through `ublk_end_request(req, res)` and do not continue into the “copy/unmap and partial completion” path.

3. **Only READ/WRITE/DRV_IN go through mapping/unmapping**
   ```c
   if (req_op(req) != REQ_OP_READ && req_op(req) != REQ_OP_WRITE &&
       req_op(req) != REQ_OP_DRV_IN)
       goto exit;
   ```
   So for FLUSH/DISCARD/WRITE_ZEROES etc., it completes directly via `ublk_end_request`. This reduces surface area.

4. **Unmap/copy completion data, then clamp length**
   ```c
   unmapped_bytes = ublk_unmap_io(need_map, req, io);

   if (unlikely(unmapped_bytes < io->res))
       io->res = unmapped_bytes;
   ```
   The only explicit length safety check here is that `io->res` is reduced if it exceeds `unmapped_bytes`. This is important: it prevents reporting completion of more bytes than were actually unmapped/copied.

5. **Complete via blk layer; may requeue**
   ```c
   requeue = blk_update_request(req, BLK_STS_OK, io->res);
   if (requeue)
       blk_mq_requeue_request(req, true);
   else ...
       __blk_mq_end_request(req, BLK_STS_OK);
   ```
   Or on error paths:
   ```c
   ublk_end_request(req, res);
   ```
   There is no path that both requeues and ends the request in this function: it’s either `requeue` or `end`.

---

## Potential vulnerability classes considered

### 1) Double completion / use-after-free
**Not evidenced here.**
- The function either:
  - calls `blk_update_request()` and then **either** requeues or ends (`__blk_mq_end_request()`), **or**
  - jumps to `exit:` and calls `ublk_end_request()` which wraps `blk_mq_end_request()`.
- There is no second completion call after `ublk_end_request()`.
- Requeue path does not call `blk_mq_end_request()` here.

Whether other threads could race to complete the same `req` is not shown, but that would be a higher-level ublk queueing bug, not something demonstrated by this snippet.

### 2) Out-of-bounds / memory corruption via `io->res`
The main concern would be: if `io->res` is attacker-controlled and used as a length, could it cause OOB in unmap/copy.

**What we can actually see:**
- `io->res` is checked for `< 0` early.
- After `ublk_unmap_io()`, it clamps `io->res` down to `unmapped_bytes` if `unmapped_bytes < io->res`.

**What we cannot see:**
- Whether `ublk_unmap_io()` itself uses `io->res` as a copy length *before* it returns `unmapped_bytes`. If it does, then the clamp would be too late and could be a bug.
- Whether `ublk_unmap_io()` computes `unmapped_bytes` based on the request’s actual payload length and performs safe bounded copying.

Because `ublk_unmap_io()` is not provided, an OOB cannot be concluded from this function alone. From *this* code, the intention is to keep accounting bounded.

### 3) Integer truncation / sign conversion issues
- `unmapped_bytes` is `unsigned int`.
- `io->res` appears to be signed (since compared to `< 0` and assigned `-EIO`).
- It passes `io->res` to `blk_update_request(..., unsigned int nr_bytes)`.

However, because negative values are filtered out before this point, the key sign-to-unsigned hazard is avoided in this function. If `io->res` can be > `UINT_MAX`, that would truncate when passed, but `io->res`’s type/range isn’t shown; in typical kernel code it’s `int` or `ssize_t` and would not exceed `UINT_MAX` in a controlled way without other bugs. Nothing here proves an overflow.

### 4) Deadlock / DoS via `local_bh_disable()` semantics
In the supplied context:
```c
#define local_bh_disable() do { } while (0)
```
So in this build/view, it’s a no-op. In real kernels it often disables softirqs. Either way, this is about avoiding a deadlock involving `fput()` task work and `open_mutex` per the comment. That’s a *liveness/correctness workaround*, not a security vulnerability. It does not introduce privilege escalation or memory safety issues by itself.

---

## Residual uncertainties (worth checking elsewhere, but not proven here)

If you continue auditing, the most security-relevant dependency is:

- **`ublk_unmap_io(need_map, req, io)`**: If it performs copying to/from request buffers using `io->res` *without bounding by request length*, and `io->res` is influenced by an untrusted ublk userspace server, that could become a kernel memory corruption or info leak. This snippet suggests the author is aware of mismatches (`unmapped_bytes < io->res`) but the safety depends on *how* unmapping/copying is implemented.

Also check:
- Who writes `io->res` (ublk userspace server response path) and whether it is validated against `blk_rq_bytes(req)` / expected transfer length before any copying.

---

## Conclusion
Based strictly on the provided code and context, `__ublk_complete_rq()` does **not** itself show a real vulnerability. It includes basic validation of negative results, constrains completion length post-unmap, and avoids obvious double-completion. Any real vulnerability would most likely come from **`ublk_unmap_io()` or upstream validation of `io->res`**, which are not included here.

drivers/block/ublk_drv.c::__ublk_complete_rq (shown) plus provided helpers ublk_end_request(), __ublk_abort_rq(), __ublk_do_auto_buf_reg() and macro stubs for local_bh_disable/enable

No concrete exploit path is demonstrated from the supplied code. The function does not expose an obvious memory-safety flaw, double-completion, or attacker-controlled pointer dereference on its own.

Attacker can trigger block I/O (e.g., read()) to a ublk-backed device (as in the call path).

For a real bug here, additional conditions would be needed in unseen code (notably ublk_unmap_io()) such as unsafe use of io->res as a copy length prior to validation.

None shown in the provided code. Potential impact would only arise if external invariants are violated (e.g., unsafe copying in ublk_unmap_io()), which is not evidenced here.

Within __ublk_complete_rq(), negative io->res is handled as an error and does not reach the unsigned length path; non-data operations (FLUSH/DISCARD/WRITE_ZEROES) bypass mapping/unmapping and complete directly; for data ops, the code clamps io->res to unmapped_bytes (post-unmap) before calling blk_update_request(), and the completion logic is mutually exclusive (either requeue or end), avoiding an apparent double-completion in this function.

io->res < 0 never reaches blk_update_request(req, ..., io->res) due to early error handling (prevents sign-to-unsigned conversion of negative lengths).

Requests not of type READ/WRITE/DRV_IN are completed via ublk_end_request() and do not enter the unmap/copy path.

After ublk_unmap_io() returns, io->res is reduced if it exceeds unmapped_bytes, preventing completion-accounting from exceeding what was unmapped.

Exactly one of: blk_mq_requeue_request() or (__blk_mq_end_request()/ublk_end_request()) is invoked per call path in this function.

Observed: `if (io->res < 0) { ... goto exit; }` before any call that takes `unsigned int nr_bytes`.

Observed: op filter `if (req_op(req) != REQ_OP_READ && req_op(req) != REQ_OP_WRITE && req_op(req) != REQ_OP_DRV_IN) goto exit;`.

Observed: clamp `if (unlikely(unmapped_bytes < io->res)) io->res = unmapped_bytes;` before `blk_update_request(req, ..., io->res)`.

Observed: requeue branch does not call end; non-requeue branch ends; error path ends via ublk_end_request().

The main unresolved risk is in ublk_unmap_io(): if it uses io->res to size copies/unmaps before returning unmapped_bytes, the clamp here would be too late. That would be a separate vulnerability, but it cannot be concluded from the supplied snippet.

Type/range of io->res is not shown. If it were wider than unsigned int, large positive values could truncate when passed to blk_update_request(); however, no evidence here shows io->res can exceed the request length or that truncation leads to memory corruption in this function.

bdev_open → read → submit_bio → __ublk_complete_rq → blk_update_request → blk_mq_end_request → fput → blkdev_release


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: no clear real vulnerability in the shown code (most likely safe), with one caveat about initialization that can’t be confirmed from the snippet

#### What the function does (security-relevant flow)
- `argp->mask` is attacker-controlled (from an NFS request).
- The code verifies the filehandle (`fh_verify`) before dereferencing the dentry/inode.
- It rejects unknown mask bits: `if (argp->mask & ~NFS_ACL_MASK) resp->status = nfserr_inval;`
- Depending on the requested bits, it fetches ACLs:
  - `get_inode_acl(inode, ACL_TYPE_ACCESS)` or `posix_acl_from_mode(...)` as a fallback when NULL.
  - `get_inode_acl(inode, ACL_TYPE_DEFAULT)` for default ACL.
- On error, it releases any ACL pointers in `resp` (`fail:`), then returns.
- On success, the release happens later in `nfsaclsvc_release_getacl()`.

#### Double-free / UAF concern: analyzed
At first glance, the combination of:
- releasing `resp->acl_access` / `resp->acl_default` in the local `fail:` label, and
- also releasing them later in `nfsaclsvc_release_getacl()`

looks like it could double-free.

However, in the shown control flow:
- If we go to `fail:`, we **still return `rpc_success`**, and the response release hook will run and call `posix_acl_release()` again.
- Whether that becomes a real double-free depends on what `resp->acl_access` / `resp->acl_default` contain at `fail:` and afterward.

Key observation grounded in the code:
- `posix_acl_release()` is safe on `NULL` (`if (acl && ...)`).
- The only time a non-NULL ACL pointer is stored into `resp->acl_access` / `resp->acl_default` is after passing `IS_ERR(acl)` checks:
  - `resp->acl_access = acl;` happens only after ensuring `acl` is neither NULL (after fallback) nor `ERR_PTR`.
  - `resp->acl_default = acl;` similarly happens only after `IS_ERR` check.

So, can `fail:` run after `resp->acl_access` or `resp->acl_default` has already been set to a real ACL pointer? Yes:
- Example: access ACL succeeded and stored in `resp->acl_access`, then default ACL fetch fails -> `goto fail;` releases `resp->acl_access`. Later the service release hook will release again **unless those pointers are cleared**.

The snippet does **not** clear them in `fail:`. That would normally be a double `posix_acl_release()`.

Why this still may not be a vulnerability:
- In the kernel NFSd XDR code patterns, `rqstp->rq_resp` structures are typically **zero-initialized per request** and/or the dispatch layer may suppress the normal release path on certain failures. But that behavior is **not shown here**, so we can’t rely on it as proven protection.
- Even if the release hook runs, `posix_acl_release()` is a refcounted release; a second release is a genuine refcount underflow/UAF risk if the pointer remains unchanged.

So the *code as shown* has a **potential** double-release pattern on the error path.

#### Is it exploitable from what we can see?
From the provided evidence alone, exploitability is **not proven**, because we’re missing one crucial invariant:
- Are `resp->acl_access` and `resp->acl_default` guaranteed to be reset to `NULL` before `nfsacld_proc_getacl()` runs (or before the release hook runs), or are they nulled after `fail:` by the framework?

If they are always initialized to `NULL` and only assigned when successful, then on the “access succeeded, default failed” path they would be non-NULL and would be double-released unless something else prevents the second release. Nothing in the provided code does.

#### Most precise classification (conditional)
- **If** the release hook (`nfsaclsvc_release_getacl`) is always called after `nfsacld_proc_getacl` returns (even on `resp->status` error), and
- **if** `resp->acl_access` / `resp->acl_default` are not cleared after `fail:`,
then this is a **double free / use-after-free risk** via refcount underflow:
- CWE-415 (Double Free) / CWE-416 (Use After Free), kernel memory corruption.

#### Why I’m not calling it a confirmed vulnerability
Because the snippet lacks the surrounding dispatch/response lifecycle code that would confirm:
- whether the release hook is always executed on this path, and
- whether the resp struct or these pointers are cleared/overwritten after `fail:`.

Given this is stable Linux kernel code and the same pattern appears in the NFSv3 ACL handler (`nfsd3_proc_getacl`), it strongly suggests there is an external invariant (e.g., response structs are zeroed and/or release semantics are different on error). But that’s inference, not shown proof.

#### Concrete hardening fix (would make it safe regardless of framework)
In `fail:` after releasing, clear the pointers to prevent any later release from acting on freed objects:
```c
fail:
	posix_acl_release(resp->acl_access);
	posix_acl_release(resp->acl_default);
	resp->acl_access = NULL;
	resp->acl_default = NULL;
	goto out;
```

#### What to check next to confirm/deny
- Where `rqstp->rq_resp` is allocated/initialized for this procedure (is it `kzalloc`/cleared each call?).
- Whether the svc dispatch path calls `nfsaclsvc_release_getacl()` unconditionally even when `resp->status` indicates failure.
- Whether XDR encoding or error handling overwrites these pointers or bypasses the release hook on failure.

Based strictly on the provided code, it’s a **potential** double-release bug pattern, but not a **confirmed** exploitable vulnerability without lifecycle/initialization evidence.

The code shows a suspicious double-release pattern on the fail: path (posix_acl_release() locally, then again in nfsaclsvc_release_getacl()), but the supplied snippet does not prove the required lifecycle conditions for this to be a real double-free/UAF (i.e., that the release hook runs unconditionally with unchanged resp->acl_* pointers after fail).

Not proven from provided code. A real exploit would require: (1) resp->acl_access or resp->acl_default to be set to a valid ACL pointer, (2) an error after that causes goto fail, (3) the same pointer to be released again later by nfsaclsvc_release_getacl without being cleared/overwritten, and (4) refcount semantics leading to premature free. The snippet does not show the RPC/XDR lifecycle that would confirm (3).

Remote attacker can issue NFS ACL GETACL calls with chosen mask bits

A request path where one ACL acquisition succeeds (stored in resp) and a later acquisition fails (triggering goto fail)

The NFSd request/response framework must still invoke nfsaclsvc_release_getacl on this error response while leaving resp->acl_* unchanged (not shown)

If the missing lifecycle conditions were true, this would risk kernel memory corruption via refcount underflow/double put (potential UAF/double free). However, those conditions are not established by the provided context, so impact cannot be asserted as real here.

From the provided code alone, there is no confirmed memory safety issue: the only demonstrated operations are guarded ACL acquisition and refcounted release. The apparent double-release requires assumptions about how rqstp->rq_resp is initialized and how/when the service calls the release hook on error; those invariants are not shown, so a real vulnerability cannot be concluded from this snippet.

Unknown request mask bits are rejected (argp->mask & ~NFS_ACL_MASK => nfserr_inval)

inode is only used after fh_verify succeeds

resp->acl_access/acl_default are assigned only after IS_ERR(acl) checks pass (so they should not hold ERR_PTR values from this function)

Not verifiable from supplied context: whether resp->acl_access/acl_default are zero-initialized per request and whether nfsaclsvc_release_getacl runs on this failure path with the same resp contents are outside the provided code.

The fail: label releases resp->acl_* but does not set them to NULL; if the framework later calls nfsaclsvc_release_getacl on the same resp without clearing those fields, a double put could occur.

The same pattern exists in nfsd3_proc_getacl, suggesting either a known-safe framework invariant or a shared latent bug; confirmation requires inspecting the svc dispatch/encoder lifecycle not included here.

Hardening would be to set resp->acl_access = NULL; resp->acl_default = NULL; after releasing in fail: to make the code robust regardless of framework behavior.

get_inode_acl (fs/nfsd/nfs2acl.c) [as provided in call path summary]