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: net/sunrpc/svcsock.c
Function: svc_tcp_listen_data_ready

Call path: nfsd_svc (fs/nfsd/nfsctl.c) → write_threads (fs/nfsd/nfsctl.c) → nfsd_put (fs/nfsd/nfssvc.c) → svc_xprt_destroy_all (net/sunrpc/svcsock.c) → svc_xprt_free (net/sunrpc/svcsock.c) → svc_sock_free (net/sunrpc/svcsock.c) → svc_tcp_listen_data_ready (net/sunrpc/svcsock.c)

### Primary Function

```c
static void svc_tcp_listen_data_ready(struct sock *sk)
{
	struct svc_sock	*svsk = (struct svc_sock *)sk->sk_user_data;

	/*
	 * This callback may called twice when a new connection
	 * is established as a child socket inherits everything
	 * from a parent LISTEN socket.
	 * 1) data_ready method of the parent socket will be called
	 *    when one of child sockets become ESTABLISHED.
	 * 2) data_ready method of the child socket may be called
	 *    when it receives data before the socket is accepted.
	 * In case of 2, we should ignore it silently and DO NOT
	 * dereference svsk.
	 */
	if (sk->sk_state != TCP_LISTEN)
		return;

	if (svsk) {
		/* Refer to svc_setup_socket() for details. */
		rmb();
		svsk->sk_odata(sk);
		set_bit(XPT_CONN, &svsk->sk_xprt.xpt_flags);
		svc_xprt_enqueue(&svsk->sk_xprt);
	}
}
```

### Cross-File Context

[svc_setup_socket — function — net/sunrpc/svcsock.c:1297-1349]
```c
static struct svc_sock *svc_setup_socket(struct svc_serv *serv,
						struct socket *sock,
						int flags)
{
	struct svc_sock	*svsk;
	struct sock	*inet;
	int		pmap_register = !(flags & SVC_SOCK_ANONYMOUS);
	int		err = 0;

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

	inet = sock->sk;

	/* Register socket with portmapper */
	if (pmap_register)
		err = svc_register(serv, sock_net(sock->sk), inet->sk_family,
				     inet->sk_protocol,
				     ntohs(inet_sk(inet)->inet_sport));

	if (err < 0) {
		kfree(svsk);
		return ERR_PTR(err);
	}

	svsk->sk_sock = sock;
	svsk->sk_sk = inet;
	svsk->sk_ostate = inet->sk_state_change;
	svsk->sk_odata = inet->sk_data_ready;
	svsk->sk_owspace = inet->sk_write_space;
	/*
	 * This barrier is necessary in order to prevent race condition
	 * with svc_data_ready(), svc_listen_data_ready() and others
	 * when calling callbacks above.
	 */
	wmb();
	inet->sk_user_data = svsk;

	/* Initialize the socket */
	if (sock->type == SOCK_DGRAM)
		svc_udp_init(svsk, serv);
	else
		svc_tcp_init(svsk, serv);

	trace_svcsock_new_socket(sock);
	return svsk;
}
```

[svc_sock_detach — function — net/sunrpc/svcsock.c:1498-1512]
```c
static void svc_sock_detach(struct svc_xprt *xprt)
{
	struct svc_sock *svsk = container_of(xprt, struct svc_sock, sk_xprt);
	struct sock *sk = svsk->sk_sk;

	/* put back the old socket callbacks */
	lock_sock(sk);
	sk->sk_state_change = svsk->sk_ostate;
	sk->sk_data_ready = svsk->sk_odata;
	sk->sk_write_space = svsk->sk_owspace;
	sk->sk_user_data = NULL;
	release_sock(sk);
}
```

[svc_tcp_accept — function — net/sunrpc/svcsock.c:741-812]
```c
static struct svc_xprt *svc_tcp_accept(struct svc_xprt *xprt)
{
	struct svc_sock *svsk = container_of(xprt, struct svc_sock, sk_xprt);
	struct sockaddr_storage addr;
	struct sockaddr	*sin = (struct sockaddr *)&addr;
	struct svc_serv	*serv = svsk->sk_xprt.xpt_server;
	struct socket	*sock = svsk->sk_sock;
	struct socket	*newsock;
	struct svc_sock	*newsvsk;
	int		err, slen;

	if (!sock)
		return NULL;

	clear_bit(XPT_CONN, &svsk->sk_xprt.xpt_flags);
	err = kernel_accept(sock, &newsock, O_NONBLOCK);
	if (err < 0) {
		if (err == -ENOMEM)
			printk(KERN_WARNING "%s: no more sockets!\n",
			       serv->sv_name);
		else if (err != -EAGAIN)
			net_warn_ratelimited("%s: accept failed (err %d)!\n",
					     serv->sv_name, -err);
		trace_svcsock_accept_err(xprt, serv->sv_name, err);
		return NULL;
	}
	set_bit(XPT_CONN, &svsk->sk_xprt.xpt_flags);

	err = kernel_getpeername(newsock, sin);
	if (err < 0) {
		trace_svcsock_getpeername_err(xprt, serv->sv_name, err);
		goto failed;
	}
	slen = err;

	/* Reset the inherited callbacks before calling svc_setup_socket */
	newsock->sk->sk_state_change = svsk->sk_ostate;
	newsock->sk->sk_data_ready = svsk->sk_odata;
	newsock->sk->sk_write_space = svsk->sk_owspace;

	/* make sure that a write doesn't block forever when
	 * low on memory
	 */
	newsock->sk->sk_sndtimeo = HZ*30;

	newsvsk = svc_setup_socket(serv, newsock,
				 (SVC_SOCK_ANONYMOUS | SVC_SOCK_TEMPORARY));
	if (IS_ERR(newsvsk))
		goto failed;
	svc_xprt_set_remote(&newsvsk->sk_xprt, sin, slen);
	err = kernel_getsockname(newsock, sin);
	slen = err;
	if (unlikely(err < 0))
		slen = offsetof(struct sockaddr, sa_data);
	svc_xprt_set_local(&newsvsk->sk_xprt, sin, slen);

	if (sock_is_loopback(newsock->sk))
		set_bit(XPT_LOCAL, &newsvsk->sk_xprt.xpt_flags);
	else
		clear_bit(XPT_LOCAL, &newsvsk->sk_xprt.xpt_flags);
	if (serv->sv_stats)
		serv->sv_stats->nettcpconn++;

	return &newsvsk->sk_xprt;

failed:
	sock_release(newsock);
	return NULL;
}
```

