Re: [PATCH 1/2] smb: client: fix off-by-8 bounds check in check_wsl_eas()
Yuanfu Xie <[email protected]> Wed, 8 Apr 2026 17:39:09 +0800
| Newsgroups | gmane.linux.kernel,gmane.linux.kernel.cifs,gmane.network.samba.internals,gmane.linux.kernel.stable |
|---|---|
| Message-ID | <[email protected]> |
[PATCH] smb: client: add bounds checks when iterating SMB1 WSL EAs Add boundary and overflow checks when traversing extended attribute (EA) entries for SMB1 WSL reparse points in cifs_query_path_info(), to prevent out-of-bounds memory accesses from malformed next_entry_offset values. This is necessary because SMB1 WSL EA iteration does not currently have any explicit bounds checking, unlike SMB2 path which has check_wsl_eas(). The new checks ensure the current EA pointer plus its size does not exceed the end of the EA buffer during iteration and before parsing. Signed-off-by: YuanfuXie <[email protected]> --- fs/smb/client/smb1ops.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 9694117050a6..940bae49b2cc 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -645,13 +645,17 @@ static int cifs_query_path_info(const unsigned int xid, if (!rc && data->reparse_point) { struct smb2_file_full_ea_info *ea; u32 next = 0; + u8 *eas_end = data->wsl.eas + data->wsl.eas_len; ea = (struct smb2_file_full_ea_info *)data->wsl.eas; do { ea = (void *)((u8 *)ea + next); + if ((u8 *)ea + sizeof(*ea) > eas_end) + break; next = le32_to_cpu(ea->next_entry_offset); } while (next); - if (le16_to_cpu(ea->ea_value_length)) { + if ((u8 *)ea + sizeof(*ea) <= eas_end && + le16_to_cpu(ea->ea_value_length)) { ea->next_entry_offset = cpu_to_le32(ALIGN(sizeof(*ea) + ea->ea_name_length + 1 + le16_to_cpu(ea->ea_value_length), 4)); @@ -691,13 +695,17 @@ static int cifs_query_path_info(const unsigned int xid, if (!rc && data->reparse_point) { struct smb2_file_full_ea_info *ea; u32 next = 0; + u8 *eas_end = data->wsl.eas + data->wsl.eas_len; ea = (struct smb2_file_full_ea_info *)data->wsl.eas; do { ea = (void *)((u8 *)ea + next); + if ((u8 *)ea + sizeof(*ea) > eas_end) + break; next = le32_to_cpu(ea->next_entry_offset); } while (next); - if (le16_to_cpu(ea->ea_value_length)) { + if ((u8 *)ea + sizeof(*ea) <= eas_end && + le16_to_cpu(ea->ea_value_length)) { ea->next_entry_offset = cpu_to_le32(ALIGN(sizeof(*ea) + ea->ea_name_length + 1 + le16_to_cpu(ea->ea_value_length), 4)); -- 2.43.0 > On Apr 6, 2026, at 21:49, Greg Kroah-Hartman <[email protected]> wrote: > > The bounds check uses (u8 *)ea + nlen + 1 + vlen as the end of the EA > name and value, but ea_data sits at offset sizeof(struct > smb2_file_full_ea_info) = 8 from ea, not at offset 0. The strncmp() > later reads ea->ea_data[0..nlen-1] and the value bytes follow at > ea_data[nlen+1..nlen+vlen], so the actual end is ea->ea_data + nlen + 1 > + vlen. Isn't pointer math fun? > > The earlier check (u8 *)ea > end - sizeof(*ea) only guarantees the > 8-byte header is in bounds, but since the last EA is placed within 8 > bytes of the end of the response, the name and value bytes are read past > the end of iov. > > Fix this mess all up by using ea->ea_data as the base for the bounds > check. > > An "untrusted" server can use this to leak up to 8 bytes of kernel heap > into the EA name comparison and influence which WSL xattr the data is > interpreted as. > > Cc: Steve French <[email protected]> > Cc: Paulo Alcantara <[email protected]> > Cc: Ronnie Sahlberg <[email protected]> > Cc: Shyam Prasad N <[email protected]> > Cc: Tom Talpey <[email protected]> > Cc: Bharath SM <[email protected]> > Cc: [email protected] > Cc: [email protected] > Cc: stable <[email protected]> > Assisted-by: gregkh_clanker_t1000 > Signed-off-by: Greg Kroah-Hartman <[email protected]> > --- > fs/smb/client/smb2inode.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c > index 364bdcff9c9d..fe1c9d776580 100644 > --- a/fs/smb/client/smb2inode.c > +++ b/fs/smb/client/smb2inode.c > @@ -128,7 +128,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) > nlen = ea->ea_name_length; > vlen = le16_to_cpu(ea->ea_value_length); > if (nlen != SMB2_WSL_XATTR_NAME_LEN || > - (u8 *)ea + nlen + 1 + vlen > end) > + (u8 *)ea->ea_data + nlen + 1 + vlen > end) > return -EINVAL; > > switch (vlen) { > -- > 2.53.0 > > > >