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/ext4/namei.c
Function: make_indexed_dir

Call path: do_sys_openat2 (fs/open.c) → path_openat (fs/namei.c) → ext4_create (fs/ext4/namei.c) → ext4_add_nondir (fs/ext4/namei.c) → ext4_add_entry (fs/ext4/namei.c) → make_indexed_dir (fs/ext4/namei.c)

### Primary Function

```c
static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
			    struct inode *dir,
			    struct inode *inode, struct buffer_head *bh)
{
	struct buffer_head *bh2;
	struct dx_root	*root;
	struct dx_frame	frames[EXT4_HTREE_LEVEL], *frame;
	struct dx_entry *entries;
	struct ext4_dir_entry_2	*de, *de2;
	char		*data2, *top;
	unsigned	len;
	int		retval;
	unsigned	blocksize;
	ext4_lblk_t  block;
	struct fake_dirent *fde;
	int csum_size = 0;

	if (ext4_has_metadata_csum(inode->i_sb))
		csum_size = sizeof(struct ext4_dir_entry_tail);

	blocksize =  dir->i_sb->s_blocksize;
	dxtrace(printk(KERN_DEBUG "Creating index: inode %lu\n", dir->i_ino));
	BUFFER_TRACE(bh, "get_write_access");
	retval = ext4_journal_get_write_access(handle, bh);
	if (retval) {
		ext4_std_error(dir->i_sb, retval);
		brelse(bh);
		return retval;
	}
	root = (struct dx_root *) bh->b_data;

	/* The 0th block becomes the root, move the dirents out */
	fde = &root->dotdot;
	de = (struct ext4_dir_entry_2 *)((char *)fde +
		ext4_rec_len_from_disk(fde->rec_len, blocksize));
	if ((char *) de >= (((char *) root) + blocksize)) {
		EXT4_ERROR_INODE(dir, "invalid rec_len for '..'");
		brelse(bh);
		return -EFSCORRUPTED;
	}
	len = ((char *) root) + (blocksize - csum_size) - (char *) de;

	/* Allocate new block for the 0th block's dirents */
	bh2 = ext4_append(handle, dir, &block);
	if (IS_ERR(bh2)) {
		brelse(bh);
		return PTR_ERR(bh2);
	}
	ext4_set_inode_flag(dir, EXT4_INODE_INDEX);
	data2 = bh2->b_data;

	memcpy(data2, de, len);
	de = (struct ext4_dir_entry_2 *) data2;
	top = data2 + len;
	while ((char *)(de2 = ext4_next_entry(de, blocksize)) < top) {
		if (ext4_check_dir_entry(dir, NULL, de, bh2, data2, len,
					 (data2 + (blocksize - csum_size) -
					  (char *) de))) {
			brelse(bh2);
			brelse(bh);
			return -EFSCORRUPTED;
		}
		de = de2;
	}
	de->rec_len = ext4_rec_len_to_disk(data2 + (blocksize - csum_size) -
					   (char *) de, blocksize);

	if (csum_size)
		ext4_initialize_dirent_tail(bh2, blocksize);

	/* Initialize the root; the dot dirents already exist */
	de = (struct ext4_dir_entry_2 *) (&root->dotdot);
	de->rec_len = ext4_rec_len_to_disk(blocksize - EXT4_DIR_REC_LEN(2),
					   blocksize);
	memset (&root->info, 0, sizeof(root->info));
	root->info.info_length = sizeof(root->info);
	root->info.hash_version = EXT4_SB(dir->i_sb)->s_def_hash_version;
	entries = root->entries;
	dx_set_block(entries, 1);
	dx_set_count(entries, 1);
	dx_set_limit(entries, dx_root_limit(dir, sizeof(root->info)));

	/* Initialize as for dx_probe */
	fname->hinfo.hash_version = root->info.hash_version;
	if (fname->hinfo.hash_version <= DX_HASH_TEA)
		fname->hinfo.hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
	fname->hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed;
	ext4fs_dirhash(dir, fname_name(fname), fname_len(fname), &fname->hinfo);

	memset(frames, 0, sizeof(frames));
	frame = frames;
	frame->entries = entries;
	frame->at = entries;
	frame->bh = bh;

	retval = ext4_handle_dirty_dx_node(handle, dir, frame->bh);
	if (retval)
		goto out_frames;
	retval = ext4_handle_dirty_dirblock(handle, dir, bh2);
	if (retval)
		goto out_frames;

	de = do_split(handle,dir, &bh2, frame, &fname->hinfo);
	if (IS_ERR(de)) {
		retval = PTR_ERR(de);
		goto out_frames;
	}

	retval = add_dirent_to_buf(handle, fname, dir, inode, de, bh2);
out_frames:
	/*
	 * Even if the block split failed, we have to properly write
	 * out all the changes we did so far. Otherwise we can end up
	 * with corrupted filesystem.
	 */
	if (retval)
		ext4_mark_inode_dirty(handle, dir);
	dx_release(frames);
	brelse(bh2);
	return retval;
}
```

### Cross-File Context

[ext4_next_entry — function — fs/ext4/namei.c:551-555]
```c
static inline struct ext4_dir_entry_2 *
ext4_next_entry(struct ext4_dir_entry_2 *p, unsigned long blocksize)
{
	return (struct ext4_dir_entry_2 *)((char *)p +
		ext4_rec_len_from_disk(p->rec_len, blocksize));
}
```

[ext4_rec_len_from_disk — function — fs/ext4/ext4.h:2232-2242]
```c
static inline unsigned int
ext4_rec_len_from_disk(__le16 dlen, unsigned blocksize)
{
	unsigned len = le16_to_cpu(dlen);

#if (PAGE_SIZE >= 65536)
	if (len == EXT4_MAX_REC_LEN || len == 0)
		return blocksize;
	return (len & 65532) | ((len & 3) << 16);
#else
	return len;
#endif
}
```

[ext4_rec_len_to_disk — function — fs/ext4/ext4.h:2245-2262]
```c
static inline __le16 ext4_rec_len_to_disk(unsigned len, unsigned blocksize)
{
	if ((len > blocksize) || (blocksize > (1 << 18)) || (len & 3))
		BUG();
#if (PAGE_SIZE >= 65536)
	if (len < 65536)
		return cpu_to_le16(len);
	if (len == blocksize) {
		if (blocksize == 65536)
			return cpu_to_le16(EXT4_MAX_REC_LEN);
		else
			return cpu_to_le16(0);
	}
	return cpu_to_le16((len & 65532) | ((len >> 16) & 3));
#else
	return cpu_to_le16(len);
#endif
}
```

[ext4_check_dir_entry — function — fs/ext4/dir.c:66-108]
```c
int __ext4_check_dir_entry(const char *function, unsigned int line,
			   struct inode *dir, struct file *filp,
			   struct ext4_dir_entry_2 *de,
			   struct buffer_head *bh, char *buf, int size,
			   unsigned int offset)
{
	const char *error_msg = NULL;
	const int rlen = ext4_rec_len_from_disk(de->rec_len,
						dir->i_sb->s_blocksize);
	const int next_offset = ((char *) de - buf) + rlen;

	if (unlikely(rlen < EXT4_DIR_REC_LEN(1)))
		error_msg = "rec_len is smaller than minimal";
	else if (unlikely(rlen % 4 != 0))
		error_msg = "rec_len % 4 != 0";
	else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len)))
		error_msg = "rec_len is too small for name_len";
	else if (unlikely(next_offset > size))
		error_msg = "directory entry overrun";
	else if (unlikely(next_offset > size - EXT4_DIR_REC_LEN(1) &&
			  next_offset != size))
		error_msg = "directory entry too close to block end";
	else if (unlikely(le32_to_cpu(de->inode) >
			le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count)))
		error_msg = "inode out of bounds";
	else
		return 0;

	if (filp)
		ext4_error_file(filp, function, line, bh->b_blocknr,
			"bad entry in directory: %s - offset=%u, "
			"inode=%u, rec_len=%d, name_len=%d, size=%d",
			error_msg, offset, le32_to_cpu(de->inode),
			rlen, de->name_len, size);
	else
		ext4_error_inode(dir, function, line, bh->b_blocknr,
			 "bad entry in directory: %s - offset=%u, "
			 "inode=%u, rec_len=%d, name_len=%d, size=%d",
			  error_msg, offset, le32_to_cpu(de->inode),
			  rlen, de->name_len, size);

	return 1;
}
```

[ext4_dir_entry_2 — struct — fs/ext4/ext4.h:2174-2180]
```c
struct ext4_dir_entry_2 {
	__le32	inode;		/* Inode number */
	__le16	rec_len;		/* Directory entry length */
	__u8	name_len;		/* Name length */
	__u8	file_type;		/* See file type macros EXT4_FT_* below */
	char	name[EXT4_NAME_LEN];	/* File name */
};
```

[fake_dirent — struct — fs/ext4/namei.c:218-224]
```c
struct fake_dirent
{
	__le32 inode;
	__le16 rec_len;
	u8 name_len;
	u8 file_type;
};
```

[EXT4_DIR_REC_LEN — macro — fs/ext4/ext4.h:2223-2224]
EXT4_DIR_REC_LEN → #define EXT4_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT4_DIR_ROUND) & \ ~EXT4_DIR_ROUND)  (fs/ext4/ext4.h:2223-2224)

[EXT4_DIR_ROUND — constant — fs/ext4/ext4.h:2221-2222]
EXT4_DIR_ROUND → #define EXT4_DIR_PAD 4 #define EXT4_DIR_ROUND (EXT4_DIR_PAD - 1)  (fs/ext4/ext4.h:2221-2222)

Control flow: make_indexed_dir converts a linear directory into an htree-indexed directory. It first validates the dotdot entry offset stays within the block. It allocates a new block via ext4_append, copies existing directory entries from the root block to the new block, and iterates through them. During iteration, ext4_next_entry computes the next entry pointer, and ext4_check_dir_entry validates each entry before the pointer advances. The function then initializes the htree root structure, calls do_split to insert the new entry, and adds the directory entry via add_dirent_to_buf. On any error, cleanup paths release buffers (bh, bh2) via brelse, release dx frames via dx_release, and mark the inode dirty before returning the error code.

Data flow: The function receives the current buffer head (bh) containing the root block, a filename, and inodes. It extracts the dotdot entry, computes the start offset of subsequent entries using ext4_rec_len_from_disk(fde->rec_len, blocksize), and validates that the resulting pointer stays within the block. The valid data region (len) is calculated and copied to a new block (bh2). During iteration, each entry's rec_len is consumed by ext4_next_entry via ext4_rec_len_from_disk, and validated by ext4_check_dir_entry which checks rlen >= EXT4_DIR_REC_LEN(1), rlen % 4 == 0, and next_offset <= size. The last entry's rec_len is updated via ext4_rec_len_to_disk to fill remaining space. The root block is restructured with htree metadata, and the hash information is populated via ext4fs_dirhash before do_split processes the tree insertion.

Verdict: Not Vulnerable