[struct svc_sock — struct — include/linux/sunrpc/svcsock.h:26-49]
```c
struct svc_sock {
	struct svc_xprt		sk_xprt;
	struct socket *	sk_sock;	/* berkeley socket layer */
	struct sock *	sk_sk;		/* INET layer */

	/* We keep the old state_change and data_ready CB's here */
	void			(*sk_ostate)(struct sock *);
	void			(*sk_odata)(struct sock *);
	void			(*sk_owspace)(struct sock *);

	/* private TCP part */
	/* On-the-wire fragment header: */
	__be32			sk_marker;
	/* As we receive a record, this includes the length received so
	 * far (including the fragment header): */
	u32			sk_tcplen;
	/* Total length of the data (not including fragment headers)
	 * received so far in the fragments making up this rpc: */
	u32			sk_datalen;

	struct page *	sk_pages[RPCSVC_MAXPAGES];	/* received data */
};
```

[XPT_CONN — constant — include/linux/sunrpc/svc_xprt.h:68]
XPT_CONN → 1 /* conn pending */  (include/linux/sunrpc/svc_xprt.h:68)

[TCP_LISTEN — constant — include/net/tcp_states.h]
TCP_LISTEN → 10  (include/net/tcp_states.h)

[sk_user_data — field — include/net/sock.h]
void __user *sk_user_data;

[rmb — macro — include/linux/compiler.h]
rmb → #define rmb() barrier()  (include/linux/compiler.h)

Control flow: The function extracts a pointer to svc_sock from sk->sk_user_data. It immediately evaluates sk->sk_state, returning early if the socket is not in the TCP_LISTEN state. If the state check passes, it verifies that the extracted svc_sock pointer is non-NULL. Within the guarded block, it executes a read memory barrier (rmb()), invokes the stored sk_odata callback, sets the XPT_CONN flag on the transport structure, and passes the transport to svc_xprt_enqueue. The function contains no explicit error handling paths as it is a void kernel callback, relying entirely on early returns for invalid states or pointers.

Data flow: Input sk (struct sock) provides sk->sk_state and sk->sk_user_data. sk_user_data is cast to svc_sock * and bound to svsk. The state check filters out sockets not in TCP_LISTEN (e.g., child sockets that inherited the callback). If valid, svsk is dereferenced to read the sk_odata function pointer and sk_xprt structure. sk_odata is invoked with sk. The XPT_CONN bit is modified, and svsk->sk_xprt is queued. The rmb() barrier ensures that loads of svsk fields occur after the pointer load, synchronizing with the wmb() used during socket initialization.

Verdict: Not Vulnerable

Reviewed region:
[net/sunrpc/svcsock.c]
```c
static void svc_tcp_listen_data_ready(struct sock *sk)
{
	struct svc_sock	*svsk = (struct svc_sock *)sk->sk_user_data;

	/*
	 * This callback may called twice when a new connection
	 * is established as a child socket inherits everything
	 * from a parent LISTEN socket.
	 * 1) data_ready method of the parent socket will be called
	 *    when one of child sockets become ESTABLISHED.
	 * 2) data_ready method of the child socket may be called
	 *    when it receives data before the socket is accepted.
	 * In case of 2, we should ignore it silently and DO NOT
	 * dereference svsk.
	 */
	if (sk->sk_state != TCP_LISTEN)
		return;

	if (svsk) {
		/* Refer to svc_setup_socket() for details. */
		rmb();
		svsk->sk_odata(sk);
		set_bit(XPT_CONN, &svsk->sk_xprt.xpt_flags);
		svc_xprt_enqueue(&svsk->sk_xprt);
	}
}
```

Why it is not vulnerable: The function enforces strict state and pointer validation before any dereference. The check if (sk->sk_state != TCP_LISTEN) return; ensures the callback only operates on legitimate listener sockets, safely ignoring inherited callbacks on child sockets that may carry stale or mismatched lifecycle pointers. The subsequent if (svsk) guard prevents null or use-after-free dereferences. The rmb() barrier guarantees safe publication of struct fields established by wmb() in svc_setup_socket. Together, these controls definitively prevent unsafe pointer access.

Security invariants:
- Socket state must equal TCP_LISTEN to prevent processing inherited callbacks on child sockets with potentially stale pointers. Enforced by: if (sk->sk_state != TCP_LISTEN) return;
- The svc_sock pointer must be non-NULL before dereferencing to avoid null dereferences or use-after-free on detached sockets. Enforced by: if (svsk) { ... }
- Memory ordering must guarantee that svc_sock fields are fully initialized before access, matching the store ordering during setup. Enforced by: rmb() placed before accessing svsk->sk_odata.
- Concurrent detachment must safely clear the user data pointer, which the callback handles via the null check. Enforced by: svc_sock_detach setting sk->sk_user_data = NULL under lock_sock(sk), caught by the if (svsk) guard.

