Memory safety / Bounds-checking (out-of-bounds read risk) in SMB client and dcache tmpfile handling

HIGH
torvalds/linux
Commit: 81dc1e4d32b0
Affected: 7.0-rc6 and earlier (pre-fix SMB3/SMB1 client code in the v7.1-rc1-part1-smb3-client-fixes merge)
2026-04-16 06:14 UTC

Description

The commit bundle contains explicit security-related fixes: (1) bounds checking addition in d_mark_tmpfile_name to prevent a potential buffer overflow when constructing a temporary file name for dcache entries. It enforces that the new name length does not exceed DNAME_INLINE_LEN - 1 before performing the copy, reducing the risk of memory corruption in path handling for tmpfiles. (2) A compatibility/feature-related adjustment to support O_TMPFILE and related tmpfile naming, including new constants and small adjustments in cifs/smb client code to correctly handle temporary file creation and naming. The triaged note mentions fixing an out-of-bounds read in symlink response parsing and an EA bounds check; these changes correlate to input validation and memory-safety improvements in the SMB client and vfs path handling. Taken together, these are concrete security-oriented fixes rather than mere dependency bumps or cosmetic changes.

Commit Details

Author: Linus Torvalds

Date: 2026-04-14 00:09 UTC

Message:

Merge tag 'v7.1-rc1-part1-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6 Pull smb client updates from Steve French: - Fix EAs bounds check - Fix OOB read in symlink response parsing - Add support for creating tmpfiles - Minor debug improvement for mount failure - Minor crypto cleanup - Add missing module description - mount fix for lease vs. nolease - Add Metze as maintainer for smbdirect - Minor error mapping header cleanup - Improve search speed of SMB1 maperror - Fix potential null ptr ref in smb2 map error tests * tag 'v7.1-rc1-part1-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6: (26 commits) smb: client: allow both 'lease' and 'nolease' mount options smb: client: get rid of d_drop()+d_add() smb: client: set ATTR_TEMPORARY with O_TMPFILE | O_EXCL smb: client: add support for O_TMPFILE vfs: introduce d_mark_tmpfile_name() MAINTAINERS: create entry for smbdirect smb: client: add missing MODULE_DESCRIPTION() to smb1maperror_test smb: client: fix OOB reads parsing symlink error response smb: client: fix off-by-8 bounds check in check_wsl_eas() smb: client: Remove unnecessary selection of CRYPTO_ECB smb/client: move smb2maperror declarations to smb2proto.h smb/client: introduce KUnit tests to check DOS/SRV err mapping search smb/client: check if SMB1 DOS/SRV error mapping arrays are sorted smb/client: use binary search for SMB1 DOS/SRV error mapping smb/client: autogenerate SMB1 DOS/SRV to POSIX error mapping smb/client: annotate smberr.h with POSIX error codes smb/client: move ERRnetlogonNotStarted to DOS error class smb/client: introduce KUnit test to check ntstatus_to_dos_map search smb/client: check if ntstatus_to_dos_map is sorted smb/client: use binary search for NT status to DOS mapping ...

Triage Assessment

Vulnerability Type: Memory safety (out-of-bounds read) / Input validation (bounds checking)

Confidence: HIGH

Reasoning:

Commit includes explicit fixes for security-relevant memory safety issues: 'Fix EAs bounds check' and 'Fix OOB read in symlink response parsing', indicating corrected bounds checking and prevention of out-of-bounds reads. Other changes relate to SMB/CIFS robustness but the flagged items demonstrate direct security remediation.

Verification Assessment

Vulnerability Type: Memory safety / Bounds-checking (out-of-bounds read risk) in SMB client and dcache tmpfile handling

Confidence: HIGH

Affected Versions: 7.0-rc6 and earlier (pre-fix SMB3/SMB1 client code in the v7.1-rc1-part1-smb3-client-fixes merge)

Code Diff