Reviewed region:
[fs/ext4/namei.c]
```c
static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
			    struct inode *dir,
			    struct inode *inode, struct buffer_head *bh)
{
	struct buffer_head *bh2;
	struct dx_root	*root;
	struct dx_frame	frames[EXT4_HTREE_LEVEL], *frame;
	struct dx_entry *entries;
	struct ext4_dir_entry_2	*de, *de2;
	char		*data2, *top;
	unsigned	len;
	int		retval;
	unsigned	blocksize;
	ext4_lblk_t  block;
	struct fake_dirent *fde;
	int csum_size = 0;

	if (ext4_has_metadata_csum(inode->i_sb))
		csum_size = sizeof(struct ext4_dir_entry_tail);

	blocksize =  dir->i_sb->s_blocksize;
	dxtrace(printk(KERN_DEBUG "Creating index: inode %lu\n", dir->i_ino));
	BUFFER_TRACE(bh, "get_write_access");
	retval = ext4_journal_get_write_access(handle, bh);
	if (retval) {
		ext4_std_error(dir->i_sb, retval);
		brelse(bh);
		return retval;
	}
	root = (struct dx_root *) bh->b_data;

	/* The 0th block becomes the root, move the dirents out */
	fde = &root->dotdot;
	de = (struct ext4_dir_entry_2 *)((char *)fde +
		ext4_rec_len_from_disk(fde->rec_len, blocksize));
	if ((char *) de >= (((char *) root) + blocksize)) {
		EXT4_ERROR_INODE(dir, "invalid rec_len for '..'");
		brelse(bh);
		return -EFSCORRUPTED;
	}
	len = ((char *) root) + (blocksize - csum_size) - (char *) de;

	/* Allocate new block for the 0th block's dirents */
	bh2 = ext4_append(handle, dir, &block);
	if (IS_ERR(bh2)) {
		brelse(bh);
		return PTR_ERR(bh2);
	}
	ext4_set_inode_flag(dir, EXT4_INODE_INDEX);
	data2 = bh2->b_data;

	memcpy(data2, de, len);
	de = (struct ext4_dir_entry_2 *) data2;
	top = data2 + len;
	while ((char *)(de2 = ext4_next_entry(de, blocksize)) < top) {
		if (ext4_check_dir_entry(dir, NULL, de, bh2, data2, len,
					 (data2 + (blocksize - csum_size) -
					  (char *) de))) {
			brelse(bh2);
			brelse(bh);
			return -EFSCORRUPTED;
		}
		de = de2;
	}
	de->rec_len = ext4_rec_len_to_disk(data2 + (blocksize - csum_size) -
					   (char *) de, blocksize);

	if (csum_size)
		ext4_initialize_dirent_tail(bh2, blocksize);

	/* Initialize the root; the dot dirents already exist */
	de = (struct ext4_dir_entry_2 *) (&root->dotdot);
	de->rec_len = ext4_rec_len_to_disk(blocksize - EXT4_DIR_REC_LEN(2),
					   blocksize);
	memset (&root->info, 0, sizeof(root->info));
	root->info.info_length = sizeof(root->info);
	root->info.hash_version = EXT4_SB(dir->i_sb)->s_def_hash_version;
	entries = root->entries;
	dx_set_block(entries, 1);
	dx_set_count(entries, 1);
	dx_set_limit(entries, dx_root_limit(dir, sizeof(root->info)));

	/* Initialize as for dx_probe */
	fname->hinfo.hash_version = root->info.hash_version;
	if (fname->hinfo.hash_version <= DX_HASH_TEA)
		fname->hinfo.hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
	fname->hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed;
	ext4fs_dirhash(dir, fname_name(fname), fname_len(fname), &fname->hinfo);

	memset(frames, 0, sizeof(frames));
	frame = frames;
	frame->entries = entries;
	frame->at = entries;
	frame->bh = bh;

	retval = ext4_handle_dirty_dx_node(handle, dir, frame->bh);
	if (retval)
		goto out_frames;
	retval = ext4_handle_dirty_dirblock(handle, dir, bh2);
	if (retval)
		goto out_frames;

	de = do_split(handle,dir, &bh2, frame, &fname->hinfo);
	if (IS_ERR(de)) {
		retval = PTR_ERR(de);
		goto out_frames;
	}

	retval = add_dirent_to_buf(handle, fname, dir, inode, de, bh2);
out_frames:
	/*
	 * Even if the block split failed, we have to properly write
	 * out all the changes we did so far. Otherwise we can end up
	 * with corrupted filesystem.
	 */
	if (retval)
		ext4_mark_inode_dirty(handle, dir);
	dx_release(frames);
	brelse(bh2);
	return retval;
}
```

Why it is not vulnerable: The directory entry iteration loop in make_indexed_dir is properly protected against invalid record lengths. Each entry is validated by ext4_check_dir_entry before the iteration pointer advances. ext4_check_dir_entry enforces that rec_len >= EXT4_DIR_REC_LEN(1) (minimum 12 bytes), rec_len % 4 == 0 (alignment), and next_offset <= size (no overrun past the block). The loop condition also independently bounds iteration by checking that ext4_next_entry's result pointer is below 'top', which is set to data2 + len. The initial bounds check validates that the first directory entry pointer does not exceed the block boundary. These layered checks ensure that no matter what rec_len values appear on disk, the iteration cannot read out-of-bounds or loop indefinitely.

Security invariants:
- Initial directory entry offset must not exceed block boundary, enforced by the check: if ((char *) de >= (((char *) root) + blocksize)) returning -EFSCORRUPTED
- Record length must be a multiple of 4 for proper alignment, enforced by ext4_check_dir_entry: rlen % 4 != 0 returns error
- Record length must be at least the minimum directory entry size, enforced by ext4_check_dir_entry: rlen < EXT4_DIR_REC_LEN(1) returns error
- Next entry offset must not exceed the valid block data size, enforced by ext4_check_dir_entry: next_offset > size returns error with message 'directory entry overrun'
- Loop iteration must terminate within the valid data region, enforced by the loop condition: (char *)(de2 = ext4_next_entry(de, blocksize)) < top where top = data2 + len
- Entry record length must be sufficient to contain the declared name, enforced by ext4_check_dir_entry: rlen < EXT4_DIR_REC_LEN(de->name_len) returns error

Invariant verification:
- Bounds validation of initial directory entry pointer before copying data: holds=true. Evidence: The check 'if ((char *) de >= (((char *) root) + blocksize)) { EXT4_ERROR_INODE(dir, "invalid rec_len for '..'"); brelse(bh); return -EFSCORRUPTED; }' ensures de does not point beyond the block, even if fde->rec_len contains corrupted values
- Record length alignment to 4-byte boundary during iteration: holds=true. Evidence: ext4_check_dir_entry checks 'rlen % 4 != 0' and returns an error with message 'rec_len % 4 != 0', preventing misaligned pointer arithmetic in ext4_next_entry
- Minimum record length enforcement prevents zero-length or undersized entries: holds=true. Evidence: ext4_check_dir_entry checks 'rlen < EXT4_DIR_REC_LEN(1)' returning error 'rec_len is smaller than minimal', ensuring ext4_next_entry always advances by at least 12 bytes
- Next entry offset does not exceed block boundary: holds=true. Evidence: ext4_check_dir_entry computes 'next_offset = ((char *) de - buf) + rlen' and checks 'next_offset > size' returning error 'directory entry overrun'. The size parameter passed is '(data2 + (blocksize - csum_size) - (char *) de)' representing remaining valid space
- Loop termination guaranteed by upper bound check on next pointer: holds=true. Evidence: The while loop condition '(char *)(de2 = ext4_next_entry(de, blocksize)) < top' ensures de2 is within valid range before ext4_check_dir_entry is called; if rec_len is large enough to push de2 beyond top, the loop terminates without processing invalid entries
- Journal write access acquired before modifying block data: holds=true. Evidence: ext4_journal_get_write_access(handle, bh) is called with error checking: 'if (retval) { ext4_std_error(dir->i_sb, retval); brelse(bh); return retval; }'
- Buffer resources released on all error paths: holds=true. Evidence: Every error return path calls brelse(bh2) and brelse(bh); the out_frames label ensures dx_release(frames) and brelse(bh2) are called, and all return paths lead through this cleanup

Residual risk notes: The validation in ext4_check_dir_entry covers the primary safety concerns for record length iteration. One minor consideration is that ext4_rec_len_from_disk on systems with PAGE_SIZE >= 65536 can produce values exceeding blocksize from certain 16-bit patterns (e.g., 0x3FFF yields 262140), but such values would cause ext4_next_entry to produce a de2 pointer far beyond 'top', causing immediate loop termination via the 'de2 < top' check before any dereference of the resulting entry occurs. The ext4_rec_len_to_disk function contains a BUG() assertion for len > blocksize, but the computed lengths in this code path are always bounded by (blocksize - csum_size) since de iterates from data2 toward data2 + len.

Yes — this code appears to contain a real vulnerability reachable from a malformed ext4 directory block.

## Observed code behavior

`make_indexed_dir()` converts a linear directory into an indexed one. It treats block 0 as a `dx_root`, then moves the existing directory entries that come after `..` into a newly allocated block:

```c
fde = &root->dotdot;
de = (struct ext4_dir_entry_2 *)((char *)fde +
	ext4_rec_len_from_disk(fde->rec_len, blocksize));
if ((char *) de >= (((char *) root) + blocksize)) {
	EXT4_ERROR_INODE(dir, "invalid rec_len for '..'");
	brelse(bh);
	return -EFSCORRUPTED;
}
len = ((char *) root) + (blocksize - csum_size) - (char *) de;
...
memcpy(data2, de, len);
```

Key points:

- `de` is derived directly from the on-disk `..` entry length (`fde->rec_len`).
- The only bound check is:

  ```c
  (char *)de >= (char *)root + blocksize
  ```

- But the copy length is computed relative to:

  ```c
  root + (blocksize - csum_size)
  ```

So when metadata checksums are enabled (`csum_size != 0`), the valid end of copyable dirent data is **before** `root + blocksize`.

## The bug

If a corrupted/malicious `fde->rec_len` places `de` in this range:

```c
root + (blocksize - csum_size) < de < root + blocksize
```

then:

```c
len = (root + (blocksize - csum_size)) - de;
```

becomes negative in pointer arithmetic terms, but `len` is an `unsigned`, so it underflows to a very large positive value.

That huge `len` is then used in:

```c
memcpy(data2, de, len);
```

This is a memory safety bug:
- out-of-bounds read from `de`
- out-of-bounds write into `data2` (`bh2->b_data`)

The later validation loop with `ext4_check_dir_entry()` does **not** prevent this, because it runs only **after** the `memcpy`.

## Why the existing checks are insufficient

The code checks only:

```c
if ((char *) de >= (((char *) root) + blocksize))
```

But the later logic uses `blocksize - csum_size` as the effective upper bound. So the guard is weaker than the subsequent assumption.

Concretely, for checksum-enabled filesystems, the code should ensure something like:

```c
(char *)de < (char *)root + (blocksize - csum_size)
```

before calculating `len` and copying.

## Attacker influence / exploitability

From the supplied call path, this function is reached during file creation:

`do_sys_openat2 → path_openat → ext4_create → ext4_add_nondir → ext4_add_entry → make_indexed_dir`

So this happens while adding an entry to an ext4 directory that is being converted to indexed form.

The attacker-controlled input is the on-disk directory block contents, specifically `root->dotdot.rec_len`.

Realistic preconditions:

- The attacker can provide or modify a malformed ext4 filesystem image, or otherwise cause ext4 to operate on corrupted directory metadata.
- The vulnerable directory is processed in a path that triggers indexing conversion, e.g. creating an entry in a directory that becomes indexed.

This is a common kernel threat model for filesystem parsers: mounting or accessing a crafted filesystem image should not lead to memory corruption.

## Impact

Likely impacts include:

- kernel heap/stack memory corruption via oversized `memcpy`
- kernel crash / denial of service
- potentially code execution in kernel context, depending on allocator/layout and exploitability

Because the copy destination is a block buffer and the length can become very large, this is more serious than a simple logic bug.

## Most precise classification

Best-fit classification:

- **Out-of-bounds read/write due to integer underflow in length calculation**
- CWE-style mapping:
  - **CWE-191**: Integer Underflow
  - leading to
  - **CWE-787**: Out-of-bounds Write
  - and **CWE-125**: Out-of-bounds Read

## Verdict

**Vulnerable.**

The root cause is that `de` is only checked against `root + blocksize`, but `len` is computed against `root + (blocksize - csum_size)`. On checksum-enabled ext4, a crafted `..` record length can place `de` inside the checksum tail region, causing unsigned underflow and a subsequent oversized `memcpy`.

The calculation and use of `len` after deriving `de` from `root->dotdot.rec_len`: `de = (struct ext4_dir_entry_2 *)((char *)fde + ext4_rec_len_from_disk(fde->rec_len, blocksize));` followed by `if ((char *) de >= (((char *) root) + blocksize)) ...` and then `len = ((char *) root) + (blocksize - csum_size) - (char *) de; ... memcpy(data2, de, len);`

The function validates `de` against `root + blocksize`, but later computes the copy length against the smaller boundary `root + (blocksize - csum_size)`. When metadata checksums are enabled, a crafted `fde->rec_len` can make `de` fall inside the checksum-tail area: `root + (blocksize - csum_size) < de < root + blocksize`. In that case, the subtraction used to compute `len` becomes negative, but `len` is an unsigned value, so it underflows to a very large positive length and is then passed to `memcpy` before any per-entry validation occurs.

Reachable during ordinary file creation in a directory that is being converted to indexed form. An attacker who can cause the kernel to operate on a malformed ext4 directory block can influence `root->dotdot.rec_len`, making `de` point into the checksum-tail region and triggering the oversized copy. This is plausibly exploitable for kernel memory corruption, though exact exploitability depends on runtime memory layout and surrounding hardening.

The kernel must process a malformed or attacker-controlled ext4 filesystem image or corrupted on-disk directory block.

The affected directory must reach the conversion path into indexed form (`make_indexed_dir`).

Metadata checksums must be enabled so that `csum_size != 0` and the effective data end is smaller than `root + blocksize`.

Out-of-bounds read from the source buffer `de`.

Out-of-bounds write into the destination directory block buffer `bh2->b_data` via oversized `memcpy`.

Potential kernel memory corruption and possible privilege escalation/code execution depending on exploit conditions.

The code intends that `de` point to the first real dirent after `..` within the valid dirent payload area.

The copied range is intended to stay within the block payload excluding an optional checksum tail.

Directory entries should be validated with `ext4_check_dir_entry()` before being trusted.

The first invariant is only partially checked: `de` is rejected if `de >= root + blocksize`, but not if `de >= root + (blocksize - csum_size)`.

The second invariant is violated because `len` is computed relative to `blocksize - csum_size` while the guard used `blocksize`.

The third invariant is not enforced before the dangerous operation: `memcpy(data2, de, len)` occurs before the loop that calls `ext4_check_dir_entry()`.

The supplied context does not show any earlier validation guaranteeing that `root->dotdot.rec_len` cannot land inside the checksum-tail region before `make_indexed_dir()` runs. Based on the code shown, the mismatch in bounds is sufficient to conclude a real memory-safety issue.

do_sys_openat2 -> path_openat -> ext4_create -> ext4_add_nondir -> ext4_add_entry -> make_indexed_dir


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

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

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

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

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

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

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

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

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

Repository: git.kernel.org/pub/scm/linux/kernel/git/stable/linux
Language: C
File: fs/notify/mark.c
Function: fsnotify_put_inode_ref

Call path: fsnotify_put_mark (fs/notify/mark.c) → fsnotify_drop_object (fs/notify/mark.c) → fsnotify_put_inode_ref (fs/notify/mark.c) → fsnotify_put_sb_watched_objects (fs/notify/mark.c) → generic_shutdown_super (fs/super.c)

### Primary Function

```c
static void fsnotify_put_sb_watched_objects(struct super_block *sb)
{
	atomic_long_t *watched_objects = fsnotify_sb_watched_objects(sb);

	/* the superblock can go away after this decrement */
	if (atomic_long_dec_and_test(watched_objects))
		wake_up_var(watched_objects);
}

static void fsnotify_get_sb_watched_objects(struct super_block *sb)
{
	atomic_long_inc(fsnotify_sb_watched_objects(sb));
}

static void fsnotify_get_inode_ref(struct inode *inode)
{
	ihold(inode);
	fsnotify_get_sb_watched_objects(inode->i_sb);
}

static void fsnotify_put_inode_ref(struct inode *inode)
{
	/* read ->i_sb before the inode can go away */
	struct super_block *sb = inode->i_sb;

	iput(inode);
	fsnotify_put_sb_watched_objects(sb);
}
```

### Cross-File Context

[fsnotify_put_sb_watched_objects — callee — fs/notify/mark.c:139-147]
```c
static void fsnotify_put_sb_watched_objects(struct super_block *sb)
{
	atomic_long_t *watched_objects = fsnotify_sb_watched_objects(sb);

	/* the superblock can go away after this decrement */
	if (atomic_long_dec_and_test(watched_objects))
		wake_up_var(watched_objects);
}
```

[fsnotify_get_sb_watched_objects — function — fs/notify/mark.c:134-137]
```c
static void fsnotify_get_sb_watched_objects(struct super_block *sb)
{
	atomic_long_inc(fsnotify_sb_watched_objects(sb));
}
```

[fsnotify_get_inode_ref — function — fs/notify/mark.c:145-150]
```c
static void fsnotify_get_inode_ref(struct inode *inode)
{
	ihold(inode);
	fsnotify_get_sb_watched_objects(inode->i_sb);
}
```

[fsnotify_drop_object — caller — fs/notify/mark.c:370-377]
```c
static void fsnotify_drop_object(unsigned int type, void *objp)
{
	if (!objp)
		return;
	/* Currently only inode references are passed to be dropped */
	if (WARN_ON_ONCE(type != FSNOTIFY_OBJ_TYPE_INODE))
		return;
	fsnotify_put_inode_ref(objp);
}
```

[fsnotify_sb_watched_objects — function — include/linux/fsnotify_backend.h:507-509]
```c
static inline atomic_long_t *fsnotify_sb_watched_objects(struct super_block *sb)
{
	return &fsnotify_sb_info(sb)->watched_objects[0];
}
```

[fsnotify_sb_info — struct — include/linux/fsnotify_backend.h:485-496]
```c
struct fsnotify_sb_info {
	struct fsnotify_mark_connector __rcu *sb_marks;
	atomic_long_t watched_objects[__FSNOTIFY_PRIO_NUM];
};
```

[__FSNOTIFY_PRIO_NUM — constant — include/linux/fsnotify_backend.h:187]
__FSNOTIFY_PRIO_NUM → 3  (include/linux/fsnotify_backend.h:187)

[fsnotify_group_prio — enum — include/linux/fsnotify_backend.h:183-188]
enum fsnotify_group_prio { FSNOTIFY_PRIO_NORMAL = 0, FSNOTIFY_PRIO_CONTENT, FSNOTIFY_PRIO_PRE_CONTENT, __FSNOTIFY_PRIO_NUM };

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: fsnotify_put_inode_ref receives an inode pointer and executes a linear sequence: (1) reads inode->i_sb into local variable sb before any cleanup, (2) calls iput(inode) to drop the inode reference which may trigger inode deallocation, (3) calls fsnotify_put_sb_watched_objects(sb) to decrement the superblock's watched_objects atomic counter. Inside fsnotify_put_sb_watched_objects, if the counter reaches zero, it calls wake_up_var on the counter to wake waiters that may be blocking on superblock cleanup. The caller fsnotify_drop_object validates the object type and forwards the inode to fsnotify_put_inode_ref.

Data flow: The inode pointer enters fsnotify_put_inode_ref as the sole parameter. The superblock pointer is extracted from inode->i_sb and stored in local variable sb before iput(inode) is called. iput(inode) drops the inode reference count and may eventually free the inode structure. The sb pointer is then passed to fsnotify_put_sb_watched_objects, which accesses sb to locate the watched_objects atomic counter, decrements it, and conditionally wakes waiters. The pairing function fsnotify_get_inode_ref acquires references in the opposite order (inode hold then sb counter increment), ensuring balanced reference counting.

Verdict: Not Vulnerable

Reviewed region:
[fs/notify/mark.c]
```c
static void fsnotify_put_sb_watched_objects(struct super_block *sb)
{
	atomic_long_t *watched_objects = fsnotify_sb_watched_objects(sb);

	/* the superblock can go away after this decrement */
	if (atomic_long_dec_and_test(watched_objects))
		wake_up_var(watched_objects);
}

static void fsnotify_get_sb_watched_objects(struct super_block *sb)
{
	atomic_long_inc(fsnotify_sb_watched_objects(sb));
}

static void fsnotify_get_inode_ref(struct inode *inode)
{
	ihold(inode);
	fsnotify_get_sb_watched_objects(inode->i_sb);
}

static void fsnotify_put_inode_ref(struct inode *inode)
{
	/* read ->i_sb before the inode can go away */
	struct super_block *sb = inode->i_sb;

	iput(inode);
	fsnotify_put_sb_watched_objects(sb);
}
```

Why it is not vulnerable: The code correctly captures the superblock pointer (sb = inode->i_sb) before calling iput(inode), as explicitly noted by the comment 'read ->i_sb before the inode can go away'. Since iput(inode) may trigger inode deallocation which could cascade to superblock deallocation, reading inode->i_sb after iput would be a use-after-free. The current ordering ensures sb remains valid for the subsequent fsnotify_put_sb_watched_objects(sb) call, which only accesses the watched_objects array within the superblock. The atomic counter in fsnotify_put_sb_watched_objects protects against premature superblock deallocation by coordinating with waiters that hold the superblock.

Security invariants:
- The superblock pointer must be captured from inode->i_sb before any operation that may invalidate the inode structure. Enforced by: struct super_block *sb = inode->i_sb; appearing as the first statement before iput(inode).
- The watched_objects atomic counter must prevent the superblock from being freed while fsnotify_put_sb_watched_objects is accessing it. Enforced by: atomic_long_dec_and_test(watched_objects) which is paired with wake_up_var to coordinate with superblock teardown code that waits for this counter to reach zero.
- Reference acquisition and release must be symmetric to prevent counter leaks or premature deallocation. Enforced by: fsnotify_get_inode_ref calling ihold(inode) and fsnotify_get_sb_watched_objects, with fsnotify_put_inode_ref calling iput(inode) and fsnotify_put_sb_watched_objects in the corresponding reverse order.
- The superblock pointer must remain valid for the duration of the fsnotify_put_sb_watched_objects call. Enforced by: the sb variable being a local copy captured before iput(inode), and the atomic counter mechanism ensuring the superblock is not freed while this function is executing.