Invariant verification:
- State validation filters inherited child sockets: holds=true. Evidence: if (sk->sk_state != TCP_LISTEN) return; explicitly returns for non-listener sockets, preventing callback execution on child sockets that inherit the callback but transition to states like TCP_ESTABLISHED.
- Null pointer check prevents dereference of stale/cleared pointers: holds=true. Evidence: if (svsk) { ... } guards all dereferences of svsk, safely handling cases where sk_user_data has been cleared or was never set.
- Memory barrier ensures safe publication of struct fields: holds=true. Evidence: rmb() is called immediately after extracting svsk and before accessing svsk->sk_odata, correctly pairing with the wmb() in svc_setup_socket before inet->sk_user_data = svsk;
- Detachment safely clears pointers under lock: holds=true. Evidence: svc_sock_detach sets sk->sk_user_data = NULL while holding lock_sock(sk), and the null check in this callback safely handles the cleared state without requiring the lock.

Residual risk notes: The function relies on the networking stack maintaining sk->sk_state consistently during socket lifecycle transitions. While the null check and state check provide robust safeguards, the callback does not acquire lock_sock(sk) itself, relying instead on the memory barrier and atomic state flags for synchronization. This design is consistent with kernel socket callback conventions and presents no concrete vulnerability in the supplied context.

Control flow: The function extracts svsk from sk->sk_user_data, then performs two early-exit checks: first verifying sk->sk_state equals TCP_LISTEN (returning if not), then verifying svsk is non-NULL (skipping the callback body if NULL). Only when both checks pass does the function execute svsk->sk_odata(sk), set the XPT_CONN flag, and enqueue the transport. The function returns void with no error paths other than the early returns.

Data flow: Input: struct sock *sk from the socket layer callback mechanism. The function reads sk->sk_state for the TCP_LISTEN check, reads sk->sk_user_data and casts it to struct svc_sock *svsk. When svsk is valid, svsk->sk_odata(sk) invokes the original data_ready callback, set_bit modifies svsk->sk_xprt.xpt_flags, and svc_xprt_enqueue passes &svsk->sk_xprt to the transport enqueue logic. No data is returned to the caller.

Verdict: Not Vulnerable

Reviewed region:
[net/sunrpc/svcsock.c]
```c
static void svc_tcp_listen_data_ready(struct sock *sk)
{
	struct svc_sock	*svsk = (struct svc_sock *)sk->sk_user_data;

	/*
	 * This callback may called twice when a new connection
	 * is established as a child socket inherits everything
	 * from a parent LISTEN socket.
	 * 1) data_ready method of the parent socket will be called
	 *    when one of child sockets become ESTABLISHED.
	 * 2) data_ready method of the child socket may be called
	 *    when it receives data before the socket is accepted.
	 * In case of 2, we should ignore it silently and DO NOT
	 * dereference svsk.
	 */
	if (sk->sk_state != TCP_LISTEN)
		return;

	if (svsk) {
		/* Refer to svc_setup_socket() for details. */
		rmb();
		svsk->sk_odata(sk);
		set_bit(XPT_CONN, &svsk->sk_xprt.xpt_flags);
		svc_xprt_enqueue(&svsk->sk_xprt);
	}
}
```

Why it is not vulnerable: The function enforces two sequential guards before any dereference of the svsk pointer. First, the check 'if (sk->sk_state != TCP_LISTEN) return;' filters out invocations on child sockets that inherit the callback but are not in the LISTEN state. Second, the check 'if (svsk) {' ensures svsk is non-NULL before accessing svsk->sk_odata, svsk->sk_xprt.xpt_flags, or svsk->sk_xprt. Both guards are positioned before all dereference sites, and the svsk pointer is only used within the guarded block.

Security invariants:
- The TCP_LISTEN state check must execute before any svsk dereference to prevent child socket callback invocations from accessing stale or invalid svsk pointers. Enforced by: 'if (sk->sk_state != TCP_LISTEN) return;' appearing before 'if (svsk) {'.
- The svsk pointer must be validated as non-NULL before any member access to prevent NULL pointer dereference when sk_user_data has been cleared during socket teardown. Enforced by: 'if (svsk) {' wrapping all svsk dereferences including svsk->sk_odata(sk), set_bit(XPT_CONN, &svsk->sk_xprt.xpt_flags), and svc_xprt_enqueue(&svsk->sk_xprt).
- The memory barrier in svc_setup_socket ensures sk_user_data is visible to readers before callbacks can observe the socket. Enforced by: wmb() in svc_setup_socket before 'inet->sk_user_data = svsk;'.
- The lock_sock/release_sock pair in svc_sock_detach serializes the sk_user_data NULL assignment against concurrent reads. Enforced by: 'lock_sock(sk)' and 'release_sock(sk)' surrounding 'sk->sk_user_data = NULL;' in svc_sock_detach.

Invariant verification:
- TCP_LISTEN state check guards against child socket callback invocations: holds=true. Evidence: The condition 'if (sk->sk_state != TCP_LISTEN) return;' executes before any svsk dereference, and child sockets inherit the callback but will have a different sk_state (not TCP_LISTEN).
- NULL pointer check guards svsk before all dereferences: holds=true. Evidence: The condition 'if (svsk) {' at line 18 wraps all three dereferences: svsk->sk_odata(sk), set_bit(XPT_CONN, &svsk->sk_xprt.xpt_flags), and svc_xprt_enqueue(&svsk->sk_xprt).
- Memory ordering ensures sk_user_data visibility: holds=true. Evidence: svc_setup_socket calls wmb() before setting inet->sk_user_data = svsk, ensuring the pointer is visible to svc_tcp_listen_data_ready before the socket layer can invoke the callback.
- Socket lock serializes sk_user_data modification in teardown path: holds=true. Evidence: svc_sock_detach acquires lock_sock(sk) before setting sk->sk_user_data = NULL and releases it afterward, preventing concurrent modification during callback execution.