diff --git a/MAINTAINERS b/MAINTAINERS index 047c9ba5265144..b4be104bb2a817 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24401,6 +24401,20 @@ T: git https://github.com/cschaufler/smack-next.git F: Documentation/admin-guide/LSM/Smack.rst F: security/smack/ +SMBDIRECT (RDMA Stream Transport with Read/Write-Offload, MS-SMBD) +M: Steve French <smfrench@gmail.com> +M: Steve French <sfrench@samba.org> +M: Namjae Jeon <linkinjeon@kernel.org> +M: Namjae Jeon <linkinjeon@samba.org> +R: Stefan Metzmacher <metze@samba.org> +R: Tom Talpey <tom@talpey.com> +L: linux-cifs@vger.kernel.org +L: samba-technical@lists.samba.org (moderated for non-subscribers) +S: Maintained +F: fs/smb/client/smbdirect.* +F: fs/smb/common/smbdirect/ +F: fs/smb/server/transport_rdma.* + SMC91x ETHERNET DRIVER M: Nicolas Pitre <nico@fluxnic.net> S: Odd Fixes diff --git a/fs/dcache.c b/fs/dcache.c index 9ceab142896f66..df11bbba0342f3 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -3196,6 +3196,25 @@ void d_mark_tmpfile(struct file *file, struct inode *inode) } EXPORT_SYMBOL(d_mark_tmpfile); +void d_mark_tmpfile_name(struct file *file, const struct qstr *name) +{ + struct dentry *dentry = file->f_path.dentry; + char *dname = dentry->d_shortname.string; + + BUG_ON(dname_external(dentry)); + BUG_ON(d_really_is_positive(dentry)); + BUG_ON(!d_unlinked(dentry)); + BUG_ON(name->len > DNAME_INLINE_LEN - 1); + spin_lock(&dentry->d_parent->d_lock); + spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); + dentry->__d_name.len = name->len; + memcpy(dname, name->name, name->len); + dname[name->len] = '\0'; + spin_unlock(&dentry->d_lock); + spin_unlock(&dentry->d_parent->d_lock); +} +EXPORT_SYMBOL(d_mark_tmpfile_name); + void d_tmpfile(struct file *file, struct inode *inode) { struct dentry *dentry = file->f_path.dentry; diff --git a/fs/smb/client/.gitignore b/fs/smb/client/.gitignore index 8a6e04ab62b9b7..66e6e2ade0bd88 100644 --- a/fs/smb/client/.gitignore +++ b/fs/smb/client/.gitignore @@ -1 +1,4 @@ +smb1_mapping_table.c +smb1_err_dos_map.c +smb1_err_srv_map.c smb2_mapping_table.c diff --git a/fs/smb/client/Kconfig b/fs/smb/client/Kconfig index 17bd368574e942..d112da38c8818b 100644 --- a/fs/smb/client/Kconfig +++ b/fs/smb/client/Kconfig @@ -9,7 +9,6 @@ config CIFS select CRYPTO_AEAD2 select CRYPTO_CCM select CRYPTO_GCM - select CRYPTO_ECB select CRYPTO_AES select CRYPTO_LIB_ARC4 select CRYPTO_LIB_MD5 @@ -217,4 +216,15 @@ config CIFS_COMPRESSION Say Y here if you want SMB traffic to be compressed. If unsure, say N. +config SMB1_KUNIT_TESTS + tristate "KUnit tests for SMB1" + depends on SMB_KUNIT_TESTS && CIFS_ALLOW_INSECURE_LEGACY + default SMB_KUNIT_TESTS + help + This builds the SMB1-specific KUnit tests. + + These tests are only enabled when legacy insecure SMB1 support + (CIFS_ALLOW_INSECURE_LEGACY) is enabled. + + If unsure, say N. endif diff --git a/fs/smb/client/Makefile b/fs/smb/client/Makefile index 1a6e1e1c9764dd..6e83b5204699c6 100644 --- a/fs/smb/client/Makefile +++ b/fs/smb/client/Makefile @@ -7,7 +7,7 @@ obj-$(CONFIG_CIFS) += cifs.o cifs-y := trace.o cifsfs.o cifs_debug.o connect.o dir.o file.o \ inode.o link.o misc.o netmisc.o smbencrypt.o transport.o \ - cached_dir.o cifs_unicode.o nterr.o cifsencrypt.o \ + cached_dir.o cifs_unicode.o cifsencrypt.o \ readdir.o ioctl.o sess.o export.o unc.o winucase.o \ smb2ops.o smb2maperror.o smb2transport.o \ smb2misc.o smb2pdu.o smb2inode.o smb2file.o cifsacl.o fs_context.o \ @@ -44,6 +44,26 @@ cifs-$(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) += \ cifs-$(CONFIG_CIFS_COMPRESSION) += compress.o compress/lz77.o +ifneq ($(CONFIG_CIFS_ALLOW_INSECURE_LEGACY),) +# +# Build the SMB1 error mapping tables from nterr.h and smberr.h +# +smb1-gen-y := smb1_mapping_table.c \ + smb1_err_dos_map.c \ + smb1_err_srv_map.c + +$(obj)/smb1_mapping_table.c: $(src)/nterr.h $(src)/gen_smb1_mapping FORCE + $(call if_changed,gen_smb1_mapping) + +$(obj)/smb1_err_%.c: $(src)/smberr.h $(src)/gen_smb1_mapping FORCE + $(call if_changed,gen_smb1_mapping) + +$(obj)/smb1maperror.o: $(addprefix $(obj)/, $(smb1-gen-y)) + +quiet_cmd_gen_smb1_mapping = GEN $@ + cmd_gen_smb1_mapping = perl $(src)/gen_smb1_mapping $< $@ +endif + # # Build the SMB2 error mapping table from smb2status.h # @@ -56,7 +76,8 @@ $(obj)/smb2maperror.o: $(obj)/smb2_mapping_table.c quiet_cmd_gen_smb2_mapping = GEN $@ cmd_gen_smb2_mapping = perl $(src)/gen_smb2_mapping $< $@ +obj-$(CONFIG_SMB1_KUNIT_TESTS) += smb1maperror_test.o obj-$(CONFIG_SMB_KUNIT_TESTS) += smb2maperror_test.o # Let Kbuild handle tracking and cleaning -targets += smb2_mapping_table.c +targets += smb2_mapping_table.c $(smb1-gen-y) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 32d0305a1239ad..2025739f070ac1 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -124,6 +124,9 @@ MODULE_PARM_DESC(dir_cache_timeout, "Number of seconds to cache directory conten /* Module-wide total cached dirents (in bytes) across all tcons */ atomic64_t cifs_dircache_bytes_used = ATOMIC64_INIT(0); +atomic_t cifs_sillycounter; +atomic_t cifs_tmpcounter; + /* * Write-only module parameter to drop all cached directory entries across * all CIFS mounts. Echo a non-zero value to trigger. @@ -1199,6 +1202,7 @@ MODULE_ALIAS("smb3"); const struct inode_operations cifs_dir_inode_ops = { .create = cifs_create, .atomic_open = cifs_atomic_open, + .tmpfile = cifs_tmpfile, .lookup = cifs_lookup, .getattr = cifs_getattr, .unlink = cifs_unlink, @@ -1911,6 +1915,12 @@ init_cifs(void) { int rc = 0; +#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY + rc = smb1_init_maperror(); + if (rc) + return rc; +#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ + rc = smb2_init_maperror(); if (rc) return rc; @@ -2148,7 +2158,6 @@ MODULE_DESCRIPTION ("VFS to access SMB3 servers e.g. Samba, Macs, Azure and Windows (and " "also older servers complying with the SNIA CIFS Specification)"); MODULE_VERSION(CIFS_VERSION); -MODULE_SOFTDEP("ecb"); MODULE_SOFTDEP("nls"); MODULE_SOFTDEP("aes"); MODULE_SOFTDEP("cmac"); diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index e320d39b01f5ec..18f9f93a01b41f 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -13,6 +13,9 @@ #define ROOT_I 2 +extern atomic_t cifs_sillycounter; +extern atomic_t cifs_tmpcounter; + /* * ino_t is 32-bits on 32-bit arch. We have to squash the 64-bit value down * so that it will fit. We use hash_64 to convert the value to 31 bits, and @@ -49,10 +52,12 @@ void cifs_sb_deactive(struct super_block *sb); /* Functions related to inodes */ extern const struct inode_operations cifs_dir_inode_ops; struct inode *cifs_root_iget(struct super_block *sb); -int cifs_create(struct mnt_idmap *idmap, struct inode *inode, +int cifs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *direntry, umode_t mode, bool excl); -int cifs_atomic_open(struct inode *inode, struct dentry *direntry, +int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct file *file, unsigned int oflags, umode_t mode); +int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, + struct file *file, umode_t mode); struct dentry *cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, unsigned int flags); int cifs_unlink(struct inode *dir, struct dentry *dentry); @@ -142,6 +147,20 @@ struct smb3_fs_context; struct dentry *cifs_smb3_do_mount(struct file_system_type *fs_type, int flags, struct smb3_fs_context *old_ctx); +char *cifs_silly_fullpath(struct dentry *dentry); + +#define CIFS_TMPNAME_PREFIX ".__smbfile_tmp" +#define CIFS_TMPNAME_PREFIX_LEN ((int)sizeof(CIFS_TMPNAME_PREFIX) - 1) +#define CIFS_TMPNAME_COUNTER_LEN ((int)sizeof(cifs_tmpcounter) * 2) +#define CIFS_TMPNAME_LEN \ + (CIFS_TMPNAME_PREFIX_LEN + CIFS_TMPNAME_COUNTER_LEN) + +#define CIFS_SILLYNAME_PREFIX ".__smbfile_silly" +#define CIFS_SILLYNAME_PREFIX_LEN ((int)sizeof(CIFS_SILLYNAME_PREFIX) - 1) +#define CIFS_SILLYNAME_COUNTER_LEN ((int)sizeof(cifs_sillycounter) * 2) +#define CIFS_SILLYNAME_LEN \ + (CIFS_SILLYNAME_PREFIX_LEN + CIFS_SILLYNAME_COUNTER_LEN) + #ifdef CONFIG_CIFS_NFSD_EXPORT extern const struct export_operations cifs_export_ops; #endif /* CONFIG_CIFS_NFSD_EXPORT */ diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 709e96e077916d..ccfde157d3befa 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -1534,9 +1534,16 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, #define CIFS_CACHE_RW_FLG (CIFS_CACHE_READ_FLG | CIFS_CACHE_WRITE_FLG) #define CIFS_CACHE_RHW_FLG (CIFS_CACHE_RW_FLG | CIFS_CACHE_HANDLE_FLG) -/* - * One of these for each file inode - */ +enum cifs_inode_flags { + CIFS_INODE_PENDING_OPLOCK_BREAK, /* oplock break in progress */ + CIFS_INODE_PENDING_WRITERS, /* Writes in progress */ + CIFS_INODE_FLAG_UNUSED, /* Unused flag */ + CIFS_INO_DELETE_PENDING, /* delete pending on server */ + CIFS_INO_INVALID_MAPPING, /* pagecache is invalid */ + CIFS_INO_LOCK, /* lock bit for synchronization */ + CIFS_INO_TMPFILE, /* for O_TMPFILE inodes */ + CIFS_INO_CLOSE_ON_LOCK, /* Not to defer the close when lock is set */ +}; struct cifsInodeInfo { struct netfs_inode netfs; /* Netfslib context and vfs inode */ @@ -1554,13 +1561,6 @@ struct cifsInodeInfo { __u32 cifsAttrs; /* e.g. DOS archive bit, sparse, compressed, system */ unsigned int oplock; /* oplock/lease level we have */ __u16 epoch; /* used to track lease state changes */ -#define CIFS_INODE_PENDING_OPLOCK_BREAK (0) /* oplock break in progress */ -#define CIFS_INODE_PENDING_WRITERS (1) /* Writes in progress */ -#define CIFS_INODE_FLAG_UNUSED (2) /* Unused flag */ -#define CIFS_INO_DELETE_PENDING (3) /* delete pending on server */ -#define CIFS_INO_INVALID_MAPPING (4) /* pagecache is invalid */ -#define CIFS_INO_LOCK (5) /* lock bit for synchronization */ -#define CIFS_INO_CLOSE_ON_LOCK (7) /* Not to defer the close when lock is set */ unsigned long flags; spinlock_t writers_lock; unsigned int writers; /* Number of writers on this inode */ @@ -2259,6 +2259,7 @@ struct smb2_compound_vars { struct kvec qi_iov; struct kvec io_iov[SMB2_IOCTL_IOV_SIZE]; struct kvec si_iov[SMB2_SET_INFO_IOV_SIZE]; + struct kvec hl_iov[SMB2_SET_INFO_IOV_SIZE]; struct kvec unlink_iov[SMB2_SET_INFO_IOV_SIZE]; struct kvec rename_iov[SMB2_SET_INFO_IOV_SIZE]; struct kvec close_iov; @@ -2383,6 +2384,8 @@ static inline int cifs_open_create_options(unsigned int oflags, int opts) opts |= CREATE_WRITE_THROUGH; if (oflags & O_DIRECT) opts |= CREATE_NO_BUFFER; + if (oflags & O_TMPFILE) + opts |= CREATE_DELETE_ON_CLOSE; return opts; } diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 884bfa1cf0b423..c24c50d732e648 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -141,7 +141,8 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode, int __cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, unsigned int find_flags, unsigned int open_flags, struct cifsFileInfo **ret_file); -int cifs_get_writable_path(struct cifs_tcon *tcon, const char *name, int flags, +int cifs_get_writable_path(struct cifs_tcon *tcon, const char *name, + struct inode *inode, int flags, struct cifsFileInfo **ret_file); struct cifsFileInfo *__find_readable_file(struct cifsInodeInfo *cifs_inode, unsigned int find_flags, diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 6d2378eeb7f681..6ea1ae7f7a4605 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -172,20 +172,44 @@ check_name(struct dentry *direntry, struct cifs_tcon *tcon) return 0; } +static char *alloc_parent_path(struct dentry *dentry, size_t namelen) +{ + struct cifs_sb_info *cifs_sb = CIFS_SB(dentry); + void *page = alloc_dentry_path(); + const char *path; + size_t size; + char *npath; -/* Inode operations in similar order to how they appear in Linux file fs.h */ + path = build_path_from_dentry(dentry->d_parent, page); + if (IS_ERR(path)) { + npath = ERR_CAST(path); + goto out; + } + + size = strlen(path) + namelen + 2; + npath = kmalloc(size, GFP_KERNEL); + if (!npath) + npath = ERR_PTR(-ENOMEM); + else + scnprintf(npath, size, "%s%c", path, CIFS_DIR_SEP(cifs_sb)); +out: + free_dentry_path(page); + return npath; +} -static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned int xid, - struct tcon_link *tlink, unsigned int oflags, umode_t mode, __u32 *oplock, - struct cifs_fid *fid, struct cifs_open_info_data *buf) +/* Inode operations in similar order to how they appear in Linux file fs.h */ +static int __cifs_do_create(struct inode *dir, struct dentry *direntry, + const char *full_path, unsigned int xid, + struct tcon_link *tlink, unsigned int oflags, + umode_t mode, __u32 *oplock, struct cifs_fid *fid, + struct cifs_open_info_data *buf, + struct inode **inode) { int rc = -ENOENT; int create_options = CREATE_NOT_DIR; int desired_access; - struct cifs_sb_info *cifs_sb = CIFS_SB(inode); + struct cifs_sb_info *cifs_sb = CIFS_SB(dir); struct cifs_tcon *tcon = tlink_tcon(tlink); - const char *full_path; - void *page = alloc_dentry_path(); struct inode *newinode = NULL; unsigned int sbflags = cifs_sb_flags(cifs_sb); int disposition; @@ -195,25 +219,20 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned int rdwr_for_fscache = 0; __le32 lease_flags = 0; + *inode = NULL; *oplock = 0; if (tcon->ses->server->oplocks) *oplock = REQ_OPLOCK; - full_path = build_path_from_dentry(direntry, page); - if (IS_ERR(full_path)) { - rc = PTR_ERR(full_path); - goto out; - } - /* If we're caching, we need to be able to fill in around partial writes. */ - if (cifs_fscache_enabled(inode) && (oflags & O_ACCMODE) == O_WRONLY) + if (cifs_fscache_enabled(dir) && (oflags & O_ACCMODE) == O_WRONLY) rdwr_for_fscache = 1; #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY if (tcon->unix_ext && cap_unix(tcon->ses) && !tcon->broken_posix_open && (CIFS_UNIX_POSIX_PATH_OPS_CAP & le64_to_cpu(tcon->fsUnixInfo.Capability))) { - rc = cifs_posix_open(full_path, &newinode, inode->i_sb, mode, + rc = cifs_posix_open(full_path, &newinode, dir->i_sb, mode, oflags, oplock, &fid->netfid, xid); switch (rc) { case 0: @@ -225,8 +244,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned if (S_ISDIR(newinode->i_mode)) { CIFSSMBClose(xid, tcon, fid->netfid); iput(newinode); - rc = -EISDIR; - goto out; + return -EISDIR; } if (!S_ISREG(newinode->i_mode)) { @@ -269,7 +287,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned break; def ... [truncated]
← Back to Alerts View on GitHub →