Invariant verification:
- Superblock pointer captured before potentially destructive inode release: holds=true. Evidence: struct super_block *sb = inode->i_sb; is the first operation in fsnotify_put_inode_ref, executed before iput(inode)
- Atomic counter protects against premature superblock deallocation during watched_objects access: holds=true. Evidence: atomic_long_dec_and_test(watched_objects) in fsnotify_put_sb_watched_objects decrements the counter atomically and only signals wake_up_var if counter reaches zero, coordinating with superblock teardown waiters
- Balanced reference counting between get and put operations: holds=true. Evidence: fsnotify_get_inode_ref pairs ihold(inode) + fsnotify_get_sb_watched_objects with fsnotify_put_inode_ref's iput(inode) + fsnotify_put_sb_watched_objects, ensuring matched increment/decrement of the watched_objects counter
- No dereference of inode after iput may have freed it: holds=true. Evidence: After iput(inode), only the locally-captured sb pointer is used in fsnotify_put_sb_watched_objects(sb); no further access to the inode occurs

Residual risk notes: The correctness of this pattern depends on all call paths that acquire inode references via fsnotify_get_inode_ref releasing them via fsnotify_put_inode_ref. If any code path acquires the sb watched_objects counter without holding a corresponding inode reference, or drops the inode reference without calling fsnotify_put_sb_watched_objects, the reference counting invariants could be violated. The behavior of the superblock teardown code that waits on the watched_objects counter is not visible in the supplied context, but the atomic coordination mechanism appears sound.

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

Verdict: not a real vulnerability in the supplied code.

Why this looked suspicious
- `fsnotify_put_inode_ref()` reads `inode->i_sb`, then calls `iput(inode)`, which may drop the last inode reference and potentially start teardown paths.
- It then uses `sb` afterward in `fsnotify_put_sb_watched_objects(sb)`.
- Superficially, that resembles a possible use-after-free of `sb`.

Observed code behavior
- Acquisition path:
  ```c
  static void fsnotify_get_inode_ref(struct inode *inode)
  {
  	ihold(inode);
  	fsnotify_get_sb_watched_objects(inode->i_sb);
  }
  ```
  This takes:
  1. an inode reference via `ihold(inode)`, and
  2. a superblock “watched objects” reference via `atomic_long_inc(...)`.

- Release path:
  ```c
  static void fsnotify_put_inode_ref(struct inode *inode)
  {
  	/* read ->i_sb before the inode can go away */
  	struct super_block *sb = inode->i_sb;

  	iput(inode);
  	fsnotify_put_sb_watched_objects(sb);
  }
  ```
  This:
  1. snapshots `inode->i_sb` before `iput(inode)`,
  2. drops the inode ref,
  3. drops the superblock watched-object ref.

- The decrement helper says:
  ```c
  /* the superblock can go away after this decrement */
  if (atomic_long_dec_and_test(watched_objects))
  	wake_up_var(watched_objects);
  ```
  That comment is important evidence: the design explicitly treats the `watched_objects` count as what keeps the superblock from going away until after this decrement.

Why this is safe
1. The superblock usage is paired with an explicit lifetime counter.
   - `fsnotify_get_inode_ref()` increments `sb->watched_objects`.
   - `fsnotify_put_inode_ref()` decrements that same counter.
   - So `fsnotify_put_inode_ref()` is not using a bare `sb` pointer after `iput`; it is using an `sb` whose lifetime is extended by the outstanding watched-object count established earlier.

2. The code reads `inode->i_sb` before `iput(inode)`.
   - This avoids dereferencing the inode after `iput`, which could free the inode.
   - The comment explicitly documents that intent.

3. The code comments establish the lifetime invariant for `sb`.
   - “the superblock can go away after this decrement” means the decrement is the final operation allowed on that reference.
   - Therefore, using `sb` to reach `watched_objects` before and during the decrement is intended to be safe.

4. The call path is consistent with teardown synchronization.
   - You provided the path:
     `fsnotify_put_mark` → `fsnotify_drop_object` → `fsnotify_put_inode_ref` → `fsnotify_put_sb_watched_objects` → `generic_shutdown_super`
   - While the `generic_shutdown_super()` implementation is not shown, the naming and the wakeup on `watched_objects` reaching zero strongly indicate superblock shutdown waits for this count to drain.
   - The `wake_up_var(watched_objects)` on zero is typical waiter notification for exactly that kind of synchronization.

Attacker influence / exploitability
- An attacker may influence whether marks exist or whether an inode reference is dropped, but from the shown code they do not control:
  - the `type` passed to `fsnotify_drop_object()` in a way that bypasses the type check,
  - the pairing between `fsnotify_get_inode_ref()` and `fsnotify_put_inode_ref()`,
  - or the lifetime rule enforced by `watched_objects`.
- No path shown lets an attacker cause `sb` to be freed before `fsnotify_put_sb_watched_objects(sb)` executes, because the outstanding watched-object ref is exactly what prevents that.

Potential concern that is not supported here
- If there were an unmatched `fsnotify_put_inode_ref()` without a prior `fsnotify_get_inode_ref()`, the counter logic could break. But no such mismatch is shown.
- If other code freed the superblock without respecting `watched_objects`, that could be a bug elsewhere. Nothing in the supplied context suggests that.

Conclusion
- This code is implementing a deliberate two-object lifetime scheme:
  - inode lifetime via `ihold/iput`
  - superblock lifetime via `watched_objects`
- Reading `inode->i_sb` before `iput()` and only allowing the superblock to disappear after `fsnotify_put_sb_watched_objects()` makes the post-`iput` use of `sb` safe.
- Based on the supplied code and comments, this is not a real use-after-free or similar vulnerability.

No concrete exploit path is supported by the supplied code. Although sb is used after iput(inode), the code shows a separate superblock lifetime reference via watched_objects that is acquired in fsnotify_get_inode_ref() and released here.

An attacker would need sb to be freed before fsnotify_put_sb_watched_objects(sb) runs.

The supplied code shows that an outstanding watched_objects reference exists for the inode-mark lifetime, which is intended to prevent that condition.

A vulnerability would require a mismatched get/put or shutdown code ignoring watched_objects, neither of which is shown here.

No demonstrated security impact from the supplied code. The apparent post-iput use of sb is guarded by the superblock watched_objects reference scheme.

The code implements paired lifetime management for two objects. fsnotify_get_inode_ref() takes both an inode reference with ihold(inode) and a superblock watched_objects reference with atomic_long_inc(fsnotify_sb_watched_objects(inode->i_sb)). fsnotify_put_inode_ref() snapshots inode->i_sb before iput(inode) so it does not dereference inode after the inode may be freed, then releases the superblock watched_objects reference. The helper comment in fsnotify_put_sb_watched_objects() explicitly states that the superblock can go away only after this decrement, indicating that watched_objects is the lifetime guard for sb.

Every inode reference acquired for fsnotify is paired with a watched_objects increment on inode->i_sb.

inode->i_sb is read before iput(inode), so inode is not dereferenced after its refcount may reach zero.

The superblock is not expected to be freed until the corresponding watched_objects decrement completes.

wake_up_var(watched_objects) on the final decrement indicates synchronization with superblock shutdown/waiters.

Acquisition path explicitly takes both inode and superblock-associated references.

fsnotify_put_inode_ref(): struct super_block *sb = inode->i_sb; iput(inode); fsnotify_put_sb_watched_objects(sb);

Release path avoids dereferencing inode after iput and uses the previously acquired superblock-associated reference.

fsnotify_put_sb_watched_objects(): /* the superblock can go away after this decrement */ if (atomic_long_dec_and_test(watched_objects)) wake_up_var(watched_objects);

Code comments and behavior indicate the decrement is the final operation before sb may be torn down.

fsnotify_drop_object() only passes inode objects to fsnotify_put_inode_ref() after a type check.

The reviewed helper is not being invoked on unrelated object types in the shown path.

This assessment is limited to the supplied code and context.

If some other path called fsnotify_put_inode_ref() without a matching fsnotify_get_inode_ref(), or if superblock teardown ignored watched_objects, that could create a bug elsewhere, but such evidence is not present here.

The exact waiting logic in generic_shutdown_super() is not shown, though the wake_up_var() usage strongly suggests intended synchronization.


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/usb/gadget/configfs.c
Function: webusb_landingPage_store

Call path: sysfs_write (fs/sysfs/file.c) → configfs_write_file (fs/configfs/file.c) → webusb_landingPage_store (drivers/usb/gadget/configfs.c) → os_desc_qw_sign_store (drivers/usb/gadget/configfs.c)

### Primary Function

```c
static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
				     size_t len)
{
	struct gadget_info *gi = webusb_item_to_gadget_info(item);
	unsigned int bytes_to_strip = 0;
	int l = len;

	if (page[l - 1] == '\n') {
		--l;
		++bytes_to_strip;
	}

	if (l > sizeof(gi->landing_page)) {
		pr_err("webusb: landingPage URL too long\n");
		return -EINVAL;
	}

	// validation
	if (strncasecmp(page, "https://",  8) == 0)
		bytes_to_strip = 8;
	else if (strncasecmp(page, "http://", 7) == 0)
		bytes_to_strip = 7;
	else
		bytes_to_strip = 0;

	if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip) {
		pr_err("webusb: landingPage URL %d bytes too long for given URL scheme\n",
			l - U8_MAX + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH - bytes_to_strip);
		return -EINVAL;
	}

	mutex_lock(&gi->lock);
	// ensure 0 bytes are set, in case the new landing page is shorter then the old one.
	memcpy_and_pad(gi->landing_page, sizeof(gi->landing_page), page, l, 0);
	mutex_unlock(&gi->lock);

	return len;
}
```

### Cross-File Context

[webusb_item_to_gadget_info — function — drivers/usb/gadget/configfs.c:999]
```c
static inline struct gadget_info *webusb_item_to_gadget_info(
		struct config_item *item)
{
	return container_of(to_config_group(item),
			struct gadget_info, webusb_group);
}
```

[os_desc_item_to_gadget_info — function — drivers/usb/gadget/configfs.c:1113]
```c
static inline struct gadget_info *os_desc_item_to_gadget_info(
		struct config_item *item)
{
	return container_of(to_config_group(item),
			struct gadget_info, os_desc_group);
}
```

[gadget_info — struct — drivers/usb/gadget/configfs.c:38]
```c
struct gadget_info {
	struct config_group group;
	struct config_group functions_group;
	struct config_group configs_group;
	struct config_group strings_group;
	struct config_group os_desc_group;
	struct config_group webusb_group;

	struct mutex lock;
	struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
	struct list_head string_list;
	struct list_head available_func;

	struct usb_composite_driver composite;
	struct usb_composite_dev cdev;
	bool use_os_desc;
	char b_vendor_code;
	char qw_sign[OS_STRING_QW_SIGN_LEN];
	bool use_webusb;
	u16 bcd_webusb_version;
	u8 b_webusb_vendor_code;
	char landing_page[WEBUSB_URL_RAW_MAX_LENGTH];

	spinlock_t spinlock;
	bool unbind;
};
```