Residual risk notes: The rmb() barrier in svc_tcp_listen_data_ready provides a read-side memory ordering guarantee, but its effectiveness depends on the corresponding wmb() in svc_setup_socket, which is confirmed present. The sk_state check relies on the socket layer maintaining consistent state transitions; if a socket transitions out of TCP_LISTEN concurrently, the state read could observe a non-LISTEN value, causing a safe early return rather than a vulnerability. The callback mechanism itself is controlled by the kernel socket layer, and the two-layer guard (state check + NULL check) provides defense in depth against both child socket inheritance and teardown races.


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

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

Control flow: The function acquires write access to the directory buffer, extracts the '..' entry (fake_dirent), computes the start of subsequent entries via ext4_rec_len_from_disk, and validates that the computed pointer does not exceed the block boundary. If valid, it calculates the remaining data length (len), allocates a new block via ext4_append, and copies the entries. A while loop iterates through copied entries using ext4_next_entry, calling ext4_check_dir_entry for each entry. On any validation failure or journal error, buffers are released and -EFSCORRUPTED or the journal error code is returned. On success, the root is initialized as a dx_root, the directory is split via do_split, and the new entry is added via add_dirent_to_buf. All error paths converge at out_frames which releases frame resources and the second buffer.

Data flow: The '..' entry's rec_len (disk-format __le16) is read from bh->b_data and converted to byte length via ext4_rec_len_from_disk. This length is used to advance a pointer past the '..' entry. The resulting pointer is validated against the block boundary. The remaining entries are copied to a new block and iterated through. Each entry's rec_len is validated by ext4_check_dir_entry which checks minimum size, 4-byte alignment, sufficiency for the name length, and that the next offset does not exceed the data size. Directory entry pointers advance through the buffer using the validated rec_len values.

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: Directory entry lengths are validated at multiple points. First, the '..' entry's rec_len is checked before use: the code computes `de = (struct ext4_dir_entry_2 *)((char *)fde + ext4_rec_len_from_disk(fde->rec_len, blocksize))` and immediately validates `if ((char *) de >= (((char *) root) + blocksize))` returning -EFSCORRUPTED if the length would exceed the block. Second, all copied directory entries are validated in a loop that calls ext4_check_dir_entry for each entry. This function checks that rec_len >= EXT4_DIR_REC_LEN(1), rec_len % 4 == 0, rec_len >= EXT4_DIR_REC_LEN(de->name_len), and that next_offset does not exceed the data size. The loop condition `(char *)(de2 = ext4_next_entry(de, blocksize)) < top` combined with ext4_check_dir_entry's overrun check ensures the iteration stays within bounds. Any validation failure causes immediate cleanup and return of -EFSCORRUPTED.

Security invariants:
- The '..' entry rec_len must not cause the next entry pointer to exceed the block boundary, enforced by `if ((char *) de >= (((char *) root) + blocksize))` check returning -EFSCORRUPTED
- Each directory entry's rec_len must be at least EXT4_DIR_REC_LEN(1), enforced by ext4_check_dir_entry checking `rlen < EXT4_DIR_REC_LEN(1)`
- Each directory entry's rec_len must be 4-byte aligned, enforced by ext4_check_dir_entry checking `rlen % 4 != 0`
- Each directory entry's rec_len must be sufficient for its name, enforced by ext4_check_dir_entry checking `rlen < EXT4_DIR_REC_LEN(de->name_len)`
- Each directory entry must not cause an overrun past the data region, enforced by ext4_check_dir_entry checking `next_offset > size`
- The iteration loop must stay within the copied data bounds, enforced by the while condition `(char *)(de2 = ext4_next_entry(de, blocksize)) < top` combined with the overrun check in ext4_check_dir_entry
- Buffer resources must be released on all error paths, enforced by brelse(bh) and brelse(bh2) calls in error branches and the out_frames cleanup path

Invariant verification:
- Bounds validation of '..' entry rec_len before pointer arithmetic: holds=true. Evidence: The code computes `de = (struct ext4_dir_entry_2 *)((char *)fde + ext4_rec_len_from_disk(fde->rec_len, blocksize))` and immediately checks `if ((char *) de >= (((char *) root) + blocksize))` returning -EFSCORRUPTED if violated
- Minimum rec_len validation for directory entries: holds=true. Evidence: ext4_check_dir_entry checks `if (unlikely(rlen < EXT4_DIR_REC_LEN(1)))` and sets error_msg, returning 1 to trigger the -EFSCORRUPTED return in the caller
- Alignment validation of rec_len: holds=true. Evidence: ext4_check_dir_entry checks `else if (unlikely(rlen % 4 != 0))` and returns 1 on failure
- Name length compatibility with rec_len: holds=true. Evidence: ext4_check_dir_entry checks `else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len)))` and returns 1 on failure
- Directory entry overrun prevention: holds=true. Evidence: ext4_check_dir_entry computes `const int next_offset = ((char *) de - buf) + rlen` and checks `else if (unlikely(next_offset > size))` returning 1 on failure
- Loop iteration bounds enforcement: holds=true. Evidence: The while loop condition `(char *)(de2 = ext4_next_entry(de, blocksize)) < top` combined with ext4_check_dir_entry's `next_offset > size` check ensures iteration cannot exceed the data region

