summaryrefslogtreecommitdiff
path: root/fs/smb
diff options
context:
space:
mode:
authorMaarten Lankhorst <dev@lankhorst.se>2026-07-13 11:32:29 +0200
committerMaarten Lankhorst <dev@lankhorst.se>2026-07-13 11:32:29 +0200
commitac3ee180a8cf9e00ffd4fc8cc670a8b2d6f9b2f2 (patch)
tree389dd45860aaa6d9386b9396bb63fc057ab6ffac /fs/smb
parente4159045c2704dfe146f0ccb0445d9d074cd6882 (diff)
parenta13c140cc289c0b7b3770bce5b3ad42ab35074aa (diff)
downloadlinux-next-ac3ee180a8cf9e00ffd4fc8cc670a8b2d6f9b2f2.tar.gz
linux-next-ac3ee180a8cf9e00ffd4fc8cc670a8b2d6f9b2f2.zip
Merge v7.2-rc3 into drm-misc-fixes
Forward from rc1 to rc3 to track upstream closer again. Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
Diffstat (limited to 'fs/smb')
-rw-r--r--fs/smb/client/cifs_fs_sb.h1
-rw-r--r--fs/smb/client/cifsacl.c2
-rw-r--r--fs/smb/client/cifsfs.c12
-rw-r--r--fs/smb/client/cifsfs.h4
-rw-r--r--fs/smb/client/cifsglob.h1
-rw-r--r--fs/smb/client/cifsproto.h2
-rw-r--r--fs/smb/client/cifssmb.c36
-rw-r--r--fs/smb/client/connect.c3
-rw-r--r--fs/smb/client/dfs_cache.c12
-rw-r--r--fs/smb/client/file.c8
-rw-r--r--fs/smb/client/inode.c13
-rw-r--r--fs/smb/client/link.c17
-rw-r--r--fs/smb/client/misc.c14
-rw-r--r--fs/smb/client/readdir.c4
-rw-r--r--fs/smb/client/reparse.c17
-rw-r--r--fs/smb/client/smb1ops.c28
-rw-r--r--fs/smb/client/smb2file.c19
-rw-r--r--fs/smb/client/smb2misc.c32
-rw-r--r--fs/smb/client/smb2ops.c19
-rw-r--r--fs/smb/client/smb2pdu.c34
-rw-r--r--fs/smb/client/smb2proto.h2
-rw-r--r--fs/smb/server/auth.c49
-rw-r--r--fs/smb/server/auth.h7
-rw-r--r--fs/smb/server/ksmbd_work.h7
-rw-r--r--fs/smb/server/mgmt/user_session.c4
-rw-r--r--fs/smb/server/mgmt/user_session.h1
-rw-r--r--fs/smb/server/oplock.c109
-rw-r--r--fs/smb/server/server.c30
-rw-r--r--fs/smb/server/smb2misc.c18
-rw-r--r--fs/smb/server/smb2pdu.c222
-rw-r--r--fs/smb/server/smbacl.c36
-rw-r--r--fs/smb/server/vfs.c14
-rw-r--r--fs/smb/server/vfs_cache.c79
-rw-r--r--fs/smb/server/vfs_cache.h1
34 files changed, 585 insertions, 272 deletions
diff --git a/fs/smb/client/cifs_fs_sb.h b/fs/smb/client/cifs_fs_sb.h
index 84e7e366b0ff..d6494e1d93cc 100644
--- a/fs/smb/client/cifs_fs_sb.h
+++ b/fs/smb/client/cifs_fs_sb.h
@@ -56,6 +56,7 @@ struct cifs_sb_info {
struct smb3_fs_context *ctx;
atomic_t active;
atomic_t mnt_cifs_flags;
+ atomic_t outstanding_rreq; /* nr of rreqs not yet fully deinitialized */
struct delayed_work prune_tlinks;
struct rcu_head rcu;
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index 07cf0e578233..9424281a7674 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -962,7 +962,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
*/
fattr->cf_mode &= ~07777;
fattr->cf_mode |=
- le32_to_cpu(ppace[i]->sid.sub_auth[2]);
+ le32_to_cpu(ppace[i]->sid.sub_auth[2]) & 07777;
break;
} else {
if (compare_sids(&(ppace[i]->sid), pownersid) == 0) {
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index ea4fc0fa68ca..4df6ca03a8de 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -311,6 +311,18 @@ static void cifs_kill_sb(struct super_block *sb)
/* Wait for all opened files to release */
flush_workqueue(deferredclose_wq);
+ /*
+ * Wait for all in-flight netfs I/O requests to finish their
+ * cleanup_work so that any cifsFileInfo final puts they queue
+ * to fileinfo_put_wq/serverclose_wq have been queued, then
+ * drain the workqueue so the cfile dentry refs are dropped to
+ * avoid the busy dentry warning.
+ */
+ wait_var_event(&cifs_sb->outstanding_rreq,
+ !atomic_read(&cifs_sb->outstanding_rreq));
+ flush_workqueue(serverclose_wq);
+ flush_workqueue(fileinfo_put_wq);
+
/* finally release root dentry */
dput(cifs_sb->root);
cifs_sb->root = NULL;
diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h
index 901e1340c986..854e672a4e37 100644
--- a/fs/smb/client/cifsfs.h
+++ b/fs/smb/client/cifsfs.h
@@ -166,6 +166,6 @@ extern const struct export_operations cifs_export_ops;
#endif /* CONFIG_CIFS_NFSD_EXPORT */
/* when changing internal version - update following two lines at same time */
-#define SMB3_PRODUCT_BUILD 60
-#define CIFS_VERSION "2.60"
+#define SMB3_PRODUCT_BUILD 61
+#define CIFS_VERSION "2.61"
#endif /* _CIFSFS_H */
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 99f9e6dca62b..08e94633a9c1 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -250,6 +250,7 @@ struct cifs_open_info_data {
bool adjust_tz;
bool reparse_point;
bool contains_posix_file_info;
+ bool unknown_nlink;
struct {
/* ioctl response buffer */
struct {
diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h
index c4ababcb51a3..00168839c123 100644
--- a/fs/smb/client/cifsproto.h
+++ b/fs/smb/client/cifsproto.h
@@ -317,7 +317,7 @@ int generate_smb311signingkey(struct cifs_ses *ses,
#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
-void cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb);
+void cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb, const char *reason, int rc);
bool couldbe_mf_symlink(const struct cifs_fattr *fattr);
int check_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb, struct cifs_fattr *fattr,
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index d39175cdf1b1..40162d5554ea 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -5829,38 +5829,6 @@ CIFSSMBSetFileDisposition(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}
-static int
-CIFSSMBSetPathInfoFB(const unsigned int xid, struct cifs_tcon *tcon,
- const char *fileName, const FILE_BASIC_INFO *data,
- const struct nls_table *nls_codepage,
- struct cifs_sb_info *cifs_sb)
-{
- int oplock = 0;
- struct cifs_open_parms oparms;
- struct cifs_fid fid;
- int rc;
-
- oparms = (struct cifs_open_parms) {
- .tcon = tcon,
- .cifs_sb = cifs_sb,
- .desired_access = GENERIC_WRITE,
- .create_options = cifs_create_options(cifs_sb, 0),
- .disposition = FILE_OPEN,
- .path = fileName,
- .fid = &fid,
- };
-
- rc = CIFS_open(xid, &oparms, &oplock, NULL);
- if (rc)
- goto out;
-
- rc = CIFSSMBSetFileInfo(xid, tcon, data, fid.netfid, current->tgid);
- CIFSSMBClose(xid, tcon, fid.netfid);
-out:
-
- return rc;
-}
-
int
CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon,
const char *fileName, const FILE_BASIC_INFO *data,
@@ -5939,10 +5907,6 @@ SetTimesRetry:
if (rc == -EAGAIN)
goto SetTimesRetry;
- if (rc == -EOPNOTSUPP)
- return CIFSSMBSetPathInfoFB(xid, tcon, fileName, data,
- nls_codepage, cifs_sb);
-
return rc;
}
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index 85aec302c89e..ba749ec25a59 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -3485,6 +3485,7 @@ int cifs_setup_cifs_sb(struct cifs_sb_info *cifs_sb)
spin_lock_init(&cifs_sb->tlink_tree_lock);
cifs_sb->tlink_tree = RB_ROOT;
+ atomic_set(&cifs_sb->outstanding_rreq, 0);
cifs_dbg(FYI, "file mode: %04ho dir mode: %04ho\n",
ctx->file_mode, ctx->dir_mode);
@@ -3875,7 +3876,7 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
* After reconnecting to a different server, unique ids won't match anymore, so we disable
* serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).
*/
- cifs_autodisable_serverino(cifs_sb);
+ cifs_autodisable_serverino(cifs_sb, "DFS failover may potentially connect to a different server, inode numbers won't match anymore", 0);
/*
* Force the use of prefix path to support failover on DFS paths that resolve to targets
* that have different prefix paths.
diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c
index 83f8cf2f8d2b..8cd93cd2f00f 100644
--- a/fs/smb/client/dfs_cache.c
+++ b/fs/smb/client/dfs_cache.c
@@ -363,10 +363,10 @@ static struct cache_dfs_tgt *alloc_target(const char *name, int path_consumed)
{
struct cache_dfs_tgt *t;
- t = kmalloc_obj(*t, GFP_ATOMIC);
+ t = kmalloc_obj(*t, GFP_KERNEL);
if (!t)
return ERR_PTR(-ENOMEM);
- t->name = kstrdup(name, GFP_ATOMIC);
+ t->name = kstrdup(name, GFP_KERNEL);
if (!t->name) {
kfree(t);
return ERR_PTR(-ENOMEM);
@@ -626,7 +626,7 @@ static int update_cache_entry_locked(struct cache_entry *ce, const struct dfs_in
target = READ_ONCE(ce->tgthint);
if (target) {
- th = kstrdup(target->name, GFP_ATOMIC);
+ th = kstrdup(target->name, GFP_KERNEL);
if (!th)
return -ENOMEM;
}
@@ -760,11 +760,11 @@ static int setup_referral(const char *path, struct cache_entry *ce,
memset(ref, 0, sizeof(*ref));
- ref->path_name = kstrdup(path, GFP_ATOMIC);
+ ref->path_name = kstrdup(path, GFP_KERNEL);
if (!ref->path_name)
return -ENOMEM;
- ref->node_name = kstrdup(target, GFP_ATOMIC);
+ ref->node_name = kstrdup(target, GFP_KERNEL);
if (!ref->node_name) {
rc = -ENOMEM;
goto err_free_path;
@@ -1328,7 +1328,7 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
* After reconnecting to a different server, unique ids won't match anymore, so we disable
* serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).
*/
- cifs_autodisable_serverino(cifs_sb);
+ cifs_autodisable_serverino(cifs_sb, "DFS failover may potentially connect to a different server, inode numbers won't match anymore", 0);
/*
* Force the use of prefix path to support failover on DFS paths that resolve to targets
* that have different prefix paths.
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index 8b25d6c9ec5e..968740e7c9c3 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -241,6 +241,7 @@ static void cifs_issue_read(struct netfs_io_subrequest *subreq)
return;
failed:
+ add_credits_and_wake_if(rdata->server, &rdata->credits, 0);
subreq->error = rc;
netfs_read_subreq_terminated(subreq);
}
@@ -287,6 +288,7 @@ static int cifs_init_request(struct netfs_io_request *rreq, struct file *file)
return smb_EIO1(smb_eio_trace_not_netfs_writeback, rreq->origin);
}
+ atomic_inc(&cifs_sb->outstanding_rreq);
return 0;
}
@@ -301,16 +303,20 @@ static void cifs_rreq_done(struct netfs_io_request *rreq)
/* we do not want atime to be less than mtime, it broke some apps */
atime = inode_set_atime_to_ts(inode, current_time(inode));
mtime = inode_get_mtime(inode);
- if (timespec64_compare(&atime, &mtime))
+ if (timespec64_compare(&atime, &mtime) < 0)
inode_set_atime_to_ts(inode, inode_get_mtime(inode));
}
static void cifs_free_request(struct netfs_io_request *rreq)
{
struct cifs_io_request *req = container_of(rreq, struct cifs_io_request, rreq);
+ struct cifs_sb_info *cifs_sb = CIFS_SB(rreq->inode->i_sb);
if (req->cfile)
cifsFileInfo_put(req->cfile);
+
+ if (atomic_dec_and_test(&cifs_sb->outstanding_rreq))
+ wake_up_var(&cifs_sb->outstanding_rreq);
}
static void cifs_free_subrequest(struct netfs_io_subrequest *subreq)
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 1dbcfd163ff0..deed04dd9b91 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -909,6 +909,8 @@ static void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
memset(fattr, 0, sizeof(*fattr));
+ if (data->unknown_nlink)
+ fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
fattr->cf_cifsattrs = le32_to_cpu(info->Attributes);
if (info->DeletePending)
fattr->cf_flags |= CIFS_FATTR_DELETE_PENDING;
@@ -1145,7 +1147,7 @@ static void cifs_set_fattr_ino(int xid, struct cifs_tcon *tcon, struct super_blo
fattr->cf_uniqueid = CIFS_I(*inode)->uniqueid;
else {
fattr->cf_uniqueid = iunique(sb, ROOT_I);
- cifs_autodisable_serverino(cifs_sb);
+ cifs_autodisable_serverino(cifs_sb, "Cannot retrieve inode number via get_srv_inum", rc);
}
return;
}
@@ -1642,7 +1644,7 @@ retry_iget5_locked:
fattr->cf_flags &= ~CIFS_FATTR_INO_COLLISION;
if (inode_has_hashed_dentries(inode)) {
- cifs_autodisable_serverino(CIFS_SB(sb));
+ cifs_autodisable_serverino(CIFS_SB(sb), "Inode number collision detected", 0);
iput(inode);
fattr->cf_uniqueid = iunique(sb, ROOT_I);
goto retry_iget5_locked;
@@ -1708,8 +1710,9 @@ struct inode *cifs_root_iget(struct super_block *sb)
iget_root:
if (!rc) {
if (fattr.cf_flags & CIFS_FATTR_JUNCTION) {
+ cifs_dbg(VFS, "Removing junction mark and disabling 'serverino' to prevent inode collisions\n");
fattr.cf_flags &= ~CIFS_FATTR_JUNCTION;
- cifs_autodisable_serverino(cifs_sb);
+ cifs_autodisable_serverino(cifs_sb, "Cannot retrieve attributes for junction point", rc);
}
inode = cifs_iget(sb, &fattr);
}
@@ -2812,9 +2815,7 @@ cifs_revalidate_mapping(struct inode *inode)
}
skip_invalidate:
- clear_bit_unlock(CIFS_INO_LOCK, flags);
- smp_mb__after_atomic();
- wake_up_bit(flags, CIFS_INO_LOCK);
+ clear_and_wake_up_bit(CIFS_INO_LOCK, flags);
return rc;
}
diff --git a/fs/smb/client/link.c b/fs/smb/client/link.c
index dd127917a340..8d5d6aca742a 100644
--- a/fs/smb/client/link.c
+++ b/fs/smb/client/link.c
@@ -234,7 +234,7 @@ cifs_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
struct cifs_open_parms oparms;
struct cifs_io_parms io_parms = {0};
int buf_type = CIFS_NO_BUFFER;
- struct cifs_open_info_data query_data;
+ struct cifs_open_info_data query_data = {};
oparms = (struct cifs_open_parms) {
.tcon = tcon,
@@ -320,7 +320,7 @@ smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
int buf_type = CIFS_NO_BUFFER;
__le16 *utf16_path;
__u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
- struct smb2_file_all_info *pfile_info = NULL;
+ struct cifs_open_info_data data = {};
oparms = (struct cifs_open_parms) {
.tcon = tcon,
@@ -336,20 +336,12 @@ smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
if (utf16_path == NULL)
return -ENOMEM;
- pfile_info = kzalloc(sizeof(struct smb2_file_all_info) + PATH_MAX * 2,
- GFP_KERNEL);
-
- if (pfile_info == NULL) {
- kfree(utf16_path);
- return -ENOMEM;
- }
-
- rc = SMB2_open(xid, &oparms, utf16_path, &oplock, pfile_info, NULL,
+ rc = SMB2_open(xid, &oparms, utf16_path, &oplock, &data, NULL,
NULL, NULL);
if (rc)
goto qmf_out_open_fail;
- if (pfile_info->EndOfFile != cpu_to_le64(CIFS_MF_SYMLINK_FILE_SIZE)) {
+ if (data.fi.EndOfFile != cpu_to_le64(CIFS_MF_SYMLINK_FILE_SIZE)) {
/* it's not a symlink */
rc = -ENOENT; /* Is there a better rc to return? */
goto qmf_out;
@@ -367,7 +359,6 @@ qmf_out:
SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
qmf_out_open_fail:
kfree(utf16_path);
- kfree(pfile_info);
return rc;
}
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index 0c54b9b79a2c..e4bac2a0b85d 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -278,7 +278,7 @@ dump_smb(void *buf, int smb_buf_length)
}
void
-cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb)
+cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb, const char *reason, int rc)
{
unsigned int sbflags = cifs_sb_flags(cifs_sb);
@@ -290,6 +290,10 @@ cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb)
atomic_andnot(CIFS_MOUNT_SERVER_INUM, &cifs_sb->mnt_cifs_flags);
cifs_sb->mnt_cifs_serverino_autodisabled = true;
+ if (rc)
+ cifs_dbg(VFS, "%s: %d\n", reason, rc);
+ else
+ cifs_dbg(VFS, "%s\n", reason);
cifs_dbg(VFS, "Autodisabling the use of server inode numbers on %s\n",
tcon ? tcon->tree_name : "new server");
cifs_dbg(VFS, "The server doesn't seem to support them properly or the files might be on different servers (DFS)\n");
@@ -752,6 +756,10 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags);
/* copy DfsPath */
+ if (le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
temp = (char *)ref + le16_to_cpu(ref->DfsPathOffset);
max_len = data_end - temp;
node->path_name = cifs_strndup_from_utf16(temp, max_len,
@@ -762,6 +770,10 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
}
/* copy link target UNC */
+ if (le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
max_len = data_end - temp;
node->node_name = cifs_strndup_from_utf16(temp, max_len,
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index a50c86bbe60f..ee5996e6d7d8 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -415,7 +415,7 @@ ffirst_retry:
if (rc == 0) {
cifsFile->invalidHandle = false;
} else if (rc == -EOPNOTSUPP && (sbflags & CIFS_MOUNT_SERVER_INUM)) {
- cifs_autodisable_serverino(cifs_sb);
+ cifs_autodisable_serverino(cifs_sb, "Cannot retrieve inode number via query_dir_first", rc);
goto ffirst_retry;
}
error_exit:
@@ -1029,7 +1029,7 @@ static int cifs_filldir(char *find_entry, struct file *file,
fattr.cf_uniqueid = de.ino;
} else {
fattr.cf_uniqueid = iunique(sb, ROOT_I);
- cifs_autodisable_serverino(cifs_sb);
+ cifs_autodisable_serverino(cifs_sb, "Cannot retrieve inode number from readdir", 0);
}
if ((sbflags & CIFS_MOUNT_MF_SYMLINKS) && couldbe_mf_symlink(&fattr))
diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c
index cd1e1eaee67a..5cc5b0410d48 100644
--- a/fs/smb/client/reparse.c
+++ b/fs/smb/client/reparse.c
@@ -67,6 +67,7 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,
char *sym = NULL;
struct kvec iov;
bool directory;
+ int path_len;
int rc = 0;
if (strlen(symname) > REPARSE_SYM_PATH_MAX)
@@ -168,7 +169,21 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,
if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/')
sym[0] = sym[1] = sym[2] = sym[5] = '_';
- path = cifs_convert_path_to_utf16(sym, cifs_sb);
+ /*
+ * On a POSIX paths mount the symlink target is stored verbatim, so
+ * convert it with cifs_strndup_to_utf16(). cifs_convert_path_to_utf16()
+ * must not be used here: it strips a leading path separator (it is
+ * meant for share-relative SMB paths), which would corrupt an absolute
+ * POSIX symlink target such as "/foo/bar". Using NO_MAP_UNI_RSVD also
+ * matches the readback path in smb2_parse_native_symlink().
+ */
+ if (sbflags & CIFS_MOUNT_POSIX_PATHS)
+ path = cifs_strndup_to_utf16(sym, strlen(sym), &path_len,
+ cifs_sb->local_nls,
+ NO_MAP_UNI_RSVD);
+ else
+ path = cifs_convert_path_to_utf16(sym, cifs_sb);
+
if (!path) {
rc = -ENOMEM;
goto out;
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index d34b3d99f6ed..dc5a8c1da623 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -505,21 +505,27 @@ static int
cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb, const char *full_path)
{
- int rc;
- FILE_ALL_INFO *file_info;
+ int rc = -EOPNOTSUPP;
+ FILE_ALL_INFO file_info;
- file_info = kmalloc_obj(FILE_ALL_INFO);
- if (file_info == NULL)
- return -ENOMEM;
+ if (tcon->ses->capabilities & CAP_NT_SMBS)
+ rc = CIFSSMBQPathInfo(xid, tcon, full_path, &file_info,
+ 0 /* not legacy */, cifs_sb->local_nls,
+ cifs_remap(cifs_sb));
- rc = CIFSSMBQPathInfo(xid, tcon, full_path, file_info,
- 0 /* not legacy */, cifs_sb->local_nls,
- cifs_remap(cifs_sb));
+ /*
+ * Non-UNICODE variant of fallback functions below expands wildcards,
+ * so they cannot be used for querying paths with wildcard characters.
+ * Therefore for such paths returns -ENOENT as they cannot exist.
+ */
+ if ((rc == -EOPNOTSUPP || rc == -EINVAL) &&
+ !(tcon->ses->capabilities & CAP_UNICODE) &&
+ strpbrk(full_path, "*?\"><"))
+ rc = -ENOENT;
if (rc == -EOPNOTSUPP || rc == -EINVAL)
- rc = SMBQueryInformation(xid, tcon, full_path, file_info,
+ rc = SMBQueryInformation(xid, tcon, full_path, &file_info,
cifs_sb->local_nls, cifs_remap(cifs_sb));
- kfree(file_info);
return rc;
}
@@ -949,7 +955,7 @@ smb_set_file_info(struct inode *inode, const char *full_path,
struct cifs_open_parms oparms;
struct cifsFileInfo *open_file;
FILE_BASIC_INFO new_buf;
- struct cifs_open_info_data query_data;
+ struct cifs_open_info_data query_data = {};
__le64 write_time = buf->LastWriteTime;
struct cifsInodeInfo *cinode = CIFS_I(inode);
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c
index 6860eff31693..5ef919bce52d 100644
--- a/fs/smb/client/smb2file.c
+++ b/fs/smb/client/smb2file.c
@@ -154,8 +154,6 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
__le16 *smb2_path;
__u8 smb2_oplock;
struct cifs_open_info_data *data = buf;
- struct smb2_file_all_info file_info = {};
- struct smb2_file_all_info *smb2_data = data ? &file_info : NULL;
struct kvec err_iov = {};
int err_buftype = CIFS_NO_BUFFER;
struct cifs_fid *fid = oparms->fid;
@@ -182,14 +180,14 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
}
smb2_oplock = SMB2_OPLOCK_LEVEL_BATCH;
- rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data, NULL, &err_iov,
+ rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, data, NULL, &err_iov,
&err_buftype);
if (rc == -EACCES && retry_without_read_attributes) {
free_rsp_buf(err_buftype, err_iov.iov_base);
memset(&err_iov, 0, sizeof(err_iov));
err_buftype = CIFS_NO_BUFFER;
oparms->desired_access &= ~FILE_READ_ATTRIBUTES;
- rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data, NULL, &err_iov,
+ rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, data, NULL, &err_iov,
&err_buftype);
}
if (rc && data) {
@@ -202,9 +200,9 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
oparms->path,
&data->symlink_target);
if (!rc) {
- memset(smb2_data, 0, sizeof(*smb2_data));
+ memset(&data->fi, 0, sizeof(data->fi));
oparms->create_options |= OPEN_REPARSE_POINT;
- rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data,
+ rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, data,
NULL, NULL, NULL);
oparms->create_options &= ~OPEN_REPARSE_POINT;
}
@@ -238,23 +236,22 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
rc = 0;
}
- if (smb2_data) {
+ if (data) {
/* if open response does not have IndexNumber field - get it */
- if (smb2_data->IndexNumber == 0) {
+ if (data->fi.IndexNumber == 0) {
rc = SMB2_get_srv_num(xid, oparms->tcon,
fid->persistent_fid,
fid->volatile_fid,
- &smb2_data->IndexNumber);
+ &data->fi.IndexNumber);
if (rc) {
/*
* let get_inode_info disable server inode
* numbers
*/
- smb2_data->IndexNumber = 0;
+ data->fi.IndexNumber = 0;
rc = 0;
}
}
- memcpy(&data->fi, smb2_data, sizeof(data->fi));
}
*oplock = smb2_oplock;
diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c
index 2a7355ce1a07..6270b33147d2 100644
--- a/fs/smb/client/smb2misc.c
+++ b/fs/smb/client/smb2misc.c
@@ -19,6 +19,8 @@
#include "nterr.h"
#include "cached_dir.h"
+static unsigned int __smb2_calc_size(void *buf, bool *have_data);
+
static int
check_smb2_hdr(struct smb2_hdr *shdr, __u64 mid)
{
@@ -145,6 +147,7 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
int command;
__u32 calc_len; /* calculated length */
__u64 mid;
+ bool have_data;
/* If server is a channel, select the primary channel */
pserver = SERVER_IS_CHAN(server) ? server->primary_server : server;
@@ -228,7 +231,8 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
}
}
- calc_len = smb2_calc_size(buf);
+ have_data = false;
+ calc_len = __smb2_calc_size(buf, &have_data);
/* For SMB2_IOCTL, OutputOffset and OutputLength are optional, so might
* be 0, and not a real miscalculation */
@@ -247,8 +251,13 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
/* Windows 7 server returns 24 bytes more */
if (calc_len + 24 == len && command == SMB2_OPLOCK_BREAK_HE)
return 0;
- /* server can return one byte more due to implied bcc[0] */
- if (calc_len == len + 1)
+ /*
+ * Server can return one byte more due to implied bcc[0].
+ * Allow it only when there is no data area; if data_length > 0
+ * the +1 gap indicates an overreported data length rather than
+ * the bcc[0] omission.
+ */
+ if (calc_len == len + 1 && !have_data)
return 0;
/*
@@ -409,14 +418,17 @@ smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *shdr)
/*
* Calculate the size of the SMB message based on the fixed header
* portion, the number of word parameters and the data portion of the message.
+ * If have_data is non-NULL, it is set to true when a non-empty data area was
+ * found (data_length > 0), allowing callers to distinguish the implied bcc[0]
+ * case (no data area) from an overreported data length.
*/
-unsigned int
-smb2_calc_size(void *buf)
+static unsigned int
+__smb2_calc_size(void *buf, bool *have_data)
{
struct smb2_pdu *pdu = buf;
struct smb2_hdr *shdr = &pdu->hdr;
int offset; /* the offset from the beginning of SMB to data area */
- int data_length; /* the length of the variable length data area */
+ int data_length = 0; /* the length of the variable length data area */
/* Structure Size has already been checked to make sure it is 64 */
int len = le16_to_cpu(shdr->StructureSize);
@@ -449,9 +461,17 @@ smb2_calc_size(void *buf)
}
calc_size_exit:
cifs_dbg(FYI, "SMB2 len %d\n", len);
+ if (have_data)
+ *have_data = (data_length > 0);
return len;
}
+unsigned int
+smb2_calc_size(void *buf)
+{
+ return __smb2_calc_size(buf, NULL);
+}
+
/* Note: caller must free return buffer */
__le16 *
cifs_convert_path_to_utf16(const char *from, struct cifs_sb_info *cifs_sb)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 06e9322a762a..6bce44b171e4 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -1772,8 +1772,8 @@ replay_again:
if (le32_to_cpu(io_rsp->OutputCount) < qi.input_buffer_length)
qi.input_buffer_length = le32_to_cpu(io_rsp->OutputCount);
if (qi.input_buffer_length > 0 &&
- le32_to_cpu(io_rsp->OutputOffset) + qi.input_buffer_length
- > rsp_iov[1].iov_len) {
+ size_add(le32_to_cpu(io_rsp->OutputOffset),
+ qi.input_buffer_length) > rsp_iov[1].iov_len) {
rc = -EFAULT;
goto out;
}
@@ -5237,7 +5237,7 @@ int __cifs_sfu_make_node(unsigned int xid, struct inode *inode,
{
struct TCP_Server_Info *server = tcon->ses->server;
struct cifs_open_parms oparms;
- struct cifs_open_info_data idata;
+ struct cifs_open_info_data idata = {};
struct cifs_io_parms io_parms = {};
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
struct cifs_fid fid;
@@ -5286,16 +5286,13 @@ int __cifs_sfu_make_node(unsigned int xid, struct inode *inode,
data = (u8 *)symname_utf16;
break;
case S_IFSOCK:
- type_len = 8;
- strscpy(type, "LnxSOCK");
- data = (u8 *)&pdev;
- data_len = sizeof(pdev);
+ /* SFU socket is system file with one zero byte */
+ type_len = 1;
+ type[0] = '\0';
break;
case S_IFIFO:
- type_len = 8;
- strscpy(type, "LnxFIFO");
- data = (u8 *)&pdev;
- data_len = sizeof(pdev);
+ /* SFU fifo is system file which is empty */
+ type_len = 0;
break;
default:
rc = -EPERM;
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index d058584b8f05..8f83ab377db1 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -2396,9 +2396,9 @@ parse_posix_ctxt(struct create_context *cc, struct smb2_file_all_info *info,
memset(posix, 0, sizeof(*posix));
- posix->nlink = le32_to_cpu(*(__le32 *)(beg + 0));
- posix->reparse_tag = le32_to_cpu(*(__le32 *)(beg + 4));
- posix->mode = le32_to_cpu(*(__le32 *)(beg + 8));
+ posix->nlink = get_unaligned_le32(beg);
+ posix->reparse_tag = get_unaligned_le32(beg + 4);
+ posix->mode = get_unaligned_le32(beg + 8);
sid = beg + 12;
sid_len = posix_info_sid_size(sid, end);
@@ -3287,7 +3287,7 @@ SMB2_open_free(struct smb_rqst *rqst)
int
SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
- __u8 *oplock, struct smb2_file_all_info *buf,
+ __u8 *oplock, struct cifs_open_info_data *buf,
struct create_posix_rsp *posix,
struct kvec *err_iov, int *buftype)
{
@@ -3302,6 +3302,7 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
int rc = 0;
int flags = 0;
int retries = 0, cur_sleep = 0;
+ struct smb2_file_all_info *file_info = buf ? &buf->fi : NULL;
replay_again:
/* reinitialize for possible replay */
@@ -3370,21 +3371,22 @@ replay_again:
oparms->fid->mid = le64_to_cpu(rsp->hdr.MessageId);
#endif /* CIFS_DEBUG2 */
- if (buf) {
- buf->CreationTime = rsp->CreationTime;
- buf->LastAccessTime = rsp->LastAccessTime;
- buf->LastWriteTime = rsp->LastWriteTime;
- buf->ChangeTime = rsp->ChangeTime;
- buf->AllocationSize = rsp->AllocationSize;
- buf->EndOfFile = rsp->EndofFile;
- buf->Attributes = rsp->FileAttributes;
- buf->NumberOfLinks = cpu_to_le32(1);
- buf->DeletePending = 0; /* successful open = not delete pending */
+ if (file_info) {
+ file_info->CreationTime = rsp->CreationTime;
+ file_info->LastAccessTime = rsp->LastAccessTime;
+ file_info->LastWriteTime = rsp->LastWriteTime;
+ file_info->ChangeTime = rsp->ChangeTime;
+ file_info->AllocationSize = rsp->AllocationSize;
+ file_info->EndOfFile = rsp->EndofFile;
+ file_info->Attributes = rsp->FileAttributes;
+ file_info->NumberOfLinks = cpu_to_le32(1);
+ buf->unknown_nlink = true;
+ file_info->DeletePending = 0; /* successful open = not delete pending */
}
rc = smb2_parse_contexts(server, &rsp_iov, &oparms->fid->epoch,
- oparms->fid->lease_key, oplock, buf, posix);
+ oparms->fid->lease_key, oplock, file_info, posix);
trace_smb3_open_done(xid, rsp->PersistentFileId, tcon->tid, ses->Suid,
oparms->create_options, oparms->desired_access,
@@ -5405,7 +5407,7 @@ int posix_info_sid_size(const void *beg, const void *end)
size_t subauth;
int total;
- if (beg + 1 > end)
+ if (beg + 2 > end)
return -1;
subauth = *(u8 *)(beg+1);
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index 78a4e1c340f9..e01effe45ae2 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -136,7 +136,7 @@ int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
struct cifs_tcon *tcon, const struct nls_table *cp);
int SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon);
int SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms,
- __le16 *path, __u8 *oplock, struct smb2_file_all_info *buf,
+ __le16 *path, __u8 *oplock, struct cifs_open_info_data *buf,
struct create_posix_rsp *posix, struct kvec *err_iov,
int *buftype);
int SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 86f521e849d5..4e7b6f0e6b8c 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -133,16 +133,17 @@ out:
* @blen: NTLMv2 blob length
* @domain_name: domain name
* @cryptkey: session crypto key
+ * @sess_key: derived session key output buffer
*
* Return: 0 on success, error number on error
*/
int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
struct ntlmv2_resp *ntlmv2, int blen, char *domain_name,
- char *cryptkey)
+ char *cryptkey, char *sess_key)
{
char ntlmv2_hash[CIFS_ENCPWD_SIZE];
char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE];
- char sess_key[SMB2_NTLMV2_SESSKEY_SIZE];
+ char base_key[SMB2_NTLMV2_SESSKEY_SIZE];
struct hmac_md5_ctx ctx;
int rc;
@@ -165,7 +166,7 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
/* Generate the session key */
hmac_md5_usingrawkey(ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE,
ntlmv2_rsp, CIFS_HMAC_MD5_HASH_SIZE,
- sess_key);
+ base_key);
if (crypto_memneq(ntlmv2->ntlmv2_hash, ntlmv2_rsp,
CIFS_HMAC_MD5_HASH_SIZE)) {
@@ -173,12 +174,12 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
goto out;
}
- memcpy(sess->sess_key, sess_key, sizeof(sess_key));
+ memcpy(sess_key, base_key, sizeof(base_key));
rc = 0;
out:
memzero_explicit(ntlmv2_hash, sizeof(ntlmv2_hash));
memzero_explicit(ntlmv2_rsp, sizeof(ntlmv2_rsp));
- memzero_explicit(sess_key, sizeof(sess_key));
+ memzero_explicit(base_key, sizeof(base_key));
return rc;
}
@@ -189,12 +190,13 @@ out:
* @blob_len: length of the @authblob message
* @conn: connection
* @sess: session of connection
+ * @sess_key: derived session key output buffer
*
* Return: 0 on success, error number on error
*/
int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
int blob_len, struct ksmbd_conn *conn,
- struct ksmbd_session *sess)
+ struct ksmbd_session *sess, char *sess_key)
{
char *domain_name;
unsigned int nt_off, dn_off;
@@ -234,7 +236,7 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
ret = ksmbd_auth_ntlmv2(conn, sess,
(struct ntlmv2_resp *)((char *)authblob + nt_off),
nt_len - CIFS_ENCPWD_SIZE,
- domain_name, conn->ntlmssp.cryptkey);
+ domain_name, conn->ntlmssp.cryptkey, sess_key);
kfree(domain_name);
if (ret)
return ret;
@@ -257,8 +259,8 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
if (!ctx_arc4)
return -ENOMEM;
- arc4_setkey(ctx_arc4, sess->sess_key, SMB2_NTLMV2_SESSKEY_SIZE);
- arc4_crypt(ctx_arc4, sess->sess_key,
+ arc4_setkey(ctx_arc4, sess_key, SMB2_NTLMV2_SESSKEY_SIZE);
+ arc4_crypt(ctx_arc4, sess_key,
(char *)authblob + sess_key_off, sess_key_len);
kfree_sensitive(ctx_arc4);
}
@@ -400,7 +402,8 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
#ifdef CONFIG_SMB_SERVER_KERBEROS5
int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
- int in_len, char *out_blob, int *out_len)
+ int in_len, char *out_blob, int *out_len,
+ char *sess_key)
{
struct ksmbd_spnego_authen_response *resp;
struct ksmbd_login_response_ext *resp_ext = NULL;
@@ -448,14 +451,14 @@ int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
} else {
if (!ksmbd_compare_user(sess->user, user)) {
ksmbd_debug(AUTH, "different user tried to reuse session\n");
- retval = -EPERM;
+ retval = -EKEYREJECTED;
ksmbd_free_user(user);
goto out;
}
ksmbd_free_user(user);
}
- memcpy(sess->sess_key, resp->payload, resp->session_key_len);
+ memcpy(sess_key, resp->payload, resp->session_key_len);
memcpy(out_blob, resp->payload + resp->session_key_len,
resp->spnego_blob_len);
*out_len = resp->spnego_blob_len;
@@ -466,7 +469,8 @@ out:
}
#else
int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
- int in_len, char *out_blob, int *out_len)
+ int in_len, char *out_blob, int *out_len,
+ char *sess_key)
{
return -EOPNOTSUPP;
}
@@ -525,7 +529,7 @@ struct derivation {
bool binding;
};
-static void generate_key(struct ksmbd_conn *conn, struct ksmbd_session *sess,
+static void generate_key(struct ksmbd_conn *conn, const char *sess_key,
struct kvec label, struct kvec context, __u8 *key,
unsigned int key_size)
{
@@ -536,7 +540,7 @@ static void generate_key(struct ksmbd_conn *conn, struct ksmbd_session *sess,
unsigned char prfhash[SMB2_HMACSHA256_SIZE];
struct hmac_sha256_ctx ctx;
- hmac_sha256_init_usingrawkey(&ctx, sess->sess_key,
+ hmac_sha256_init_usingrawkey(&ctx, sess_key,
SMB2_NTLMV2_SESSKEY_SIZE);
hmac_sha256_update(&ctx, i, 4);
hmac_sha256_update(&ctx, label.iov_base, label.iov_len);
@@ -559,18 +563,21 @@ static int generate_smb3signingkey(struct ksmbd_session *sess,
const struct derivation *signing)
{
struct channel *chann;
- char *key;
+ char *key, *sess_key;
chann = lookup_chann_list(sess, conn);
if (!chann)
return 0;
- if (conn->dialect >= SMB30_PROT_ID && signing->binding)
+ if (conn->dialect >= SMB30_PROT_ID && signing->binding) {
key = chann->smb3signingkey;
- else
+ sess_key = chann->sess_key;
+ } else {
key = sess->smb3signingkey;
+ sess_key = sess->sess_key;
+ }
- generate_key(conn, sess, signing->label, signing->context, key,
+ generate_key(conn, sess_key, signing->label, signing->context, key,
SMB3_SIGN_KEY_SIZE);
if (!(conn->dialect >= SMB30_PROT_ID && signing->binding))
@@ -627,11 +634,11 @@ static void generate_smb3encryptionkey(struct ksmbd_conn *conn,
struct ksmbd_session *sess,
const struct derivation_twin *ptwin)
{
- generate_key(conn, sess, ptwin->encryption.label,
+ generate_key(conn, sess->sess_key, ptwin->encryption.label,
ptwin->encryption.context, sess->smb3encryptionkey,
SMB3_ENC_DEC_KEY_SIZE);
- generate_key(conn, sess, ptwin->decryption.label,
+ generate_key(conn, sess->sess_key, ptwin->decryption.label,
ptwin->decryption.context,
sess->smb3decryptionkey, SMB3_ENC_DEC_KEY_SIZE);
diff --git a/fs/smb/server/auth.h b/fs/smb/server/auth.h
index 5767aabc63c9..f14b7c033264 100644
--- a/fs/smb/server/auth.h
+++ b/fs/smb/server/auth.h
@@ -41,17 +41,18 @@ int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
void ksmbd_copy_gss_neg_header(void *buf);
int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
struct ntlmv2_resp *ntlmv2, int blen, char *domain_name,
- char *cryptkey);
+ char *cryptkey, char *sess_key);
int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
int blob_len, struct ksmbd_conn *conn,
- struct ksmbd_session *sess);
+ struct ksmbd_session *sess, char *sess_key);
int ksmbd_decode_ntlmssp_neg_blob(struct negotiate_message *negblob,
int blob_len, struct ksmbd_conn *conn);
unsigned int
ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
struct ksmbd_conn *conn);
int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
- int in_len, char *out_blob, int *out_len);
+ int in_len, char *out_blob, int *out_len,
+ char *sess_key);
void ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
int n_vec, char *sig);
void ksmbd_sign_smb3_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index df0554a2c50d..88104f0cf363 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -67,6 +67,13 @@ struct ksmbd_work {
/* Number of granted credits */
unsigned int credits_granted;
+ /*
+ * Credit charge added to conn->outstanding_credits at receive time
+ * for the SMB2 PDU currently being processed, pending release. Zero
+ * once the charge has been returned (on the response or error path).
+ */
+ unsigned short credit_charge;
+
/* response smb header size */
unsigned int response_sz;
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index de58aed76cb4..d6331184ebfc 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -255,7 +255,7 @@ static void free_channel_list(struct ksmbd_session *sess)
down_write(&sess->chann_lock);
xa_for_each(&sess->ksmbd_chann_list, index, chann) {
xa_erase(&sess->ksmbd_chann_list, index);
- kfree(chann);
+ kfree_sensitive(chann);
}
xa_destroy(&sess->ksmbd_chann_list);
@@ -449,7 +449,7 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
if (!chann)
return -ENOENT;
- kfree(chann);
+ kfree_sensitive(chann);
return 0;
}
diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h
index 6aebd385be84..8893a9aaede7 100644
--- a/fs/smb/server/mgmt/user_session.h
+++ b/fs/smb/server/mgmt/user_session.h
@@ -19,6 +19,7 @@
struct ksmbd_file_table;
struct channel {
+ char sess_key[SMB2_NTLMV2_SESSKEY_SIZE];
__u8 smb3signingkey[SMB3_SIGN_KEY_SIZE];
struct ksmbd_conn *conn;
};
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 31dd9f3479b2..3c55ae5d6a11 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -278,10 +278,24 @@ struct oplock_info *opinfo_get(struct ksmbd_file *fp)
return opinfo;
}
-static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
+struct oplock_snapshot {
+ bool durable_open;
+ bool durable_detached;
+ unsigned long long fid;
+};
+
+static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci,
+ struct ksmbd_file *skip_fp,
+ struct oplock_snapshot *snapshot)
{
struct oplock_info *opinfo;
+ if (snapshot) {
+ snapshot->durable_open = false;
+ snapshot->durable_detached = false;
+ snapshot->fid = KSMBD_NO_FID;
+ }
+
down_read(&ci->m_lock);
opinfo = list_first_entry_or_null(&ci->m_op_list, struct oplock_info,
op_entry);
@@ -295,6 +309,16 @@ static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
opinfo = NULL;
}
}
+
+ if (opinfo && snapshot && opinfo->o_fp &&
+ opinfo->o_fp != skip_fp &&
+ READ_ONCE(opinfo->o_fp->is_durable)) {
+ snapshot->durable_open = true;
+ snapshot->durable_detached =
+ !READ_ONCE(opinfo->o_fp->conn) ||
+ !READ_ONCE(opinfo->o_fp->tcon);
+ snapshot->fid = opinfo->fid;
+ }
}
up_read(&ci->m_lock);
@@ -314,7 +338,7 @@ void opinfo_put(struct oplock_info *opinfo)
static bool ksmbd_inode_has_lease(struct ksmbd_inode *ci)
{
- struct oplock_info *opinfo = opinfo_get_list(ci);
+ struct oplock_info *opinfo = opinfo_get_list(ci, NULL, NULL);
bool is_lease;
if (!opinfo)
@@ -1180,6 +1204,36 @@ again:
return err;
}
+struct oplock_break_entry {
+ struct list_head list;
+ struct oplock_info *opinfo;
+};
+
+static int oplock_break_add(struct list_head *head, struct oplock_info *opinfo)
+{
+ struct oplock_break_entry *ent;
+
+ ent = kmalloc_obj(struct oplock_break_entry, KSMBD_DEFAULT_GFP);
+ if (!ent)
+ return -ENOMEM;
+
+ ent->opinfo = opinfo;
+ list_add_tail(&ent->list, head);
+ return 0;
+}
+
+static void oplock_break_drain_none(struct list_head *head)
+{
+ struct oplock_break_entry *ent, *tmp;
+
+ list_for_each_entry_safe(ent, tmp, head, list) {
+ oplock_break(ent->opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
+ list_del(&ent->list);
+ opinfo_put(ent->opinfo);
+ kfree(ent);
+ }
+}
+
void destroy_lease_table(struct ksmbd_conn *conn)
{
struct lease_table *lb, *lbtmp;
@@ -1289,6 +1343,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
{
struct oplock_info *opinfo;
struct ksmbd_inode *p_ci = NULL;
+ LIST_HEAD(brk_list);
if (lctx->version != 2)
return;
@@ -1314,12 +1369,14 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
continue;
}
- oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
- opinfo_put(opinfo);
+ if (oplock_break_add(&brk_list, opinfo))
+ opinfo_put(opinfo);
}
}
up_read(&p_ci->m_lock);
+ oplock_break_drain_none(&brk_list);
+
ksmbd_inode_put(p_ci);
}
@@ -1327,6 +1384,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
{
struct oplock_info *opinfo;
struct ksmbd_inode *p_ci = NULL;
+ LIST_HEAD(brk_list);
rcu_read_lock();
opinfo = rcu_dereference(fp->f_opinfo);
@@ -1355,12 +1413,14 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
continue;
}
- oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
- opinfo_put(opinfo);
+ if (oplock_break_add(&brk_list, opinfo))
+ opinfo_put(opinfo);
}
}
up_read(&p_ci->m_lock);
+ oplock_break_drain_none(&brk_list);
+
ksmbd_inode_put(p_ci);
}
@@ -1385,6 +1445,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
struct oplock_info *opinfo = NULL, *prev_opinfo = NULL;
struct ksmbd_inode *ci = fp->f_ci;
struct lease_table *new_lb = NULL;
+ struct oplock_snapshot prev_op_snapshot;
bool prev_op_has_lease;
bool prev_durable_open = false;
bool prev_durable_detached = false;
@@ -1452,7 +1513,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
goto out;
}
}
- prev_opinfo = opinfo_get_list(ci);
+ prev_opinfo = opinfo_get_list(ci, fp, &prev_op_snapshot);
if (!prev_opinfo ||
(prev_opinfo->level == SMB2_OPLOCK_LEVEL_NONE && lctx)) {
opinfo_put(prev_opinfo);
@@ -1474,13 +1535,9 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
goto op_break_not_needed;
}
- if (prev_opinfo->o_fp && prev_opinfo->o_fp != fp &&
- prev_opinfo->o_fp->is_durable) {
- prev_durable_open = true;
- prev_durable_detached = !prev_opinfo->o_fp->conn ||
- !prev_opinfo->o_fp->tcon;
- prev_fid = prev_opinfo->fid;
- }
+ prev_durable_open = prev_op_snapshot.durable_open;
+ prev_durable_detached = prev_op_snapshot.durable_detached;
+ prev_fid = prev_op_snapshot.fid;
err = oplock_break(prev_opinfo, break_level, work,
share_ret < 0 && prev_opinfo->is_lease);
@@ -1571,7 +1628,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
struct oplock_info *brk_opinfo;
bool sent_break = false;
- brk_opinfo = opinfo_get_list(fp->f_ci);
+ brk_opinfo = opinfo_get_list(fp->f_ci, NULL, NULL);
if (!brk_opinfo)
return false;
if (brk_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH &&
@@ -1602,9 +1659,11 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
bool send_interim, bool send_oplock_break)
{
struct oplock_info *op, *brk_op;
+ struct oplock_break_entry *ent, *tmp;
struct ksmbd_inode *ci;
struct ksmbd_conn *conn = work->conn;
bool sent_interim = false;
+ LIST_HEAD(brk_list);
if (!test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_OPLOCKS))
@@ -1646,6 +1705,22 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
SMB2_LEASE_KEY_SIZE))
goto next;
brk_op->open_trunc = is_trunc;
+
+ /*
+ * Defer the break until ci->m_lock is released: oplock_break()
+ * may block waiting for the lease break acknowledgment, and the
+ * close that wakes that wait needs ci->m_lock for write.
+ */
+ if (!oplock_break_add(&brk_list, brk_op))
+ continue;
+next:
+ opinfo_put(brk_op);
+ }
+ up_read(&ci->m_lock);
+
+ list_for_each_entry_safe(ent, tmp, &brk_list, list) {
+ brk_op = ent->opinfo;
+
if (!brk_op->is_lease && !send_oplock_break) {
brk_op->level = SMB2_OPLOCK_LEVEL_NONE;
brk_op->op_state = OPLOCK_STATE_NONE;
@@ -1657,10 +1732,10 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
false);
}
sent_interim = true;
-next:
+ list_del(&ent->list);
opinfo_put(brk_op);
+ kfree(ent);
}
- up_read(&ci->m_lock);
if (op)
opinfo_put(op);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 36feda7e0942..f5baba934840 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -199,6 +199,12 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
else
conn->ops->set_rsp_status(work,
STATUS_USER_SESSION_DELETED);
+ if (conn->ops->is_sign_req(work, conn->ops->get_cmd_val(work))) {
+ struct smb2_hdr *rsp_hdr;
+
+ rsp_hdr = ksmbd_resp_buf_curr(work);
+ rsp_hdr->Flags |= SMB2_FLAGS_SIGNED;
+ }
goto send;
} else if (rc > 0) {
rc = conn->ops->get_ksmbd_tcon(work);
@@ -237,11 +243,31 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (work->sess &&
(work->sess->sign || smb3_11_final_sess_setup_resp(work) ||
- conn->ops->is_sign_req(work, command)))
- conn->ops->set_sign_rsp(work);
+ conn->ops->is_sign_req(work, command))) {
+ if (command == SMB2_SESSION_SETUP_HE &&
+ work->sess->dialect >= SMB30_PROT_ID &&
+ conn->dialect < SMB30_PROT_ID)
+ smb3_set_sign_rsp(work);
+ else
+ conn->ops->set_sign_rsp(work);
+ }
} while (is_chained == true);
send:
+ /*
+ * Release any credit charge still outstanding for this request. On
+ * the normal path smb2_set_rsp_credits() already returned it, but the
+ * abort, error and send-no-response paths skip that call, so the
+ * charge would otherwise leak and eventually exhaust the connection's
+ * outstanding credit window.
+ */
+ if (work->credit_charge) {
+ spin_lock(&conn->credits_lock);
+ conn->outstanding_credits -= work->credit_charge;
+ work->credit_charge = 0;
+ spin_unlock(&conn->credits_lock);
+ }
+
if (work->tcon)
ksmbd_tree_connect_put(work->tcon);
smb3_preauth_hash_rsp(work);
diff --git a/fs/smb/server/smb2misc.c b/fs/smb/server/smb2misc.c
index a1ddca21c47b..c0c4edd092c2 100644
--- a/fs/smb/server/smb2misc.c
+++ b/fs/smb/server/smb2misc.c
@@ -261,8 +261,12 @@ calc_size_exit:
static inline int smb2_query_info_req_len(struct smb2_query_info_req *h)
{
- return le32_to_cpu(h->InputBufferLength) +
- le32_to_cpu(h->OutputBufferLength);
+ return le32_to_cpu(h->InputBufferLength);
+}
+
+static inline int smb2_query_info_resp_len(struct smb2_query_info_req *h)
+{
+ return le32_to_cpu(h->OutputBufferLength);
}
static inline int smb2_set_info_req_len(struct smb2_set_info_req *h)
@@ -297,9 +301,10 @@ static inline int smb2_ioctl_resp_len(struct smb2_ioctl_req *h)
le32_to_cpu(h->MaxOutputResponse);
}
-static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
+static int smb2_validate_credit_charge(struct ksmbd_work *work,
struct smb2_hdr *hdr)
{
+ struct ksmbd_conn *conn = work->conn;
unsigned int req_len = 0, expect_resp_len = 0, calc_credit_num, max_len;
unsigned short credit_charge = le16_to_cpu(hdr->CreditCharge);
void *__hdr = hdr;
@@ -308,6 +313,7 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
switch (hdr->Command) {
case SMB2_QUERY_INFO:
req_len = smb2_query_info_req_len(__hdr);
+ expect_resp_len = smb2_query_info_resp_len(__hdr);
break;
case SMB2_SET_INFO:
req_len = smb2_set_info_req_len(__hdr);
@@ -356,8 +362,10 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
ksmbd_debug(SMB, "Limits exceeding the maximum allowable outstanding requests, given : %u, pending : %u\n",
credit_charge, conn->outstanding_credits);
ret = 1;
- } else
+ } else {
conn->outstanding_credits += credit_charge;
+ work->credit_charge = credit_charge;
+ }
spin_unlock(&conn->credits_lock);
@@ -460,7 +468,7 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
validate_credit:
if ((work->conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
- smb2_validate_credit_charge(work->conn, hdr))
+ smb2_validate_credit_charge(work, hdr))
return 1;
return 0;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 5859fa68bb84..b73167785e87 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -61,6 +61,9 @@ static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
(FILE_ATTRIBUTE_MASK & ~(FILE_ATTRIBUTE_INTEGRITY_STREAM | \
FILE_ATTRIBUTE_NO_SCRUB_DATA))
+/* Windows reports automatic write-time updates at roughly 15 ms resolution. */
+#define KSMBD_WRITE_TIME_RESOLUTION (15ULL * 10000)
+
/**
* check_session_id() - check for valid session id in smb header
* @conn: connection instance
@@ -95,6 +98,47 @@ struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn
return chann;
}
+#define KSMBD_MAX_CHANNELS 32
+
+static int register_session_channel(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn,
+ const char *sess_key)
+{
+ struct channel *chann, *old;
+ unsigned long index;
+ unsigned int count = 0;
+ int rc = 0;
+
+ down_write(&sess->chann_lock);
+ if (xa_load(&sess->ksmbd_chann_list, (long)conn))
+ goto out;
+
+ xa_for_each(&sess->ksmbd_chann_list, index, chann)
+ count++;
+ if (count >= KSMBD_MAX_CHANNELS) {
+ rc = -ENOSPC;
+ goto out;
+ }
+
+ chann = kmalloc_obj(struct channel, KSMBD_DEFAULT_GFP);
+ if (!chann) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ chann->conn = conn;
+ memcpy(chann->sess_key, sess_key, sizeof(chann->sess_key));
+ old = xa_store(&sess->ksmbd_chann_list, (long)conn, chann,
+ KSMBD_DEFAULT_GFP);
+ if (xa_is_err(old)) {
+ kfree_sensitive(chann);
+ rc = xa_err(old);
+ }
+out:
+ up_write(&sess->chann_lock);
+ return rc;
+}
+
/**
* smb2_get_ksmbd_tcon() - get tree connection information using a tree id.
* @work: smb work
@@ -363,6 +407,7 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
conn->total_credits -= credit_charge;
conn->outstanding_credits -= credit_charge;
+ work->credit_charge = 0;
credits_requested = max_t(unsigned short,
le16_to_cpu(req_hdr->CreditRequest), 1);
@@ -1643,9 +1688,11 @@ static int ntlm_authenticate(struct ksmbd_work *work,
{
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
- struct channel *chann = NULL, *old;
struct ksmbd_user *user;
+ char channel_key[SMB2_NTLMV2_SESSKEY_SIZE] = {};
+ char *auth_key = conn->binding ? channel_key : sess->sess_key;
u64 prev_id;
+ bool binding = conn->binding;
int sz, rc;
ksmbd_debug(SMB, "authenticate phase\n");
@@ -1687,7 +1734,7 @@ static int ntlm_authenticate(struct ksmbd_work *work,
if (!ksmbd_compare_user(sess->user, user)) {
ksmbd_free_user(user);
- return -EPERM;
+ return -EKEYREJECTED;
}
ksmbd_free_user(user);
} else {
@@ -1704,11 +1751,13 @@ static int ntlm_authenticate(struct ksmbd_work *work,
sz = conn->mechTokenLen;
else
sz = le16_to_cpu(req->SecurityBufferLength);
- rc = ksmbd_decode_ntlmssp_auth_blob(authblob, sz, conn, sess);
+ rc = ksmbd_decode_ntlmssp_auth_blob(authblob, sz, conn, sess,
+ auth_key);
if (rc) {
set_user_flag(sess->user, KSMBD_USER_FLAG_BAD_PASSWORD);
ksmbd_debug(SMB, "authentication failed\n");
- return -EPERM;
+ rc = -EPERM;
+ goto out;
}
}
@@ -1743,37 +1792,30 @@ static int ntlm_authenticate(struct ksmbd_work *work,
binding_session:
if (conn->dialect >= SMB30_PROT_ID) {
- chann = lookup_chann_list(sess, conn);
- if (!chann) {
- chann = kmalloc_obj(struct channel, KSMBD_DEFAULT_GFP);
- if (!chann)
- return -ENOMEM;
-
- chann->conn = conn;
- down_write(&sess->chann_lock);
- old = xa_store(&sess->ksmbd_chann_list, (long)conn, chann,
- KSMBD_DEFAULT_GFP);
- up_write(&sess->chann_lock);
- if (xa_is_err(old)) {
- kfree(chann);
- return xa_err(old);
- }
- }
+ rc = register_session_channel(sess, conn, auth_key);
+ if (rc)
+ goto out;
}
if (conn->ops->generate_signingkey) {
rc = conn->ops->generate_signingkey(sess, conn);
if (rc) {
ksmbd_debug(SMB, "SMB3 signing key generation failed\n");
- return -EINVAL;
+ rc = -EINVAL;
+ goto out;
}
}
if (!ksmbd_conn_lookup_dialect(conn)) {
pr_err("fail to verify the dialect\n");
- return -ENOENT;
+ rc = -ENOENT;
+ goto out;
}
- return 0;
+ rc = 0;
+out:
+ if (binding)
+ memzero_explicit(channel_key, sizeof(channel_key));
+ return rc;
}
#ifdef CONFIG_SMB_SERVER_KERBEROS5
@@ -1784,8 +1826,10 @@ static int krb5_authenticate(struct ksmbd_work *work,
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
char *in_blob, *out_blob;
- struct channel *chann = NULL, *old;
+ char channel_key[SMB2_NTLMV2_SESSKEY_SIZE] = {};
+ char *auth_key = conn->binding ? channel_key : sess->sess_key;
u64 prev_sess_id;
+ bool binding = conn->binding;
int in_len, out_len;
int retval;
@@ -1798,10 +1842,12 @@ static int krb5_authenticate(struct ksmbd_work *work,
(le16_to_cpu(rsp->SecurityBufferOffset) + 4);
retval = ksmbd_krb5_authenticate(sess, in_blob, in_len,
- out_blob, &out_len);
+ out_blob, &out_len, auth_key);
if (retval) {
ksmbd_debug(SMB, "krb5 authentication failed\n");
- return -EINVAL;
+ if (retval != -EKEYREJECTED)
+ retval = -EINVAL;
+ goto out;
}
/* Check previous session */
@@ -1838,37 +1884,30 @@ static int krb5_authenticate(struct ksmbd_work *work,
binding_session:
if (conn->dialect >= SMB30_PROT_ID) {
- chann = lookup_chann_list(sess, conn);
- if (!chann) {
- chann = kmalloc_obj(struct channel, KSMBD_DEFAULT_GFP);
- if (!chann)
- return -ENOMEM;
-
- chann->conn = conn;
- down_write(&sess->chann_lock);
- old = xa_store(&sess->ksmbd_chann_list, (long)conn,
- chann, KSMBD_DEFAULT_GFP);
- up_write(&sess->chann_lock);
- if (xa_is_err(old)) {
- kfree(chann);
- return xa_err(old);
- }
- }
+ retval = register_session_channel(sess, conn, auth_key);
+ if (retval)
+ goto out;
}
if (conn->ops->generate_signingkey) {
retval = conn->ops->generate_signingkey(sess, conn);
if (retval) {
ksmbd_debug(SMB, "SMB3 signing key generation failed\n");
- return -EINVAL;
+ retval = -EINVAL;
+ goto out;
}
}
if (!ksmbd_conn_lookup_dialect(conn)) {
pr_err("fail to verify the dialect\n");
- return -ENOENT;
+ retval = -ENOENT;
+ goto out;
}
- return 0;
+ retval = 0;
+out:
+ if (binding)
+ memzero_explicit(channel_key, sizeof(channel_key));
+ return retval;
}
#else
static int krb5_authenticate(struct ksmbd_work *work,
@@ -1974,13 +2013,36 @@ int smb2_sess_setup(struct ksmbd_work *work)
} else if ((conn->dialect < SMB30_PROT_ID ||
server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) &&
(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
- sess = NULL;
+ sess = ksmbd_session_lookup_slowpath(le64_to_cpu(req->hdr.SessionId));
+ if (sess) {
+ int sign_ret;
+
+ work->sess = sess;
+ if (sess->dialect >= SMB30_PROT_ID)
+ sign_ret = smb3_check_sign_req(work);
+ else
+ sign_ret = smb2_check_sign_req(work);
+ if (sess->state != SMB2_SESSION_VALID ||
+ !(req->hdr.Flags & SMB2_FLAGS_SIGNED) ||
+ !sign_ret) {
+ ksmbd_user_session_put(sess);
+ work->sess = NULL;
+ sess = NULL;
+ }
+ }
rc = -EACCES;
goto out_err;
} else {
sess = ksmbd_session_lookup(conn,
le64_to_cpu(req->hdr.SessionId));
if (!sess) {
+ sess = ksmbd_session_lookup_slowpath(le64_to_cpu(req->hdr.SessionId));
+ if (sess && !lookup_chann_list(sess, conn)) {
+ ksmbd_user_session_put(sess);
+ sess = NULL;
+ }
+ }
+ if (!sess) {
rc = -ENOENT;
goto out_err;
}
@@ -2090,12 +2152,19 @@ out_err:
rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
else if (rc == -EFAULT)
rsp->hdr.Status = STATUS_NETWORK_SESSION_EXPIRED;
- else if (rc == -ENOMEM)
+ else if (rc == -ENOMEM || rc == -ENOSPC)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
else if (rc == -EOPNOTSUPP)
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ else if (rc == -EKEYREJECTED)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
else if (rc)
rsp->hdr.Status = STATUS_LOGON_FAILURE;
+ if ((rsp->hdr.Status == STATUS_USER_SESSION_DELETED ||
+ (rsp->hdr.Status == STATUS_INVALID_PARAMETER &&
+ (req->Flags & SMB2_SESSION_REQ_FLAG_BINDING))) &&
+ (req->hdr.Flags & SMB2_FLAGS_SIGNED))
+ rsp->hdr.Flags |= SMB2_FLAGS_SIGNED;
if (conn->mechToken) {
kfree(conn->mechToken);
@@ -2103,6 +2172,17 @@ out_err:
}
if (rc < 0) {
+ if (sess && conn->dialect == SMB311_PROT_ID &&
+ (req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
+ struct preauth_session *preauth_sess;
+
+ preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id);
+ if (preauth_sess) {
+ list_del(&preauth_sess->preauth_entry);
+ kfree(preauth_sess);
+ }
+ }
+
/*
* SecurityBufferOffset should be set to zero
* in session setup error response.
@@ -2129,8 +2209,16 @@ out_err:
sess->last_active = jiffies;
sess->state = SMB2_SESSION_EXPIRED;
}
- ksmbd_user_session_put(sess);
- work->sess = NULL;
+ /*
+ * Keep the binding session reference until the response is
+ * signed and sent. Error responses for a signed binding
+ * request are signed with the existing session signing key.
+ */
+ if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) ||
+ work->sess != sess) {
+ ksmbd_user_session_put(sess);
+ work->sess = NULL;
+ }
if (try_delay) {
ksmbd_conn_set_need_reconnect(conn);
ssleep(5);
@@ -3942,6 +4030,7 @@ reconnected_fp:
time = ksmbd_UnixTimeToNT(stat.atime);
rsp->LastAccessTime = cpu_to_le64(time);
time = ksmbd_UnixTimeToNT(stat.mtime);
+ fp->open_mtime = time;
rsp->LastWriteTime = cpu_to_le64(time);
rsp->ChangeTime = cpu_to_le64(fp->change_time);
/*
@@ -6408,6 +6497,9 @@ int smb2_close(struct ksmbd_work *work)
time = ksmbd_UnixTimeToNT(stat.atime);
rsp->LastAccessTime = cpu_to_le64(time);
time = ksmbd_UnixTimeToNT(stat.mtime);
+ if (time > fp->open_mtime &&
+ time - fp->open_mtime < KSMBD_WRITE_TIME_RESOLUTION)
+ time = fp->open_mtime;
rsp->LastWriteTime = cpu_to_le64(time);
rsp->ChangeTime = cpu_to_le64(fp->change_time);
ksmbd_fd_put(work, fp);
@@ -6920,16 +7012,18 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
}
case FILE_LINK_INFORMATION:
{
- if (!(fp->daccess & FILE_DELETE_LE)) {
- pr_err("no right to delete : 0x%x\n", fp->daccess);
- return -EACCES;
- }
+ struct smb2_file_link_info *file_info;
if (buf_len < sizeof(struct smb2_file_link_info))
return -EMSGSIZE;
- return smb2_create_link(work, work->tcon->share_conf,
- (struct smb2_file_link_info *)buffer,
+ file_info = (struct smb2_file_link_info *)buffer;
+ if (file_info->ReplaceIfExists && !(fp->daccess & FILE_DELETE_LE)) {
+ pr_err("no right to delete : 0x%x\n", fp->daccess);
+ return -EACCES;
+ }
+
+ return smb2_create_link(work, work->tcon->share_conf, file_info,
buf_len, fp->filp,
work->conn->local_nls);
}
@@ -7077,6 +7171,8 @@ err_out:
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
else if (rc == -EMSGSIZE)
rsp->hdr.Status = STATUS_INFO_LENGTH_MISMATCH;
+ else if (rc == -ENOSPC || rc == -EFBIG)
+ rsp->hdr.Status = STATUS_DISK_FULL;
else if (rc == -ESHARE)
rsp->hdr.Status = STATUS_SHARING_VIOLATION;
else if (rc == -ENOENT)
@@ -7324,7 +7420,7 @@ int smb2_read(struct ksmbd_work *work)
ksmbd_debug(SMB, "filename %pD, offset %lld, len %zu\n",
fp->filp, offset, length);
- aux_payload_buf = kvzalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
+ aux_payload_buf = kvmalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
if (!aux_payload_buf) {
err = -ENOMEM;
goto out;
@@ -9480,7 +9576,6 @@ bool smb2_is_sign_req(struct ksmbd_work *work, unsigned int command)
if ((rcv_hdr2->Flags & SMB2_FLAGS_SIGNED) &&
command != SMB2_NEGOTIATE_HE &&
- command != SMB2_SESSION_SETUP_HE &&
command != SMB2_OPLOCK_BREAK_HE)
return true;
@@ -9592,9 +9687,13 @@ int smb3_check_sign_req(struct ksmbd_work *work)
} else {
chann = lookup_chann_list(work->sess, conn);
if (!chann) {
- return 0;
+ if (le16_to_cpu(hdr->Command) != SMB2_SESSION_SETUP_HE ||
+ !(hdr->Flags & SMB2_FLAGS_SIGNED))
+ return 0;
+ signing_key = work->sess->smb3signingkey;
+ } else {
+ signing_key = chann->smb3signingkey;
}
- signing_key = chann->smb3signingkey;
}
if (!signing_key) {
@@ -9629,13 +9728,14 @@ void smb3_set_sign_rsp(struct ksmbd_work *work)
struct channel *chann;
char signature[SMB2_CMACAES_SIZE];
struct kvec *iov;
+ u16 command = conn->ops->get_cmd_val(work);
int n_vec = 1;
char *signing_key;
hdr = ksmbd_resp_buf_curr(work);
- if (conn->binding == false &&
- le16_to_cpu(hdr->Command) == SMB2_SESSION_SETUP_HE) {
+ if (command == SMB2_SESSION_SETUP_HE &&
+ (!conn->binding || hdr->Status != STATUS_SUCCESS)) {
signing_key = work->sess->smb3signingkey;
} else {
chann = lookup_chann_list(work->sess, work->conn);
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index 340ea98fa494..67b39b4d218c 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -258,6 +258,7 @@ static int sid_to_id(struct mnt_idmap *idmap,
struct smb_sid *psid, uint sidtype,
struct smb_fattr *fattr)
{
+ const struct smb_sid *sid_prefix;
int rc = -EINVAL;
/*
@@ -279,6 +280,12 @@ static int sid_to_id(struct mnt_idmap *idmap,
kuid_t uid;
uid_t id;
+ /* Only the server domain RID has a local uid representation. */
+ sid_prefix = &server_conf.domain_sid;
+ if (psid->num_subauth != sid_prefix->num_subauth + 1 ||
+ compare_sids(psid, sid_prefix))
+ return -EINVAL;
+
id = le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]);
uid = KUIDT_INIT(id);
uid = from_vfsuid(idmap, &init_user_ns, VFSUIDT_INIT(uid));
@@ -290,6 +297,12 @@ static int sid_to_id(struct mnt_idmap *idmap,
kgid_t gid;
gid_t id;
+ /* Local gids are represented by S-1-22-2-<gid>. */
+ sid_prefix = &sid_unix_groups;
+ if (psid->num_subauth != sid_prefix->num_subauth + 1 ||
+ compare_sids(psid, sid_prefix))
+ return -EINVAL;
+
id = le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]);
gid = KGIDT_INIT(id);
gid = from_vfsgid(idmap, &init_user_ns, VFSGIDT_INIT(gid));
@@ -374,6 +387,7 @@ static void parse_dacl(struct mnt_idmap *idmap,
{
int i, ret;
u16 num_aces = 0;
+ u16 dacl_size;
unsigned int acl_size;
char *acl_base;
struct smb_ace **ppace;
@@ -403,7 +417,11 @@ static void parse_dacl(struct mnt_idmap *idmap,
if (num_aces <= 0)
return;
- if (num_aces > (le16_to_cpu(pdacl->size) - sizeof(struct smb_acl)) /
+ dacl_size = le16_to_cpu(pdacl->size);
+ if (dacl_size < sizeof(struct smb_acl))
+ return;
+
+ if (num_aces > (dacl_size - sizeof(struct smb_acl)) /
(offsetof(struct smb_ace, sid) +
offsetof(struct smb_sid, sub_auth) + sizeof(__le16)))
return;
@@ -740,12 +758,18 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap,
if (nt_ace_size > aces_size)
break;
+ if (ntace->sid.num_subauth == 0 ||
+ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES)
+ goto next_ace;
+
memcpy((char *)pndace + size, ntace, nt_ace_size);
if (check_add_overflow(size, nt_ace_size, &size))
break;
+ num_aces++;
+
+next_ace:
aces_size -= nt_ace_size;
ntace = (struct smb_ace *)((char *)ntace + nt_ace_size);
- num_aces++;
}
}
@@ -889,9 +913,9 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
rc = sid_to_id(idmap, owner_sid_ptr, SIDOWNER, fattr);
if (rc) {
- pr_err("%s: Error %d mapping Owner SID to uid\n",
- __func__, rc);
+ ksmbd_debug(SMB, "Owner SID has no Unix uid mapping\n");
owner_sid_ptr = NULL;
+ rc = 0;
}
}
@@ -907,9 +931,9 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
}
rc = sid_to_id(idmap, group_sid_ptr, SIDUNIX_GROUP, fattr);
if (rc) {
- pr_err("%s: Error %d mapping Group SID to gid\n",
- __func__, rc);
+ ksmbd_debug(SMB, "Group SID has no Unix gid mapping\n");
group_sid_ptr = NULL;
+ rc = 0;
}
}
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index f5fa22d87603..d0a0ad15d803 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1487,8 +1487,8 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
if (rc < 0)
pr_err("Failed to store XATTR ntacl :%d\n", rc);
- kfree(sd_ndr.data);
out:
+ kfree(sd_ndr.data);
kfree(acl_ndr.data);
kfree(smb_acl);
kfree(def_smb_acl);
@@ -1504,7 +1504,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
struct ndr n;
struct inode *inode = d_inode(dentry);
struct ndr acl_ndr = {0};
- struct xattr_ntacl acl;
+ struct xattr_ntacl acl = {0};
struct xattr_smb_acl *smb_acl = NULL, *def_smb_acl = NULL;
__u8 cmp_hash[XATTR_SD_HASH_SIZE] = {0};
@@ -1515,7 +1515,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
n.length = rc;
rc = ndr_decode_v4_ntacl(&n, &acl);
if (rc)
- goto free_n_data;
+ goto out_free;
smb_acl = ksmbd_vfs_make_xattr_posix_acl(idmap, inode,
ACL_TYPE_ACCESS);
@@ -1541,6 +1541,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
*pntsd = acl.sd_buf;
if (acl.sd_size < sizeof(struct smb_ntsd)) {
pr_err("sd size is invalid\n");
+ rc = -EINVAL;
goto out_free;
}
@@ -1560,8 +1561,6 @@ out_free:
kfree(acl.sd_buf);
*pntsd = NULL;
}
-
-free_n_data:
kfree(n.data);
return rc;
}
@@ -1576,14 +1575,15 @@ int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap,
err = ndr_encode_dos_attr(&n, da);
if (err)
- return err;
+ goto out;
err = ksmbd_vfs_setxattr(idmap, path, XATTR_NAME_DOS_ATTRIBUTE,
(void *)n.data, n.offset, 0, get_write);
if (err)
ksmbd_debug(SMB, "failed to store dos attribute in xattr\n");
- kfree(n.data);
+out:
+ kfree(n.data);
return err;
}
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index fde22742d193..d95c405eab11 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -581,7 +581,20 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
if (fp->conn || !fp->is_durable ||
fp->f_state != FP_INITED)
continue;
- list_move_tail(&fp->node, &dispose);
+
+ /*
+ * Claim the close before unlinking fp from m_fp_list.
+ * refcount == 1 means only the durable lifetime ref is
+ * left. Add a transient ref so final close can drop both.
+ */
+ write_lock(&global_ft.lock);
+ if (atomic_read(&fp->refcount) == 1) {
+ atomic_inc(&fp->refcount);
+ __ksmbd_remove_durable_fd(fp);
+ ksmbd_mark_fp_closed(fp);
+ list_move_tail(&fp->node, &dispose);
+ }
+ write_unlock(&global_ft.lock);
}
}
up_write(&ci->m_lock);
@@ -589,16 +602,18 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
/*
* Drop our lookup reference before closing so the last __ksmbd_close_fd()
* can drop m_count to zero and unlink the delete-on-close file. The
- * collected handles still hold references, so ci stays valid until they
- * are closed below.
+ * collected handles still hold the transient reference taken above, so
+ * ci stays valid until they are closed below.
*/
ksmbd_inode_put(ci);
while (!list_empty(&dispose)) {
fp = list_first_entry(&dispose, struct ksmbd_file, node);
list_del_init(&fp->node);
- __ksmbd_close_fd(NULL, fp);
- closed = true;
+ if (atomic_sub_and_test(2, &fp->refcount)) {
+ __ksmbd_close_fd(NULL, fp);
+ closed = true;
+ }
}
return closed;
@@ -838,19 +853,24 @@ int ksmbd_close_fd_app_instance_id(char *app_instance_id)
return 0;
opinfo = opinfo_get(fp);
- if (!opinfo || !opinfo->sess)
+ if (!opinfo)
goto out;
+ down_read(&fp->f_ci->m_lock);
+ if (!opinfo->conn) {
+ up_read(&fp->f_ci->m_lock);
+ goto out;
+ }
+
ft = &opinfo->sess->file_table;
write_lock(&ft->lock);
- if (fp->f_state == FP_INITED) {
- if (has_file_id(fp->volatile_id)) {
- idr_remove(ft->idr, fp->volatile_id);
- fp->volatile_id = KSMBD_NO_FID;
- }
+ if (fp->f_state == FP_INITED && has_file_id(fp->volatile_id)) {
+ idr_remove(ft->idr, fp->volatile_id);
+ fp->volatile_id = KSMBD_NO_FID;
n_to_drop = ksmbd_mark_fp_closed(fp);
}
write_unlock(&ft->lock);
+ up_read(&fp->f_ci->m_lock);
opinfo_put(opinfo);
opinfo = NULL;
@@ -1461,16 +1481,21 @@ void ksmbd_stop_durable_scavenger(void)
static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user)
{
+ char *name;
+
if (!user)
return -EINVAL;
/* Duplicate the user name to ensure identity persistence */
- fp->owner.name = kstrdup(user->name, GFP_KERNEL);
- if (!fp->owner.name)
+ name = kstrdup(user->name, GFP_KERNEL);
+ if (!name)
return -ENOMEM;
+ spin_lock(&fp->f_lock);
fp->owner.uid = user->uid;
fp->owner.gid = user->gid;
+ fp->owner.name = name;
+ spin_unlock(&fp->f_lock);
return 0;
}
@@ -1488,18 +1513,24 @@ static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user)
{
- if (!user || !fp->owner.name)
+ bool ret = false;
+
+ if (!user)
return false;
+ spin_lock(&fp->f_lock);
+ if (!fp->owner.name)
+ goto out;
+
/* Check if the UID and GID match first (fast path) */
if (fp->owner.uid != user->uid || fp->owner.gid != user->gid)
- return false;
+ goto out;
/* Validate the account name to ensure the same SecurityContext */
- if (strcmp(fp->owner.name, user->name))
- return false;
-
- return true;
+ ret = (strcmp(fp->owner.name, user->name) == 0);
+out:
+ spin_unlock(&fp->f_lock);
+ return ret;
}
static bool session_fd_check(struct ksmbd_tree_connect *tcon,
@@ -1530,11 +1561,13 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
conn = fp->conn;
ci = fp->f_ci;
down_write(&ci->m_lock);
- list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
+ list_for_each_entry_rcu(op, &ci->m_op_list, op_entry,
+ lockdep_is_held(&ci->m_lock)) {
if (op->conn != conn)
continue;
ksmbd_conn_put(op->conn);
op->conn = NULL;
+ op->sess = NULL;
}
up_write(&ci->m_lock);
@@ -1685,16 +1718,20 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
ci = fp->f_ci;
down_write(&ci->m_lock);
- list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
+ list_for_each_entry_rcu(op, &ci->m_op_list, op_entry,
+ lockdep_is_held(&ci->m_lock)) {
if (op->conn)
continue;
op->conn = ksmbd_conn_get(fp->conn);
+ op->sess = work->sess;
}
up_write(&ci->m_lock);
+ spin_lock(&fp->f_lock);
fp->owner.uid = fp->owner.gid = 0;
kfree(fp->owner.name);
fp->owner.name = NULL;
+ spin_unlock(&fp->f_lock);
return 0;
}
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 287f3e675cd3..b9e27307a26c 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -105,6 +105,7 @@ struct ksmbd_file {
__u64 change_time;
__u64 allocation_size;
__u64 itime;
+ __u64 open_mtime;
bool is_nt_open;
bool attrib_only;