[OS_STRING_QW_SIGN_LEN — constant — include/linux/usb/composite.h]
OS_STRING_QW_SIGN_LEN → 16  (include/linux/usb/composite.h)

[WEBUSB_URL_RAW_MAX_LENGTH — constant — include/linux/usb/webusb.h]
WEBUSB_URL_RAW_MAX_LENGTH → 256  (include/linux/usb/webusb.h)

[WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH — constant — include/linux/usb/webusb.h]
WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH → 3  (include/linux/usb/webusb.h)

[U8_MAX — constant — include/linux/kernel.h]
U8_MAX → 0xFF  (include/linux/kernel.h)

Control flow: sysfs write -> configfs_write_file -> webusb_landingPage_store or os_desc_qw_sign_store -> access page[l-1] without checking len>0

Data flow: User-controlled sysfs write passes 'len' parameter directly to store function; len=0 causes page[l-1]=page[-1] OOB access

Verdict: Vulnerable
Vulnerable region:
[drivers/usb/gadget/configfs.c]
```c
unsigned int bytes_to_strip = 0;
	int l = len;

	if (page[l - 1] == '\n') {
		--l;
		++bytes_to_strip;
	}

	if (l > sizeof(gi->landing_page)) {
		pr_err("webusb: landingPage URL too long\n");
		return -EINVAL;
	}

	// validation
	if (strncasecmp(page, "https://",  8) == 0)
		bytes_to_strip = 8;
	else if (strncasecmp(page, "http://", 7) == 0)
		bytes_to_strip = 7;
	else
		bytes_to_strip = 0;
```
CWE: CWE-125
Severity: MEDIUM

Root cause: In webusb_landingPage_store(), the code accesses page[l - 1] to check for a trailing newline before validating that len > 0. When an empty string (len=0) is written, this produces page[-1], an out-of-bounds memory access. The same issue exists in os_desc_qw_sign_store().

Exploitability: An attacker with access to the USB gadget configfs sysfs interface can trigger the OOB read by writing an empty string to /sys/kernel/config/usb_gadget/.../os_desc/qw_sign or .../webusb/landingPage

Attack preconditions:
- USB gadget configfs is mounted and configured
- Attacker has write access to the qw_sign or landingPage sysfs attributes
- len parameter passed to store function is 0

Likely impact: An attacker with access to the USB gadget configfs sysfs interface can trigger the OOB read by writing an empty string to /sys/kernel/config/usb_gadget/.../os_desc/qw_sign or .../webusb/landingPage

**Verdict: likely a real bug, but limited-impact and only exploitable if a zero-length configfs/sysfs write reaches this store callback.**

## What the code does

`webusb_landingPage_store()` takes a user-supplied buffer `page` and length `len`, trims a trailing newline, validates the URL length/scheme, and copies it into:

```c
char landing_page[WEBUSB_URL_RAW_MAX_LENGTH]; // 256 bytes
```

The copy itself is bounded:

```c
memcpy_and_pad(gi->landing_page, sizeof(gi->landing_page), page, l, 0);
```

and there is an earlier size check:

```c
if (l > sizeof(gi->landing_page))
    return -EINVAL;
```

So this is **not** a straightforward overflow.

---

## The real issue

The function dereferences `page[l - 1]` **before checking that `len > 0`:**

```c
int l = len;

if (page[l - 1] == '\n') {
    --l;
    ++bytes_to_strip;
}
```

Since `len` comes from the write path, it is attacker-controlled. If `len == 0`, then:

- `l == 0`
- `page[l - 1]` becomes `page[-1]`

That is an **out-of-bounds read** one byte before the supplied buffer.

### Why this matters

This is kernel code. An OOB read in kernel space is a memory safety bug.

In this specific function, the byte read is only compared to `'\n'`; it is not copied back to user space, so this is **not an obvious info leak**. But it can still cause:

- a kernel fault if the address before `page` is invalid,
- or at least a sanitizer/KASAN-detectable invalid access,
- and thus a potential **local DoS**.

---

## Exploitability analysis

### Attacker control
The attacker controls:

- the contents of `page`
- the write length `len`

### Missing guard
There is **no** check like:

```c
if (len == 0)
    return -EINVAL;
```

before `page[l - 1]` is accessed.

### Practical precondition
The only real uncertainty is whether the surrounding sysfs/configfs write path can actually invoke this callback with `len == 0`.

From the supplied code, we cannot prove either:

- that zero-length writes are filtered out earlier, or
- that they definitely reach this function.

So the bug is **present in this function**, but **runtime reachability depends on behavior in the write helpers not shown here**.

---

## What is *not* vulnerable here

Other suspected issues are adequately guarded in the shown code:

### No destination overflow
```c
if (l > sizeof(gi->landing_page))
    return -EINVAL;
```

and later:

```c
memcpy_and_pad(..., sizeof(gi->landing_page), ..., l, 0);
```

So the write into `gi->landing_page[256]` is bounded.

### URL descriptor-length check
The logic:

```c
if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip)
```

also prevents oversized values for the encoded WebUSB URL descriptor.

### `int l = len`
The `size_t`→`int` narrowing could be suspicious in isolation, but for configfs/sysfs attribute writes this is typically small. From the supplied context, there is no concrete path showing attacker-controlled multi-gigabyte lengths reaching this function, so that is not a justified finding here.

---

## Classification

Most precise classification from the shown code:

- **CWE-125: Out-of-bounds Read**
- Root cause: **missing validation for zero-length input before indexing `page[len - 1]`**

---

## Bottom line

**Yes, this function contains a real memory-safety bug**: if called with `len == 0`, it performs an out-of-bounds read at `page[-1]`.

**Impact:** probably limited to local kernel crash/DoS rather than privilege escalation or disclosure.

**Residual uncertainty:** whether the sysfs/configfs write path shown only by name actually allows zero-length writes to reach this callback. If it does, this is exploitable; if it short-circuits them earlier, the bug exists but is not reachable through that path.

trailing-newline check before validating len

The function indexes the user-supplied buffer at page[len - 1] without first ensuring len > 0. When len == 0, this becomes page[-1], an out-of-bounds read. The later length checks and bounded copy do not guard this earlier access.

A local attacker controlling the configfs/sysfs write contents and length can trigger the invalid read if a zero-length write reaches this callback. The observed access is a 1-byte read before the buffer, used only for newline comparison, so realistic impact is kernel fault/DoS rather than data exfiltration. Reachability depends on whether the surrounding write path permits zero-length writes to this store callback.

The attacker can write to the relevant configfs attribute backing webusb_landingPage_store.

A zero-length write must be accepted by the sysfs/configfs path and dispatched to this callback.

The kernel must execute this function with len == 0; no earlier guard for empty writes is shown in the supplied context.

Kernel out-of-bounds read of 1 byte before the provided buffer.

Potential local denial of service if the invalid access faults or is caught by sanitizers.

No concrete evidence in the shown code of a kernel heap/stack overwrite or direct information leak.

Aside from the zero-length case, the destination write is bounded: l is checked against sizeof(gi->landing_page), and memcpy_and_pad uses the destination size explicitly. The WebUSB descriptor-size check also constrains valid URL length. Therefore, the shown bug is not a buffer overflow into gi->landing_page.

gi->landing_page is a fixed 256-byte array (WEBUSB_URL_RAW_MAX_LENGTH).

Writes into gi->landing_page are bounded by both an explicit size check and memcpy_and_pad destination sizing.

The WebUSB URL descriptor length is constrained by U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + scheme-prefix stripping.

The store operation is serialized with gi->lock during the state update.

Verified by `if (l > sizeof(gi->landing_page)) return -EINVAL;` followed by `memcpy_and_pad(gi->landing_page, sizeof(gi->landing_page), page, l, 0);`.

Verified by the `if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip)` check after determining scheme stripping.

No `len == 0` or `l <= 0` guard exists before `page[l - 1]`.

The supplied context does not include fs/sysfs/file.c or fs/configfs/file.c implementations, so zero-length write reachability cannot be proven from the excerpt alone.

The `size_t` to `int` narrowing (`int l = len`) is observable, but no concrete oversized write path is shown here; the demonstrated issue is the unchecked zero-length case.

If higher layers reject zero-length writes, the bug remains in-function but may be unreachable through the stated call path.

[{'id': 'CWE-125', 'name': 'Out-of-bounds Read'}]


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/jffs2/compr_rtime.c
Function: jffs2_rtime_decompress

Call path: jffs2_rtime_decompress (fs/jffs2/compr_rtime.c) → jffs2_decompress (fs/jffs2/compr.c) → jffs2_compressors_init (fs/jffs2/compr.c)

### Primary Function

```c
static int jffs2_rtime_decompress(unsigned char *data_in,
				  unsigned char *cpage_out,
				  uint32_t srclen, uint32_t destlen)
{
	unsigned short positions[256];
	int outpos = 0;
	int pos=0;

	memset(positions,0,sizeof(positions));

	while (outpos<destlen) {
		unsigned char value;
		int backoffs;
		int repeat;

		value = data_in[pos++];
		cpage_out[outpos++] = value; /* first the verbatim copied byte */
		repeat = data_in[pos++];
		backoffs = positions[value];

		positions[value]=outpos;
		if (repeat) {
			if (backoffs + repeat >= outpos) {
				while(repeat) {
					cpage_out[outpos++] = cpage_out[backoffs++];
					repeat--;
				}
			} else {
				memcpy(&cpage_out[outpos],&cpage_out[backoffs],repeat);
				outpos+=repeat;
			}
		}
	}
	return 0;
}
```

### Cross-File Context

[JFFS2_RTIME_PRIORITY — constant — fs/jffs2/compr.h:22]
JFFS2_RTIME_PRIORITY → 50  (fs/jffs2/compr.h:22)

[jffs2_compressor — struct — fs/jffs2/compr.h:35-54]
```c
struct jffs2_compressor {
	struct list_head list;
	int priority;
	char *name;
	char compr;
	int (*compress)(unsigned char *data_in, unsigned char *cpage_out,
			uint32_t *srclen, uint32_t *destlen);
	int (*decompress)(unsigned char *cdata_in, unsigned char *data_out,
			  uint32_t cdatalen, uint32_t datalen);
	int usecount;
	int disabled;
	unsigned char *compr_buf;
	uint32_t compr_buf_size;
	uint32_t stat_compr_orig_size;
	uint32_t stat_compr_new_size;
	uint32_t stat_compr_blocks;
	uint32_t stat_decompr_blocks;
};
```