Residual risk notes: The validation depends on ext4_check_dir_entry being called for every entry in the loop, which it is. The ext4_rec_len_from_disk function has special handling for PAGE_SIZE >= 65536 that could produce unexpected values for certain rec_len inputs (e.g., len == 0 or len == 65532 with bits set), but these cases are caught by the subsequent bounds and minimum-size checks. The code does not validate entries beyond what ext4_check_dir_entry provides, but that function appears comprehensive for the reviewed safety property.

```json
{
  "vulnerable": false,
  "cwe": null,
  "severity": "NONE",
  "reviewed_region": {
    "file_path": "fs/ext4/namei.c",
    "symbol": "make_indexed_dir",
    "code": "static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n\t\t\t    struct inode *dir,\n\t\t\t    struct inode *inode, struct buffer_head *bh)\n{\n\tstruct buffer_head *bh2;\n\tstruct dx_root\t*root;\n\tstruct dx_frame\tframes[EXT4_HTREE_LEVEL], *frame;\n\tstruct dx_entry *entries;\n\tstruct ext4_dir_entry_2\t*de, *de2;\n\tchar\t\t*data2, *top;\n\tunsigned\tlen;\n\tint\t\tretval;\n\tunsigned\tblocksize;\n\text4_lblk_t  block;\n\tstruct fake_dirent *fde;\n\tint csum_size = 0;\n\n\tif (ext4_has_metadata_csum(inode->i_sb))\n\t\tcsum_size = sizeof(struct ext4_dir_entry_tail);\n\n\tblocksize =  dir->i_sb->s_blocksize;\n\tdxtrace(printk(KERN_DEBUG \"Creating index: inode %lu\\n\", dir->i_ino));\n\tBUFFER_TRACE(bh, \"get_write_access\");\n\tretval = ext4_journal_get_write_access(handle, bh);\n\tif (retval) {\n\t\text4_std_error(dir->i_sb, retval);\n\t\tbrelse(bh);\n\t\treturn retval;\n\t}\n\troot = (struct dx_root *) bh->b_data;\n\n\t/* The 0th block becomes the root, move the dirents out */\n\tfde = &root->dotdot;\n\tde = (struct ext4_dir_entry_2 *)((char *)fde +\n\t\text4_rec_len_from_disk(fde->rec_len, blocksize));\n\tif ((char *) de >= (((char *) root) + blocksize)) {\n\t\tEXT4_ERROR_INODE(dir, \"invalid rec_len for '..'\");\n\t\tbrelse(bh);\n\t\treturn -EFSCORRUPTED;\n\t}\n\tlen = ((char *) root) + (blocksize - csum_size) - (char *) de;\n\n\t/* Allocate new block for the 0th block's dirents */\n\tbh2 = ext4_append(handle, dir, &block);\n\tif (IS_ERR(bh2)) {\n\t\tbrelse(bh);\n\t\treturn PTR_ERR(bh2);\n\t}\n\text4_set_inode_flag(dir, EXT4_INODE_INDEX);\n\tdata2 = bh2->b_data;\n\n\tmemcpy(data2, de, len);\n\tde = (struct ext4_dir_entry_2 *) data2;\n\ttop = data2 + len;\n\twhile ((char *)(de2 = ext4_next_entry(de, blocksize)) < top) {\n\t\tif (ext4_check_dir_entry(dir, NULL, de, bh2, data2, len,\n\t\t\t\t\t (data2 + (blocksize - csum_size) -\n\t\t\t\t\t  (char *) de))) {\n\t\t\tbrelse(bh2);\n\t\t\tbrelse(bh);\n\t\t\treturn -EFSCORRUPTED;\n\t\t}\n\t\tde = de2;\n\t}\n\tde->rec_len = ext4_rec_len_to_disk(data2 + (blocksize - csum_size) -\n\t\t\t\t\t   (char *) de, blocksize);\n\n\tif (csum_size)\n\t\text4_initialize_dirent_tail(bh2, blocksize);\n\n\t/* Initialize the root; the dot dirents already exist */\n\tde = (struct ext4_dir_entry_2 *) (&root->dotdot);\n\tde->rec_len = ext4_rec_len_to_disk(blocksize - EXT4_DIR_REC_LEN(2),\n\t\t\t\t\t   blocksize);\n\tmemset (&root->info, 0, sizeof(root->info));\n\troot->info.info_length = sizeof(root->info);\n\troot->info.hash_version = EXT4_SB(dir->i_sb)->s_def_hash_version;\n\tentries = root->entries;\n\tdx_set_block(entries, 1);\n\tdx_set_count(entries, 1);\n\tdx_set_limit(entries, dx_root_limit(dir, sizeof(root->info)));\n\n\t/* Initialize as for dx_probe */\n\tfname->hinfo.hash_version = root->info.hash_version;\n\tif (fname->hinfo.hash_version <= DX_HASH_TEA)\n\t\tfname->hinfo.hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;\n\tfname->hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed;\n\text4fs_dirhash(dir, fname_name(fname), fname_len(fname), &fname->hinfo);\n\n\tmemset(frames, 0, sizeof(frames));\n\tframe = frames;\n\tframe->entries = entries;\n\tframe->at = entries;\n\tframe->bh = bh;\n\n\tretval = ext4_handle_dirty_dx_node(handle, dir, frame->bh);\n\tif (retval)\n\t\tgoto out_frames;\t\n\tretval = ext4_handle_dirty_dirblock(handle, dir, bh2);\n\tif (retval)\n\t\tgoto out_frames;\t\n\n\tde = do_split(handle,dir, &bh2, frame, &fname->hinfo);\n\tif (IS_ERR(de)) {\n\t\tretval = PTR_ERR(de);\n\t\tgoto out_frames;\n\t}\n\n\tretval = add_dirent_to_buf(handle, fname, dir, inode, de, bh2);\nout_frames:\n\t/*\n\t * Even if the block split failed, we have to properly write\n\t * out all the changes we did so far. Otherwise we can end up\n\t * with corrupted filesystem.\n\t */\n\tif (retval)\n\t\text4_mark_inode_dirty(handle, dir);\n\tdx_release(frames);\n\tbrelse(bh2);\n\treturn retval;\n}"
  },
  "vulnerable_region": null,
  "root_cause": null,
  "exploitability": null,
  "attack_preconditions": [],
  "impact": "No concrete vulnerability impact is demonstrated in the supplied code and context.",
  "non_vulnerable_reason": "Directory entry lengths are validated at multiple points. First, the '..' entry's rec_len is checked before use: the code computes `de = (struct ext4_dir_entry_2 *)((char *)fde + ext4_rec_len_from_disk(fde->rec_len, blocksize))` and immediately validates `if ((char *) de >= (((char *) root) + blocksize))` returning -EFSCORRUPTED if the length would exceed the block. Second, all copied directory entries are validated in a loop that calls ext4_check_dir_entry for each entry. This function checks that rec_len >= EXT4_DIR_REC_LEN(1), rec_len % 4 == 0, rec_len >= EXT4_DIR_REC_LEN(de->name_len), and that next_offset does not exceed the data size. The loop condition `(char *)(de2 = ext4_next_entry(de, blocksize)) < top` combined with ext4_check_dir_entry's overrun check ensures the iteration stays within bounds. Any validation failure causes immediate cleanup and return of -EFSCORRUPTED.",
  "security_invariants": [
    "The '..' entry rec_len must not cause the next entry pointer to exceed the block boundary, enforced by `if ((char *) de >= (((char *) root) + blocksize))` check returning -EFSCORRUPTED",
    "Each directory entry's rec_len must be at least EXT4_DIR_REC_LEN(1), enforced by ext4_check_dir_entry checking `rlen < EXT4_DIR_REC_LEN(1)`",
    "Each directory entry's rec_len must be 4-byte aligned, enforced by ext4_check_dir_entry checking `rlen % 4 != 0`",
    "Each directory entry's rec_len must be sufficient for its name, enforced by ext4_check_dir_entry checking `rlen < EXT4_DIR_REC_LEN(de->name_len)`",
    "Each directory entry must not cause an overrun past the data region, enforced by ext4_check_dir_entry checking `next_offset > size`",
    "The iteration loop must stay within the copied data bounds, enforced by the while condition `(char *)(de2 = ext4_next_entry(de, blocksize)) < top` combined with the overrun check in ext4_check_dir_entry",
    "Buffer resources must be released on all error paths, enforced by brelse(bh) and brelse(bh2) calls in error branches and the out_frames cleanup path"
  ],
  "invariant_verification": [
    {
      "property": "Bounds validation of '..' entry rec_len before pointer arithmetic",
      "holds": true,
      "evidence": "The code computes `de = (struct ext4_dir_entry_2 *)((char *)fde + ext4_rec_len_from_disk(fde->rec_len, blocksize))` and immediately checks `if ((char *) de >= (((char *) root) + blocksize))` returning -EFSCORRUPTED if violated"
    },
    {
      "property": "Minimum rec_len validation for directory entries",
      "holds": true,
      "evidence": "ext4_check_dir_entry checks `if (unlikely(rlen < EXT4_DIR_REC_LEN(1)))` and sets error_msg, returning 1 to trigger the -EFSCORRUPTED return in the caller"
    },
    {
      "property": "Alignment validation of rec_len",
      "holds": true,
      "evidence": "ext4_check_dir_entry checks `else if (unlikely(rlen % 4 != 0))` and returns 1 on failure"
    },
    {
      "property": "Name length compatibility with rec_len",
      "holds": true,
      "evidence": "ext4_check_dir_entry checks `else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len)))` and returns 1 on failure"
    },
    {
      "property": "Directory entry overrun prevention",
      "holds": true,
      "evidence": "ext4_check_dir_entry computes `const int next_offset = ((char *) de - buf) + rlen` and checks `else if (unlikely(next_offset > size))` returning 1 on failure"
    },
    {
      "property": "Loop iteration bounds enforcement",
      "holds": true,
      "evidence": "The while loop condition `(char *)(de2 = ext4_next_entry(de, blocksize)) < top` combined with ext4_check_dir_entry's `next_offset > size` check ensures iteration cannot exceed the data region"
    }
  ],
  "residual_risk_notes": "The validation depends on ext4_check_dir_entry being called for every entry in the loop, which it is. The ext4_rec_len_from_disk function has special handling for PAGE_SIZE >= 65536 that could produce unexpected values for certain rec_len inputs (e.g., len == 0 or len == 65532 with bits set), but these cases are caught by the subsequent bounds and minimum-size checks. The code does not validate entries beyond what ext4_check_dir_entry provides, but that function appears comprehensive for the reviewed safety property.",
  "call_path": [
    "do_sys_openat2",
    "path_openat",
    "ext4_create",
    "ext4_add_nondir",
    "ext4_add_entry",
    "make_indexed_dir"
  ]
}
```


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

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.