[jffs2_rtime_comp — constant — fs/jffs2/compr_rtime.c:101-113]
jffs2_rtime_comp → { .priority = JFFS2_RTIME_PRIORITY, .name = "rtime", .compr = JFFS2_COMPR_RTIME, .compress = &jffs2_rtime_compress, .decompress = &jffs2_rtime_decompress, #ifdef JFFS2_RTIME_DISABLED .disabled = 1, #else .disabled = 0, #endif }  (fs/jffs2/compr_rtime.c:101-113)

Control flow: jffs2_rtime_decompress is called from jffs2_decompress with attacker-controlled srclen/destlen and data_in buffer. The function iterates through data_in reading value/repeat pairs, copying data to cpage_out. Without the bounds fix, large repeat values can cause outpos to exceed destlen.

Data flow: data_in (attacker-controlled compressed data), cpage_out (destination buffer), srclen (compressed data length), destlen (destination buffer size). The repeat field from data_in directly controls how many bytes are written to cpage_out.

Verdict: Vulnerable
Vulnerable region:
[fs/jffs2/compr_rtime.c]
```c
uint32_t srclen, uint32_t destlen)
{
	unsigned short positions[256];
	int outpos = 0;
	int pos=0;

	memset(positions,0,sizeof(positions));

	while (outpos<destlen) {
		unsigned char value;
		int backoffs;
		int repeat;

		value = data_in[pos++];
		cpage_out[outpos++] = value; /* first the verbatim copied byte */
		repeat = data_in[pos++];
		backoffs = positions[value];

		positions[value]=outpos;
		if (repeat) {
			if (backoffs + repeat >= outpos) {
```
CWE: CWE-787
Severity: HIGH

Root cause: In jffs2_rtime_decompress(), the bounds check only verifies that backoffs+repeat does not exceed outpos in the first branch, but does not check if outpos+repeat exceeds destlen before entering the copy loops. When processing corrupted compressed data with a large repeat value, outpos can be incremented beyond destlen, causing out-of-bounds writes via the while loop or memcpy.

Exploitability: Attacker needs ability to store or provide a crafted JFFS2 filesystem image with corrupted rtime compression data. The repeat value from corrupted data can cause outpos to exceed destlen.

Attack preconditions:
- Attacker can store or provide access to a crafted JFFS2 filesystem image
- Target system must mount or process the malicious JFFS2 image
- Compressed data must have repeat > 0 and backoffs+repeat < outpos to trigger memcpy path
- No additional privileges required beyond filesystem access

Likely impact: Attacker needs ability to store or provide a crafted JFFS2 filesystem image with corrupted rtime compression data. The repeat value from corrupted data can cause outpos to exceed destlen.

Yes. This function appears to contain a real memory-safety vulnerability.

## Observed code facts

```c
static int jffs2_rtime_decompress(unsigned char *data_in,
				  unsigned char *cpage_out,
				  uint32_t srclen, uint32_t destlen)
{
	unsigned short positions[256];
	int outpos = 0;
	int pos=0;

	memset(positions,0,sizeof(positions));

	while (outpos<destlen) {
		unsigned char value;
		int backoffs;
		int repeat;

		value = data_in[pos++];
		cpage_out[outpos++] = value;
		repeat = data_in[pos++];
		backoffs = positions[value];

		positions[value]=outpos;
		if (repeat) {
			if (backoffs + repeat >= outpos) {
				while(repeat) {
					cpage_out[outpos++] = cpage_out[backoffs++];
					repeat--;
				}
			} else {
				memcpy(&cpage_out[outpos],&cpage_out[backoffs],repeat);
				outpos+=repeat;
			}
		}
	}
	return 0;
}
```

### What the function does
- Reads compressed input from `data_in`
- Writes decompressed bytes to `cpage_out`
- Receives both:
  - `srclen`: compressed input length
  - `destlen`: expected output length

## Vulnerable behavior

### 1. No bounds check on `data_in` against `srclen`
The function reads:
- `data_in[pos++]` for `value`
- `data_in[pos++]` for `repeat`

But it never checks whether `pos < srclen` before either read.

So if the compressed stream is malformed or truncated, the function will read past the end of `data_in`.

This is especially significant because `srclen` is provided as an argument and then completely ignored.

### 2. No bounds check preventing writes past `destlen`
The loop condition is only:

```c
while (outpos < destlen)
```

But inside one iteration it may write:
- 1 byte for `value`
- plus `repeat` more bytes

There is no check that `1 + repeat` fits in the remaining output space.

For example, if `outpos == destlen - 1`, the function still:
- writes the literal byte, making `outpos == destlen`
- then, if `repeat != 0`, continues writing more bytes past the end of `cpage_out`

Both branches have this problem:

```c
while(repeat) {
	cpage_out[outpos++] = cpage_out[backoffs++];
	repeat--;
}
```

and

```c
memcpy(&cpage_out[outpos], &cpage_out[backoffs], repeat);
outpos += repeat;
```

So malformed input can cause an out-of-bounds write.

## Data-flow and attacker influence

The attacker-controlled data here is the compressed JFFS2 block contents in `data_in`.

The function directly uses attacker-derived bytes as:
- the next literal output byte (`value`)
- the repeat count (`repeat`)

`repeat` controls how many extra output bytes are written, with no validation against remaining output capacity.

Since this is a filesystem decompressor in the kernel, a malicious filesystem image or flash contents could realistically supply crafted compressed data that reaches this function during mount or file read.

## Are there any guards elsewhere?

From the supplied context, the decompressor interface is:

```c
int (*decompress)(unsigned char *cdata_in, unsigned char *data_out,
		  uint32_t cdatalen, uint32_t datalen);
```

So the caller passes both compressed and decompressed lengths. That is the intended protection boundary.

But in this specific function:
- `srclen`/`cdatalen` is ignored entirely
- `destlen` is only used as a loop-entry condition, not as a per-write bound

Thus, even if the caller passes correct lengths, this function does not enforce them safely.

## Exploitability

### Out-of-bounds read
A crafted short/truncated compressed stream can make `pos` exceed `srclen`, causing reads beyond `data_in`.

Impact:
- kernel memory disclosure is possible in principle, depending on surrounding memory use
- more commonly, fault/crash

### Out-of-bounds write
A crafted `repeat` can make `outpos` exceed `destlen`, writing beyond `cpage_out`.

Impact:
- kernel heap or page-buffer corruption
- likely crash
- possibly privilege escalation depending on allocator/layout and reachability

Because this is kernel-space decompression of attacker-influenced filesystem data, the out-of-bounds write is the more serious issue.

## Important nuance about `backoffs`
`backoffs = positions[value]` comes from the internal `positions` table, which is initialized to zero and updated with prior output positions. So the source offset is not directly attacker-controlled as an arbitrary index.

That means the main problem is not an arbitrary source pointer. The core issue is that the function allows the copy length (`repeat`) to exceed the remaining output buffer, causing writes past the destination.

## Verdict

This is a real vulnerability.

### Root cause
Improper validation of compressed input and output bounds:
- `srclen` is ignored, allowing out-of-bounds reads from `data_in`
- `repeat` is not checked against remaining `destlen`, allowing out-of-bounds writes to `cpage_out`

### Likely classification
- **CWE-787: Out-of-bounds Write**
- also **CWE-125: Out-of-bounds Read**

The out-of-bounds write is the strongest primary classification.

## Short conclusion
`jffs2_rtime_decompress()` is unsafe against malformed compressed data. It trusts the encoded stream too much and fails to enforce either input-size or output-size limits, making kernel memory corruption possible from a crafted JFFS2 image.

The decompressor does not enforce either supplied length parameter safely. It ignores srclen entirely when consuming compressed input bytes from data_in, and it uses destlen only as a loop-entry condition rather than checking that each iteration's writes (1 literal byte plus repeat copied bytes) fit within cpage_out. As a result, malformed compressed input can drive both reads past data_in and writes past cpage_out.

Attacker-controlled compressed JFFS2 data can directly influence both the literal byte and the repeat count. A crafted stream can cause pos to advance beyond srclen, producing out-of-bounds reads, and can set repeat so that outpos exceeds destlen, producing kernel-memory corruption. Because this is a filesystem decompressor operating in kernel context, a malicious filesystem image or flash contents is a realistic trigger source.

An attacker must be able to supply or modify JFFS2 compressed data that is processed by the rtime decompressor.

The crafted data must reach jffs2_rtime_decompress through normal filesystem parsing/decompression.

The rtime compressor/decompressor must be enabled and selected for the target data.

Out-of-bounds write to the decompression output buffer in kernel memory

Out-of-bounds read from the compressed input buffer

Likely kernel crash or memory corruption

Potential for more serious exploitation depending on surrounding allocator/layout and reachability

A decompressor must not read beyond the provided compressed input length (srclen/cdatalen).

A decompressor must not write beyond the provided destination/output length (destlen/datalen).

Copy lengths derived from untrusted compressed data must be checked against remaining output capacity before copying.

srclen is passed to the function but never referenced.

The loop condition only checks outpos < destlen at iteration start.

After writing the first literal byte, the function may still copy repeat additional bytes without verifying that outpos + repeat <= destlen.

Both the bytewise copy loop and memcpy path can write past cpage_out.

The source offset backoffs comes from the internal positions table rather than an arbitrary attacker-supplied offset, which limits source-index control but does not prevent the out-of-bounds destination write.

The exact exploit impact depends on buffer placement and surrounding kernel memory, which is not shown in the supplied context.

[{'id': 'CWE-787', 'name': 'Out-of-bounds Write'}, {'id': 'CWE-125', 'name': 'Out-of-bounds Read'}]


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/pci/endpoint/functions/pci-epf-test.c
Function: pci_epf_test_set_bar

Call path: pci_epf_test_epc_init (drivers/pci/endpoint/functions/pci-epf-test.c) → pci_epf_test_set_bar (drivers/pci/endpoint/functions/pci-epf-test.c) → pci_epc_set_bar (drivers/pci/endpoint/functions/pci-epf-test.c) → pci_epf_free_space (linux/pci-epf.h) → pci_epf_test_alloc_space (drivers/pci/endpoint/functions/pci-epf-test.c) → pci_epf_test_free_space (drivers/pci/endpoint/functions/pci-epf-test.c)

### Primary Function

```c
static int pci_epf_test_set_bar(struct pci_epf *epf)
{
	int bar, ret;
	struct pci_epc *epc = epf->epc;
	struct device *dev = &epf->dev;
	struct pci_epf_test *epf_test = epf_get_drvdata(epf);
	enum pci_barno test_reg_bar = epf_test->test_reg_bar;

	for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
		if (!epf_test->reg[bar])
			continue;

		ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no,
				      &epf->bar[bar]);
		if (ret) {
			pci_epf_free_space(epf, epf_test->reg[bar], bar,
					   PRIMARY_INTERFACE);
			epf_test->reg[bar] = NULL;
			dev_err(dev, "Failed to set BAR%d\n", bar);
			if (bar == test_reg_bar)
				return ret;
		}
	}

	return 0;
}
```

### Cross-File Context

[pci_epf_test_free_space — helper — drivers/pci/endpoint/functions/pci-epf-test.c:921-934]
```c
static void pci_epf_test_free_space(struct pci_epf *epf)
{
	struct pci_epf_test *epf_test = epf_get_drvdata(epf);
	int bar;

	for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
		if (!epf_test->reg[bar])
			continue;

		pci_epf_free_space(epf, epf_test->reg[bar], bar,
				   PRIMARY_INTERFACE);
		epf_test->reg[bar] = NULL;
	}
}
```

[pci_epf_test — struct — drivers/pci/endpoint/functions/pci-epf-test.c:53-69]
```c
struct pci_epf_test {
	void			*reg[PCI_STD_NUM_BARS];
	struct pci_epf		*epf;
	enum pci_barno		test_reg_bar;
	size_t			msix_table_offset;
	struct delayed_work	cmd_handler;
	struct dma_chan		*dma_chan_tx;
	struct dma_chan		*dma_chan_rx;
	struct dma_chan		*transfer_chan;
	dma_cookie_t		transfer_cookie;
	enum dma_status		transfer_status;
	struct completion	transfer_complete;
	bool			dma_supported;
	bool			dma_private;
	const struct pci_epc_features *epc_features;
};
```

[PCI_STD_NUM_BARS — constant — include/linux/pci_regs.h]
PCI_STD_NUM_BARS → 6  (include/linux/pci_regs.h)

[PRIMARY_INTERFACE — constant — include/linux/pci-epf.h]
PRIMARY_INTERFACE → 0  (include/linux/pci-epf.h)

[pci_epf_free_space — callee — include/linux/pci-epf.h]
```c
void pci_epf_free_space(struct pci_epf *epf, void *addr, enum pci_barno bar,
			     u8 interface);
```

[pci_epf_alloc_space — function — include/linux/pci-epf.h]
```c
void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar,
				  const struct pci_epc_features *features, u8 interface);
```

Control flow: The function iterates through PCI bars (0 to PCI_STD_NUM_BARS-1, which is 6). For each bar, it first checks if epf_test->reg[bar] is NULL and skips processing if so. It then calls pci_epc_set_bar() to configure the BAR. On success, the loop continues to the next bar. On error (ret != 0), the error path executes: pci_epf_free_space() is called to deallocate the memory region, then epf_test->reg[bar] is immediately set to NULL, an error is logged, and if bar equals test_reg_bar, the function returns the error code immediately. Otherwise, the loop continues to the next bar. The function returns 0 if all bars are processed successfully.

Data flow: The input pointer epf_test->reg[bar] (from the pci_epf_test struct array of 6 void pointers) holds the address of allocated memory for each BAR. When pci_epc_set_bar() fails, epf_test->reg[bar] is passed to pci_epf_free_space() for deallocation. Immediately after the free call, epf_test->reg[bar] is reassigned to NULL. The loop guard `if (!epf_test->reg[bar]) continue;` ensures that any nulled entries are skipped in subsequent iterations, preventing access to the now-freed memory through a dangling pointer.

Verdict: Not Vulnerable

Reviewed region:
[drivers/pci/endpoint/functions/pci-epf-test.c]
```c
static int pci_epf_test_set_bar(struct pci_epf *epf)
{
	int bar, ret;
	struct pci_epc *epc = epf->epc;
	struct device *dev = &epf->dev;
	struct pci_epf_test *epf_test = epf_get_drvdata(epf);
	enum pci_barno test_reg_bar = epf_test->test_reg_bar;

	for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
		if (!epf_test->reg[bar])
			continue;

		ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no,
				      &epf->bar[bar]);
		if (ret) {
			pci_epf_free_space(epf, epf_test->reg[bar], bar,
					   PRIMARY_INTERFACE);
			epf_test->reg[bar] = NULL;
			dev_err(dev, "Failed to set BAR%d\n", bar);
			if (bar == test_reg_bar)
				return ret;
		}
	}

	return 0;
}
```

Why it is not vulnerable: In the error handling path of pci_epf_test_set_bar, when pci_epc_set_bar() returns an error, the code calls pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE) to deallocate the memory, and immediately on the next line executes epf_test->reg[bar] = NULL to nullify the pointer. This ensures that no dangling pointer remains accessible after deallocation. Additionally, the loop guard `if (!epf_test->reg[bar]) continue;` at the top of each iteration prevents any subsequent processing of bars whose pointers have been nulled. The same pattern is confirmed in the helper function pci_epf_test_free_space, which also nulls each pointer immediately after freeing.

Security invariants:
- After calling pci_epf_free_space() to deallocate epf_test->reg[bar], the pointer must be immediately set to NULL. This is enforced by the statement `epf_test->reg[bar] = NULL;` appearing directly after `pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE);` in the error handling block.
- The array index bar must remain within bounds [0, PCI_STD_NUM_BARS). This is enforced by the loop condition `for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)` where PCI_STD_NUM_BARS is defined as 6.
- Freed/nulled pointers must not be dereferenced in subsequent iterations. This is enforced by the guard `if (!epf_test->reg[bar]) continue;` at the start of each loop iteration.
- The pci_epf_test struct must have reg array sized to PCI_STD_NUM_BARS elements. This is verified by the struct definition `void *reg[PCI_STD_NUM_BARS];`.

Invariant verification:
- Pointer nullification immediately after memory deallocation in error path: holds=true. Evidence: In the error handling block: `pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE);` is followed immediately by `epf_test->reg[bar] = NULL;` with no intervening code that could access the now-invalid pointer.
- Array bounds safety for reg pointer access: holds=true. Evidence: The loop iterates with `bar < PCI_STD_NUM_BARS` (which equals 6), and the struct defines `void *reg[PCI_STD_NUM_BARS]`, ensuring all accesses are within bounds.
- Null pointer guard prevents use-after-free in loop: holds=true. Evidence: The statement `if (!epf_test->reg[bar]) continue;` at the top of each iteration ensures that once a pointer is nulled (either initially or after a failed BAR setup), it will not be accessed for pci_epc_set_bar() or any other operation in subsequent iterations.
- Helper function pci_epf_test_free_space also nulls pointers after free: holds=true. Evidence: The helper function iterates all bars and for each valid entry, calls `pci_epf_free_space()` followed by `epf_test->reg[bar] = NULL;`, maintaining the same invariant as the error path in the main function.

Residual risk notes: The function only handles the error case for a single BAR at a time. If pci_epc_set_bar() fails for a bar that is not test_reg_bar, the function continues processing remaining bars. If a later bar also fails, the function continues rather than unwinding previously set bars. However, this is a resource management design choice rather than a pointer nullification issue. The pci_epc_set_bar() and pci_epf_free_space() are declared but their implementations are not in the supplied context, so we cannot verify that pci_epc_set_bar() cannot corrupt the reg array or that pci_epf_free_space() properly handles the provided parameters. The invariant verification assumes these callees behave correctly and do not access epf_test->reg[bar] after it is passed to them for deallocation.

**Verdict: no real vulnerability shown in the supplied code**

### Observed behavior

`pci_epf_test_set_bar()` iterates over all 6 standard PCI BARs:

```c
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
	if (!epf_test->reg[bar])
		continue;

	ret = pci_epc_set_bar(..., &epf->bar[bar]);
	if (ret) {
		pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE);
		epf_test->reg[bar] = NULL;
		...
		if (bar == test_reg_bar)
			return ret;
	}
}
```

For each BAR that has allocated backing memory in `epf_test->reg[bar]`:

- it tries to register/configure the BAR via `pci_epc_set_bar()`
- if that fails:
  - it frees the allocated space for that BAR
  - sets `epf_test->reg[bar] = NULL`
  - logs an error
  - returns an error **only** if the failed BAR is `test_reg_bar`

### Security-relevant data flow

Relevant state:
- `epf_test->reg[bar]` tracks allocated memory per BAR.
- On failure, that pointer is immediately freed and nulled.
- The cleanup helper `pci_epf_test_free_space()` later does:

```c
if (!epf_test->reg[bar])
	continue;
pci_epf_free_space(...);
epf_test->reg[bar] = NULL;
```

So the same pointer will **not** be freed twice through normal cleanup, because failed BARs are nulled before later cleanup runs.

### Why this is not a double-free / UAF

A likely concern here is:

1. `pci_epf_test_set_bar()` frees `epf_test->reg[bar]` on error
2. later `pci_epf_test_free_space()` might free it again

But the code explicitly prevents that by doing:

```c
epf_test->reg[bar] = NULL;
```

and the cleanup helper skips NULL entries.

There is also no direct use of `epf_test->reg[bar]` after it is freed in this function.

### About partial failure handling

Another possible concern is that the function returns success even if some non-`test_reg_bar` BAR setup failed. From the code shown, that is **policy/logic**, not a security flaw by itself.

What the code actually does:

- `test_reg_bar` is treated as mandatory
- other BARs appear optional
- if an optional BAR setup fails, its memory is cleaned up and the function continues

That may affect functionality, but the supplied code does not show memory corruption, privilege escalation, or unsafe exposure caused by this behavior.

### Residual uncertainty

From the supplied context alone, we cannot fully verify:
- whether `pci_epc_set_bar()` has side effects that would require additional rollback of `epf->bar[bar]`
- whether there are concurrency assumptions elsewhere around `epf_test->reg[]`

However, based strictly on the shown code:
- allocation/free tracking is consistent
- cleanup is idempotent due to nulling pointers
- no concrete exploitable memory-safety bug is evident

## Conclusion

**No concrete vulnerability is demonstrated in the provided code.**
The failure path correctly frees BAR-specific space and nulls the pointer, which prevents double-free in later cleanup. The selective return behavior for `test_reg_bar` appears intentional rather than security-relevant.

No concrete exploitability is shown from the supplied code. The main plausible concern—freeing BAR memory on setup failure and later freeing it again—is prevented by clearing the pointer immediately after free.

None established from the provided code. A caller could trigger BAR setup failures, but the shown failure handling does not itself expose a memory-safety or privilege boundary violation.

At most, non-mandatory BARs may fail to be configured and the function may still return success unless the failed BAR is test_reg_bar. This is a functional/initialization policy issue in the shown code, not a demonstrated security impact.

For each BAR with allocated space, pci_epc_set_bar() is attempted. If it fails, pci_epf_free_space() is called for that BAR and epf_test->reg[bar] is set to NULL immediately. The cleanup helper pci_epf_test_free_space() later checks for NULL before freeing, so the same BAR buffer is not freed twice through the shown paths. There is also no direct dereference or use of epf_test->reg[bar] after it is freed in pci_epf_test_set_bar().

epf_test->reg[bar] acts as ownership state for allocated BAR backing memory.

After a failed pci_epc_set_bar(), the corresponding BAR memory is freed and ownership is cleared via epf_test->reg[bar] = NULL.

pci_epf_test_free_space() frees only non-NULL epf_test->reg[bar] entries.

The primary function frees on failure and nulls the pointer in the same block. The provided helper pci_epf_test_free_space() loops over all bars, skips NULL entries, and nulls entries after freeing. Given these two code snippets, the free/null discipline is consistent and prevents double-free across the shown call paths.

The supplied context does not show pci_epc_set_bar() internals, so any hidden side effects there cannot be evaluated.

The code returns success when non-test_reg_bar BAR setup fails; this may leave partial initialization state, but no concrete security consequence is demonstrated from the provided snippets.

No concurrency context is shown, so races involving epf_test->reg[] cannot be assessed here.


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

Repository: github.com/tensorflow/tensorflow
Language: C++
File: tensorflow/core/kernels/inplace_ops.cc
Function: tensorflow::ParallelConcatUpdate<tensorflow::CPUDevice>::Compute

Call path: _ParallelConcatUpdate (tensorflow/core/ops/array_ops.cc) → ParallelConcatUpdate::Compute (tensorflow/core/kernels/inplace_ops.cc) → tensorflow::functor::DoParallelConcat (tensorflow/core/kernels/inplace_ops.cc) → tensorflow::functor::DoParallelConcatUpdate (tensorflow/core/kernels/inplace_ops.cc)

### Primary Function