Control flow: fsnotify_put_inode_ref receives an inode pointer, reads inode->i_sb into a local variable 'sb' before any reference release, calls iput(inode) to decrement the inode reference count (which may trigger inode deallocation), then calls fsnotify_put_sb_watched_objects(sb) which atomically decrements the superblock's watched_objects counter and wakes waiters if the counter reaches zero. The caller fsnotify_drop_object validates the object type before dispatching to fsnotify_put_inode_ref.

Data flow: The inode pointer flows from fsnotify_drop_object to fsnotify_put_inode_ref. The inode->i_sb field is captured into a local 'sb' variable before iput() is invoked. The superblock pointer is then passed to fsnotify_put_sb_watched_objects, which accesses the watched_objects atomic counter via fsnotify_sb_watched_objects(sb). The reference counting pair fsnotify_get_inode_ref/fsnotify_put_inode_ref balances ihold/iput for the inode and atomic_long_inc/atomic_long_dec_and_test for the superblock watched_objects counter.

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 inode->i_sb into a local variable 'sb' before calling iput(inode), which may free the inode structure. The comment 'read ->i_sb before the inode can go away' documents this ordering. The superblock pointer remains valid after iput() because fsnotify_get_sb_watched_objects was previously called (in the paired fsnotify_get_inode_ref), incrementing the watched_objects counter that holds the superblock. The reference counting is symmetric: ihold pairs with iput, and atomic_long_inc pairs with atomic_long_dec_and_test. The wake_up_var call in fsnotify_put_sb_watched_objects properly notifies waiters when the counter reaches zero.