```c++
void Compute(OpKernelContext* ctx) override {
    auto value = ctx->input(0);
    auto update = ctx->input(1);

    OP_REQUIRES(
        ctx, value.dims() == update.dims(),
        errors::InvalidArgument("value and update shape doesn't match: ",
                                value.shape().DebugString(), " vs. ",
                                update.shape().DebugString()));
    for (int i = 1; i < value.dims(); ++i) {
      OP_REQUIRES(
          ctx, value.dim_size(i) == update.dim_size(i),
          errors::InvalidArgument("value and update shape doesn't match ",
                                  value.shape().DebugString(), " vs. ",
                                  update.shape().DebugString()));
    }
    OP_REQUIRES(ctx, 1 == update.dim_size(0),
                errors::InvalidArgument("update shape doesn't match: ",
                                        update.shape().DebugString()));

    Tensor output = value;  // This creates an alias intentionally.
    const auto& d = ctx->eigen_device<Device>();
    OP_REQUIRES_OK(
        ctx, ::tensorflow::functor::DoParallelConcat(d, update, loc_, &output));
    ctx->set_output(0, output);
  }
```

### Cross-File Context

[tensorflow::ParallelConcatUpdate — class — tensorflow/core/kernels/inplace_ops.cc:65-110]
template <typename Device> class ParallelConcatUpdate : public OpKernel { public: explicit ParallelConcatUpdate(OpKernelConstruction* ctx) : OpKernel(ctx) { OP_REQUIRES_OK(ctx, ctx->GetAttr("loc", &loc_)); } void Compute(OpKernelContext* ctx) override; private: int32 loc_; };

[tensorflow::functor::DoParallelConcat — callee — tensorflow/core/kernels/inplace_ops.cc:43-59]
```c++
template <>
Status DoParallelConcat(const CPUDevice& d, const Tensor& value, int32_t loc,
                        Tensor* output) {
  CHECK_EQ(value.dtype(), output->dtype());
  switch (value.dtype()) {
#define CASE(type)                  \
  case DataTypeToEnum<type>::value: \
    return DoParallelConcatUpdate<CPUDevice, type>(d, value, loc, output);
    TF_CALL_POD_TYPES(CASE);
    TF_CALL_tstring(CASE);
    TF_CALL_variant(CASE);
#undef CASE
    default:
      return errors::InvalidArgument("Unsupported data type: ",
                                     DataTypeString(value.dtype()));
  }
}
```

[tensorflow::functor::DoParallelConcatUpdate — callee — tensorflow/core/kernels/inplace_ops.cc:32-41]
```c++
template <typename Device, typename T>
Status DoParallelConcatUpdate(const Device& d, const Tensor& value, int32_t loc,
                              Tensor* output) {
  auto Tvalue = value.shaped<T, 2>({1, value.NumElements()});
  auto Toutput = output->flat_outer_dims<T>();
  auto nrows = Toutput.dimension(0);
  auto r = (loc % nrows + nrows) % nrows;  // Guard index range.
  Toutput.template chip<0>(r).device(d) = Tvalue.template chip<0>(0);
  return Status::OK();
}
```

[tensorflow::OpKernel — class — tensorflow/core/framework/op_kernel.h]
class OpKernel

Control flow: Compute method receives value and update tensors from context inputs. It validates shape compatibility between value and update, then calls DoParallelConcat which internally calls DoParallelConcatUpdate to perform the actual parallel concat operation using eigen tensor operations.

Data flow: User-controlled input: value tensor from ctx->input(0), update tensor from ctx->input(1), loc_ attribute from kernel construction. The value tensor's shape (specifically dim_size(0)) flows to DoParallelConcatUpdate where it determines memory access bounds. No validation of value.dim_size(0) > loc_ exists in pre-fix version.

Verdict: Vulnerable
Vulnerable region:
[tensorflow/core/kernels/inplace_ops.cc]
```c++
void Compute(OpKernelContext* ctx) override {
    auto value = ctx->input(0);
    auto update = ctx->input(1);

    OP_REQUIRES(
        ctx, value.dims() == update.dims(),
        errors::InvalidArgument("value and update shape doesn't match: ",
                                value.shape().DebugString(), " vs. ",
                                update.shape().DebugString()));
    for (int i = 1; i < value.dims(); ++i) {
      OP_REQUIRES(
          ctx, value.dim_size(i) == update.dim_size(i),
          errors::InvalidArgument("value and update shape doesn't match ",
                                  value.shape().DebugString(), " vs. ",
                                  update.shape().DebugString()));
    }
    OP_REQUIRES(ctx, 1 == update.dim_size(0),
                errors::InvalidArgument("update shape doesn't match: ",
                                        update.shape().DebugString()));

    Tensor output = value;  // This creates an alias intentionally.
    const auto& d = ctx->eigen_device<Device>();
    OP_REQUIRES_OK(
        ctx, ::tensorflow::functor::DoParallelConcat(d, update, loc_, &output));
    ctx->set_output(0, output);
  }

 private:
  int32 loc_;
};
```
CWE: CWE-369
Severity: HIGH

Root cause: The ParallelConcatUpdate::Compute method does not validate that the 0th dimension of the value tensor is greater than loc_ before passing it to DoParallelConcatUpdate. In DoParallelConcatUpdate, the code computes 'auto r = (loc % nrows + nrows) % nrows' where nrows comes from the output tensor's dimension(0). If value.dim_size(0) <= loc_, nrows can be 0 or less, leading to out-of-bounds memory access or undefined behavior (potential division by zero if nrows becomes 0).

Exploitability: An attacker can craft a TensorFlow model or input that invokes _ParallelConcatUpdate with value.dim_size(0) <= loc_, where loc_ is a user-controllable attribute. The parallel_concat operation with shape=0 and insufficient values triggers this condition.

Attack preconditions:
- Attacker can provide a TensorFlow model using _ParallelConcatUpdate or parallel_concat with shape=0
- Attacker can control the 'loc' attribute or shape parameter
- The values tensor has 0th dimension <= loc_

Likely impact: An attacker can craft a TensorFlow model or input that invokes _ParallelConcatUpdate with value.dim_size(0) <= loc_, where loc_ is a user-controllable attribute. The parallel_concat operation with shape=0 and insufficient values triggers this condition.

Yes — this code appears to contain a real denial-of-service bug on CPU.

## Observed code path

`ParallelConcatUpdate<CPUDevice>::Compute`:

1. Reads:
   - `value = ctx->input(0)`
   - `update = ctx->input(1)`

2. Validates:
   - same rank: `value.dims() == update.dims()`
   - dimensions `1..N-1` match
   - `update.dim_size(0) == 1`

3. Creates `Tensor output = value;` as an alias.

4. Calls:
   - `DoParallelConcat(d, update, loc_, &output)`

`DoParallelConcat` dispatches by dtype to `DoParallelConcatUpdate<CPUDevice, T>`.

`DoParallelConcatUpdate` does:

```c++
auto Tvalue = value.shaped<T, 2>({1, value.NumElements()});
auto Toutput = output->flat_outer_dims<T>();
auto nrows = Toutput.dimension(0);
auto r = (loc % nrows + nrows) % nrows;  // Guard index range.
Toutput.template chip<0>(r).device(d) = Tvalue.template chip<0>(0);
```

## Vulnerable behavior

The supposed “guard”:

```c++
auto r = (loc % nrows + nrows) % nrows;
```

assumes `nrows != 0`.

But `nrows` comes from the first dimension of `output` (which aliases `value`), and **`Compute` never checks that `value.dim_size(0) > 0`**.

In fact, the validation explicitly allows `value.dim_size(0)` to differ from `update.dim_size(0)`; it only requires dimensions `1..` to match and `update.dim_size(0) == 1`.

So an attacker can supply:

- `value` with first dimension `0`
- `update` with first dimension `1`
- all remaining dimensions equal

Example valid-by-this-code shapes:
- `value`: `[0, 8]`
- `update`: `[1, 8]`

These pass all checks in `Compute`.

Then in `DoParallelConcatUpdate`:
- `Toutput = output->flat_outer_dims<T>()`
- `nrows = Toutput.dimension(0)` becomes `0`
- `loc % nrows` performs modulo by zero

That is undefined behavior in C++ and in practice can crash the process.

## Data/control-flow reasoning

### Attacker influence
The attacker can control:
- tensor shapes of `value` and `update`
- `loc` attribute (via graph/op construction)

The dangerous value is `nrows`, derived from `value`’s first dimension.

### Existing guards
The code does have shape checks, but they do **not** enforce:
- `value.dim_size(0) > 0`
- `value.dim_size(0) == update.dim_size(0)`

So they do not prevent `nrows == 0`.

### Why this is exploitable
This is not just a logic bug; it reaches arithmetic with a zero divisor before any safe fallback.

A crafted graph/input that invokes this CPU kernel with `value.shape[0] == 0` can trigger a runtime crash. That is a realistic denial of service in any environment executing untrusted TensorFlow graphs or inputs.

## Impact

Likely impact: **process crash / denial of service**.

From the shown code, there is no clear path to out-of-bounds memory write before the modulo; the immediate issue is the divide/modulo-by-zero.

## Most precise classification

- **CWE-369: Divide by Zero**
- Impact: **Denial of Service**

## Residual uncertainty

The only meaningful uncertainty is whether higher-level op construction or shape inference elsewhere forbids zero-sized first dimensions for this op. Nothing in the supplied code enforces that, and the provided `Compute` implementation itself accepts such shapes. Based on the code shown, this is a real bug in the kernel logic.

## Verdict

**Vulnerable.**
`ParallelConcatUpdate<CPUDevice>::Compute` allows a `value` tensor with first dimension `0`, which leads `DoParallelConcatUpdate` to compute `loc % nrows` with `nrows == 0`, causing undefined behavior / crash.

Compute validates rank equality, dimensions 1..N-1 equality, and update.dim_size(0) == 1, but it does not require value/output first dimension to be nonzero. The callee then derives nrows from output->flat_outer_dims<T>().dimension(0) and performs loc % nrows. If value.dim_size(0) == 0, then nrows == 0 and the modulo operation divides by zero.

An attacker who can cause this kernel to run with crafted tensor shapes can supply a zero-row value tensor and a one-row update tensor with matching remaining dimensions. This passes the shown validation and reaches modulo-by-zero in the CPU kernel, likely terminating the process.

The attacker can invoke or influence execution of the ParallelConcatUpdate CPU kernel.

The attacker can supply or construct tensors such that value.dim_size(0) == 0 and update.dim_size(0) == 1.

The remaining tensor dimensions match so that the existing shape checks pass.

No higher-layer validation outside the supplied code rejects zero-sized first dimensions before this kernel runs.

value and update must have the same rank

Dimensions 1..N-1 of value and update must match

No invariant in the shown code enforces value.dim_size(0) > 0 before computing loc % nrows

For i from 1 to dims-1: value.dim_size(i) == update.dim_size(i)

value.dim_size(0) > 0 (or equivalently nrows > 0 before modulo)

DoParallelConcat is called with update as the source tensor and output as the destination

DoParallelConcatUpdate computes nrows from output, i.e. from value's first dimension

If value's first dimension is zero, nrows is zero and loc % nrows is undefined

The supplied code is sufficient to show the divide-by-zero path on CPU.

There is some uncertainty whether separate op registration, shape inference, or higher-level graph validation elsewhere in TensorFlow forbids zero-sized first dimensions for this op, but no such restriction appears in the provided code path.

From the shown implementation, the immediate issue is denial of service rather than demonstrated memory corruption.

[{'id': 'CWE-369', 'name': 'Divide by Zero'}]