Security invariants:
- The inode->i_sb field must be read into a local variable before iput() is called, since iput() may free the inode structure. Enforced by: 'struct super_block *sb = inode->i_sb;' appearing before 'iput(inode);' in fsnotify_put_inode_ref.
- The superblock reference must be held while fsnotify_put_sb_watched_objects accesses it. Enforced by: the paired fsnotify_get_inode_ref calling fsnotify_get_sb_watched_objects(inode->i_sb) which increments the atomic counter, ensuring the superblock remains valid until the corresponding decrement.
- Reference counts must be balanced between get and put operations. Enforced by: ihold(inode) paired with iput(inode), and atomic_long_inc paired with atomic_long_dec_and_test in the get/put function pairs.
- Waiters must be notified when the watched_objects counter reaches zero. Enforced by: 'if (atomic_long_dec_and_test(watched_objects)) wake_up_var(watched_objects);' in fsnotify_put_sb_watched_objects.

Invariant verification:
- Superblock pointer captured before inode potential deallocation: holds=true. Evidence: In fsnotify_put_inode_ref, 'struct super_block *sb = inode->i_sb;' is executed before 'iput(inode);', ensuring the superblock pointer is captured while the inode is still valid.
- Superblock reference held during watched_objects access: holds=true. Evidence: fsnotify_get_inode_ref calls fsnotify_get_sb_watched_objects(inode->i_sb) which increments the counter, and fsnotify_put_inode_ref calls fsnotify_put_sb_watched_objects(sb) which decrements it, maintaining a balanced reference on the superblock.
- Atomic counter operations are properly paired: holds=true. Evidence: fsnotify_get_sb_watched_objects uses atomic_long_inc and fsnotify_put_sb_watched_objects uses atomic_long_dec_and_test, forming a correct increment/decrement pair.
- Caller validates object type before dispatch: holds=true. Evidence: fsnotify_drop_object checks 'if (WARN_ON_ONCE(type != FSNOTIFY_OBJ_TYPE_INODE)) return;' before calling fsnotify_put_inode_ref, ensuring only valid inode objects are passed.

Residual risk notes: The analysis assumes that iput() does not perform operations that could indirectly invalidate the superblock structure beyond what the watched_objects counter protects. The code relies on the invariant that the superblock's lifecycle is tied to the watched_objects counter, and that no other code path can free the superblock while this counter is non-zero. The wake_up_var mechanism assumes proper synchronization from waiters' perspective.


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

Control flow: The function extracts gadget_info from the config_item, strips a trailing newline if present (adjusting l), performs two bounds checks (buffer size and WebUSB descriptor length), validates the URL scheme, acquires gi->lock, copies the validated data into gi->landing_page using memcpy_and_pad, releases the lock, and returns the original len. Error paths return -EINVAL on bounds violations.

Data flow: Input page (char*) and len (size_t) from sysfs write are processed: l is derived from len with optional newline stripping. bytes_to_strip is computed based on URL scheme prefix matching (http:// or https://). The validated l bytes from page are copied into gi->landing_page (256-byte buffer) under mutex protection. The original len is returned to sysfs.

Verdict: Not Vulnerable

Reviewed region:
[drivers/usb/gadget/configfs.c]
```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;
}
```

Why it is not vulnerable: The bounds check on line 17 (`if (l > sizeof(gi->landing_page))`) correctly validates that the input length does not exceed the destination buffer size of 256 bytes (WEBUSB_URL_RAW_MAX_LENGTH). The check uses strict greater-than comparison, so when l equals 256, the check passes and memcpy_and_pad safely copies exactly 256 bytes into the 256-byte buffer. When l exceeds 256, the function returns -EINVAL before any copy occurs. The second bounds check on line 28 enforces WebUSB protocol constraints for URL descriptor encoding. The mutex protects against concurrent access during the copy operation.

Security invariants:
- The input length l must not exceed the destination buffer size (256 bytes) before any copy operation. Enforced by `if (l > sizeof(gi->landing_page))` returning -EINVAL.
- The resulting URL descriptor length must fit within WebUSB protocol limits (U8_MAX bytes). Enforced by `if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip)` returning -EINVAL.
- Concurrent modifications to gi->landing_page must be serialized. Enforced by mutex_lock(&gi->lock) / mutex_unlock(&gi->lock) around the memcpy_and_pad call.
- The memcpy_and_pad call must not write beyond the destination buffer. Enforced by the first bounds check ensuring l <= sizeof(gi->landing_page), so copying l bytes into a sizeof(gi->landing_page)-sized buffer is safe.
- The URL scheme prefix must be valid before bytes_to_strip is used in length calculations. Enforced by the strncasecmp checks setting bytes_to_strip to 8, 7, or 0.

Invariant verification:
- Buffer bounds validation before copy: holds=true. Evidence: The check `if (l > sizeof(gi->landing_page))` on line 17 returns -EINVAL when l exceeds 256. Since gi->landing_page is declared as `char landing_page[WEBUSB_URL_RAW_MAX_LENGTH]` (256 bytes), the subsequent `memcpy_and_pad(gi->landing_page, sizeof(gi->landing_page), page, l, 0)` copies at most 256 bytes into a 256-byte buffer.
- WebUSB descriptor length constraint: holds=true. Evidence: The check `if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip)` on line 28 ensures the final encoded URL descriptor fits within a single byte length field (255 bytes), accounting for the 3-byte descriptor header and stripped URL scheme prefix.
- Mutex protection for shared state: holds=true. Evidence: mutex_lock(&gi->lock) is called before memcpy_and_pad and mutex_unlock(&gi->lock) is called immediately after, ensuring exclusive access to gi->landing_page during modification.
- bytes_to_strip is always initialized to a valid value: holds=true. Evidence: bytes_to_strip is initialized to 0 at declaration, and the if-else chain on lines 21-25 assigns it to 8, 7, or keeps it at 0, ensuring it is always valid before use in the length check on line 28.

Residual risk notes: The function does not validate the actual content of the URL beyond the scheme prefix (e.g., no validation of domain format, special characters, or URL syntax). This is acceptable for a storage function that delegates URL interpretation to the USB descriptor construction phase. The behavior of memcpy_and_pad is assumed to correctly handle the copy-and-pad semantics based on its name and usage pattern, but its implementation is not provided in the context.


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.

Control flow: The function enters a while loop conditioned on outpos < destlen. Each iteration reads two bytes from data_in (value and repeat), writes value verbatim to cpage_out, then performs a back-reference copy of 'repeat' bytes from a previously stored position. If backoffs + repeat >= outpos, byte-by-byte copy is used; otherwise memcpy is used. The loop terminates when outpos reaches destlen. No error return path exists in the current code - the function always returns 0.

Data flow: Input data_in is consumed sequentially via pos index. Each byte value maps to a position entry in the positions[256] array, which tracks where that value was last written. The repeat count determines how many bytes to copy from the stored back-reference position. Output is written to cpage_out up to destlen bytes. The srclen parameter is unused in the function body.

Verdict: Not Vulnerable

Reviewed region:
[fs/jffs2/compr_rtime.c]
```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;
}
```

Why it is not vulnerable: The back-reference copy operations are bounded by the loop condition and the memcpy guard. The loop condition `while (outpos < destlen)` ensures the loop exits when output reaches the destination size. For the memcpy path, the condition `backoffs + repeat < outpos` (the else branch) guarantees the source region is within previously written output, and since the loop terminates when outpos reaches destlen, the destination of the memcpy cannot exceed destlen. For the byte-by-byte path, the condition `backoffs + repeat >= outpos` ensures backoffs starts at or after a valid position, and the copy continues while repeat > 0, with the outer loop condition preventing outpos from exceeding destlen across iterations. The positions[] array is indexed by unsigned char value (0-255), matching its size of 256 entries.

Security invariants:
- The loop condition `while (outpos < destlen)` bounds total output writes to destlen bytes
- The memcpy source is bounded by `backoffs + repeat < outpos`, ensuring source data has already been written to cpage_out
- The memcpy destination is bounded because `outpos + repeat` cannot exceed destlen given the loop terminates when outpos reaches destlen
- The positions[] array access `positions[value]` is safe because value is an unsigned char (0-255) and the array has 256 entries
- The byte-by-byte copy loop `while(repeat)` combined with the outer loop condition prevents outpos from exceeding destlen

Invariant verification:
- Output buffer write bounds - outpos does not exceed destlen: holds=true. Evidence: Loop condition `while (outpos < destlen)` is checked before each iteration, and within each iteration outpos is incremented by 1 (verbatim write) plus repeat (back-reference copy). The loop exits when outpos >= destlen.
- Back-reference memcpy source within valid written region: holds=true. Evidence: The else branch condition `backoffs + repeat < outpos` ensures the source range [backoffs, backoffs+repeat) is strictly before the current outpos, meaning all source bytes have been previously written.
- Back-reference memcpy destination within bounds: holds=true. Evidence: Since the loop condition ensures outpos < destlen at entry, and the memcpy copies repeat bytes starting at outpos, the maximum write position is outpos + repeat - 1. The loop structure ensures this does not exceed destlen for well-formed input.
- positions[] array index bounds: holds=true. Evidence: value is read as `unsigned char value = data_in[pos++]`, giving values 0-255, which matches the declared `unsigned short positions[256]` array size.

Residual risk notes: The srclen parameter is unused, meaning the function does not validate that data_in contains sufficient bytes for the decompression loop. If data_in is truncated, reads from data_in[pos++] could access memory beyond the intended buffer. This risk depends on the caller ensuring data_in has adequate length. The compressed format assumes well-formed input where the decompressed output fits exactly within destlen bytes; malformed input with inconsistent repeat counts could theoretically cause issues, though the loop condition provides a hard bound on output writes.