summaryrefslogtreecommitdiff
path: root/fs/smb
diff options
context:
space:
mode:
Diffstat (limited to 'fs/smb')
-rw-r--r--fs/smb/Kconfig1
-rw-r--r--fs/smb/Makefile1
-rw-r--r--fs/smb/client/.gitignore3
-rw-r--r--fs/smb/client/Kconfig18
-rw-r--r--fs/smb/client/Makefile34
-rw-r--r--fs/smb/client/cached_dir.c13
-rw-r--r--fs/smb/client/cached_dir.h2
-rw-r--r--fs/smb/client/cifs_debug.c125
-rw-r--r--fs/smb/client/cifs_fs_sb.h1
-rw-r--r--fs/smb/client/cifs_spnego.c16
-rw-r--r--fs/smb/client/cifs_swn.c320
-rw-r--r--fs/smb/client/cifs_unicode.c1
-rw-r--r--fs/smb/client/cifsacl.c468
-rw-r--r--fs/smb/client/cifsencrypt.c74
-rw-r--r--fs/smb/client/cifsfs.c242
-rw-r--r--fs/smb/client/cifsfs.h32
-rw-r--r--fs/smb/client/cifsglob.h70
-rw-r--r--fs/smb/client/cifsproto.h39
-rw-r--r--fs/smb/client/cifssmb.c113
-rw-r--r--fs/smb/client/compress.c16
-rw-r--r--fs/smb/client/compress.h28
-rw-r--r--fs/smb/client/compress/lz77.c235
-rw-r--r--fs/smb/client/compress/lz77.h15
-rw-r--r--fs/smb/client/connect.c86
-rw-r--r--fs/smb/client/dfs_cache.c63
-rw-r--r--fs/smb/client/dir.c375
-rw-r--r--fs/smb/client/file.c481
-rw-r--r--fs/smb/client/fs_context.c321
-rw-r--r--fs/smb/client/fs_context.h2
-rw-r--r--fs/smb/client/fscache.c6
-rw-r--r--fs/smb/client/gen_smb1_mapping124
-rw-r--r--fs/smb/client/inode.c238
-rw-r--r--fs/smb/client/ioctl.c154
-rw-r--r--fs/smb/client/link.c18
-rw-r--r--fs/smb/client/misc.c206
-rw-r--r--fs/smb/client/namespace.c1
-rw-r--r--fs/smb/client/netlink.c6
-rw-r--r--fs/smb/client/nterr.c704
-rw-r--r--fs/smb/client/nterr.h1082
-rw-r--r--fs/smb/client/readdir.c38
-rw-r--r--fs/smb/client/reparse.c96
-rw-r--r--fs/smb/client/reparse.h24
-rw-r--r--fs/smb/client/sess.c117
-rw-r--r--fs/smb/client/smb1encrypt.c20
-rw-r--r--fs/smb/client/smb1maperror.c869
-rw-r--r--fs/smb/client/smb1maperror_test.c78
-rw-r--r--fs/smb/client/smb1misc.c3
-rw-r--r--fs/smb/client/smb1ops.c64
-rw-r--r--fs/smb/client/smb1pdu.h14
-rw-r--r--fs/smb/client/smb1proto.h17
-rw-r--r--fs/smb/client/smb1transport.c65
-rw-r--r--fs/smb/client/smb2file.c68
-rw-r--r--fs/smb/client/smb2glob.h12
-rw-r--r--fs/smb/client/smb2inode.c474
-rw-r--r--fs/smb/client/smb2maperror.c28
-rw-r--r--fs/smb/client/smb2maperror_test.c13
-rw-r--r--fs/smb/client/smb2misc.c124
-rw-r--r--fs/smb/client/smb2ops.c636
-rw-r--r--fs/smb/client/smb2pdu.c268
-rw-r--r--fs/smb/client/smb2pdu.h35
-rw-r--r--fs/smb/client/smb2proto.h19
-rw-r--r--fs/smb/client/smb2transport.c144
-rw-r--r--fs/smb/client/smbdirect.c3107
-rw-r--r--fs/smb/client/smbdirect.h21
-rw-r--r--fs/smb/client/smberr.h415
-rw-r--r--fs/smb/client/trace.h169
-rw-r--r--fs/smb/client/transport.c26
-rw-r--r--fs/smb/client/xattr.c4
-rw-r--r--fs/smb/common/Makefile3
-rw-r--r--fs/smb/common/compress/compress.c404
-rw-r--r--fs/smb/common/compress/compress.h29
-rw-r--r--fs/smb/common/compress/lz77.c461
-rw-r--r--fs/smb/common/compress/lz77.h46
-rw-r--r--fs/smb/common/fscc.h374
-rw-r--r--fs/smb/common/smb2pdu.h398
-rw-r--r--fs/smb/common/smb2status.h1
-rw-r--r--fs/smb/common/smbdirect/smbdirect.h44
-rw-r--r--fs/smb/common/smbfsctl.h11
-rw-r--r--fs/smb/common/smbglob.h1
-rw-r--r--fs/smb/server/Kconfig9
-rw-r--r--fs/smb/server/Makefile2
-rw-r--r--fs/smb/server/auth.c360
-rw-r--r--fs/smb/server/auth.h15
-rw-r--r--fs/smb/server/compress.c260
-rw-r--r--fs/smb/server/compress.h17
-rw-r--r--fs/smb/server/connection.c480
-rw-r--r--fs/smb/server/connection.h75
-rw-r--r--fs/smb/server/crypto_ctx.c58
-rw-r--r--fs/smb/server/crypto_ctx.h17
-rw-r--r--fs/smb/server/ksmbd_netlink.h8
-rw-r--r--fs/smb/server/ksmbd_work.c64
-rw-r--r--fs/smb/server/ksmbd_work.h31
-rw-r--r--fs/smb/server/mgmt/share_config.c111
-rw-r--r--fs/smb/server/mgmt/share_config.h25
-rw-r--r--fs/smb/server/mgmt/tree_connect.c19
-rw-r--r--fs/smb/server/mgmt/user_config.c24
-rw-r--r--fs/smb/server/mgmt/user_session.c414
-rw-r--r--fs/smb/server/mgmt/user_session.h14
-rw-r--r--fs/smb/server/misc.c47
-rw-r--r--fs/smb/server/misc.h12
-rw-r--r--fs/smb/server/oplock.c1203
-rw-r--r--fs/smb/server/oplock.h28
-rw-r--r--fs/smb/server/proc.c172
-rw-r--r--fs/smb/server/server.c150
-rw-r--r--fs/smb/server/server.h2
-rw-r--r--fs/smb/server/smb2misc.c102
-rw-r--r--fs/smb/server/smb2ops.c22
-rw-r--r--fs/smb/server/smb2pdu.c4828
-rw-r--r--fs/smb/server/smb2pdu.h108
-rw-r--r--fs/smb/server/smb_common.c62
-rw-r--r--fs/smb/server/smb_common.h10
-rw-r--r--fs/smb/server/smbacl.c495
-rw-r--r--fs/smb/server/smbacl.h5
-rw-r--r--fs/smb/server/smbfsctl.h91
-rw-r--r--fs/smb/server/stats.h58
-rw-r--r--fs/smb/server/transport_ipc.c63
-rw-r--r--fs/smb/server/transport_rdma.c2958
-rw-r--r--fs/smb/server/transport_rdma.h6
-rw-r--r--fs/smb/server/transport_tcp.c71
-rw-r--r--fs/smb/server/vfs.c761
-rw-r--r--fs/smb/server/vfs.h23
-rw-r--r--fs/smb/server/vfs_cache.c1065
-rw-r--r--fs/smb/server/vfs_cache.h79
-rw-r--r--fs/smb/smbdirect/Kconfig9
-rw-r--r--fs/smb/smbdirect/Makefile18
-rw-r--r--fs/smb/smbdirect/accept.c868
-rw-r--r--fs/smb/smbdirect/connect.c926
-rw-r--r--fs/smb/smbdirect/connection.c2190
-rw-r--r--fs/smb/smbdirect/debug.c88
-rw-r--r--fs/smb/smbdirect/devices.c277
-rw-r--r--fs/smb/smbdirect/internal.h141
-rw-r--r--fs/smb/smbdirect/listen.c308
-rw-r--r--fs/smb/smbdirect/main.c121
-rw-r--r--fs/smb/smbdirect/mr.c496
-rw-r--r--fs/smb/smbdirect/pdu.h (renamed from fs/smb/common/smbdirect/smbdirect_pdu.h)4
-rw-r--r--fs/smb/smbdirect/rw.c255
-rw-r--r--fs/smb/smbdirect/socket.c791
-rw-r--r--fs/smb/smbdirect/socket.h (renamed from fs/smb/common/smbdirect/smbdirect_socket.h)303
138 files changed, 23170 insertions, 12523 deletions
diff --git a/fs/smb/Kconfig b/fs/smb/Kconfig
index 85f7ad5fbc5e..e549e189ee6a 100644
--- a/fs/smb/Kconfig
+++ b/fs/smb/Kconfig
@@ -4,6 +4,7 @@
source "fs/smb/client/Kconfig"
source "fs/smb/server/Kconfig"
+source "fs/smb/smbdirect/Kconfig"
config SMBFS
tristate
diff --git a/fs/smb/Makefile b/fs/smb/Makefile
index 9a1bf59a1a65..353b1c2eefc4 100644
--- a/fs/smb/Makefile
+++ b/fs/smb/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SMBFS) += common/
+obj-$(CONFIG_SMBDIRECT) += smbdirect/
obj-$(CONFIG_CIFS) += client/
obj-$(CONFIG_SMB_SERVER) += server/
diff --git a/fs/smb/client/.gitignore b/fs/smb/client/.gitignore
index 8a6e04ab62b9..66e6e2ade0bd 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 17bd368574e9..2b7db5fb0fd9 100644
--- a/fs/smb/client/Kconfig
+++ b/fs/smb/client/Kconfig
@@ -5,12 +5,11 @@ config CIFS
select NLS
select NLS_UCS2_UTILS
select CRYPTO
- select CRYPTO_CMAC
select CRYPTO_AEAD2
select CRYPTO_CCM
select CRYPTO_GCM
- select CRYPTO_ECB
select CRYPTO_AES
+ select CRYPTO_LIB_AES_CBC_MACS
select CRYPTO_LIB_ARC4
select CRYPTO_LIB_MD5
select CRYPTO_LIB_SHA256
@@ -181,7 +180,9 @@ if CIFS
config CIFS_SMB_DIRECT
bool "SMB Direct support"
- depends on CIFS=m && INFINIBAND && INFINIBAND_ADDR_TRANS || CIFS=y && INFINIBAND=y && INFINIBAND_ADDR_TRANS=y
+ depends on CIFS && INFINIBAND && INFINIBAND_ADDR_TRANS
+ depends on CIFS=m || INFINIBAND=y
+ select SMBDIRECT
help
Enables SMB Direct support for SMB 3.0, 3.02 and 3.1.1.
SMB Direct allows transferring SMB packets over RDMA. If unsure,
@@ -217,4 +218,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 3abd357d6df6..fc6b9d35c962 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 \
@@ -42,18 +42,42 @@ cifs-$(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) += \
smb1session.o \
smb1transport.o
-cifs-$(CONFIG_CIFS_COMPRESSION) += compress.o compress/lz77.o
+cifs-$(CONFIG_CIFS_COMPRESSION) += compress.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
#
$(obj)/smb2_mapping_table.c: $(src)/../common/smb2status.h \
- $(src)/gen_smb2_mapping
- $(call cmd,gen_smb2_mapping)
+ $(src)/gen_smb2_mapping FORCE
+ $(call if_changed,gen_smb2_mapping)
$(obj)/smb2maperror.o: $(obj)/smb2_mapping_table.c
quiet_cmd_gen_smb2_mapping = GEN $@
cmd_gen_smb2_mapping = perl $(src)/gen_smb2_mapping $< $@
-clean-files += smb2_mapping_table.c
+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 $(smb1-gen-y)
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 04bb95091f49..88d5e9a32f28 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -286,6 +286,14 @@ replay_again:
&rqst[0], &oplock, &oparms, utf16_path);
if (rc)
goto oshr_free;
+
+ if (oplock != SMB2_OPLOCK_LEVEL_II) {
+ rc = -EINVAL;
+ cifs_dbg(FYI, "%s: Oplock level %d not suitable for cached directory\n",
+ __func__, oplock);
+ goto oshr_free;
+ }
+
smb2_set_next_command(tcon, &rqst[0]);
memset(&qi_iov, 0, sizeof(qi_iov));
@@ -593,7 +601,7 @@ done:
* Invalidate all cached dirs when a TCON has been reset
* due to a session loss.
*/
-void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
+void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync)
{
struct cached_fids *cfids = tcon->cfids;
struct cached_fid *cfid, *q;
@@ -625,7 +633,8 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
/* run laundromat unconditionally now as there might have been previously queued work */
mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
- flush_delayed_work(&cfids->laundromat_work);
+ if (sync)
+ flush_delayed_work(&cfids->laundromat_work);
}
static void
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index 19d5592512e4..fc756836da95 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -90,7 +90,7 @@ void close_cached_dir(struct cached_fid *cfid);
void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
const char *name, struct cifs_sb_info *cifs_sb);
void close_all_cached_dirs(struct cifs_sb_info *cifs_sb);
-void invalidate_all_cached_dirs(struct cifs_tcon *tcon);
+void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync);
bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]);
#endif /* _CACHED_DIR_H */
diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 217444e3e6d0..3761d3ad6088 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -23,7 +23,6 @@
#endif
#ifdef CONFIG_CIFS_SMB_DIRECT
#include "smbdirect.h"
-#include "../common/smbdirect/smbdirect_pdu.h"
#endif
#include "cifs_swn.h"
#include "cached_dir.h"
@@ -307,6 +306,9 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v)
LIST_HEAD(entry);
seq_puts(m, "# Version:1\n");
+#ifdef CONFIG_CIFS_DEBUG
+ seq_puts(m, "# Write 0 to this file to drop all cached directory entries\n");
+#endif /* CONFIG_CIFS_DEBUG */
seq_puts(m, "# Format:\n");
seq_puts(m, "# <tree id> <sess id> <persistent fid> <lease-key> <path>\n");
@@ -354,6 +356,51 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v)
return 0;
}
+#ifdef CONFIG_CIFS_DEBUG
+static int cifs_debug_dirs_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, cifs_debug_dirs_proc_show, NULL);
+}
+
+/* Drop all cached directory entries across all CIFS mounts. */
+static ssize_t cifs_debug_dirs_proc_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ int rc, v;
+
+ rc = kstrtoint_from_user(buffer, count, 10, &v);
+ if (rc)
+ return rc;
+
+ if (v == 0) {
+ struct TCP_Server_Info *server;
+ struct cifs_ses *ses;
+ struct cifs_tcon *tcon;
+
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+ if (cifs_ses_exiting(ses))
+ continue;
+ list_for_each_entry(tcon, &ses->tcon_list, tcon_list)
+ invalidate_all_cached_dirs(tcon, false);
+ }
+ }
+ spin_unlock(&cifs_tcp_ses_lock);
+ }
+
+ return count;
+}
+
+static const struct proc_ops cifs_debug_dirs_proc_ops = {
+ .proc_open = cifs_debug_dirs_proc_open,
+ .proc_read = seq_read,
+ .proc_lseek = seq_lseek,
+ .proc_release = single_release,
+ .proc_write = cifs_debug_dirs_proc_write,
+};
+#endif /* CONFIG_CIFS_DEBUG */
+
static __always_inline const char *compression_alg_str(__le16 alg)
{
switch (alg) {
@@ -452,11 +499,6 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
c = 0;
spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
-#ifdef CONFIG_CIFS_SMB_DIRECT
- struct smbdirect_socket *sc;
- struct smbdirect_socket_parameters *sp;
-#endif
-
/* channel info will be printed as a part of sessions below */
if (SERVER_IS_CHAN(server))
continue;
@@ -471,66 +513,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
seq_printf(m, "\nClientGUID: %pUL", server->client_guid);
spin_unlock(&server->srv_lock);
#ifdef CONFIG_CIFS_SMB_DIRECT
- if (!server->rdma)
- goto skip_rdma;
-
- if (!server->smbd_conn) {
- seq_printf(m, "\nSMBDirect transport not available");
- goto skip_rdma;
- }
- sc = &server->smbd_conn->socket;
- sp = &sc->parameters;
-
- seq_printf(m, "\nSMBDirect protocol version: 0x%x "
- "transport status: %s (%u)",
- SMBDIRECT_V1,
- smbdirect_socket_status_string(sc->status),
- sc->status);
- seq_printf(m, "\nConn receive_credit_max: %u "
- "send_credit_target: %u max_send_size: %u",
- sp->recv_credit_max,
- sp->send_credit_target,
- sp->max_send_size);
- seq_printf(m, "\nConn max_fragmented_recv_size: %u "
- "max_fragmented_send_size: %u max_receive_size:%u",
- sp->max_fragmented_recv_size,
- sp->max_fragmented_send_size,
- sp->max_recv_size);
- seq_printf(m, "\nConn keep_alive_interval: %u "
- "max_readwrite_size: %u rdma_readwrite_threshold: %u",
- sp->keepalive_interval_msec * 1000,
- sp->max_read_write_size,
- server->rdma_readwrite_threshold);
- seq_printf(m, "\nDebug count_get_receive_buffer: %llu "
- "count_put_receive_buffer: %llu count_send_empty: %llu",
- sc->statistics.get_receive_buffer,
- sc->statistics.put_receive_buffer,
- sc->statistics.send_empty);
- seq_printf(m, "\nRead Queue "
- "count_enqueue_reassembly_queue: %llu "
- "count_dequeue_reassembly_queue: %llu "
- "reassembly_data_length: %u "
- "reassembly_queue_length: %u",
- sc->statistics.enqueue_reassembly_queue,
- sc->statistics.dequeue_reassembly_queue,
- sc->recv_io.reassembly.data_length,
- sc->recv_io.reassembly.queue_length);
- seq_printf(m, "\nCurrent Credits send_credits: %u "
- "receive_credits: %u receive_credit_target: %u",
- atomic_read(&sc->send_io.credits.count),
- atomic_read(&sc->recv_io.credits.count),
- sc->recv_io.credits.target);
- seq_printf(m, "\nPending send_pending: %u ",
- atomic_read(&sc->send_io.pending.count));
- seq_printf(m, "\nMR responder_resources: %u "
- "max_frmr_depth: %u mr_type: 0x%x",
- sp->responder_resources,
- sp->max_frmr_depth,
- sc->mr_io.type);
- seq_printf(m, "\nMR mr_ready_count: %u mr_used_count: %u",
- atomic_read(&sc->mr_io.ready.count),
- atomic_read(&sc->mr_io.used.count));
-skip_rdma:
+ smbd_debug_proc_show(server, m);
#endif
seq_printf(m, "\nNumber of credits: %d,%d,%d Dialect 0x%x",
server->credits,
@@ -771,7 +754,7 @@ static ssize_t cifs_stats_proc_write(struct file *file,
atomic_set(&server->smb2slowcmd[i], 0);
server->time_per_cmd[i] = 0;
server->slowest_cmd[i] = 0;
- server->fastest_cmd[0] = 0;
+ server->fastest_cmd[i] = 0;
}
#endif /* CONFIG_CIFS_STATS2 */
list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
@@ -950,9 +933,11 @@ cifs_proc_init(void)
proc_create_single("open_files", 0400, proc_fs_cifs,
cifs_debug_files_proc_show);
- proc_create_single("open_dirs", 0400, proc_fs_cifs,
- cifs_debug_dirs_proc_show);
-
+#ifdef CONFIG_CIFS_DEBUG
+ proc_create("open_dirs", 0600, proc_fs_cifs, &cifs_debug_dirs_proc_ops);
+#else /* CONFIG_CIFS_DEBUG */
+ proc_create_single("open_dirs", 0400, proc_fs_cifs, cifs_debug_dirs_proc_show);
+#endif /* !CONFIG_CIFS_DEBUG */
proc_create("Stats", 0644, proc_fs_cifs, &cifs_stats_proc_ops);
proc_create("cifsFYI", 0644, proc_fs_cifs, &cifsFYI_proc_ops);
proc_create("traceSMB", 0644, proc_fs_cifs, &traceSMB_proc_ops);
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/cifs_spnego.c b/fs/smb/client/cifs_spnego.c
index 3a41bbada04c..44c407275680 100644
--- a/fs/smb/client/cifs_spnego.c
+++ b/fs/smb/client/cifs_spnego.c
@@ -8,6 +8,7 @@
*/
#include <linux/list.h>
+#include <linux/cred.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <keys/user-type.h>
@@ -40,12 +41,27 @@ cifs_spnego_key_destroy(struct key *key)
kfree(key->payload.data[0]);
}
+static int
+cifs_spnego_key_vet_description(const char *description)
+{
+ /*
+ * cifs.spnego descriptions are authority-bearing inputs to cifs.upcall.
+ * They are only valid when produced by CIFS while using the private
+ * spnego_cred installed below. Do not let userspace create this type
+ * of key through request_key(2)/add_key(2), since the helper treats
+ * pid/uid/creduid/upcall_target as kernel-originating fields.
+ */
+ if (current_cred() != spnego_cred)
+ return -EPERM;
+ return 0;
+}
/*
* keytype for CIFS spnego keys
*/
struct key_type cifs_spnego_key_type = {
.name = "cifs.spnego",
+ .vet_description = cifs_spnego_key_vet_description,
.instantiate = cifs_spnego_key_instantiate,
.destroy = cifs_spnego_key_destroy,
.describe = user_describe,
diff --git a/fs/smb/client/cifs_swn.c b/fs/smb/client/cifs_swn.c
index 9753a432d099..c49ecddf4a33 100644
--- a/fs/smb/client/cifs_swn.c
+++ b/fs/smb/client/cifs_swn.c
@@ -28,10 +28,54 @@ struct cifs_swn_reg {
bool net_name_notify;
bool share_name_notify;
bool ip_notify;
+};
- struct cifs_tcon *tcon;
+struct cifs_swn_reg_info {
+ int id;
+ unsigned int ref_count;
+ const char *net_name;
+ const char *share_name;
+ bool net_name_notify;
+ bool share_name_notify;
+ bool ip_notify;
};
+static void cifs_swn_snapshot_reg(struct cifs_swn_reg *swnreg,
+ struct cifs_swn_reg_info *info)
+{
+ info->id = swnreg->id;
+ info->ref_count = kref_read(&swnreg->ref_count);
+ info->net_name = swnreg->net_name;
+ info->share_name = swnreg->share_name;
+ info->net_name_notify = swnreg->net_name_notify;
+ info->share_name_notify = swnreg->share_name_notify;
+ info->ip_notify = swnreg->ip_notify;
+}
+
+static int cifs_swn_dup_reg(struct cifs_swn_reg *swnreg,
+ struct cifs_swn_reg_info *info)
+{
+ cifs_swn_snapshot_reg(swnreg, info);
+
+ info->net_name = kstrdup(swnreg->net_name, GFP_KERNEL);
+ if (!info->net_name)
+ return -ENOMEM;
+
+ info->share_name = kstrdup(swnreg->share_name, GFP_KERNEL);
+ if (!info->share_name) {
+ kfree(info->net_name);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void cifs_swn_free_reg_info(struct cifs_swn_reg_info *info)
+{
+ kfree(info->net_name);
+ kfree(info->share_name);
+}
+
static int cifs_swn_auth_info_krb(struct cifs_tcon *tcon, struct sk_buff *skb)
{
int ret;
@@ -73,7 +117,8 @@ static int cifs_swn_auth_info_ntlm(struct cifs_tcon *tcon, struct sk_buff *skb)
* The authentication information to connect to the witness service is bundled
* into the message.
*/
-static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
+static int cifs_swn_send_register_message(struct cifs_swn_reg_info *swnreg,
+ struct cifs_tcon *tcon)
{
struct sk_buff *skb;
struct genlmsghdr *hdr;
@@ -109,10 +154,10 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
* told to switch to it (client move message). In these cases we unregister from the
* server address and register to the new address when we receive the notification.
*/
- if (swnreg->tcon->ses->server->use_swn_dstaddr)
- addr = &swnreg->tcon->ses->server->swn_dstaddr;
+ if (tcon->ses->server->use_swn_dstaddr)
+ addr = &tcon->ses->server->swn_dstaddr;
else
- addr = &swnreg->tcon->ses->server->dstaddr;
+ addr = &tcon->ses->server->dstaddr;
ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), addr);
if (ret < 0)
@@ -136,10 +181,10 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
goto nlmsg_fail;
}
- authtype = cifs_select_sectype(swnreg->tcon->ses->server, swnreg->tcon->ses->sectype);
+ authtype = cifs_select_sectype(tcon->ses->server, tcon->ses->sectype);
switch (authtype) {
case Kerberos:
- ret = cifs_swn_auth_info_krb(swnreg->tcon, skb);
+ ret = cifs_swn_auth_info_krb(tcon, skb);
if (ret < 0) {
cifs_dbg(VFS, "%s: Failed to get kerberos auth info: %d\n", __func__, ret);
goto nlmsg_fail;
@@ -147,7 +192,7 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
break;
case NTLMv2:
case RawNTLMSSP:
- ret = cifs_swn_auth_info_ntlm(swnreg->tcon, skb);
+ ret = cifs_swn_auth_info_ntlm(tcon, skb);
if (ret < 0) {
cifs_dbg(VFS, "%s: Failed to get NTLM auth info: %d\n", __func__, ret);
goto nlmsg_fail;
@@ -176,7 +221,8 @@ nlmsg_fail:
/*
* Sends an uregister message to the userspace daemon based on the registration
*/
-static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg)
+static int cifs_swn_send_unregister_message(struct cifs_swn_reg_info *swnreg,
+ struct cifs_tcon *tcon)
{
struct sk_buff *skb;
struct genlmsghdr *hdr;
@@ -205,7 +251,7 @@ static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg)
goto nlmsg_fail;
ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage),
- &swnreg->tcon->ses->server->dstaddr);
+ &tcon->ses->server->dstaddr);
if (ret < 0)
goto nlmsg_fail;
@@ -242,6 +288,88 @@ nlmsg_fail:
}
/*
+ * Allocation-free mirror of extract_hostname() + extract_sharename() from
+ * fs/smb/client/unc.c. Those helpers kmalloc(GFP_KERNEL); this runs under
+ * cifs_tcp_ses_lock and tcon->tc_lock, both spinlocks, so we mirror their
+ * parsing in place against the caller's stable net_name/share_name strings.
+ * Keep in sync with unc.c.
+ */
+static bool cifs_swn_tcon_matches(struct cifs_tcon *tcon,
+ const char *net_name,
+ const char *share_name)
+{
+ const char *unc = tcon->tree_name;
+ const char *host, *share, *delim;
+ size_t host_len, share_len;
+
+ if (!tcon->use_witness)
+ return false;
+
+ /* extract_hostname: require strlen(unc) >= 3 */
+ if (strnlen(unc, 3) < 3)
+ return false;
+ /* extract_hostname: skip all leading '\' characters */
+ for (host = unc; *host == '\\'; host++)
+ ;
+ if (!*host)
+ return false;
+ delim = strchr(host, '\\');
+ if (!delim)
+ return false;
+ host_len = delim - host;
+ if (strlen(net_name) != host_len ||
+ strncasecmp(host, net_name, host_len))
+ return false;
+
+ /* extract_sharename: start at unc + 2, then first '\' onward */
+ share = unc + 2;
+ delim = strchr(share, '\\');
+ if (!delim)
+ return false;
+ share = delim + 1;
+ share_len = strlen(share);
+
+ return strlen(share_name) == share_len &&
+ !strncasecmp(share, share_name, share_len);
+}
+
+/*
+ * One SWN registration id represents one net/share name pair. Multiple
+ * mounted tcons can therefore share the id. Pick a live representative at
+ * use time instead of caching the first tcon pointer in the registration.
+ */
+static struct cifs_tcon *cifs_swn_get_tcon(struct cifs_swn_reg_info *swnreg)
+{
+ struct TCP_Server_Info *server;
+ struct cifs_ses *ses;
+ struct cifs_tcon *tcon;
+
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+ list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+ spin_lock(&tcon->tc_lock);
+ if (tcon->status == TID_EXITING ||
+ !cifs_swn_tcon_matches(tcon, swnreg->net_name,
+ swnreg->share_name)) {
+ spin_unlock(&tcon->tc_lock);
+ continue;
+ }
+ ++tcon->tc_count;
+ trace_smb3_tcon_ref(tcon->debug_id,
+ tcon->tc_count,
+ netfs_trace_tcon_ref_get_swn_notify);
+ spin_unlock(&tcon->tc_lock);
+ spin_unlock(&cifs_tcp_ses_lock);
+ return tcon;
+ }
+ }
+ }
+ spin_unlock(&cifs_tcp_ses_lock);
+ return NULL;
+}
+
+/*
* Try to find a matching registration for the tcon's server name and share name.
* Calls to this function must be protected by cifs_swnreg_idr_mutex.
* TODO Try to avoid memory allocations
@@ -297,7 +425,7 @@ static struct cifs_swn_reg *cifs_find_swn_reg(struct cifs_tcon *tcon)
/*
* Get a registration for the tcon's server and share name, allocating a new one if it does not
- * exists
+ * exist.
*/
static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)
{
@@ -315,7 +443,7 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)
goto unlock;
}
- reg = kmalloc_obj(struct cifs_swn_reg, GFP_ATOMIC);
+ reg = kmalloc_obj(struct cifs_swn_reg);
if (reg == NULL) {
ret = -ENOMEM;
goto fail_unlock;
@@ -323,7 +451,7 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)
kref_init(&reg->ref_count);
- reg->id = idr_alloc(&cifs_swnreg_idr, reg, 1, 0, GFP_ATOMIC);
+ reg->id = idr_alloc(&cifs_swnreg_idr, reg, 1, 0, GFP_KERNEL);
if (reg->id < 0) {
cifs_dbg(FYI, "%s: failed to allocate registration id\n", __func__);
ret = reg->id;
@@ -347,8 +475,6 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)
reg->net_name_notify = true;
reg->share_name_notify = true;
reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT);
-
- reg->tcon = tcon;
unlock:
mutex_unlock(&cifs_swnreg_idr_mutex);
@@ -368,11 +494,6 @@ fail_unlock:
static void cifs_swn_reg_release(struct kref *ref)
{
struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count);
- int ret;
-
- ret = cifs_swn_send_unregister_message(swnreg);
- if (ret < 0)
- cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", __func__, ret);
idr_remove(&cifs_swnreg_idr, swnreg->id);
kfree(swnreg->net_name);
@@ -380,23 +501,33 @@ static void cifs_swn_reg_release(struct kref *ref)
kfree(swnreg);
}
-static void cifs_put_swn_reg(struct cifs_swn_reg *swnreg)
+static void cifs_put_swn_reg_locked(struct cifs_swn_reg *swnreg,
+ struct cifs_tcon *tcon)
{
- mutex_lock(&cifs_swnreg_idr_mutex);
+ if (kref_read(&swnreg->ref_count) == 1) {
+ struct cifs_swn_reg_info swnreg_info;
+ int ret;
+
+ cifs_swn_snapshot_reg(swnreg, &swnreg_info);
+ ret = cifs_swn_send_unregister_message(&swnreg_info, tcon);
+ if (ret < 0)
+ cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n",
+ __func__, ret);
+ }
+
kref_put(&swnreg->ref_count, cifs_swn_reg_release);
- mutex_unlock(&cifs_swnreg_idr_mutex);
}
-static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const char *name, int state)
+static int cifs_swn_resource_state_changed(struct cifs_tcon *tcon, const char *name, int state)
{
switch (state) {
case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE:
cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n", __func__, name);
- cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true);
+ cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
break;
case CIFS_SWN_RESOURCE_STATE_AVAILABLE:
cifs_dbg(FYI, "%s: resource name '%s' become available\n", __func__, name);
- cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true);
+ cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
break;
case CIFS_SWN_RESOURCE_STATE_UNKNOWN:
cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __func__, name);
@@ -502,7 +633,7 @@ unlock:
return ret;
}
-static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_storage *addr)
+static int cifs_swn_client_move(struct cifs_tcon *tcon, struct sockaddr_storage *addr)
{
struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
@@ -512,14 +643,17 @@ static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_sto
else if (addr->ss_family == AF_INET6)
cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr);
- return cifs_swn_reconnect(swnreg->tcon, addr);
+ return cifs_swn_reconnect(tcon, addr);
}
int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
{
struct cifs_swn_reg *swnreg;
+ struct cifs_swn_reg_info swnreg_info;
+ struct cifs_tcon *tcon;
char name[256];
int type;
+ int ret = 0;
if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]) {
int swnreg_id;
@@ -527,21 +661,34 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
swnreg_id = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]);
mutex_lock(&cifs_swnreg_idr_mutex);
swnreg = idr_find(&cifs_swnreg_idr, swnreg_id);
- mutex_unlock(&cifs_swnreg_idr_mutex);
if (swnreg == NULL) {
+ mutex_unlock(&cifs_swnreg_idr_mutex);
cifs_dbg(FYI, "%s: registration id %d not found\n", __func__, swnreg_id);
return -EINVAL;
}
+ ret = cifs_swn_dup_reg(swnreg, &swnreg_info);
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+ if (ret)
+ return ret;
} else {
cifs_dbg(FYI, "%s: missing registration id attribute\n", __func__);
return -EINVAL;
}
+ tcon = cifs_swn_get_tcon(&swnreg_info);
+ if (!tcon) {
+ cifs_dbg(FYI, "%s: registration id %d has no live tcon\n",
+ __func__, swnreg_info.id);
+ ret = -ENODEV;
+ goto free_info;
+ }
+
if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]) {
type = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]);
} else {
cifs_dbg(FYI, "%s: missing notification type attribute\n", __func__);
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
switch (type) {
@@ -553,15 +700,18 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
sizeof(name));
} else {
cifs_dbg(FYI, "%s: missing resource name attribute\n", __func__);
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]) {
state = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]);
} else {
cifs_dbg(FYI, "%s: missing resource state attribute\n", __func__);
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
- return cifs_swn_resource_state_changed(swnreg, name, state);
+ ret = cifs_swn_resource_state_changed(tcon, name, state);
+ break;
}
case CIFS_SWN_NOTIFICATION_CLIENT_MOVE: {
struct sockaddr_storage addr;
@@ -570,28 +720,36 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
nla_memcpy(&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP], sizeof(addr));
} else {
cifs_dbg(FYI, "%s: missing IP address attribute\n", __func__);
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
- return cifs_swn_client_move(swnreg, &addr);
+ ret = cifs_swn_client_move(tcon, &addr);
+ break;
}
default:
cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type);
break;
}
- return 0;
+out:
+ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
+free_info:
+ cifs_swn_free_reg_info(&swnreg_info);
+ return ret;
}
int cifs_swn_register(struct cifs_tcon *tcon)
{
struct cifs_swn_reg *swnreg;
+ struct cifs_swn_reg_info swnreg_info;
int ret;
swnreg = cifs_get_swn_reg(tcon);
if (IS_ERR(swnreg))
return PTR_ERR(swnreg);
- ret = cifs_swn_send_register_message(swnreg);
+ cifs_swn_snapshot_reg(swnreg, &swnreg_info);
+ ret = cifs_swn_send_register_message(&swnreg_info, tcon);
if (ret < 0) {
cifs_dbg(VFS, "%s: Failed to send swn register message: %d\n", __func__, ret);
/* Do not put the swnreg or return error, the echo task will retry */
@@ -612,35 +770,68 @@ int cifs_swn_unregister(struct cifs_tcon *tcon)
return PTR_ERR(swnreg);
}
+ cifs_put_swn_reg_locked(swnreg, tcon);
mutex_unlock(&cifs_swnreg_idr_mutex);
- cifs_put_swn_reg(swnreg);
-
return 0;
}
-void cifs_swn_dump(struct seq_file *m)
+/*
+ * Snapshot one registration under cifs_swnreg_idr_mutex and return. Callers
+ * intentionally do the per-registration network/genlmsg work without the
+ * mutex held, both to keep the critical section short and to avoid nesting
+ * cifs_swnreg_idr_mutex inside the higher tc_lock when a live tcon is then
+ * pinned for the send.
+ */
+static int cifs_swn_get_next_reg_info(int *id, struct cifs_swn_reg_info *info)
{
struct cifs_swn_reg *swnreg;
+ int ret = 0;
+
+ mutex_lock(&cifs_swnreg_idr_mutex);
+ swnreg = idr_get_next(&cifs_swnreg_idr, id);
+ if (swnreg) {
+ ret = cifs_swn_dup_reg(swnreg, info);
+ if (!ret) {
+ *id = swnreg->id + 1;
+ ret = 1;
+ }
+ }
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+
+ return ret;
+}
+
+void cifs_swn_dump(struct seq_file *m)
+{
+ struct cifs_swn_reg_info swnreg_info;
+ struct cifs_tcon *tcon;
struct sockaddr_in *sa;
struct sockaddr_in6 *sa6;
- int id;
+ int id = 0;
+ int ret;
seq_puts(m, "Witness registrations:");
- mutex_lock(&cifs_swnreg_idr_mutex);
- idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) {
+ while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) {
seq_printf(m, "\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s Ip address: ",
- id, kref_read(&swnreg->ref_count),
- swnreg->net_name, swnreg->net_name_notify ? "(y)" : "(n)",
- swnreg->share_name, swnreg->share_name_notify ? "(y)" : "(n)");
- switch (swnreg->tcon->ses->server->dstaddr.ss_family) {
+ swnreg_info.id, swnreg_info.ref_count,
+ swnreg_info.net_name, swnreg_info.net_name_notify ? "(y)" : "(n)",
+ swnreg_info.share_name, swnreg_info.share_name_notify ? "(y)" : "(n)");
+
+ tcon = cifs_swn_get_tcon(&swnreg_info);
+ if (!tcon) {
+ seq_puts(m, "(no live tcon)");
+ goto next;
+ }
+
+ switch (tcon->ses->server->dstaddr.ss_family) {
case AF_INET:
- sa = (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr;
+ sa = (struct sockaddr_in *)&tcon->ses->server->dstaddr;
seq_printf(m, "%pI4", &sa->sin_addr.s_addr);
break;
case AF_INET6:
- sa6 = (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr;
+ sa6 = (struct sockaddr_in6 *)&tcon->ses->server->dstaddr;
seq_printf(m, "%pI6", &sa6->sin6_addr.s6_addr);
if (sa6->sin6_scope_id)
seq_printf(m, "%%%u", sa6->sin6_scope_id);
@@ -648,23 +839,38 @@ void cifs_swn_dump(struct seq_file *m)
default:
seq_puts(m, "(unknown)");
}
- seq_printf(m, "%s", swnreg->ip_notify ? "(y)" : "(n)");
+ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
+next:
+ seq_printf(m, "%s", swnreg_info.ip_notify ? "(y)" : "(n)");
+ cifs_swn_free_reg_info(&swnreg_info);
}
- mutex_unlock(&cifs_swnreg_idr_mutex);
+ if (ret < 0)
+ seq_printf(m, "\nFailed to snapshot witness registration: %d", ret);
seq_puts(m, "\n");
}
void cifs_swn_check(void)
{
- struct cifs_swn_reg *swnreg;
- int id;
+ struct cifs_swn_reg_info swnreg_info;
+ struct cifs_tcon *tcon;
+ int id = 0;
int ret;
- mutex_lock(&cifs_swnreg_idr_mutex);
- idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) {
- ret = cifs_swn_send_register_message(swnreg);
+ while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) {
+ tcon = cifs_swn_get_tcon(&swnreg_info);
+ if (!tcon) {
+ cifs_dbg(FYI, "%s: registration id %d has no live tcon\n",
+ __func__, swnreg_info.id);
+ goto free_info;
+ }
+
+ ret = cifs_swn_send_register_message(&swnreg_info, tcon);
if (ret < 0)
cifs_dbg(FYI, "%s: Failed to send register message: %d\n", __func__, ret);
+ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
+free_info:
+ cifs_swn_free_reg_info(&swnreg_info);
}
- mutex_unlock(&cifs_swnreg_idr_mutex);
+ if (ret < 0)
+ cifs_dbg(FYI, "%s: Failed to snapshot registration: %d\n", __func__, ret);
}
diff --git a/fs/smb/client/cifs_unicode.c b/fs/smb/client/cifs_unicode.c
index e2edc207cef2..4a8a591f4bca 100644
--- a/fs/smb/client/cifs_unicode.c
+++ b/fs/smb/client/cifs_unicode.c
@@ -6,6 +6,7 @@
*/
#include <linux/fs.h>
#include <linux/slab.h>
+#include <linux/unaligned.h>
#include "cifs_fs_sb.h"
#include "cifs_unicode.h"
#include "cifsglob.h"
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index f4cb3018a358..c5e47a835f99 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -68,6 +68,9 @@ cifs_idmap_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
{
char *payload;
+ if (prep->datalen > U16_MAX)
+ return -EINVAL;
+
/*
* If the payload is less than or equal to the size of a pointer, then
* an allocation here is wasteful. Just copy the data directly to the
@@ -97,8 +100,23 @@ cifs_idmap_key_destroy(struct key *key)
kfree(key->payload.data[0]);
}
+static int
+cifs_idmap_key_vet_description(const char *description)
+{
+ /*
+ * cifs.idmap descriptions are authority-bearing inputs to the
+ * cifs.idmap upcall helper. Only allow the kernel to create this
+ * type of key using the private root_cred installed in
+ * init_cifs_idmap; reject userspace request_key(2)/add_key(2).
+ */
+ if (current_cred() != root_cred)
+ return -EPERM;
+ return 0;
+}
+
static struct key_type cifs_idmap_key_type = {
.name = "cifs.idmap",
+ .vet_description = cifs_idmap_key_vet_description,
.instantiate = cifs_idmap_key_instantiate,
.destroy = cifs_idmap_key_destroy,
.describe = user_describe,
@@ -275,6 +293,73 @@ cifs_copy_sid(struct smb_sid *dst, const struct smb_sid *src)
return size;
}
+static int parse_sid(const struct smb_sid *psid, const char *end_of_acl)
+{
+ unsigned int sid_len;
+
+ /* SID must contain the fixed header before num_subauth is trusted. */
+ if (end_of_acl < (const char *)psid + CIFS_SID_BASE_SIZE) {
+ cifs_dbg(VFS, "ACL too small to parse SID %p\n", psid);
+ return -EINVAL;
+ }
+ if (psid->num_subauth > SID_MAX_SUB_AUTHORITIES) {
+ cifs_dbg(VFS, "SID contains too many subauthorities %u\n",
+ psid->num_subauth);
+ return -EINVAL;
+ }
+
+ sid_len = CIFS_SID_BASE_SIZE + psid->num_subauth * sizeof(__le32);
+ if (end_of_acl < (const char *)psid + sid_len) {
+ cifs_dbg(VFS, "ACL too small to parse SID %p\n", psid);
+ return -EINVAL;
+ }
+
+#ifdef CONFIG_CIFS_DEBUG2
+ if (psid->num_subauth) {
+ int i;
+
+ cifs_dbg(FYI, "SID revision %d num_auth %d\n",
+ psid->revision, psid->num_subauth);
+
+ for (i = 0; i < psid->num_subauth; i++) {
+ cifs_dbg(FYI, "SID sub_auth[%d]: 0x%x\n",
+ i, le32_to_cpu(psid->sub_auth[i]));
+ }
+
+ cifs_dbg(FYI, "RID 0x%x\n",
+ le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]));
+ }
+#endif
+
+ return 0;
+}
+
+static int sid_from_sd(const struct smb_ntsd *pntsd, __u32 secdesclen,
+ __u32 sid_offset, struct smb_sid **sid)
+{
+ struct smb_sid *psid;
+ char *end_of_acl;
+
+ if (secdesclen < sizeof(struct smb_ntsd)) {
+ cifs_dbg(VFS, "ACL too small to parse security descriptor\n");
+ return -EINVAL;
+ }
+ end_of_acl = (char *)pntsd + secdesclen;
+
+ if (sid_offset < sizeof(struct smb_ntsd) ||
+ sid_offset > secdesclen - CIFS_SID_BASE_SIZE) {
+ cifs_dbg(VFS, "Server returned illegal SID offset\n");
+ return -EINVAL;
+ }
+
+ psid = (struct smb_sid *)((char *)pntsd + sid_offset);
+ if (parse_sid(psid, end_of_acl))
+ return -EINVAL;
+
+ *sid = psid;
+ return 0;
+}
+
static int
id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid)
{
@@ -515,14 +600,14 @@ exit_cifs_idmap(void)
}
/* copy ntsd, owner sid, and group sid from a security descriptor to another */
-static __u32 copy_sec_desc(const struct smb_ntsd *pntsd,
- struct smb_ntsd *pnntsd,
- __u32 sidsoffset,
- struct smb_sid *pownersid,
- struct smb_sid *pgrpsid)
+static int copy_sec_desc(const struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
+ __u32 sidsoffset, __u32 secdesclen,
+ __u32 *pnsecdesclen, struct smb_sid *pownersid,
+ struct smb_sid *pgrpsid)
{
struct smb_sid *owner_sid_ptr, *group_sid_ptr;
struct smb_sid *nowner_sid_ptr, *ngroup_sid_ptr;
+ int rc;
/* copy security descriptor control portion */
pnntsd->revision = pntsd->revision;
@@ -533,28 +618,34 @@ static __u32 copy_sec_desc(const struct smb_ntsd *pntsd,
pnntsd->gsidoffset = cpu_to_le32(sidsoffset + sizeof(struct smb_sid));
/* copy owner sid */
- if (pownersid)
+ if (pownersid) {
owner_sid_ptr = pownersid;
- else
- owner_sid_ptr = (struct smb_sid *)((char *)pntsd +
- le32_to_cpu(pntsd->osidoffset));
+ } else {
+ rc = sid_from_sd(pntsd, secdesclen,
+ le32_to_cpu(pntsd->osidoffset), &owner_sid_ptr);
+ if (rc)
+ return rc;
+ }
nowner_sid_ptr = (struct smb_sid *)((char *)pnntsd + sidsoffset);
cifs_copy_sid(nowner_sid_ptr, owner_sid_ptr);
/* copy group sid */
- if (pgrpsid)
+ if (pgrpsid) {
group_sid_ptr = pgrpsid;
- else
- group_sid_ptr = (struct smb_sid *)((char *)pntsd +
- le32_to_cpu(pntsd->gsidoffset));
+ } else {
+ rc = sid_from_sd(pntsd, secdesclen,
+ le32_to_cpu(pntsd->gsidoffset), &group_sid_ptr);
+ if (rc)
+ return rc;
+ }
ngroup_sid_ptr = (struct smb_sid *)((char *)pnntsd + sidsoffset +
sizeof(struct smb_sid));
cifs_copy_sid(ngroup_sid_ptr, group_sid_ptr);
- return sidsoffset + (2 * sizeof(struct smb_sid));
+ *pnsecdesclen = sidsoffset + (2 * sizeof(struct smb_sid));
+ return 0;
}
-
/*
change posix mode to reflect permissions
pmode is the existing mode (we only want to overwrite part of this
@@ -758,6 +849,77 @@ static void dump_ace(struct smb_ace *pace, char *end_of_acl)
}
#endif
+static int validate_dacl(struct smb_acl *pdacl, char *end_of_acl)
+{
+ int i, ace_hdr_size, ace_size, min_ace_size;
+ u16 dacl_size, num_aces;
+ char *acl_base, *end_of_dacl;
+ struct smb_ace *pace;
+
+ if (!pdacl)
+ return 0;
+
+ if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl)) {
+ cifs_dbg(VFS, "ACL too small to parse DACL\n");
+ return -EINVAL;
+ }
+
+ dacl_size = le16_to_cpu(pdacl->size);
+ if (dacl_size < sizeof(struct smb_acl) ||
+ end_of_acl < (char *)pdacl + dacl_size) {
+ cifs_dbg(VFS, "ACL too small to parse DACL\n");
+ return -EINVAL;
+ }
+
+ num_aces = le16_to_cpu(pdacl->num_aces);
+ if (!num_aces)
+ return 0;
+
+ ace_hdr_size = offsetof(struct smb_ace, sid) +
+ offsetof(struct smb_sid, sub_auth);
+ min_ace_size = ace_hdr_size + sizeof(__le32);
+ if (num_aces > (dacl_size - sizeof(struct smb_acl)) / min_ace_size) {
+ cifs_dbg(VFS, "ACL too small to parse DACL\n");
+ return -EINVAL;
+ }
+
+ end_of_dacl = (char *)pdacl + dacl_size;
+ acl_base = (char *)pdacl;
+ ace_size = sizeof(struct smb_acl);
+
+ for (i = 0; i < num_aces; ++i) {
+ if (end_of_dacl - acl_base < ace_size) {
+ cifs_dbg(VFS, "ACL too small to parse ACE\n");
+ return -EINVAL;
+ }
+
+ pace = (struct smb_ace *)(acl_base + ace_size);
+ acl_base = (char *)pace;
+
+ if (end_of_dacl - acl_base < ace_hdr_size ||
+ pace->sid.num_subauth == 0 ||
+ pace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) {
+ cifs_dbg(VFS, "ACL too small to parse ACE\n");
+ return -EINVAL;
+ }
+
+ ace_size = ace_hdr_size + sizeof(__le32) * pace->sid.num_subauth;
+ if (end_of_dacl - acl_base < ace_size ||
+ le16_to_cpu(pace->size) < ace_size) {
+ cifs_dbg(VFS, "ACL too small to parse ACE\n");
+ return -EINVAL;
+ }
+
+ ace_size = le16_to_cpu(pace->size);
+ if (end_of_dacl - acl_base < ace_size) {
+ cifs_dbg(VFS, "ACL too small to parse ACE\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
struct smb_sid *pownersid, struct smb_sid *pgrpsid,
struct cifs_fattr *fattr, bool mode_from_special_sid)
@@ -777,12 +939,8 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
return;
}
- /* validate that we do not go past end of acl */
- if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl) ||
- end_of_acl < (char *)pdacl + le16_to_cpu(pdacl->size)) {
- cifs_dbg(VFS, "ACL too small to parse DACL\n");
+ if (validate_dacl(pdacl, end_of_acl))
return;
- }
cifs_dbg(NOISY, "DACL revision %d size %d num aces %d\n",
le16_to_cpu(pdacl->revision), le16_to_cpu(pdacl->size),
@@ -800,37 +958,19 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
if (num_aces > 0) {
umode_t denied_mode = 0;
- if (num_aces > (le16_to_cpu(pdacl->size) - sizeof(struct smb_acl)) /
- (offsetof(struct smb_ace, sid) +
- offsetof(struct smb_sid, sub_auth) + sizeof(__le16)))
- return;
-
ppace = kmalloc_objs(struct smb_ace *, num_aces);
if (!ppace)
return;
for (i = 0; i < num_aces; ++i) {
- if (end_of_acl - acl_base < acl_size)
- break;
-
ppace[i] = (struct smb_ace *) (acl_base + acl_size);
- acl_base = (char *)ppace[i];
- acl_size = offsetof(struct smb_ace, sid) +
- offsetof(struct smb_sid, sub_auth);
-
- if (end_of_acl - acl_base < acl_size ||
- ppace[i]->sid.num_subauth == 0 ||
- ppace[i]->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
- (end_of_acl - acl_base <
- acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth) ||
- (le16_to_cpu(ppace[i]->size) <
- acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth))
- break;
#ifdef CONFIG_CIFS_DEBUG2
- dump_ace(ppace[i], end_of_acl);
+ dump_ace(ppace[i],
+ (char *)pdacl + le16_to_cpu(pdacl->size));
#endif
if (mode_from_special_sid &&
+ ppace[i]->sid.num_subauth >= 3 &&
(compare_sids(&(ppace[i]->sid),
&sid_unix_NFS_mode) == 0)) {
/*
@@ -840,7 +980,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) {
@@ -870,6 +1010,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
(void *)ppace[i],
sizeof(struct smb_ace)); */
+ acl_base = (char *)ppace[i];
acl_size = le16_to_cpu(ppace[i]->size);
}
@@ -955,13 +1096,13 @@ unsigned int setup_special_user_owner_ACE(struct smb_ace *pntace)
static void populate_new_aces(char *nacl_base,
struct smb_sid *pownersid,
struct smb_sid *pgrpsid,
- __u64 *pnmode, u16 *pnum_aces, u16 *pnsize,
+ __u64 *pnmode, u16 *pnum_aces, u32 *pnsize,
bool modefromsid,
bool posix)
{
__u64 nmode;
u16 num_aces = 0;
- u16 nsize = 0;
+ u32 nsize = 0;
__u64 user_mode;
__u64 group_mode;
__u64 other_mode;
@@ -1060,16 +1201,17 @@ set_size:
*pnsize = nsize;
}
-static __u16 replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *pndacl,
- struct smb_sid *pownersid, struct smb_sid *pgrpsid,
- struct smb_sid *pnownersid, struct smb_sid *pngrpsid)
+static int replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *pndacl,
+ struct smb_sid *pownersid, struct smb_sid *pgrpsid,
+ struct smb_sid *pnownersid, struct smb_sid *pngrpsid,
+ int *aclflag, u16 *pnsize)
{
int i;
u16 size = 0;
struct smb_ace *pntace = NULL;
char *acl_base = NULL;
u16 src_num_aces = 0;
- u16 nsize = 0;
+ u32 nsize = 0;
struct smb_ace *pnntace = NULL;
char *nacl_base = NULL;
u16 ace_size = 0;
@@ -1086,18 +1228,24 @@ static __u16 replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *p
pntace = (struct smb_ace *) (acl_base + size);
pnntace = (struct smb_ace *) (nacl_base + nsize);
- if (pnownersid && compare_sids(&pntace->sid, pownersid) == 0)
+ if (pnownersid && compare_sids(&pntace->sid, pownersid) == 0) {
ace_size = cifs_copy_ace(pnntace, pntace, pnownersid);
- else if (pngrpsid && compare_sids(&pntace->sid, pgrpsid) == 0)
+ *aclflag |= CIFS_ACL_DACL;
+ } else if (pngrpsid && compare_sids(&pntace->sid, pgrpsid) == 0) {
ace_size = cifs_copy_ace(pnntace, pntace, pngrpsid);
- else
+ *aclflag |= CIFS_ACL_DACL;
+ } else {
ace_size = cifs_copy_ace(pnntace, pntace, NULL);
+ }
size += le16_to_cpu(pntace->size);
nsize += ace_size;
+ if (nsize > U16_MAX)
+ return -EOVERFLOW;
}
- return nsize;
+ *pnsize = nsize;
+ return 0;
}
static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl,
@@ -1109,7 +1257,7 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl,
struct smb_ace *pntace = NULL;
char *acl_base = NULL;
u16 src_num_aces = 0;
- u16 nsize = 0;
+ u32 nsize = 0;
struct smb_ace *pnntace = NULL;
char *nacl_base = NULL;
u16 num_aces = 0;
@@ -1160,6 +1308,8 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl,
nsize += cifs_copy_ace(pnntace, pntace, NULL);
num_aces++;
+ if (nsize > U16_MAX)
+ return -EOVERFLOW;
next_ace:
size += le16_to_cpu(pntace->size);
@@ -1176,42 +1326,25 @@ next_ace:
}
finalize_dacl:
+ /* The DACL size field is 16-bit on the wire, see MS-DTYP 2.4.5 */
+ if (nsize > U16_MAX)
+ return -EOVERFLOW;
+
pndacl->num_aces = cpu_to_le16(num_aces);
pndacl->size = cpu_to_le16(nsize);
return 0;
}
-static int parse_sid(struct smb_sid *psid, char *end_of_acl)
+static bool dacl_offset_valid(unsigned int acl_len, __u32 dacloffset)
{
- /* BB need to add parm so we can store the SID BB */
-
- /* validate that we do not go past end of ACL - sid must be at least 8
- bytes long (assuming no sub-auths - e.g. the null SID */
- if (end_of_acl < (char *)psid + 8) {
- cifs_dbg(VFS, "ACL too small to parse SID %p\n", psid);
- return -EINVAL;
- }
-
-#ifdef CONFIG_CIFS_DEBUG2
- if (psid->num_subauth) {
- int i;
- cifs_dbg(FYI, "SID revision %d num_auth %d\n",
- psid->revision, psid->num_subauth);
-
- for (i = 0; i < psid->num_subauth; i++) {
- cifs_dbg(FYI, "SID sub_auth[%d]: 0x%x\n",
- i, le32_to_cpu(psid->sub_auth[i]));
- }
+ if (acl_len < sizeof(struct smb_acl))
+ return false;
- /* BB add length check to make sure that we do not have huge
- num auths and therefore go off the end */
- cifs_dbg(FYI, "RID 0x%x\n",
- le32_to_cpu(psid->sub_auth[psid->num_subauth-1]));
- }
-#endif
+ if (dacloffset < sizeof(struct smb_ntsd))
+ return false;
- return 0;
+ return dacloffset <= acl_len - sizeof(struct smb_acl);
}
@@ -1222,54 +1355,69 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
{
int rc = 0;
struct smb_sid *owner_sid_ptr, *group_sid_ptr;
+ unsigned int sbflags = cifs_sb_flags(cifs_sb);
struct smb_acl *dacl_ptr; /* no need for SACL ptr */
- char *end_of_acl = ((char *)pntsd) + acl_len;
- __u32 dacloffset;
+ char *end_of_acl;
+ __u32 dacloffset, osidoffset, gsidoffset;
if (pntsd == NULL)
return smb_EIO(smb_eio_trace_null_pointers);
+ if (acl_len < (int)sizeof(struct smb_ntsd)) {
+ cifs_dbg(VFS, "ACL too small to parse security descriptor\n");
+ return -EINVAL;
+ }
+ end_of_acl = ((char *)pntsd) + acl_len;
- owner_sid_ptr = (struct smb_sid *)((char *)pntsd +
- le32_to_cpu(pntsd->osidoffset));
- group_sid_ptr = (struct smb_sid *)((char *)pntsd +
- le32_to_cpu(pntsd->gsidoffset));
+ osidoffset = le32_to_cpu(pntsd->osidoffset);
+ gsidoffset = le32_to_cpu(pntsd->gsidoffset);
dacloffset = le32_to_cpu(pntsd->dacloffset);
- dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
cifs_dbg(NOISY, "revision %d type 0x%x ooffset 0x%x goffset 0x%x sacloffset 0x%x dacloffset 0x%x\n",
- pntsd->revision, pntsd->type, le32_to_cpu(pntsd->osidoffset),
- le32_to_cpu(pntsd->gsidoffset),
+ pntsd->revision, pntsd->type, osidoffset, gsidoffset,
le32_to_cpu(pntsd->sacloffset), dacloffset);
-/* cifs_dump_mem("owner_sid: ", owner_sid_ptr, 64); */
- rc = parse_sid(owner_sid_ptr, end_of_acl);
+ fattr->cf_uid = cifs_sb->ctx->linux_uid;
+ fattr->cf_gid = cifs_sb->ctx->linux_gid;
+
+ rc = sid_from_sd(pntsd, acl_len, osidoffset, &owner_sid_ptr);
if (rc) {
cifs_dbg(FYI, "%s: Error %d parsing Owner SID\n", __func__, rc);
return rc;
}
- rc = sid_to_id(cifs_sb, owner_sid_ptr, fattr, SIDOWNER);
- if (rc) {
- cifs_dbg(FYI, "%s: Error %d mapping Owner SID to uid\n",
- __func__, rc);
- return rc;
+ if (!(sbflags & CIFS_MOUNT_OVERR_UID)) {
+ rc = sid_to_id(cifs_sb, owner_sid_ptr, fattr, SIDOWNER);
+ if (rc) {
+ cifs_dbg(FYI, "%s: Error %d mapping Owner SID to uid\n",
+ __func__, rc);
+ return rc;
+ }
}
- rc = parse_sid(group_sid_ptr, end_of_acl);
+ rc = sid_from_sd(pntsd, acl_len, gsidoffset, &group_sid_ptr);
if (rc) {
- cifs_dbg(FYI, "%s: Error %d mapping Owner SID to gid\n",
+ cifs_dbg(FYI, "%s: Error %d parsing Group SID\n",
__func__, rc);
return rc;
}
- rc = sid_to_id(cifs_sb, group_sid_ptr, fattr, SIDGROUP);
- if (rc) {
- cifs_dbg(FYI, "%s: Error %d mapping Group SID to gid\n",
- __func__, rc);
- return rc;
+ if (!(sbflags & CIFS_MOUNT_OVERR_GID)) {
+ rc = sid_to_id(cifs_sb, group_sid_ptr, fattr, SIDGROUP);
+ if (rc) {
+ cifs_dbg(FYI, "%s: Error %d mapping Group SID to gid\n",
+ __func__, rc);
+ return rc;
+ }
}
- if (dacloffset)
+ if (dacloffset) {
+ if (!dacl_offset_valid(acl_len, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
+ return -EINVAL;
+ }
+
+ dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
parse_dacl(dacl_ptr, end_of_acl, owner_sid_ptr,
group_sid_ptr, fattr, get_mode_from_special_sid);
- else
+ } else {
cifs_dbg(FYI, "no ACL\n"); /* BB grant all or default perms? */
+ }
return rc;
}
@@ -1287,22 +1435,41 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
struct smb_sid *nowner_sid_ptr = NULL, *ngroup_sid_ptr = NULL;
struct smb_acl *dacl_ptr = NULL; /* no need for SACL ptr */
struct smb_acl *ndacl_ptr = NULL; /* no need for SACL ptr */
- char *end_of_acl = ((char *)pntsd) + secdesclen;
+ char *end_of_acl;
u16 size = 0;
+ __u32 osidoffset, gsidoffset;
+
+ if (secdesclen < sizeof(struct smb_ntsd)) {
+ cifs_dbg(VFS, "ACL too small to parse security descriptor\n");
+ return -EINVAL;
+ }
+ end_of_acl = ((char *)pntsd) + secdesclen;
dacloffset = le32_to_cpu(pntsd->dacloffset);
if (dacloffset) {
- dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
- if (end_of_acl < (char *)dacl_ptr + le16_to_cpu(dacl_ptr->size)) {
- cifs_dbg(VFS, "Server returned illegal ACL size\n");
+ if (!dacl_offset_valid(secdesclen, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
return -EINVAL;
}
+
+ dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
+ rc = validate_dacl(dacl_ptr, end_of_acl);
+ if (rc)
+ return rc;
}
- owner_sid_ptr = (struct smb_sid *)((char *)pntsd +
- le32_to_cpu(pntsd->osidoffset));
- group_sid_ptr = (struct smb_sid *)((char *)pntsd +
- le32_to_cpu(pntsd->gsidoffset));
+ osidoffset = le32_to_cpu(pntsd->osidoffset);
+ gsidoffset = le32_to_cpu(pntsd->gsidoffset);
+ rc = sid_from_sd(pntsd, secdesclen, osidoffset, &owner_sid_ptr);
+ if (rc) {
+ cifs_dbg(FYI, "%s: Error %d parsing Owner SID\n", __func__, rc);
+ return rc;
+ }
+ rc = sid_from_sd(pntsd, secdesclen, gsidoffset, &group_sid_ptr);
+ if (rc) {
+ cifs_dbg(FYI, "%s: Error %d parsing Group SID\n", __func__, rc);
+ return rc;
+ }
if (pnmode && *pnmode != NO_CHANGE_64) { /* chmod */
ndacloffset = sizeof(struct smb_ntsd);
@@ -1315,11 +1482,15 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr,
pnmode, mode_from_sid, posix);
+ if (rc)
+ return rc;
sidsoffset = ndacloffset + le16_to_cpu(ndacl_ptr->size);
/* copy the non-dacl portion of secdesc */
- *pnsecdesclen = copy_sec_desc(pntsd, pnntsd, sidsoffset,
- NULL, NULL);
+ rc = copy_sec_desc(pntsd, pnntsd, sidsoffset, secdesclen,
+ pnsecdesclen, NULL, NULL);
+ if (rc)
+ return rc;
*aclflag |= CIFS_ACL_DACL;
} else {
@@ -1388,16 +1559,21 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
if (dacloffset) {
/* Replace ACEs for old owner with new one */
- size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr,
- owner_sid_ptr, group_sid_ptr,
- nowner_sid_ptr, ngroup_sid_ptr);
+ rc = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr,
+ owner_sid_ptr, group_sid_ptr,
+ nowner_sid_ptr, ngroup_sid_ptr,
+ aclflag, &size);
+ if (rc)
+ goto chown_chgrp_exit;
ndacl_ptr->size = cpu_to_le16(size);
}
sidsoffset = ndacloffset + le16_to_cpu(ndacl_ptr->size);
/* copy the non-dacl portion of secdesc */
- *pnsecdesclen = copy_sec_desc(pntsd, pnntsd, sidsoffset,
- nowner_sid_ptr, ngroup_sid_ptr);
+ rc = copy_sec_desc(pntsd, pnntsd, sidsoffset, secdesclen,
+ pnsecdesclen, nowner_sid_ptr, ngroup_sid_ptr);
+ if (rc)
+ goto chown_chgrp_exit;
chown_chgrp_exit:
/* errors could jump here. So make sure we return soon after this */
@@ -1489,7 +1665,7 @@ struct smb_ntsd *get_cifs_acl(struct cifs_sb_info *cifs_sb,
struct cifsFileInfo *open_file = NULL;
if (inode)
- open_file = find_readable_file(CIFS_I(inode), true);
+ open_file = find_readable_file(CIFS_I(inode), FIND_FSUID_ONLY);
if (!open_file)
return get_cifs_acl_by_path(cifs_sb, path, pacllen, info);
@@ -1605,7 +1781,7 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
kuid_t uid, kgid_t gid)
{
int rc = 0;
- int aclflag = CIFS_ACL_DACL; /* default flag to set */
+ int aclflag = 0;
__u32 secdesclen = 0;
__u32 nsecdesclen = 0;
__u32 dacloffset = 0;
@@ -1661,12 +1837,26 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
nsecdesclen = sizeof(struct smb_ntsd) + (sizeof(struct smb_sid) * 2);
dacloffset = le32_to_cpu(pntsd->dacloffset);
if (dacloffset) {
+ if (!dacl_offset_valid(secdesclen, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
+ rc = -EINVAL;
+ goto id_mode_to_cifs_acl_exit;
+ }
+
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
- if (mode_from_sid)
- nsecdesclen +=
- le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace);
- else /* cifsacl */
- nsecdesclen += le16_to_cpu(dacl_ptr->size);
+ rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen);
+ if (rc) {
+ kfree(pntsd);
+ cifs_put_tlink(tlink);
+ return rc;
+ }
+ /*
+ * Worst case: every ACE is rewritten with a new SID of
+ * SID_MAX_SUB_AUTHORITIES sub-auths -> sizeof(smb_ace) each,
+ * plus the smb_acl header replace_sids_and_copy_aces() emits.
+ */
+ nsecdesclen += sizeof(struct smb_acl) +
+ le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace);
}
}
@@ -1677,7 +1867,7 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
* descriptor parameters, and security descriptor itself
*/
nsecdesclen = max_t(u32, nsecdesclen, DEFAULT_SEC_DESC_LEN);
- pnntsd = kmalloc(nsecdesclen, GFP_KERNEL);
+ pnntsd = kzalloc(nsecdesclen, GFP_KERNEL);
if (!pnntsd) {
kfree(pntsd);
cifs_put_tlink(tlink);
@@ -1689,14 +1879,24 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
cifs_dbg(NOISY, "build_sec_desc rc: %d\n", rc);
- if (ops->set_acl == NULL)
- rc = -EOPNOTSUPP;
+ if (rc != 0)
+ goto id_mode_to_cifs_acl_exit;
- if (!rc) {
- /* Set the security descriptor */
- rc = ops->set_acl(pnntsd, nsecdesclen, inode, path, aclflag);
- cifs_dbg(NOISY, "set_cifs_acl rc: %d\n", rc);
+ if (aclflag == 0) {
+ cifs_dbg(FYI, "set_cifs_acl aclflag=0, no change mapped\n");
+ goto id_mode_to_cifs_acl_exit;
}
+
+ if (ops->set_acl == NULL) {
+ rc = -EOPNOTSUPP;
+ goto id_mode_to_cifs_acl_exit;
+ }
+
+ /* Set the security descriptor */
+ rc = ops->set_acl(pnntsd, nsecdesclen, inode, path, aclflag);
+ cifs_dbg(NOISY, "set_cifs_acl rc: %d\n", rc);
+
+id_mode_to_cifs_acl_exit:
cifs_put_tlink(tlink);
kfree(pnntsd);
diff --git a/fs/smb/client/cifsencrypt.c b/fs/smb/client/cifsencrypt.c
index 3d731f3af235..71a2a59123f5 100644
--- a/fs/smb/client/cifsencrypt.c
+++ b/fs/smb/client/cifsencrypt.c
@@ -22,49 +22,33 @@
#include <linux/fips.h>
#include <linux/iov_iter.h>
#include <crypto/aead.h>
+#include <crypto/aes-cbc-macs.h>
#include <crypto/arc4.h>
#include <crypto/md5.h>
#include <crypto/sha2.h>
-static int cifs_sig_update(struct cifs_calc_sig_ctx *ctx,
- const u8 *data, size_t len)
+static size_t cifs_sig_step(void *iter_base, size_t progress, size_t len,
+ void *priv, void *priv2)
{
- if (ctx->md5) {
- md5_update(ctx->md5, data, len);
- return 0;
- }
- if (ctx->hmac) {
- hmac_sha256_update(ctx->hmac, data, len);
- return 0;
- }
- return crypto_shash_update(ctx->shash, data, len);
+ struct cifs_calc_sig_ctx *ctx = priv;
+
+ if (ctx->md5)
+ md5_update(ctx->md5, iter_base, len);
+ else if (ctx->hmac)
+ hmac_sha256_update(ctx->hmac, iter_base, len);
+ else
+ aes_cmac_update(ctx->cmac, iter_base, len);
+ return 0; /* Return value is length *not* processed, i.e. 0. */
}
-static int cifs_sig_final(struct cifs_calc_sig_ctx *ctx, u8 *out)
+static void cifs_sig_final(struct cifs_calc_sig_ctx *ctx, u8 *out)
{
- if (ctx->md5) {
+ if (ctx->md5)
md5_final(ctx->md5, out);
- return 0;
- }
- if (ctx->hmac) {
+ else if (ctx->hmac)
hmac_sha256_final(ctx->hmac, out);
- return 0;
- }
- return crypto_shash_final(ctx->shash, out);
-}
-
-static size_t cifs_sig_step(void *iter_base, size_t progress, size_t len,
- void *priv, void *priv2)
-{
- struct cifs_calc_sig_ctx *ctx = priv;
- int ret, *pret = priv2;
-
- ret = cifs_sig_update(ctx, iter_base, len);
- if (ret < 0) {
- *pret = ret;
- return len;
- }
- return 0;
+ else
+ aes_cmac_final(ctx->cmac, out);
}
/*
@@ -75,9 +59,8 @@ static int cifs_sig_iter(const struct iov_iter *iter, size_t maxsize,
{
struct iov_iter tmp_iter = *iter;
size_t did;
- int err;
- did = iterate_and_advance_kernel(&tmp_iter, maxsize, ctx, &err,
+ did = iterate_and_advance_kernel(&tmp_iter, maxsize, ctx, NULL,
cifs_sig_step);
if (did != maxsize)
return smb_EIO2(smb_eio_trace_sig_iter, did, maxsize);
@@ -108,11 +91,8 @@ int __cifs_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
if (rc < 0)
return rc;
- rc = cifs_sig_final(ctx, signature);
- if (rc)
- cifs_dbg(VFS, "%s: Could not generate hash\n", __func__);
-
- return rc;
+ cifs_sig_final(ctx, signature);
+ return 0;
}
/* Build a proper attribute value/target info pairs blob.
@@ -269,12 +249,13 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash,
E_md4hash(ses->password, nt_hash, nls_cp);
hmac_md5_init_usingrawkey(&hmac_ctx, nt_hash, CIFS_NTHASH_SIZE);
+ memzero_explicit(nt_hash, sizeof(nt_hash));
/* convert ses->user_name to unicode */
len = ses->user_name ? strlen(ses->user_name) : 0;
user = kmalloc(2 + (len * 2), GFP_KERNEL);
if (user == NULL)
- return -ENOMEM;
+ goto out_nomem;
if (len) {
len = cifs_strtoUTF16(user, ses->user_name, len, nls_cp);
@@ -292,7 +273,7 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash,
domain = kmalloc(2 + (len * 2), GFP_KERNEL);
if (domain == NULL)
- return -ENOMEM;
+ goto out_nomem;
len = cifs_strtoUTF16((__le16 *)domain, ses->domainName, len,
nls_cp);
@@ -304,7 +285,7 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash,
server = kmalloc(2 + (len * 2), GFP_KERNEL);
if (server == NULL)
- return -ENOMEM;
+ goto out_nomem;
len = cifs_strtoUTF16((__le16 *)server, ses->ip_addr, len, nls_cp);
hmac_md5_update(&hmac_ctx, (const u8 *)server, 2 * len);
@@ -313,6 +294,10 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash,
hmac_md5_final(&hmac_ctx, ntlmv2_hash);
return 0;
+
+out_nomem:
+ memzero_explicit(&hmac_ctx, sizeof(hmac_ctx));
+ return -ENOMEM;
}
static void CalcNTLMv2_response(const struct cifs_ses *ses, char *ntlmv2_hash)
@@ -483,6 +468,7 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp)
rc = 0;
unlock:
cifs_server_unlock(ses->server);
+ memzero_explicit(ntlmv2_hash, sizeof(ntlmv2_hash));
setup_ntlmv2_rsp_ret:
kfree_sensitive(tiblob);
@@ -523,8 +509,6 @@ calc_seckey(struct cifs_ses *ses)
void
cifs_crypto_secmech_release(struct TCP_Server_Info *server)
{
- cifs_free_hash(&server->secmech.aes_cmac);
-
if (server->secmech.enc) {
crypto_free_aead(server->secmech.enc);
server->secmech.enc = NULL;
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index 427558404aa5..7ecd70efdfea 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/fs.h>
+#include <linux/fs_context.h>
#include <linux/filelock.h>
#include <linux/mount.h>
#include <linux/slab.h>
@@ -30,6 +31,7 @@
#include <linux/xattr.h>
#include <linux/mm.h>
#include <linux/key-type.h>
+#include <linux/fileattr.h>
#include <uapi/linux/magic.h>
#include <net/ipv6.h>
#include "cifsfs.h"
@@ -124,42 +126,8 @@ 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);
-/*
- * Write-only module parameter to drop all cached directory entries across
- * all CIFS mounts. Echo a non-zero value to trigger.
- */
-static void cifs_drop_all_dir_caches(void)
-{
- struct TCP_Server_Info *server;
- struct cifs_ses *ses;
- struct cifs_tcon *tcon;
-
- spin_lock(&cifs_tcp_ses_lock);
- list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
- list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
- if (cifs_ses_exiting(ses))
- continue;
- list_for_each_entry(tcon, &ses->tcon_list, tcon_list)
- invalidate_all_cached_dirs(tcon);
- }
- }
- spin_unlock(&cifs_tcp_ses_lock);
-}
-
-static int cifs_param_set_drop_dir_cache(const char *val, const struct kernel_param *kp)
-{
- bool bv;
- int rc = kstrtobool(val, &bv);
-
- if (rc)
- return rc;
- if (bv)
- cifs_drop_all_dir_caches();
- return 0;
-}
-
-module_param_call(drop_dir_cache, cifs_param_set_drop_dir_cache, NULL, NULL, 0200);
-MODULE_PARM_DESC(drop_dir_cache, "Write 1 to drop all cached directory entries across all CIFS mounts");
+atomic_t cifs_sillycounter;
+atomic_t cifs_tmpcounter;
#ifdef CONFIG_CIFS_STATS2
unsigned int slow_rsp_threshold = 1;
@@ -332,10 +300,28 @@ static void cifs_kill_sb(struct super_block *sb)
/*
* We need to release all dentries for the cached directories
- * before we kill the sb.
+ * and close all deferred file handles before we kill the sb.
*/
if (cifs_sb->root) {
close_all_cached_dirs(cifs_sb);
+ cifs_close_all_deferred_files_sb(cifs_sb);
+
+ /* Wait for all pending oplock breaks to complete */
+ flush_workqueue(cifsoplockd_wq);
+ /* 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);
@@ -454,6 +440,7 @@ cifs_alloc_inode(struct super_block *sb)
return NULL;
cifs_inode->cifsAttrs = ATTR_ARCHIVE; /* default */
cifs_inode->time = 0;
+ cifs_inode->time_last_write = 0;
/*
* Until the file is open and we have gotten oplock info back from the
* server, can not assume caching of file data or metadata.
@@ -464,7 +451,8 @@ cifs_alloc_inode(struct super_block *sb)
spin_lock_init(&cifs_inode->writers_lock);
cifs_inode->writers = 0;
cifs_inode->netfs.inode.i_blkbits = 14; /* 2**14 = CIFS_MAX_MSGSIZE */
- cifs_inode->netfs.remote_i_size = 0;
+ cifs_inode->netfs._remote_i_size = 0;
+ cifs_inode->netfs._zero_point = 0;
cifs_inode->uniqueid = 0;
cifs_inode->createtime = 0;
cifs_inode->epoch = 0;
@@ -704,6 +692,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
seq_puts(s, ",seal");
else if (tcon->ses->server->ignore_signature)
seq_puts(s, ",signloosely");
+ if (cifs_sb->ctx->compress)
+ seq_puts(s, ",compress");
if (tcon->nocase)
seq_puts(s, ",nocase");
if (tcon->nodelete)
@@ -868,7 +858,6 @@ static void cifs_umount_begin(struct super_block *sb)
spin_unlock(&tcon->tc_lock);
spin_unlock(&cifs_tcp_ses_lock);
- cifs_close_all_deferred_files(tcon);
/* cancel_brl_requests(tcon); */ /* BB mark all brl mids as exiting */
/* cancel_notify_requests(tcon); */
if (tcon->ses && tcon->ses->server) {
@@ -997,26 +986,19 @@ cifs_get_root(struct smb3_fs_context *ctx, struct super_block *sb)
return dentry;
}
-static int cifs_set_super(struct super_block *sb, void *data)
-{
- struct cifs_mnt_data *mnt_data = data;
- sb->s_fs_info = mnt_data->cifs_sb;
- return set_anon_super(sb, NULL);
-}
-
struct dentry *
-cifs_smb3_do_mount(struct file_system_type *fs_type,
- int flags, struct smb3_fs_context *old_ctx)
+cifs_smb3_do_mount(struct fs_context *fc, struct smb3_fs_context *old_ctx)
{
struct cifs_mnt_data mnt_data;
struct cifs_sb_info *cifs_sb;
struct super_block *sb;
struct dentry *root;
+ unsigned int saved_sb_flags;
int rc;
if (cifsFYI) {
- cifs_dbg(FYI, "%s: devname=%s flags=0x%x\n", __func__,
- old_ctx->source, flags);
+ cifs_dbg(FYI, "%s: devname=%s sb_flags=0x%x\n", __func__,
+ old_ctx->source, fc->sb_flags);
} else {
cifs_info("Attempting to mount %s\n", old_ctx->source);
}
@@ -1043,7 +1025,7 @@ cifs_smb3_do_mount(struct file_system_type *fs_type,
rc = cifs_mount(cifs_sb, cifs_sb->ctx);
if (rc) {
- if (!(flags & SB_SILENT))
+ if (!(fc->sb_flags & SB_SILENT))
cifs_dbg(VFS, "cifs_mount failed w/return code = %d\n",
rc);
root = ERR_PTR(rc);
@@ -1052,12 +1034,27 @@ cifs_smb3_do_mount(struct file_system_type *fs_type,
mnt_data.ctx = cifs_sb->ctx;
mnt_data.cifs_sb = cifs_sb;
- mnt_data.flags = flags;
-
- /* BB should we make this contingent on mount parm? */
- flags |= SB_NODIRATIME | SB_NOATIME;
+ mnt_data.flags = 0;
- sb = sget(fs_type, cifs_match_super, cifs_set_super, flags, &mnt_data);
+ /*
+ * sb->s_flags is set from fc->sb_flags by alloc_super(). CIFS has
+ * historically forced SB_NODIRATIME | SB_NOATIME on every mount and
+ * ignored the caller-supplied SB_* flags. Preserve that behaviour by
+ * overriding fc->sb_flags around the sget_fc() call.
+ *
+ * Hand cifs_sb to sget_fc() via fc->s_fs_info; sget_fc() copies it
+ * onto sb->s_fs_info before running set() and clears fc->s_fs_info
+ * on successful publish. Pass the rest of the per-mount context to
+ * cifs_match_super() through fc->sget_key.
+ */
+ saved_sb_flags = fc->sb_flags;
+ fc->sb_flags = SB_NODIRATIME | SB_NOATIME;
+ fc->s_fs_info = cifs_sb;
+ fc->sget_key = &mnt_data;
+ sb = sget_fc(fc, cifs_match_super, set_anon_super_fc);
+ fc->sget_key = NULL;
+ fc->s_fs_info = NULL;
+ fc->sb_flags = saved_sb_flags;
if (IS_ERR(sb)) {
cifs_umount(cifs_sb);
return ERR_CAST(sb);
@@ -1193,9 +1190,60 @@ struct file_system_type smb3_fs_type = {
MODULE_ALIAS_FS("smb3");
MODULE_ALIAS("smb3");
+int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb);
+ struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+ struct inode *inode = d_inode(dentry);
+ u32 attrs;
+
+ /* Preserve FS_COMPR_FL previously reported by cifs_ioctl(). */
+ if (CIFS_I(inode)->cifsAttrs & ATTR_COMPRESSED)
+ fa->flags |= FS_COMPR_FL;
+
+ /*
+ * FS_CASEFOLD_FL is defined by UAPI as a folder attribute,
+ * and userspace tools (e.g., lsattr) display it only on
+ * directories. Confine the case-handling bits to directories
+ * to match that convention; for non-directories the share's
+ * case semantics are still discoverable through the parent.
+ */
+ if (!S_ISDIR(inode->i_mode))
+ return 0;
+
+ /*
+ * The server's FS_ATTRIBUTE_INFORMATION response, cached on
+ * the tcon at mount, reflects the share's case-handling
+ * semantics after any POSIX extensions negotiation. Prefer
+ * it over the client-local nocase mount option, which only
+ * governs dentry comparison on this superblock.
+ *
+ * QueryFSInfo is best-effort at mount; when it did not
+ * populate fsAttrInfo, MaxPathNameComponentLength remains
+ * zero. In that case fall back to nocase so the reporting
+ * matches the comparison behavior installed on the sb.
+ */
+ if (le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength) == 0) {
+ if (tcon->nocase) {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ }
+ return 0;
+ }
+ attrs = le32_to_cpu(tcon->fsAttrInfo.Attributes);
+ if (!(attrs & FILE_CASE_SENSITIVE_SEARCH)) {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ }
+ if (!(attrs & FILE_CASE_PRESERVED_NAMES))
+ fa->fsx_xflags |= FS_XFLAG_CASENONPRESERVING;
+ return 0;
+}
+
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,
@@ -1210,6 +1258,7 @@ const struct inode_operations cifs_dir_inode_ops = {
.listxattr = cifs_listxattr,
.get_acl = cifs_get_acl,
.set_acl = cifs_set_acl,
+ .fileattr_get = cifs_fileattr_get,
};
const struct inode_operations cifs_file_inode_ops = {
@@ -1220,6 +1269,7 @@ const struct inode_operations cifs_file_inode_ops = {
.fiemap = cifs_fiemap,
.get_acl = cifs_get_acl,
.set_acl = cifs_set_acl,
+ .fileattr_get = cifs_fileattr_get,
};
const char *cifs_get_link(struct dentry *dentry, struct inode *inode,
@@ -1266,7 +1316,7 @@ static int cifs_precopy_set_eof(struct inode *src_inode, struct cifsInodeInfo *s
struct cifsFileInfo *writeable_srcfile;
int rc = -EINVAL;
- writeable_srcfile = find_writable_file(src_cifsi, FIND_WR_FSUID_ONLY);
+ writeable_srcfile = find_writable_file(src_cifsi, FIND_FSUID_ONLY);
if (writeable_srcfile) {
if (src_tcon->ses->server->ops->set_file_size)
rc = src_tcon->ses->server->ops->set_file_size(
@@ -1333,7 +1383,8 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
struct cifsFileInfo *smb_file_src = src_file->private_data;
struct cifsFileInfo *smb_file_target = dst_file->private_data;
struct cifs_tcon *target_tcon, *src_tcon;
- unsigned long long destend, fstart, fend, old_size, new_size;
+ unsigned long long i_size, new_size;
+ unsigned long long destend, fstart, fend;
unsigned int xid;
int rc;
@@ -1362,8 +1413,19 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
*/
lock_two_nondirectories(target_inode, src_inode);
- if (len == 0)
- len = src_inode->i_size - off;
+ if (len == 0) {
+ loff_t src_size = i_size_read(src_inode);
+
+ if (off > src_size) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+ len = src_size - off;
+ if (!len) {
+ rc = 0;
+ goto unlock;
+ }
+ }
cifs_dbg(FYI, "clone range\n");
@@ -1377,7 +1439,7 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
* Advance the EOF marker after the flush above to the end of the range
* if it's short of that.
*/
- if (src_cifsi->netfs.remote_i_size < off + len) {
+ if (netfs_read_remote_i_size(src_inode) < off + len) {
rc = cifs_precopy_set_eof(src_inode, src_cifsi, src_tcon, xid, off + len);
if (rc < 0)
goto unlock;
@@ -1398,26 +1460,24 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
rc = cifs_flush_folio(target_inode, destend, &fstart, &fend, false);
if (rc)
goto unlock;
- if (fend > target_cifsi->netfs.zero_point)
- target_cifsi->netfs.zero_point = fend + 1;
- old_size = target_cifsi->netfs.remote_i_size;
+
+ spin_lock(&target_inode->i_lock);
+ if (fend > target_cifsi->netfs._zero_point)
+ netfs_write_zero_point(target_inode, fend + 1);
+ i_size = target_inode->i_size;
+ spin_unlock(&target_inode->i_lock);
/* Discard all the folios that overlap the destination region. */
cifs_dbg(FYI, "about to discard pages %llx-%llx\n", fstart, fend);
truncate_inode_pages_range(&target_inode->i_data, fstart, fend);
- fscache_invalidate(cifs_inode_cookie(target_inode), NULL,
- i_size_read(target_inode), 0);
+ fscache_invalidate(cifs_inode_cookie(target_inode), NULL, i_size, 0);
rc = -EOPNOTSUPP;
if (target_tcon->ses->server->ops->duplicate_extents) {
rc = target_tcon->ses->server->ops->duplicate_extents(xid,
smb_file_src, smb_file_target, off, len, destoff);
- if (rc == 0 && new_size > old_size) {
- truncate_setsize(target_inode, new_size);
- fscache_resize_cookie(cifs_inode_cookie(target_inode),
- new_size);
- } else if (rc == -EOPNOTSUPP) {
+ if (rc == -EOPNOTSUPP) {
/*
* copy_file_range syscall man page indicates EINVAL
* is returned e.g when "fd_in and fd_out refer to the
@@ -1432,13 +1492,21 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
rc = -EINVAL;
}
}
- if (rc == 0 && new_size > target_cifsi->netfs.zero_point)
- target_cifsi->netfs.zero_point = new_size;
+ if (rc == 0) {
+ spin_lock(&target_inode->i_lock);
+ if (new_size > target_cifsi->netfs._zero_point)
+ netfs_write_zero_point(target_inode, new_size);
+ spin_unlock(&target_inode->i_lock);
+ }
}
- /* force revalidate of size and timestamps of target file now
- that target is updated on the server */
- CIFS_I(target_inode)->time = 0;
+ /*
+ * On success, duplicate_extents already updated the target inode attrs
+ * or marked them stale if the refresh failed. On failure, mark attrs
+ * stale because EOF may have changed before the clone failed.
+ */
+ if (rc)
+ CIFS_I(target_inode)->time = 0;
unlock:
/* although unlocking in the reverse order from locking is not
strictly necessary here it is a little cleaner to be consistent */
@@ -1504,7 +1572,7 @@ ssize_t cifs_file_copychunk_range(unsigned int xid,
* Advance the EOF marker after the flush above to the end of the range
* if it's short of that.
*/
- if (src_cifsi->netfs.remote_i_size < off + len) {
+ if (netfs_read_remote_i_size(src_inode) < off + len) {
rc = cifs_precopy_set_eof(src_inode, src_cifsi, src_tcon, xid, off + len);
if (rc < 0)
goto unlock;
@@ -1532,8 +1600,12 @@ ssize_t cifs_file_copychunk_range(unsigned int xid,
fscache_resize_cookie(cifs_inode_cookie(target_inode),
i_size_read(target_inode));
}
- if (rc > 0 && destoff + rc > target_cifsi->netfs.zero_point)
- target_cifsi->netfs.zero_point = destoff + rc;
+ if (rc > 0) {
+ spin_lock(&target_inode->i_lock);
+ if (destoff + rc > target_cifsi->netfs._zero_point)
+ netfs_write_zero_point(target_inode, destoff + rc);
+ spin_unlock(&target_inode->i_lock);
+ }
}
file_accessed(src_file);
@@ -1631,7 +1703,7 @@ const struct file_operations cifs_file_strict_ops = {
const struct file_operations cifs_file_direct_ops = {
.read_iter = netfs_unbuffered_read_iter,
- .write_iter = netfs_file_write_iter,
+ .write_iter = cifs_direct_write_iter,
.open = cifs_open,
.release = cifs_close,
.lock = cifs_lock,
@@ -1687,7 +1759,7 @@ const struct file_operations cifs_file_strict_nobrl_ops = {
const struct file_operations cifs_file_direct_nobrl_ops = {
.read_iter = netfs_unbuffered_read_iter,
- .write_iter = netfs_file_write_iter,
+ .write_iter = cifs_direct_write_iter,
.open = cifs_open,
.release = cifs_close,
.fsync = cifs_fsync,
@@ -1908,6 +1980,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;
@@ -2145,10 +2223,8 @@ 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");
MODULE_SOFTDEP("aead2");
MODULE_SOFTDEP("ccm");
MODULE_SOFTDEP("gcm");
diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h
index e320d39b01f5..0c85daa8386e 100644
--- a/fs/smb/client/cifsfs.h
+++ b/fs/smb/client/cifsfs.h
@@ -10,9 +10,13 @@
#define _CIFSFS_H
#include <linux/hash.h>
+#include <linux/dcache.h>
#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 +53,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,
- struct dentry *direntry, umode_t mode, bool excl);
-int cifs_atomic_open(struct inode *inode, struct dentry *direntry,
+int cifs_create(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *direntry, umode_t mode);
+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);
@@ -83,6 +89,9 @@ extern const struct inode_operations cifs_file_inode_ops;
extern const struct inode_operations cifs_symlink_inode_ops;
extern const struct inode_operations cifs_namespace_inode_operations;
+struct file_kattr;
+int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+
/* Functions related to files and directories */
extern const struct netfs_request_ops cifs_req_ops;
@@ -98,6 +107,7 @@ int cifs_closedir(struct inode *inode, struct file *file);
ssize_t cifs_strict_readv(struct kiocb *iocb, struct iov_iter *to);
ssize_t cifs_strict_writev(struct kiocb *iocb, struct iov_iter *from);
ssize_t cifs_file_write_iter(struct kiocb *iocb, struct iov_iter *from);
+ssize_t cifs_direct_write_iter(struct kiocb *iocb, struct iov_iter *from);
ssize_t cifs_loose_read_iter(struct kiocb *iocb, struct iov_iter *iter);
int cifs_flock(struct file *file, int cmd, struct file_lock *fl);
int cifs_lock(struct file *file, int cmd, struct file_lock *flock);
@@ -137,16 +147,26 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, struct file *src_file,
long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg);
void cifs_setsize(struct inode *inode, loff_t offset);
+void cifs_resize_file_locked(struct inode *inode, loff_t offset);
+struct fs_context;
struct smb3_fs_context;
-struct dentry *cifs_smb3_do_mount(struct file_system_type *fs_type, int flags,
+struct dentry *cifs_smb3_do_mount(struct fs_context *fc,
struct smb3_fs_context *old_ctx);
+char *cifs_silly_fullpath(struct dentry *dentry);
+
+#define CIFS_TMPNAME_PREFIX ".__smbfile_tmp"
+#define CIFS_TMPNAME_LEN (DNAME_INLINE_LEN - 1)
+
+#define CIFS_SILLYNAME_PREFIX ".__smbfile_silly"
+#define CIFS_SILLYNAME_LEN (DNAME_INLINE_LEN - 1)
+
#ifdef CONFIG_CIFS_NFSD_EXPORT
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 59
-#define CIFS_VERSION "2.59"
+#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 6f9b6c72962b..79e4e84f8985 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -20,9 +20,9 @@
#include <linux/utsname.h>
#include <linux/sched/mm.h>
#include <linux/netfs.h>
+#include <linux/fcntl.h>
#include "cifs_fs_sb.h"
#include "cifsacl.h"
-#include <crypto/internal/hash.h>
#include <uapi/linux/cifs/cifs_mount.h>
#include "../common/smbglob.h"
#include "../common/smb2pdu.h"
@@ -220,10 +220,8 @@ struct session_key {
char *response;
};
-/* crypto hashing related structure/fields, not specific to a sec mech */
+/* encryption related structure/fields, not specific to a sec mech */
struct cifs_secmech {
- struct shash_desc *aes_cmac; /* block-cipher based MAC function, for SMB3 signatures */
-
struct crypto_aead *enc; /* smb3 encryption AEAD TFM (AES-CCM and AES-GCM) */
struct crypto_aead *dec; /* smb3 decryption AEAD TFM (AES-CCM and AES-GCM) */
};
@@ -252,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 {
@@ -427,7 +426,7 @@ struct smb_version_operations {
int (*set_file_info)(struct inode *, const char *, FILE_BASIC_INFO *,
const unsigned int);
int (*set_compression)(const unsigned int, struct cifs_tcon *,
- struct cifsFileInfo *);
+ struct cifsFileInfo *, __u16);
/* check if we can send an echo or nor */
bool (*can_echo)(struct TCP_Server_Info *);
/* send echo request */
@@ -791,6 +790,8 @@ struct TCP_Server_Info {
struct {
bool requested; /* "compress" mount option set*/
bool enabled; /* actually negotiated with server */
+ bool chained; /* chained transforms were negotiated */
+ bool pattern; /* Pattern_V1 chained payloads were negotiated */
__le16 alg; /* preferred alg negotiated with server */
} compression;
__u16 signing_algorithm;
@@ -1393,6 +1394,7 @@ struct cifs_search_info {
bool emptyDir:1;
bool unicode:1;
bool smallBuf:1; /* so we know which buf_release function to call */
+ bool is_dynamic_buf:1; /* dynamically allocated buffer - can be variable size */
};
#define ACL_NO_MODE ((umode_t)(-1))
@@ -1533,9 +1535,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 */
@@ -1553,17 +1562,11 @@ 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 */
unsigned long time; /* jiffies of last update of inode */
+ unsigned long time_last_write; /* jiffies of last writable close or truncate */
u64 uniqueid; /* server inode number */
u64 createtime; /* creation time on server */
__u8 lease_key[SMB2_LEASE_KEY_SIZE]; /* lease key for this inode */
@@ -1884,12 +1887,12 @@ static inline bool is_replayable_error(int error)
}
-/* cifs_get_writable_file() flags */
-enum cifs_writable_file_flags {
- FIND_WR_ANY = 0U,
- FIND_WR_FSUID_ONLY = (1U << 0),
- FIND_WR_WITH_DELETE = (1U << 1),
- FIND_WR_NO_PENDING_DELETE = (1U << 2),
+enum cifs_find_flags {
+ FIND_ANY = 0U,
+ FIND_FSUID_ONLY = (1U << 0),
+ FIND_WITH_DELETE = (1U << 1),
+ FIND_NO_PENDING_DELETE = (1U << 2),
+ FIND_OPEN_FLAGS = (1U << 3),
};
#define MID_FREE 0
@@ -1906,6 +1909,7 @@ enum cifs_writable_file_flags {
#define CIFS_NO_BUFFER 0 /* Response buffer not returned */
#define CIFS_SMALL_BUFFER 1
#define CIFS_LARGE_BUFFER 2
+#define CIFS_DYNAMIC_BUFFER 3 /* Dynamically allocated buffer */
#define CIFS_IOVEC 4 /* array of response buffers */
/* Type of Request to SendReceive2 */
@@ -2258,6 +2262,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;
@@ -2322,7 +2327,7 @@ static inline void mid_execute_callback(struct TCP_Server_Info *server,
struct cifs_calc_sig_ctx {
struct md5_ctx *md5;
struct hmac_sha256_ctx *hmac;
- struct shash_desc *shash;
+ struct aes_cmac_ctx *cmac;
};
#define CIFS_RECONN_DELAY_SECS 30
@@ -2375,4 +2380,25 @@ static inline bool cifs_forced_shutdown(const struct cifs_sb_info *sbi)
return cifs_sb_flags(sbi) & CIFS_MOUNT_SHUTDOWN;
}
+static inline int cifs_open_create_options(unsigned int oflags, int opts)
+{
+ /* O_SYNC also has bit for O_DSYNC so following check picks up either */
+ if (oflags & O_SYNC)
+ opts |= CREATE_WRITE_THROUGH;
+ if (oflags & O_DIRECT)
+ opts |= CREATE_NO_BUFFER;
+ if (oflags & O_TMPFILE)
+ opts |= CREATE_DELETE_ON_CLOSE;
+ return opts;
+}
+
+/*
+ * inode->i_blocks is counted in 512-byte units, independent of
+ * inode->i_blksize.
+ */
+#define CIFS_INO_BLOCK_SIZE 512ULL
+#define CIFS_INO_BLOCKS(size) \
+ DIV_ROUND_UP_ULL((u64)(size), CIFS_INO_BLOCK_SIZE)
+#define CIFS_INO_BYTES(blocks) ((u64)(blocks) * CIFS_INO_BLOCK_SIZE)
+
#endif /* _CIFS_GLOB_H */
diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h
index 96d6b5325aa3..00168839c123 100644
--- a/fs/smb/client/cifsproto.h
+++ b/fs/smb/client/cifsproto.h
@@ -19,6 +19,7 @@
struct statfs;
struct smb_rqst;
struct smb3_fs_context;
+struct fs_context;
/*
*****************************************************************
@@ -89,7 +90,6 @@ int cifs_handle_standard(struct TCP_Server_Info *server,
struct mid_q_entry *mid);
char *smb3_fs_context_fullpath(const struct smb3_fs_context *ctx, char dirsep);
int smb3_parse_devname(const char *devname, struct smb3_fs_context *ctx);
-int smb3_parse_opt(const char *options, const char *key, char **val);
int cifs_ipaddr_cmp(struct sockaddr *srcaddr, struct sockaddr *rhs);
bool cifs_match_ipaddr(struct sockaddr *srcaddr, struct sockaddr *rhs);
int cifs_discard_remaining_data(struct TCP_Server_Info *server);
@@ -138,12 +138,15 @@ void cifs_write_subrequest_terminated(struct cifs_io_subrequest *wdata,
ssize_t result);
struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode,
int flags);
-int cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, int flags,
+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,
+ struct inode *inode, int flags,
struct cifsFileInfo **ret_file);
-int cifs_get_writable_path(struct cifs_tcon *tcon, const char *name, int flags,
- struct cifsFileInfo **ret_file);
-struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
- bool fsuid_only);
+struct cifsFileInfo *__find_readable_file(struct cifsInodeInfo *cifs_inode,
+ unsigned int find_flags,
+ unsigned int open_flags);
int cifs_get_readable_path(struct cifs_tcon *tcon, const char *name,
struct cifsFileInfo **ret_file);
int cifs_get_hardlink_path(struct cifs_tcon *tcon, struct inode *inode,
@@ -233,7 +236,7 @@ void cifs_mount_put_conns(struct cifs_mount_ctx *mnt_ctx);
int cifs_mount_get_session(struct cifs_mount_ctx *mnt_ctx);
int cifs_is_path_remote(struct cifs_mount_ctx *mnt_ctx);
int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx);
-int cifs_match_super(struct super_block *sb, void *data);
+int cifs_match_super(struct super_block *sb, struct fs_context *fc);
int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx);
void cifs_umount(struct cifs_sb_info *cifs_sb);
void cifs_mark_open_files_invalid(struct cifs_tcon *tcon);
@@ -261,6 +264,7 @@ void cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode);
void cifs_close_all_deferred_files(struct cifs_tcon *tcon);
+void cifs_close_all_deferred_files_sb(struct cifs_sb_info *cifs_sb);
void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
struct dentry *dentry);
@@ -313,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,
@@ -347,9 +351,6 @@ int __cifs_calc_signature(struct smb_rqst *rqst,
enum securityEnum cifs_select_sectype(struct TCP_Server_Info *server,
enum securityEnum requested);
-int cifs_alloc_hash(const char *name, struct shash_desc **sdesc);
-void cifs_free_hash(struct shash_desc **sdesc);
-
int cifs_try_adding_channels(struct cifs_ses *ses);
int smb3_update_ses_channels(struct cifs_ses *ses,
struct TCP_Server_Info *server,
@@ -595,4 +596,20 @@ static inline void cifs_sg_set_buf(struct sg_table *sgtable,
}
}
+static inline int cifs_get_writable_file(struct cifsInodeInfo *cifs_inode,
+ unsigned int find_flags,
+ struct cifsFileInfo **ret_file)
+{
+ find_flags &= ~FIND_OPEN_FLAGS;
+ return __cifs_get_writable_file(cifs_inode, find_flags, 0, ret_file);
+}
+
+static inline struct cifsFileInfo *
+find_readable_file(struct cifsInodeInfo *cinode, unsigned int find_flags)
+{
+ find_flags &= ~FIND_OPEN_FLAGS;
+ find_flags |= FIND_NO_PENDING_DELETE;
+ return __find_readable_file(cinode, find_flags, 0);
+}
+
#endif /* _CIFSPROTO_H */
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 3990a9012264..6dddbd84b93b 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -615,6 +615,11 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses,
tcon->tid = smb_buffer_response->Tid;
bcc_ptr = pByteArea(smb_buffer_response);
bytes_left = get_bcc(smb_buffer_response);
+ if (bytes_left < 2) {
+ rc = smb_EIO2(smb_eio_trace_tcon_bcc_too_small,
+ bytes_left, 2);
+ goto out;
+ }
length = strnlen(bcc_ptr, bytes_left - 2);
if (smb_buffer->Flags2 & SMBFLG2_UNICODE)
is_unicode = true;
@@ -670,6 +675,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses,
reset_cifs_unix_caps(xid, tcon, NULL, NULL);
}
}
+out:
cifs_buf_release(smb_buffer);
return rc;
}
@@ -1465,6 +1471,7 @@ cifs_readv_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid)
struct cifs_io_subrequest *rdata = mid->callback_data;
struct netfs_inode *ictx = netfs_inode(rdata->rreq->inode);
struct cifs_tcon *tcon = tlink_tcon(rdata->req->cfile->tlink);
+ struct inode *inode = &ictx->inode;
struct smb_rqst rqst = { .rq_iov = rdata->iov,
.rq_nvec = 1,
.rq_iter = rdata->subreq.io_iter };
@@ -1538,7 +1545,7 @@ do_retry:
} else {
size_t trans = rdata->subreq.transferred + rdata->got_bytes;
if (trans < rdata->subreq.len &&
- rdata->subreq.start + trans >= ictx->remote_i_size) {
+ rdata->subreq.start + trans >= netfs_read_remote_i_size(inode)) {
rdata->result = 0;
__set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags);
} else if (rdata->got_bytes > 0) {
@@ -1680,8 +1687,10 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms,
pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16));
/* tcon and ses pointer are checked in smb_init */
- if (tcon->ses->server == NULL)
+ if (!tcon->ses->server) {
+ cifs_small_buf_release(pSMB);
return -ECONNABORTED;
+ }
pSMB->AndXCommand = 0xFF; /* none */
pSMB->Fid = netfid;
@@ -1710,8 +1719,17 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms,
pSMBr = (READ_RSP *)rsp_iov.iov_base;
if (rc) {
cifs_dbg(VFS, "Send error in read = %d\n", rc);
+ } else if (rsp_iov.iov_len < tcon->ses->server->vals->read_rsp_size) {
+ /* check that the received response can hold a whole READ_RSP */
+ cifs_dbg(FYI, "%s: server returned short header. got=%zu expected=%zu\n",
+ __func__, rsp_iov.iov_len,
+ tcon->ses->server->vals->read_rsp_size);
+ rc = smb_EIO2(smb_eio_trace_read_rsp_short,
+ rsp_iov.iov_len, tcon->ses->server->vals->read_rsp_size);
+ *nbytes = 0;
} else {
- int data_length = le16_to_cpu(pSMBr->DataLengthHigh);
+ unsigned int data_length = le16_to_cpu(pSMBr->DataLengthHigh);
+ __u16 data_offset = le16_to_cpu(pSMBr->DataOffset);
data_length = data_length << 16;
data_length += le16_to_cpu(pSMBr->DataLength);
*nbytes = data_length;
@@ -1719,14 +1737,21 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms,
/*check that DataLength would not go beyond end of SMB */
if ((data_length > CIFSMaxBufSize)
|| (data_length > count)) {
- cifs_dbg(FYI, "bad length %d for count %d\n",
- data_length, count);
+ cifs_dbg(FYI, "%s: bad length %u for count %u\n",
+ __func__, data_length, count);
rc = smb_EIO2(smb_eio_trace_read_overlarge,
data_length, count);
*nbytes = 0;
+ } else if (data_offset < sizeof(*pSMBr) ||
+ (size_t)data_offset + data_length > rsp_iov.iov_len) {
+ /* check that the data lies within the received response */
+ cifs_dbg(FYI, "%s: bad data offset %u length %u for response of %zu\n",
+ __func__, data_offset, data_length, rsp_iov.iov_len);
+ rc = smb_EIO2(smb_eio_trace_read_bad_offset,
+ data_offset, data_length);
+ *nbytes = 0;
} else {
- pReadData = (char *) (&pSMBr->hdr.Protocol) +
- le16_to_cpu(pSMBr->DataOffset);
+ pReadData = (char *) (&pSMBr->hdr.Protocol) + data_offset;
/* if (rc = copy_to_user(buf, pReadData, data_length)) {
cifs_dbg(VFS, "Faulting on read rc = %d\n",rc);
rc = -EFAULT;
@@ -1795,8 +1820,10 @@ CIFSSMBWrite(const unsigned int xid, struct cifs_io_parms *io_parms,
pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16));
/* tcon and ses pointer are checked in smb_init */
- if (tcon->ses->server == NULL)
+ if (!tcon->ses->server) {
+ cifs_buf_release(pSMB);
return -ECONNABORTED;
+ }
pSMB->AndXCommand = 0xFF; /* none */
pSMB->Fid = netfid;
@@ -2076,8 +2103,10 @@ CIFSSMBWrite2(const unsigned int xid, struct cifs_io_parms *io_parms,
pSMB->hdr.PidHigh = cpu_to_le16((__u16)(pid >> 16));
/* tcon and ses pointer are checked in smb_init */
- if (tcon->ses->server == NULL)
+ if (!tcon->ses->server) {
+ cifs_small_buf_release(pSMB);
return -ECONNABORTED;
+ }
pSMB->AndXCommand = 0xFF; /* none */
pSMB->Fid = netfid;
@@ -3051,7 +3080,7 @@ int cifs_query_reparse_point(const unsigned int xid,
end = 2 + get_bcc(&io_rsp->hdr) + (__u8 *)&io_rsp->ByteCount;
start = (__u8 *)&io_rsp->hdr.Protocol + data_offset;
- if (start >= end) {
+ if (start >= end || (size_t)(end - start) < sizeof(*buf)) {
rc = smb_EIO2(smb_eio_trace_qreparse_data_area,
(unsigned long)start - (unsigned long)io_rsp,
(unsigned long)end - (unsigned long)io_rsp);
@@ -3206,7 +3235,7 @@ out_close:
int
CIFSSMB_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
- __u16 fid)
+ __u16 fid, __u16 compression_state)
{
int rc = 0;
int bytes_returned;
@@ -3221,7 +3250,7 @@ CIFSSMB_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
in_len = rc;
- pSMB->compression_state = cpu_to_le16(COMPRESSION_FORMAT_DEFAULT);
+ pSMB->compression_state = cpu_to_le16(compression_state);
pSMB->TotalParameterCount = 0;
pSMB->TotalDataCount = cpu_to_le32(2);
@@ -3542,6 +3571,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
int rc = 0;
int bytes_returned = 0;
__u16 params, byte_count, data_count, param_offset, offset;
+ size_t cifs_acl_size, bytes_available;
cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName);
setAclRetry:
@@ -3561,8 +3591,7 @@ setAclRetry:
}
params = 6 + name_len;
pSMB->MaxParameterCount = cpu_to_le16(2);
- /* BB find max SMB size from sess */
- pSMB->MaxDataCount = cpu_to_le16(1000);
+ pSMB->MaxDataCount = cpu_to_le16(min_t(unsigned int, CIFSMaxBufSize, USHRT_MAX));
pSMB->MaxSetupCount = 0;
pSMB->Reserved = 0;
pSMB->Flags = 0;
@@ -3574,6 +3603,15 @@ setAclRetry:
parm_data = ((char *)pSMB) + offset;
pSMB->ParameterOffset = cpu_to_le16(param_offset);
+ /* make sure we can fit the larger cifs_posix_aces in the buffer */
+ cifs_acl_size = sizeof(struct cifs_posix_acl) +
+ (acl->a_count * sizeof(struct cifs_posix_ace));
+ bytes_available = (CIFSMaxBufSize + MAX_HEADER_SIZE(tcon->ses->server)) - offset;
+ if (cifs_acl_size > bytes_available || cifs_acl_size > USHRT_MAX) {
+ rc = -E2BIG;
+ goto setACLerrorExit;
+ }
+
/* convert to on the wire format for POSIX ACL */
data_count = posix_acl_to_cifs(parm_data, acl, acl_type);
@@ -5828,38 +5866,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,
@@ -5938,10 +5944,6 @@ SetTimesRetry:
if (rc == -EAGAIN)
goto SetTimesRetry;
- if (rc == -EOPNOTSUPP)
- return CIFSSMBSetPathInfoFB(xid, tcon, fileName, data,
- nls_codepage, cifs_sb);
-
return rc;
}
@@ -6348,8 +6350,10 @@ CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon,
int name_len;
int rc = 0;
int bytes_returned = 0;
- __u16 params, param_offset, byte_count, offset, count;
+ __u16 params, param_offset;
+ unsigned int byte_count, offset, count;
int remap = cifs_remap(cifs_sb);
+ unsigned int total_len;
cifs_dbg(FYI, "In SetEA\n");
SetEARetry:
@@ -6401,6 +6405,13 @@ SetEARetry:
pSMB->Reserved3 = 0;
pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION);
byte_count = 3 /* pad */ + params + count;
+ if (check_add_overflow(in_len, byte_count, &total_len) ||
+ byte_count > U16_MAX ||
+ total_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) {
+ cifs_dbg(VFS, "EA request too large: %u bytes\n", total_len);
+ cifs_buf_release(pSMB);
+ return -E2BIG;
+ }
pSMB->DataCount = cpu_to_le16(count);
parm_data->list_len = cpu_to_le32(count);
parm_data->list.EA_flags = 0;
diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c
index 3d1e73f5d9af..8f0860970741 100644
--- a/fs/smb/client/compress.c
+++ b/fs/smb/client/compress.c
@@ -22,7 +22,7 @@
#include "cifsproto.h"
#include "smb2proto.h"
-#include "compress/lz77.h"
+#include "../common/compress/lz77.h"
#include "compress.h"
/*
@@ -44,6 +44,11 @@ struct bucket {
unsigned int count;
};
+static inline size_t pow4(size_t n)
+{
+ return n * n * n * n;
+}
+
/*
* has_low_entropy() - Compute Shannon entropy of the sampled data.
* @bkt: Bytes counts of the sample.
@@ -65,7 +70,6 @@ static bool has_low_entropy(struct bucket *bkt, size_t slen)
const size_t threshold = 65, max_entropy = 8 * ilog2(16);
size_t i, p, p2, len, sum = 0;
-#define pow4(n) (n * n * n * n)
len = ilog2(pow4(slen));
for (i = 0; i < 256 && bkt[i].count > 0; i++) {
@@ -329,18 +333,14 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_s
goto err_free;
}
- /*
- * This is just overprovisioning, as the algorithm will error out if @dst reaches 7/8
- * of @slen.
- */
- dlen = slen;
+ dlen = smb_lz77_compressed_alloc_size(slen);
dst = kvzalloc(dlen, GFP_KERNEL);
if (!dst) {
ret = -ENOMEM;
goto err_free;
}
- ret = lz77_compress(src, slen, dst, &dlen);
+ ret = smb_lz77_compress(src, slen, dst, &dlen);
if (!ret) {
struct smb2_compression_hdr hdr = { 0 };
struct smb_rqst comp_rq = { .rq_nvec = 3, };
diff --git a/fs/smb/client/compress.h b/fs/smb/client/compress.h
index 2679baca129b..e08e6d339d21 100644
--- a/fs/smb/client/compress.h
+++ b/fs/smb/client/compress.h
@@ -18,6 +18,7 @@
#include <linux/uio.h>
#include <linux/kernel.h>
#include "../common/smb2pdu.h"
+#include "../common/compress/compress.h"
#include "cifsglob.h"
/* sizeof(smb2_compression_hdr) - sizeof(OriginalPayloadSize) */
@@ -34,29 +35,6 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq,
compress_send_fn send_fn);
bool should_compress(const struct cifs_tcon *tcon, const struct smb_rqst *rq);
-/*
- * smb_compress_alg_valid() - Validate a compression algorithm.
- * @alg: Compression algorithm to check.
- * @valid_none: Conditional check whether NONE algorithm should be
- * considered valid or not.
- *
- * If @alg is SMB3_COMPRESS_NONE, this function returns @valid_none.
- *
- * Note that 'NONE' (0) compressor type is considered invalid in protocol
- * negotiation, as it's never requested to/returned from the server.
- *
- * Return: true if @alg is valid/supported, false otherwise.
- */
-static __always_inline int smb_compress_alg_valid(__le16 alg, bool valid_none)
-{
- if (alg == SMB3_COMPRESS_NONE)
- return valid_none;
-
- if (alg == SMB3_COMPRESS_LZ77 || alg == SMB3_COMPRESS_PATTERN)
- return true;
-
- return false;
-}
#else /* !CONFIG_CIFS_COMPRESSION */
static inline int smb_compress(void *unused1, void *unused2, void *unused3)
{
@@ -68,9 +46,5 @@ static inline bool should_compress(void *unused1, void *unused2)
return false;
}
-static inline int smb_compress_alg_valid(__le16 unused1, bool unused2)
-{
- return -EOPNOTSUPP;
-}
#endif /* !CONFIG_CIFS_COMPRESSION */
#endif /* _SMB_COMPRESS_H */
diff --git a/fs/smb/client/compress/lz77.c b/fs/smb/client/compress/lz77.c
deleted file mode 100644
index 96e8a8057a77..000000000000
--- a/fs/smb/client/compress/lz77.c
+++ /dev/null
@@ -1,235 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2024, SUSE LLC
- *
- * Authors: Enzo Matsumiya <ematsumiya@suse.de>
- *
- * Implementation of the LZ77 "plain" compression algorithm, as per MS-XCA spec.
- */
-#include <linux/slab.h>
-#include <linux/sizes.h>
-#include <linux/count_zeros.h>
-#include <linux/unaligned.h>
-
-#include "lz77.h"
-
-/*
- * Compression parameters.
- */
-#define LZ77_MATCH_MIN_LEN 4
-#define LZ77_MATCH_MIN_DIST 1
-#define LZ77_MATCH_MAX_DIST SZ_1K
-#define LZ77_HASH_LOG 15
-#define LZ77_HASH_SIZE (1 << LZ77_HASH_LOG)
-#define LZ77_STEP_SIZE sizeof(u64)
-
-static __always_inline u8 lz77_read8(const u8 *ptr)
-{
- return get_unaligned(ptr);
-}
-
-static __always_inline u64 lz77_read64(const u64 *ptr)
-{
- return get_unaligned(ptr);
-}
-
-static __always_inline void lz77_write8(u8 *ptr, u8 v)
-{
- put_unaligned(v, ptr);
-}
-
-static __always_inline void lz77_write16(u16 *ptr, u16 v)
-{
- put_unaligned_le16(v, ptr);
-}
-
-static __always_inline void lz77_write32(u32 *ptr, u32 v)
-{
- put_unaligned_le32(v, ptr);
-}
-
-static __always_inline u32 lz77_match_len(const void *wnd, const void *cur, const void *end)
-{
- const void *start = cur;
- u64 diff;
-
- /* Safe for a do/while because otherwise we wouldn't reach here from the main loop. */
- do {
- diff = lz77_read64(cur) ^ lz77_read64(wnd);
- if (!diff) {
- cur += LZ77_STEP_SIZE;
- wnd += LZ77_STEP_SIZE;
-
- continue;
- }
-
- /* This computes the number of common bytes in @diff. */
- cur += count_trailing_zeros(diff) >> 3;
-
- return (cur - start);
- } while (likely(cur + LZ77_STEP_SIZE < end));
-
- while (cur < end && lz77_read8(cur++) == lz77_read8(wnd++))
- ;
-
- return (cur - start);
-}
-
-static __always_inline void *lz77_write_match(void *dst, void **nib, u32 dist, u32 len)
-{
- len -= 3;
- dist--;
- dist <<= 3;
-
- if (len < 7) {
- lz77_write16(dst, dist + len);
-
- return dst + 2;
- }
-
- dist |= 7;
- lz77_write16(dst, dist);
- dst += 2;
- len -= 7;
-
- if (!*nib) {
- lz77_write8(dst, umin(len, 15));
- *nib = dst;
- dst++;
- } else {
- u8 *b = *nib;
-
- lz77_write8(b, *b | umin(len, 15) << 4);
- *nib = NULL;
- }
-
- if (len < 15)
- return dst;
-
- len -= 15;
- if (len < 255) {
- lz77_write8(dst, len);
-
- return dst + 1;
- }
-
- lz77_write8(dst, 0xff);
- dst++;
- len += 7 + 15;
- if (len <= 0xffff) {
- lz77_write16(dst, len);
-
- return dst + 2;
- }
-
- lz77_write16(dst, 0);
- dst += 2;
- lz77_write32(dst, len);
-
- return dst + 4;
-}
-
-noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen)
-{
- const void *srcp, *end;
- void *dstp, *nib, *flag_pos;
- u32 flag_count = 0;
- long flag = 0;
- u64 *htable;
-
- srcp = src;
- end = src + slen;
- dstp = dst;
- nib = NULL;
- flag_pos = dstp;
- dstp += 4;
-
- htable = kvcalloc(LZ77_HASH_SIZE, sizeof(*htable), GFP_KERNEL);
- if (!htable)
- return -ENOMEM;
-
- /* Main loop. */
- do {
- u32 dist, len = 0;
- const void *wnd;
- u64 hash;
-
- hash = ((lz77_read64(srcp) << 24) * 889523592379ULL) >> (64 - LZ77_HASH_LOG);
- wnd = src + htable[hash];
- htable[hash] = srcp - src;
- dist = srcp - wnd;
-
- if (dist && dist < LZ77_MATCH_MAX_DIST)
- len = lz77_match_len(wnd, srcp, end);
-
- if (len < LZ77_MATCH_MIN_LEN) {
- lz77_write8(dstp, lz77_read8(srcp));
-
- dstp++;
- srcp++;
-
- flag <<= 1;
- flag_count++;
- if (flag_count == 32) {
- lz77_write32(flag_pos, flag);
- flag_count = 0;
- flag_pos = dstp;
- dstp += 4;
- }
-
- continue;
- }
-
- /*
- * Bail out if @dstp reached >= 7/8 of @slen -- already compressed badly, not worth
- * going further.
- */
- if (unlikely(dstp - dst >= slen - (slen >> 3))) {
- *dlen = slen;
- goto out;
- }
-
- dstp = lz77_write_match(dstp, &nib, dist, len);
- srcp += len;
-
- flag = (flag << 1) | 1;
- flag_count++;
- if (flag_count == 32) {
- lz77_write32(flag_pos, flag);
- flag_count = 0;
- flag_pos = dstp;
- dstp += 4;
- }
- } while (likely(srcp + LZ77_STEP_SIZE < end));
-
- while (srcp < end) {
- u32 c = umin(end - srcp, 32 - flag_count);
-
- memcpy(dstp, srcp, c);
-
- dstp += c;
- srcp += c;
-
- flag <<= c;
- flag_count += c;
- if (flag_count == 32) {
- lz77_write32(flag_pos, flag);
- flag_count = 0;
- flag_pos = dstp;
- dstp += 4;
- }
- }
-
- flag <<= (32 - flag_count);
- flag |= (1 << (32 - flag_count)) - 1;
- lz77_write32(flag_pos, flag);
-
- *dlen = dstp - dst;
-out:
- kvfree(htable);
-
- if (*dlen < slen)
- return 0;
-
- return -EMSGSIZE;
-}
diff --git a/fs/smb/client/compress/lz77.h b/fs/smb/client/compress/lz77.h
deleted file mode 100644
index cdcb191b48a2..000000000000
--- a/fs/smb/client/compress/lz77.h
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * Copyright (C) 2024, SUSE LLC
- *
- * Authors: Enzo Matsumiya <ematsumiya@suse.de>
- *
- * Implementation of the LZ77 "plain" compression algorithm, as per MS-XCA spec.
- */
-#ifndef _SMB_COMPRESS_LZ77_H
-#define _SMB_COMPRESS_LZ77_H
-
-#include <linux/kernel.h>
-
-int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen);
-#endif /* _SMB_COMPRESS_LZ77_H */
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index 3bad2c5c523d..28e1ddeb6182 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -6,6 +6,7 @@
*
*/
#include <linux/fs.h>
+#include <linux/fs_context.h>
#include <linux/net.h>
#include <linux/string.h>
#include <linux/sched/mm.h>
@@ -20,7 +21,6 @@
#include <linux/delay.h>
#include <linux/completion.h>
#include <linux/kthread.h>
-#include <linux/pagevec.h>
#include <linux/freezer.h>
#include <linux/namei.h>
#include <linux/uuid.h>
@@ -174,6 +174,8 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server,
nserver = ses->chans[i].server;
if (!nserver)
continue;
+ if (!list_empty(&nserver->rlist))
+ continue;
nserver->srv_count++;
list_add(&nserver->rlist, &reco);
}
@@ -182,11 +184,15 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server,
}
}
+ spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry_safe(server, nserver, &reco, rlist) {
list_del_init(&server->rlist);
set_need_reco(server);
+ spin_unlock(&cifs_tcp_ses_lock);
cifs_put_tcp_session(server, 0);
+ spin_lock(&cifs_tcp_ses_lock);
}
+ spin_unlock(&cifs_tcp_ses_lock);
}
/*
@@ -457,8 +463,8 @@ static int __reconnect_target_locked(struct TCP_Server_Info *server,
server->hostname = hostname;
spin_unlock(&server->srv_lock);
} else {
- cifs_dbg(FYI, "%s: couldn't extract hostname or address from dfs target: %ld\n",
- __func__, PTR_ERR(hostname));
+ cifs_dbg(FYI, "%s: couldn't extract hostname or address from dfs target: %pe\n",
+ __func__, hostname);
cifs_dbg(FYI, "%s: default to last target server: %s\n", __func__,
server->hostname);
}
@@ -1067,6 +1073,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server)
spin_unlock(&server->srv_lock);
cancel_delayed_work_sync(&server->echo);
+ cancel_delayed_work_sync(&server->reconnect);
spin_lock(&server->srv_lock);
server->tcpStatus = CifsExiting;
@@ -1143,7 +1150,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server)
put_net(cifs_net_ns(server));
kfree(server->leaf_fullpath);
kfree(server->hostname);
- kfree(server);
+ kfree_sensitive(server);
length = atomic_dec_return(&tcpSesAllocCount);
if (length > 0)
@@ -1823,6 +1830,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
spin_lock_init(&tcp_ses->mid_counter_lock);
INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
+ INIT_LIST_HEAD(&tcp_ses->rlist);
INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
mutex_init(&tcp_ses->reconnect_mutex);
@@ -1872,14 +1880,6 @@ smbd_connected:
* this will succeed. No need for try_module_get().
*/
__module_get(THIS_MODULE);
- tcp_ses->tsk = kthread_run(cifs_demultiplex_thread,
- tcp_ses, "cifsd");
- if (IS_ERR(tcp_ses->tsk)) {
- rc = PTR_ERR(tcp_ses->tsk);
- cifs_dbg(VFS, "error %d create cifsd thread\n", rc);
- module_put(THIS_MODULE);
- goto out_err_crypto_release;
- }
tcp_ses->min_offload = ctx->min_offload;
tcp_ses->retrans = ctx->retrans;
/*
@@ -1887,9 +1887,7 @@ smbd_connected:
* to the struct since the kernel thread not created yet
* no need to spinlock this update of tcpStatus
*/
- spin_lock(&tcp_ses->srv_lock);
tcp_ses->tcpStatus = CifsNeedNegotiate;
- spin_unlock(&tcp_ses->srv_lock);
if ((ctx->max_credits < 20) || (ctx->max_credits > 60000))
tcp_ses->max_credits = SMB2_MAX_CREDITS_AVAILABLE;
@@ -1898,7 +1896,16 @@ smbd_connected:
tcp_ses->nr_targets = 1;
tcp_ses->ignore_signature = ctx->ignore_signature;
- /* thread spawned, put it on the list */
+
+ tcp_ses->tsk = kthread_create(cifs_demultiplex_thread,
+ tcp_ses, "cifsd");
+ if (IS_ERR(tcp_ses->tsk)) {
+ rc = PTR_ERR(tcp_ses->tsk);
+ cifs_dbg(VFS, "error %d create cifsd thread\n", rc);
+ module_put(THIS_MODULE);
+ goto out_err_crypto_release;
+ }
+ /* thread created, put it on the list */
spin_lock(&cifs_tcp_ses_lock);
list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
spin_unlock(&cifs_tcp_ses_lock);
@@ -1906,6 +1913,12 @@ smbd_connected:
/* queue echo request delayed work */
queue_delayed_work(cifsiod_wq, &tcp_ses->echo, tcp_ses->echo_interval);
+ /*
+ * Use split create/wake logic to ensure that tcp_ses is fully populated
+ * and tcp_ses->tsk is valid
+ */
+ wake_up_process(tcp_ses->tsk);
+
return tcp_ses;
out_err_crypto_release:
@@ -1921,6 +1934,7 @@ out_err:
kfree(tcp_ses->leaf_fullpath);
if (tcp_ses->ssocket)
sock_release(tcp_ses->ssocket);
+ smbd_destroy(tcp_ses);
kfree(tcp_ses);
}
return ERR_PTR(rc);
@@ -1955,6 +1969,10 @@ static int match_session(struct cifs_ses *ses,
case Kerberos:
if (!uid_eq(ctx->cred_uid, ses->cred_uid))
return 0;
+ if (strncmp(ses->user_name ?: "",
+ ctx->username ?: "",
+ CIFS_MAX_USERNAME_LEN))
+ return 0;
break;
case NTLMv2:
case RawNTLMSSP:
@@ -2988,9 +3006,9 @@ static int match_prepath(struct super_block *sb,
}
int
-cifs_match_super(struct super_block *sb, void *data)
+cifs_match_super(struct super_block *sb, struct fs_context *fc)
{
- struct cifs_mnt_data *mnt_data = data;
+ struct cifs_mnt_data *mnt_data = fc->sget_key;
struct smb3_fs_context *ctx;
struct cifs_sb_info *cifs_sb;
struct TCP_Server_Info *tcp_srv;
@@ -3476,6 +3494,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);
@@ -3606,7 +3625,6 @@ int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx)
server = mnt_ctx->server;
ctx = mnt_ctx->fs_ctx;
cifs_sb = mnt_ctx->cifs_sb;
- sbflags = cifs_sb_flags(cifs_sb);
/* search for existing tcon to this server share */
tcon = cifs_get_tcon(mnt_ctx->ses, ctx);
@@ -3621,9 +3639,10 @@ int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx)
* path (i.e., do not remap / and \ and do not map any special characters)
*/
if (tcon->posix_extensions) {
- sbflags |= CIFS_MOUNT_POSIX_PATHS;
- sbflags &= ~(CIFS_MOUNT_MAP_SFM_CHR |
- CIFS_MOUNT_MAP_SPECIAL_CHR);
+ atomic_or(CIFS_MOUNT_POSIX_PATHS, &cifs_sb->mnt_cifs_flags);
+ atomic_andnot(CIFS_MOUNT_MAP_SFM_CHR |
+ CIFS_MOUNT_MAP_SPECIAL_CHR,
+ &cifs_sb->mnt_cifs_flags);
}
#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
@@ -3647,6 +3666,7 @@ int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx)
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
tcon->unix_ext = 0; /* server does not support them */
+ sbflags = cifs_sb_flags(cifs_sb);
/* do not care if a following call succeed - informational */
if (!tcon->pipe && server->ops->qfs_tcon) {
server->ops->qfs_tcon(mnt_ctx->xid, tcon, cifs_sb);
@@ -3671,7 +3691,6 @@ int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx)
out:
mnt_ctx->tcon = tcon;
- atomic_set(&cifs_sb->mnt_cifs_flags, sbflags);
return rc;
}
@@ -3866,7 +3885,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.
@@ -4179,14 +4198,25 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
return rc;
}
-static int
-cifs_set_vol_auth(struct smb3_fs_context *ctx, struct cifs_ses *ses)
+static int set_fs_context_auth(struct smb3_fs_context *ctx,
+ struct cifs_ses *ses)
{
ctx->sectype = ses->sectype;
- /* krb5 is special, since we don't need username or pw */
- if (ctx->sectype == Kerberos)
+ /*
+ * krb5 is special as we might need to pass username (passwordless) down
+ * to cifs.upcall(8) for keytab.
+ */
+ if (ctx->sectype == Kerberos) {
+ if (ses->user_name && ses->user_name[0]) {
+ ctx->username = kstrndup(ses->user_name,
+ CIFS_MAX_USERNAME_LEN,
+ GFP_KERNEL);
+ if (!ctx->username)
+ return -ENOMEM;
+ }
return 0;
+ }
return cifs_set_cifscreds(ctx, ses);
}
@@ -4226,7 +4256,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
ctx->dfs_root_ses = master_tcon->ses->dfs_root_ses;
ctx->unicode = master_tcon->ses->unicode;
- rc = cifs_set_vol_auth(ctx, master_tcon->ses);
+ rc = set_fs_context_auth(ctx, master_tcon->ses);
if (rc) {
tcon = ERR_PTR(rc);
goto out;
diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c
index 83f8cf2f8d2b..29dfd7595941 100644
--- a/fs/smb/client/dfs_cache.c
+++ b/fs/smb/client/dfs_cache.c
@@ -122,6 +122,9 @@ static inline void free_tgts(struct cache_entry *ce)
kfree(t->name);
kfree(t);
}
+
+ ce->numtgts = 0;
+ WRITE_ONCE(ce->tgthint, NULL);
}
static inline void flush_cache_ent(struct cache_entry *ce)
@@ -363,10 +366,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);
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);
@@ -386,13 +389,6 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
struct cache_dfs_tgt *target;
int i;
- ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL);
- ce->etime = get_expire_time(ce->ttl);
- ce->srvtype = refs[0].server_type;
- ce->hdr_flags = refs[0].flags;
- ce->ref_flags = refs[0].ref_flag;
- ce->path_consumed = refs[0].path_consumed;
-
for (i = 0; i < numrefs; i++) {
struct cache_dfs_tgt *t;
@@ -407,12 +403,19 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
} else {
list_add_tail(&t->list, &ce->tlist);
}
- ce->numtgts++;
}
target = list_first_entry_or_null(&ce->tlist, struct cache_dfs_tgt,
list);
+
WRITE_ONCE(ce->tgthint, target);
+ ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL);
+ ce->etime = get_expire_time(ce->ttl);
+ ce->srvtype = refs[0].server_type;
+ ce->hdr_flags = refs[0].flags;
+ ce->ref_flags = refs[0].ref_flag;
+ ce->path_consumed = refs[0].path_consumed;
+ ce->numtgts = numrefs;
return 0;
}
@@ -626,13 +629,12 @@ 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;
}
free_tgts(ce);
- ce->numtgts = 0;
rc = copy_ref_data(refs, numrefs, ce, th);
@@ -760,11 +762,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;
@@ -869,13 +871,22 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl
goto out_free_path;
}
- if (ref)
- rc = setup_referral(path, ce, ref, get_tgt_name(ce));
- else
+ if (ref) {
+ char *target = get_tgt_name(ce);
+
+ if (IS_ERR(target)) {
+ rc = PTR_ERR(target);
+ goto out_unlock;
+ }
+ rc = setup_referral(path, ce, ref, target);
+ } else {
rc = 0;
+ }
+
if (!rc && tgt_list)
rc = get_targets(ce, tgt_list);
+out_unlock:
up_read(&htable_rw_lock);
out_free_path:
@@ -915,10 +926,17 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
goto out_unlock;
}
- if (ref)
- rc = setup_referral(path, ce, ref, get_tgt_name(ce));
- else
+ if (ref) {
+ char *target = get_tgt_name(ce);
+
+ if (IS_ERR(target)) {
+ rc = PTR_ERR(target);
+ goto out_unlock;
+ }
+ rc = setup_referral(path, ce, ref, target);
+ } else {
rc = 0;
+ }
if (!rc && tgt_list)
rc = get_targets(ce, tgt_list);
@@ -959,7 +977,8 @@ void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt
t = READ_ONCE(ce->tgthint);
- if (unlikely(!strcasecmp(it->it_name, t->name)))
+ /* Check 't' in case ce->tgthint was cleared by free_tgts() */
+ if (t && unlikely(!strcasecmp(it->it_name, t->name)))
goto out_unlock;
list_for_each_entry(t, &ce->tlist, list) {
@@ -1328,7 +1347,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/dir.c b/fs/smb/client/dir.c
index 953f1fee8cb8..6fa6d48fdfd3 100644
--- a/fs/smb/client/dir.c
+++ b/fs/smb/client/dir.c
@@ -115,11 +115,7 @@ char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *pa
}
if (dirsep != '/') {
/* BB test paths to Windows with '/' in the midst of prepath */
- char *p;
-
- for (p = s; *p; p++)
- if (*p == '/')
- *p = dirsep;
+ strreplace(s, '/', dirsep);
}
if (dfsplen) {
s -= dfsplen;
@@ -172,22 +168,46 @@ 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;
+ unsigned int sbflags = cifs_sb_flags(cifs_sb);
int disposition;
struct TCP_Server_Info *server = tcon->ses->server;
struct cifs_open_parms oparms;
@@ -195,25 +215,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:
@@ -222,11 +237,16 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
goto cifs_create_get_file_info;
}
+ if ((oflags & __O_REGULAR) && !S_ISREG(newinode->i_mode)) {
+ CIFSSMBClose(xid, tcon, fid->netfid);
+ iput(newinode);
+ return -EFTYPE;
+ }
+
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 +289,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
break;
default:
- goto out;
+ return rc;
}
/*
* fallthrough to retry, using older open call, this is case
@@ -287,27 +307,32 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
desired_access |= GENERIC_WRITE;
if (rdwr_for_fscache == 1)
desired_access |= GENERIC_READ;
+ if (oflags & O_TMPFILE)
+ desired_access |= DELETE;
disposition = FILE_OVERWRITE_IF;
- if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ if (oflags & O_CREAT) {
+ if (oflags & O_EXCL)
+ disposition = FILE_CREATE;
+ else if (oflags & O_TRUNC)
+ disposition = FILE_OVERWRITE_IF;
+ else
+ disposition = FILE_OPEN_IF;
+ } else if (oflags & O_TMPFILE) {
disposition = FILE_CREATE;
- else if ((oflags & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC))
- disposition = FILE_OVERWRITE_IF;
- else if ((oflags & O_CREAT) == O_CREAT)
- disposition = FILE_OPEN_IF;
- else
+ } else {
cifs_dbg(FYI, "Create flag not set in create function\n");
+ }
/*
* BB add processing to set equivalent of mode - e.g. via CreateX with
* ACLs
*/
- if (!server->ops->open) {
- rc = -ENOSYS;
- goto out;
- }
+ if (!server->ops->open)
+ return -EOPNOTSUPP;
+ create_options |= cifs_open_create_options(oflags, create_options);
/*
* if we're not using unix extensions, see if we need to set
* ATTR_READONLY on the create call
@@ -357,17 +382,16 @@ retry_open:
rdwr_for_fscache = 2;
goto retry_open;
}
- goto out;
+ return rc;
}
if (rdwr_for_fscache == 2)
- cifs_invalidate_cache(inode, FSCACHE_INVAL_DIO_WRITE);
+ cifs_invalidate_cache(dir, FSCACHE_INVAL_DIO_WRITE);
#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
/*
* If Open reported that we actually created a file then we now have to
* set the mode if possible.
*/
- sbflags = cifs_sb_flags(cifs_sb);
if ((tcon->unix_ext) && (*oplock & CIFS_CREATE_ACTION)) {
struct cifs_unix_set_info_args args = {
.mode = mode,
@@ -379,8 +403,8 @@ retry_open:
if (sbflags & CIFS_MOUNT_SET_UID) {
args.uid = current_fsuid();
- if (inode->i_mode & S_ISGID)
- args.gid = inode->i_gid;
+ if (dir->i_mode & S_ISGID)
+ args.gid = dir->i_gid;
else
args.gid = current_fsgid();
} else {
@@ -402,14 +426,14 @@ retry_open:
cifs_create_get_file_info:
/* server might mask mode so we have to query for it */
if (tcon->unix_ext)
- rc = cifs_get_inode_info_unix(&newinode, full_path, inode->i_sb,
+ rc = cifs_get_inode_info_unix(&newinode, full_path, dir->i_sb,
xid);
else {
#else
{
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
/* TODO: Add support for calling POSIX query info here, but passing in fid */
- rc = cifs_get_inode_info(&newinode, full_path, buf, inode->i_sb, xid, fid);
+ rc = cifs_get_inode_info(&newinode, full_path, buf, dir->i_sb, xid, fid);
if (newinode) {
if (server->ops->set_lease_key)
server->ops->set_lease_key(newinode, fid);
@@ -418,8 +442,8 @@ cifs_create_get_file_info:
newinode->i_mode = mode;
if (sbflags & CIFS_MOUNT_SET_UID) {
newinode->i_uid = current_fsuid();
- if (inode->i_mode & S_ISGID)
- newinode->i_gid = inode->i_gid;
+ if (dir->i_mode & S_ISGID)
+ newinode->i_gid = dir->i_gid;
else
newinode->i_gid = current_fsgid();
}
@@ -436,17 +460,18 @@ cifs_create_set_dentry:
goto out_err;
}
- if (newinode)
+ if (newinode) {
+ if ((oflags & __O_REGULAR) && !S_ISREG(newinode->i_mode)) {
+ rc = -EFTYPE;
+ goto out_err;
+ }
if (S_ISDIR(newinode->i_mode)) {
rc = -EISDIR;
goto out_err;
}
+ }
- d_drop(direntry);
- d_add(direntry, newinode);
-
-out:
- free_dentry_path(page);
+ *inode = newinode;
return rc;
out_err:
@@ -454,14 +479,44 @@ out_err:
server->ops->close(xid, tcon, fid);
if (newinode)
iput(newinode);
- goto out;
+ return rc;
+}
+
+static int cifs_do_create(struct inode *dir, 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,
+ struct inode **inode)
+{
+ void *page = alloc_dentry_path();
+ const char *full_path;
+ int rc;
+
+ full_path = build_path_from_dentry(direntry, page);
+ if (IS_ERR(full_path)) {
+ rc = PTR_ERR(full_path);
+ } else {
+ rc = __cifs_do_create(dir, direntry, full_path, xid,
+ tlink, oflags, mode, oplock,
+ fid, buf, inode);
+ }
+ free_dentry_path(page);
+ return rc;
}
-int
-cifs_atomic_open(struct inode *inode, struct dentry *direntry,
- struct file *file, unsigned int oflags, umode_t mode)
+
+/*
+ * Look up, create and open a CIFS file.
+ *
+ * The initial dentry state is in-lookup or hashed-negative. On success, dentry
+ * will become hashed-positive by calling d_splice_alias() if in-lookup,
+ * otherwise d_instantiate().
+ */
+int cifs_atomic_open(struct inode *dir, struct dentry *direntry,
+ struct file *file, unsigned int oflags, umode_t mode)
{
- struct cifs_sb_info *cifs_sb = CIFS_SB(inode);
+ struct cifs_sb_info *cifs_sb = CIFS_SB(dir);
struct cifs_open_info_data buf = {};
struct TCP_Server_Info *server;
struct cifsFileInfo *file_info;
@@ -470,6 +525,8 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry,
struct tcon_link *tlink;
struct cifs_tcon *tcon;
unsigned int sbflags;
+ struct dentry *alias;
+ struct inode *inode;
unsigned int xid;
__u32 oplock;
int rc;
@@ -496,13 +553,13 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry,
if (!d_in_lookup(direntry))
return -ENOENT;
- return finish_no_open(file, cifs_lookup(inode, direntry, 0));
+ return finish_no_open(file, cifs_lookup(dir, direntry, 0));
}
xid = get_xid();
cifs_dbg(FYI, "parent inode = 0x%p name is: %pd and dentry = 0x%p\n",
- inode, direntry, direntry);
+ dir, direntry, direntry);
tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink)) {
@@ -523,13 +580,21 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry,
cifs_add_pending_open(&fid, tlink, &open);
- rc = cifs_do_create(inode, direntry, xid, tlink, oflags, mode,
- &oplock, &fid, &buf);
+ rc = cifs_do_create(dir, direntry, xid, tlink, oflags, mode,
+ &oplock, &fid, &buf, &inode);
if (rc) {
cifs_del_pending_open(&open);
goto out;
}
+ if (d_in_lookup(direntry)) {
+ alias = d_splice_alias(inode, direntry);
+ if (!IS_ERR_OR_NULL(alias))
+ direntry = alias;
+ } else {
+ d_instantiate(direntry, inode);
+ }
+
if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
file->f_mode |= FMODE_CREATED;
@@ -569,9 +634,16 @@ out_free_xid:
return rc;
}
-int cifs_create(struct mnt_idmap *idmap, struct inode *inode,
- struct dentry *direntry, umode_t mode, bool excl)
+/*
+ * Create a CIFS file.
+ *
+ * The initial dentry state is hashed-negative. On success, dentry will become
+ * hashed-positive by calling d_instantiate().
+ */
+int cifs_create(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *direntry, umode_t mode)
{
+ struct cifs_sb_info *cifs_sb = CIFS_SB(dir);
int rc;
unsigned int xid = get_xid();
/*
@@ -585,19 +657,20 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *inode,
struct tcon_link *tlink;
struct cifs_tcon *tcon;
struct TCP_Server_Info *server;
+ struct inode *inode;
struct cifs_fid fid;
__u32 oplock;
struct cifs_open_info_data buf = {};
cifs_dbg(FYI, "cifs_create parent inode = 0x%p name is: %pd and dentry = 0x%p\n",
- inode, direntry, direntry);
+ dir, direntry, direntry);
- if (unlikely(cifs_forced_shutdown(CIFS_SB(inode->i_sb)))) {
+ if (unlikely(cifs_forced_shutdown(cifs_sb))) {
rc = smb_EIO(smb_eio_trace_forced_shutdown);
goto out_free_xid;
}
- tlink = cifs_sb_tlink(CIFS_SB(inode->i_sb));
+ tlink = cifs_sb_tlink(cifs_sb);
rc = PTR_ERR(tlink);
if (IS_ERR(tlink))
goto out_free_xid;
@@ -608,9 +681,13 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *inode,
if (server->ops->new_lease_key)
server->ops->new_lease_key(&fid);
- rc = cifs_do_create(inode, direntry, xid, tlink, oflags, mode, &oplock, &fid, &buf);
- if (!rc && server->ops->close)
- server->ops->close(xid, tcon, &fid);
+ rc = cifs_do_create(dir, direntry, xid, tlink, oflags,
+ mode, &oplock, &fid, &buf, &inode);
+ if (!rc) {
+ d_instantiate(direntry, inode);
+ if (server->ops->close)
+ server->ops->close(xid, tcon, &fid);
+ }
cifs_free_open_info(&buf);
cifs_put_tlink(tlink);
@@ -959,6 +1036,172 @@ static int cifs_ci_compare(const struct dentry *dentry,
return 0;
}
+static int set_tmpfile_attr(const unsigned int xid, unsigned int oflags,
+ struct inode *inode, const char *full_path,
+ struct TCP_Server_Info *server)
+{
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ FILE_BASIC_INFO fi;
+
+ cinode->cifsAttrs |= ATTR_HIDDEN;
+ if (oflags & O_EXCL)
+ cinode->cifsAttrs |= ATTR_TEMPORARY;
+
+ fi = (FILE_BASIC_INFO) {
+ .Attributes = cpu_to_le32(cinode->cifsAttrs),
+ };
+ return server->ops->set_file_info(inode, full_path, &fi, xid);
+}
+
+/*
+ * Create a hidden temporary CIFS file with delete-on-close bit set.
+ *
+ * The initial dentry state is unhashed-negative. On success, dentry will
+ * become unhashed-positive by calling d_instantiate().
+ */
+int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir,
+ struct file *file, umode_t mode)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct cifs_sb_info *cifs_sb = CIFS_SB(dir);
+ size_t namesize = CIFS_TMPNAME_LEN + 1;
+ char *path __free(kfree) = NULL, *name;
+ unsigned int oflags = file->f_flags;
+ int retries = 0, max_retries = 16;
+ struct TCP_Server_Info *server;
+ struct cifs_pending_open open;
+ struct cifsFileInfo *cfile;
+ struct cifs_fid fid = {};
+ struct tcon_link *tlink;
+ struct cifs_tcon *tcon;
+ unsigned int sbflags;
+ struct inode *inode;
+ unsigned int xid;
+ __u32 oplock;
+ int namelen;
+ int rc;
+
+ if (unlikely(cifs_forced_shutdown(cifs_sb)))
+ return smb_EIO(smb_eio_trace_forced_shutdown);
+
+ tlink = cifs_sb_tlink(cifs_sb);
+ if (IS_ERR(tlink))
+ return PTR_ERR(tlink);
+ tcon = tlink_tcon(tlink);
+ server = tcon->ses->server;
+
+ xid = get_xid();
+
+ if (server->vals->protocol_id < SMB20_PROT_ID) {
+ cifs_dbg(VFS | ONCE, "O_TMPFILE is supported only in SMB2+\n");
+ rc = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (server->ops->new_lease_key)
+ server->ops->new_lease_key(&fid);
+ cifs_add_pending_open(&fid, tlink, &open);
+
+ path = alloc_parent_path(dentry, namesize - 1);
+ if (IS_ERR(path)) {
+ cifs_del_pending_open(&open);
+ rc = PTR_ERR(path);
+ path = NULL;
+ goto out;
+ }
+
+ name = path + strlen(path);
+ do {
+ /* Append tmpfile name to @path */
+ namelen = scnprintf(name, namesize, CIFS_TMPNAME_PREFIX "%x",
+ atomic_inc_return(&cifs_tmpcounter));
+ rc = __cifs_do_create(dir, dentry, path, xid, tlink, oflags,
+ mode, &oplock, &fid, NULL, &inode);
+ if (!rc) {
+ rc = d_mark_tmpfile_name(file, &QSTR_LEN(name, namelen));
+ if (rc) {
+ cifs_dbg(VFS | ONCE, "%s: failed to set filename in dentry: %d\n",
+ __func__, rc);
+ rc = -EISDIR;
+ iput(inode);
+ goto err_open;
+ }
+ set_nlink(inode, 0);
+ mark_inode_dirty(inode);
+ d_instantiate(dentry, inode);
+ break;
+ }
+ } while (unlikely(rc == -EEXIST) && ++retries < max_retries);
+
+ if (rc) {
+ if (rc == -ENOENT)
+ rc = -EOPNOTSUPP;
+ cifs_del_pending_open(&open);
+ goto out;
+ }
+
+ rc = finish_open(file, dentry, generic_file_open);
+ if (rc)
+ goto err_open;
+
+ sbflags = cifs_sb_flags(cifs_sb);
+ if ((file->f_flags & O_DIRECT) && (sbflags & CIFS_MOUNT_STRICT_IO)) {
+ if (sbflags & CIFS_MOUNT_NO_BRL)
+ file->f_op = &cifs_file_direct_nobrl_ops;
+ else
+ file->f_op = &cifs_file_direct_ops;
+ }
+
+ cfile = cifs_new_fileinfo(&fid, file, tlink, oplock, NULL);
+ if (!cfile) {
+ rc = -ENOMEM;
+ goto err_open;
+ }
+
+ rc = set_tmpfile_attr(xid, oflags, inode, path, server);
+ if (rc)
+ goto out;
+
+ fscache_use_cookie(cifs_inode_cookie(file_inode(file)),
+ file->f_mode & FMODE_WRITE);
+out:
+ cifs_put_tlink(tlink);
+ free_xid(xid);
+ return rc;
+err_open:
+ cifs_del_pending_open(&open);
+ if (server->ops->close)
+ server->ops->close(xid, tcon, &fid);
+ goto out;
+}
+
+char *cifs_silly_fullpath(struct dentry *dentry)
+{
+ unsigned char name[CIFS_SILLYNAME_LEN + 1];
+ int retries = 0, max_retries = 16;
+ size_t namesize = sizeof(name);
+ struct dentry *sdentry = NULL;
+ char *path;
+
+ do {
+ dput(sdentry);
+ scnprintf(name, namesize, CIFS_SILLYNAME_PREFIX "%x",
+ atomic_inc_return(&cifs_sillycounter));
+ sdentry = lookup_noperm(&QSTR(name), dentry->d_parent);
+ if (IS_ERR(sdentry))
+ return ERR_CAST(sdentry);
+ if (d_is_negative(sdentry)) {
+ dput(sdentry);
+ path = alloc_parent_path(dentry, CIFS_SILLYNAME_LEN);
+ if (!IS_ERR(path))
+ strcat(path, name);
+ return path;
+ }
+ } while (++retries < max_retries);
+ dput(sdentry);
+ return ERR_PTR(-EBUSY);
+}
+
const struct dentry_operations cifs_ci_dentry_ops = {
.d_revalidate = cifs_d_revalidate,
.d_hash = cifs_ci_hash,
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index f3ddcdf406c8..0d428517f454 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -15,13 +15,12 @@
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <linux/pagemap.h>
-#include <linux/pagevec.h>
#include <linux/writeback.h>
#include <linux/task_io_accounting_ops.h>
#include <linux/delay.h>
#include <linux/mount.h>
#include <linux/slab.h>
-#include <linux/swap.h>
+#include <linux/swap_ops.h>
#include <linux/mm.h>
#include <asm/div64.h>
#include "cifsfs.h"
@@ -242,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);
}
@@ -255,7 +255,7 @@ static void cifs_begin_writeback(struct netfs_io_request *wreq)
struct cifs_io_request *req = container_of(wreq, struct cifs_io_request, rreq);
int ret;
- ret = cifs_get_writable_file(CIFS_I(wreq->inode), FIND_WR_ANY, &req->cfile);
+ ret = cifs_get_writable_file(CIFS_I(wreq->inode), FIND_ANY, &req->cfile);
if (ret) {
cifs_dbg(VFS, "No writable handle in writepages ret=%d\n", ret);
return;
@@ -288,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;
}
@@ -302,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)
@@ -394,7 +399,7 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
}
spin_unlock(&tcon->open_file_lock);
- invalidate_all_cached_dirs(tcon);
+ invalidate_all_cached_dirs(tcon, true);
spin_lock(&tcon->tc_lock);
if (tcon->status == TID_IN_FILES_INVALIDATE)
tcon->status = TID_NEED_TCON;
@@ -406,22 +411,29 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
*/
}
-static inline int cifs_convert_flags(unsigned int flags, int rdwr_for_fscache)
+static inline int cifs_convert_flags(unsigned int oflags, int rdwr_for_fscache)
{
- if ((flags & O_ACCMODE) == O_RDONLY)
- return GENERIC_READ;
- else if ((flags & O_ACCMODE) == O_WRONLY)
- return rdwr_for_fscache == 1 ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_WRITE;
- else if ((flags & O_ACCMODE) == O_RDWR) {
+ int flags = 0;
+
+ if (oflags & O_TMPFILE)
+ flags |= DELETE;
+
+ if ((oflags & O_ACCMODE) == O_RDONLY)
+ return flags | GENERIC_READ;
+ if ((oflags & O_ACCMODE) == O_WRONLY) {
+ return flags | (rdwr_for_fscache == 1 ?
+ (GENERIC_READ | GENERIC_WRITE) : GENERIC_WRITE);
+ }
+ if ((oflags & O_ACCMODE) == O_RDWR) {
/* GENERIC_ALL is too much permission to request
can cause unnecessary access denied on create */
/* return GENERIC_ALL; */
- return (GENERIC_READ | GENERIC_WRITE);
+ return flags | GENERIC_READ | GENERIC_WRITE;
}
- return (READ_CONTROL | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES |
- FILE_WRITE_EA | FILE_APPEND_DATA | FILE_WRITE_DATA |
- FILE_READ_DATA);
+ return flags | READ_CONTROL | FILE_WRITE_ATTRIBUTES |
+ FILE_READ_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA |
+ FILE_WRITE_DATA | FILE_READ_DATA;
}
#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
@@ -584,15 +596,8 @@ static int cifs_nt_open(const char *full_path, struct inode *inode, struct cifs_
*********************************************************************/
disposition = cifs_get_disposition(f_flags);
-
/* BB pass O_SYNC flag through on file attributes .. BB */
-
- /* O_SYNC also has bit for O_DSYNC so following check picks up either */
- if (f_flags & O_SYNC)
- create_options |= CREATE_WRITE_THROUGH;
-
- if (f_flags & O_DIRECT)
- create_options |= CREATE_NO_BUFFER;
+ create_options |= cifs_open_create_options(f_flags, create_options);
retry_open:
oparms = (struct cifs_open_parms) {
@@ -703,6 +708,7 @@ struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->deferred_close_scheduled = false;
+ cfile->status_file_deleted = file->f_flags & O_TMPFILE;
cfile->tlink = cifs_get_tlink(tlink);
INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
INIT_WORK(&cfile->put, cifsFileInfo_put_work);
@@ -711,8 +717,6 @@ struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
mutex_init(&cfile->fh_mutex);
spin_lock_init(&cfile->file_info_lock);
- cifs_sb_active(inode->i_sb);
-
/*
* If the server returned a read oplock and we have mandatory brlocks,
* set oplock level to None.
@@ -736,6 +740,8 @@ struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
/* if readable file instance put first in list*/
spin_lock(&cinode->open_file_lock);
+ if (file->f_flags & O_TMPFILE)
+ set_bit(CIFS_INO_TMPFILE, &cinode->flags);
fid->purge_cache = false;
server->ops->set_fid(cfile, fid, oplock);
@@ -767,7 +773,6 @@ static void cifsFileInfo_put_final(struct cifsFileInfo *cifs_file)
struct inode *inode = d_inode(cifs_file->dentry);
struct cifsInodeInfo *cifsi = CIFS_I(inode);
struct cifsLockInfo *li, *tmp;
- struct super_block *sb = inode->i_sb;
/*
* Delete any outstanding lock records. We'll lose them when the file
@@ -785,7 +790,6 @@ static void cifsFileInfo_put_final(struct cifsFileInfo *cifs_file)
cifs_put_tlink(cifs_file->tlink);
dput(cifs_file->dentry);
- cifs_sb_deactive(sb);
kfree(cifs_file->symlink_target);
kfree(cifs_file);
}
@@ -911,6 +915,14 @@ void _cifsFileInfo_put(struct cifsFileInfo *cifs_file,
cifs_set_oplock_level(cifsi, 0);
}
+ if (OPEN_FMODE(cifs_file->f_flags) & FMODE_WRITE) {
+ /* Stamp while open_file_lock is held; covers all close paths
+ * including background I/O. Pairs with smp_load_acquire() in
+ * is_size_safe_to_change().
+ */
+ smp_store_release(&cifsi->time_last_write, jiffies);
+ }
+
spin_unlock(&cifsi->open_file_lock);
spin_unlock(&tcon->open_file_lock);
@@ -967,7 +979,7 @@ int cifs_file_flush(const unsigned int xid, struct inode *inode,
return tcon->ses->server->ops->flush(xid, tcon,
&cfile->fid);
}
- rc = cifs_get_writable_file(CIFS_I(inode), FIND_WR_ANY, &cfile);
+ rc = cifs_get_writable_file(CIFS_I(inode), FIND_ANY, &cfile);
if (!rc) {
tcon = tlink_tcon(cfile->tlink);
rc = tcon->ses->server->ops->flush(xid, tcon, &cfile->fid);
@@ -987,26 +999,50 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry)
struct cifs_tcon *tcon;
int rc;
- rc = filemap_write_and_wait(inode->i_mapping);
- if (is_interrupt_error(rc))
+ rc = inode_lock_killable(inode);
+ if (rc)
return -ERESTARTSYS;
+
+ filemap_invalidate_lock(inode->i_mapping);
+
+ rc = filemap_write_and_wait(inode->i_mapping);
+ if (is_interrupt_error(rc)) {
+ rc = -ERESTARTSYS;
+ goto out;
+ }
mapping_set_error(inode->i_mapping, rc);
- cfile = find_writable_file(cinode, FIND_WR_FSUID_ONLY);
+ cfile = find_writable_file(cinode, FIND_FSUID_ONLY);
rc = cifs_file_flush(xid, inode, cfile);
if (!rc) {
if (cfile) {
+ struct netfs_inode *ictx = netfs_inode(inode);
+
tcon = tlink_tcon(cfile->tlink);
server = tcon->ses->server;
+ netfs_wb_begin(ictx, false);
rc = server->ops->set_file_size(xid, tcon,
cfile, 0, false);
- }
- if (!rc) {
- netfs_resize_file(&cinode->netfs, 0, true);
- cifs_setsize(inode, 0);
- inode->i_blocks = 0;
+ if (!rc) {
+ netfs_resize_file(&cinode->netfs, 0, true);
+ cifs_setsize(inode, 0);
+ cifs_invalidate_cache(inode, 0);
+ }
+ netfs_wb_end(ictx);
+ } else {
+ /*
+ * No cached handle; evict stale pages so they can't
+ * be served after the file is later extended; let
+ * the server's O_TRUNC open response set the i_size
+ */
+ truncate_inode_pages(inode->i_mapping, 0);
+ cifs_invalidate_cache(inode, 0);
}
}
+
+out:
+ filemap_invalidate_unlock(inode->i_mapping);
+ inode_unlock(inode);
if (cfile)
cifsFileInfo_put(cfile);
return rc;
@@ -1072,32 +1108,32 @@ int cifs_open(struct inode *inode, struct file *file)
/* Get the cached handle as SMB2 close is deferred */
if (OPEN_FMODE(file->f_flags) & FMODE_WRITE) {
- rc = cifs_get_writable_path(tcon, full_path,
- FIND_WR_FSUID_ONLY |
- FIND_WR_NO_PENDING_DELETE,
- &cfile);
+ rc = __cifs_get_writable_file(CIFS_I(inode),
+ FIND_FSUID_ONLY |
+ FIND_NO_PENDING_DELETE |
+ FIND_OPEN_FLAGS,
+ file->f_flags, &cfile);
} else {
- rc = cifs_get_readable_path(tcon, full_path, &cfile);
+ cfile = __find_readable_file(CIFS_I(inode),
+ FIND_NO_PENDING_DELETE |
+ FIND_OPEN_FLAGS,
+ file->f_flags);
+ rc = cfile ? 0 : -ENOENT;
}
if (rc == 0) {
- unsigned int oflags = file->f_flags & ~(O_CREAT|O_EXCL|O_TRUNC);
- unsigned int cflags = cfile->f_flags & ~(O_CREAT|O_EXCL|O_TRUNC);
-
- if (cifs_convert_flags(oflags, 0) == cifs_convert_flags(cflags, 0) &&
- (oflags & (O_SYNC|O_DIRECT)) == (cflags & (O_SYNC|O_DIRECT))) {
- file->private_data = cfile;
- spin_lock(&CIFS_I(inode)->deferred_lock);
- cifs_del_deferred_close(cfile);
- spin_unlock(&CIFS_I(inode)->deferred_lock);
- goto use_cache;
- }
- _cifsFileInfo_put(cfile, true, false);
- } else {
- /* hard link on the defeered close file */
- rc = cifs_get_hardlink_path(tcon, inode, file);
- if (rc)
- cifs_close_deferred_file(CIFS_I(inode));
- }
+ trace_smb3_open_cached(xid, tcon->tid, tcon->ses->Suid,
+ cfile->fid.persistent_fid,
+ file->f_flags, cfile->f_flags);
+ file->private_data = cfile;
+ spin_lock(&CIFS_I(inode)->deferred_lock);
+ cifs_del_deferred_close(cfile);
+ spin_unlock(&CIFS_I(inode)->deferred_lock);
+ goto use_cache;
+ }
+ /* hard link on the deferred close file */
+ rc = cifs_get_hardlink_path(tcon, inode, file);
+ if (rc)
+ cifs_close_deferred_file(CIFS_I(inode));
if (server->oplocks)
oplock = REQ_OPLOCK;
@@ -1318,13 +1354,8 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
rdwr_for_fscache = 1;
desired_access = cifs_convert_flags(cfile->f_flags, rdwr_for_fscache);
-
- /* O_SYNC also has bit for O_DSYNC so following check picks up either */
- if (cfile->f_flags & O_SYNC)
- create_options |= CREATE_WRITE_THROUGH;
-
- if (cfile->f_flags & O_DIRECT)
- create_options |= CREATE_NO_BUFFER;
+ create_options |= cifs_open_create_options(cfile->f_flags,
+ create_options);
if (server->ops->get_lease_key)
server->ops->get_lease_key(inode, &cfile->fid);
@@ -1425,11 +1456,12 @@ void smb2_deferred_work_close(struct work_struct *work)
{
struct cifsFileInfo *cfile = container_of(work,
struct cifsFileInfo, deferred.work);
+ struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry));
- spin_lock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
+ spin_lock(&cinode->deferred_lock);
cifs_del_deferred_close(cfile);
cfile->deferred_close_scheduled = false;
- spin_unlock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
+ spin_unlock(&cinode->deferred_lock);
_cifsFileInfo_put(cfile, true, false);
}
@@ -1452,6 +1484,7 @@ int cifs_close(struct inode *inode, struct file *file)
struct cifsInodeInfo *cinode = CIFS_I(inode);
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
struct cifs_deferred_close *dclose;
+ struct cifs_tcon *tcon;
cifs_fscache_unuse_inode_cookie(inode, file->f_mode & FMODE_WRITE);
@@ -1478,11 +1511,22 @@ int cifs_close(struct inode *inode, struct file *file)
cifsFileInfo_get(cfile);
} else {
/* Deferred close for files */
- queue_delayed_work(deferredclose_wq,
- &cfile->deferred, cifs_sb->ctx->closetimeo);
- cfile->deferred_close_scheduled = true;
- spin_unlock(&cinode->deferred_lock);
- return 0;
+ tcon = tlink_tcon(cfile->tlink);
+ trace_smb3_close_cached(tcon->tid, tcon->ses->Suid,
+ cfile->fid.persistent_fid,
+ cifs_sb->ctx->closetimeo);
+ /*
+ * Each queued execution owns one reference.
+ * If nothing was queued, the reference of
+ * the closing file is dropped below.
+ */
+ if (queue_delayed_work(deferredclose_wq,
+ &cfile->deferred,
+ cifs_sb->ctx->closetimeo)) {
+ cfile->deferred_close_scheduled = true;
+ spin_unlock(&cinode->deferred_lock);
+ return 0;
+ }
}
spin_unlock(&cinode->deferred_lock);
_cifsFileInfo_put(cfile, true, false);
@@ -1566,6 +1610,8 @@ int cifs_closedir(struct inode *inode, struct file *file)
cfile->srch_inf.ntwrk_buf_start = NULL;
if (cfile->srch_inf.smallBuf)
cifs_small_buf_release(buf);
+ else if (cfile->srch_inf.is_dynamic_buf)
+ kfree(buf);
else
cifs_buf_release(buf);
}
@@ -1641,6 +1687,9 @@ cifs_find_fid_lock_conflict(struct cifs_fid_locks *fdlocks, __u64 offset,
continue;
if (conf_lock)
*conf_lock = li;
+ trace_smb3_lock_conflict(cfile->fid.persistent_fid,
+ offset, length, type,
+ li->offset, li->length, li->type, li->pid);
return true;
}
return false;
@@ -1722,7 +1771,7 @@ cifs_lock_add(struct cifsFileInfo *cfile, struct cifsLockInfo *lock)
*/
static int
cifs_lock_add_if(struct cifsFileInfo *cfile, struct cifsLockInfo *lock,
- bool wait)
+ bool wait, unsigned int xid)
{
struct cifsLockInfo *conf_lock;
struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry));
@@ -1737,7 +1786,13 @@ try_again:
lock->type, lock->flags, &conf_lock,
CIFS_LOCK_OP);
if (!exist && cinode->can_cache_brlcks) {
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+
list_add_tail(&lock->llist, &cfile->llist->locks);
+ trace_smb3_lock_cached(xid, cfile->fid.persistent_fid,
+ tcon->tid, tcon->ses->Suid,
+ lock->offset, lock->length,
+ lock->type, 1, 0);
up_write(&cinode->lock_sem);
return rc;
}
@@ -2352,7 +2407,7 @@ cifs_setlk(struct file *file, struct file_lock *flock, __u32 type,
if (!lock)
return -ENOMEM;
- rc = cifs_lock_add_if(cfile, lock, wait_flag);
+ rc = cifs_lock_add_if(cfile, lock, wait_flag, xid);
if (rc < 0) {
kfree(lock);
return rc;
@@ -2508,30 +2563,96 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *flock)
return rc;
}
+static void cifs_update_i_blocks_for_write(struct inode *inode, loff_t start,
+ loff_t end)
+{
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ u64 allocated_end = CIFS_INO_BYTES(inode->i_blocks);
+ u64 blocks;
+
+ if (cinode->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)
+ return;
+
+ /*
+ * Grow the local estimate only across the currently known allocated
+ * prefix. A write beyond that may leave a hole.
+ */
+ if ((u64)start > allocated_end)
+ return;
+
+ blocks = CIFS_INO_BLOCKS(end);
+ if ((u64)inode->i_blocks < blocks)
+ inode->i_blocks = blocks;
+}
+
+static void cifs_update_i_blocks_after_write(struct kiocb *iocb,
+ ssize_t written)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+ loff_t end = iocb->ki_pos;
+
+ if (written <= 0)
+ return;
+
+ spin_lock(&inode->i_lock);
+ cifs_update_i_blocks_for_write(inode, end - written, end);
+ spin_unlock(&inode->i_lock);
+}
+
void cifs_write_subrequest_terminated(struct cifs_io_subrequest *wdata, ssize_t result)
{
struct netfs_io_request *wreq = wdata->rreq;
- struct netfs_inode *ictx = netfs_inode(wreq->inode);
+ struct inode *inode = wreq->inode;
+ struct netfs_inode *ictx = netfs_inode(inode);
loff_t wrend;
if (result > 0) {
+ spin_lock(&inode->i_lock);
+
wrend = wdata->subreq.start + wdata->subreq.transferred + result;
- if (wrend > ictx->zero_point &&
+ if (wrend > ictx->_zero_point &&
(wdata->rreq->origin == NETFS_UNBUFFERED_WRITE ||
wdata->rreq->origin == NETFS_DIO_WRITE))
- ictx->zero_point = wrend;
- if (wrend > ictx->remote_i_size)
+ netfs_write_zero_point(inode, wrend);
+ if (wrend > ictx->_remote_i_size)
netfs_resize_file(ictx, wrend, true);
+ cifs_update_i_blocks_for_write(inode, wdata->subreq.start,
+ wrend);
+
+ spin_unlock(&inode->i_lock);
}
netfs_write_subrequest_terminated(&wdata->subreq, result);
}
-struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
- bool fsuid_only)
+static bool open_flags_match(struct cifsInodeInfo *cinode,
+ unsigned int oflags, unsigned int cflags)
+{
+ struct inode *inode = &cinode->netfs.inode;
+ int crw = 0, orw = 0;
+
+ oflags &= ~(O_CREAT | O_EXCL | O_TRUNC);
+ cflags &= ~(O_CREAT | O_EXCL | O_TRUNC);
+
+ if (cifs_fscache_enabled(inode)) {
+ if (OPEN_FMODE(cflags) & FMODE_WRITE)
+ crw = 1;
+ if (OPEN_FMODE(oflags) & FMODE_WRITE)
+ orw = 1;
+ }
+ if (cifs_convert_flags(oflags, orw) != cifs_convert_flags(cflags, crw))
+ return false;
+
+ return (oflags & (O_SYNC | O_DIRECT)) == (cflags & (O_SYNC | O_DIRECT));
+}
+
+struct cifsFileInfo *__find_readable_file(struct cifsInodeInfo *cifs_inode,
+ unsigned int find_flags,
+ unsigned int open_flags)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(cifs_inode);
+ bool fsuid_only = find_flags & FIND_FSUID_ONLY;
struct cifsFileInfo *open_file = NULL;
/* only filter by fsuid on multiuser mounts */
@@ -2545,6 +2666,13 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
list_for_each_entry(open_file, &cifs_inode->openFileList, flist) {
if (fsuid_only && !uid_eq(open_file->uid, current_fsuid()))
continue;
+ if ((find_flags & FIND_NO_PENDING_DELETE) &&
+ open_file->status_file_deleted)
+ continue;
+ if ((find_flags & FIND_OPEN_FLAGS) &&
+ !open_flags_match(cifs_inode, open_flags,
+ open_file->f_flags))
+ continue;
if (OPEN_FMODE(open_file->f_flags) & FMODE_READ) {
if ((!open_file->invalidHandle)) {
/* found a good file */
@@ -2563,18 +2691,17 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
}
/* Return -EBADF if no handle is found and general rc otherwise */
-int
-cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, int flags,
- struct cifsFileInfo **ret_file)
+int __cifs_get_writable_file(struct cifsInodeInfo *cifs_inode,
+ unsigned int find_flags, unsigned int open_flags,
+ struct cifsFileInfo **ret_file)
{
struct cifsFileInfo *open_file, *inv_file = NULL;
+ bool fsuid_only, with_delete;
struct cifs_sb_info *cifs_sb;
bool any_available = false;
- int rc = -EBADF;
unsigned int refind = 0;
- bool fsuid_only = flags & FIND_WR_FSUID_ONLY;
- bool with_delete = flags & FIND_WR_WITH_DELETE;
*ret_file = NULL;
+ int rc = -EBADF;
/*
* Having a null inode here (because mapping->host was set to zero by
@@ -2588,8 +2715,13 @@ cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, int flags,
return rc;
}
+ if (test_bit(CIFS_INO_TMPFILE, &cifs_inode->flags))
+ find_flags = FIND_ANY;
+
cifs_sb = CIFS_SB(cifs_inode);
+ with_delete = find_flags & FIND_WITH_DELETE;
+ fsuid_only = find_flags & FIND_FSUID_ONLY;
/* only filter by fsuid on multiuser mounts */
if (!(cifs_sb_flags(cifs_sb) & CIFS_MOUNT_MULTIUSER))
fsuid_only = false;
@@ -2607,9 +2739,13 @@ refind_writable:
continue;
if (with_delete && !(open_file->fid.access & DELETE))
continue;
- if ((flags & FIND_WR_NO_PENDING_DELETE) &&
+ if ((find_flags & FIND_NO_PENDING_DELETE) &&
open_file->status_file_deleted)
continue;
+ if ((find_flags & FIND_OPEN_FLAGS) &&
+ !open_flags_match(cifs_inode, open_flags,
+ open_file->f_flags))
+ continue;
if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) {
if (!open_file->invalidHandle) {
/* found a good writable file */
@@ -2669,16 +2805,19 @@ find_writable_file(struct cifsInodeInfo *cifs_inode, int flags)
return cfile;
}
-int
-cifs_get_writable_path(struct cifs_tcon *tcon, const char *name,
- int flags,
- struct cifsFileInfo **ret_file)
+int cifs_get_writable_path(struct cifs_tcon *tcon, const char *name,
+ struct inode *inode, int flags,
+ struct cifsFileInfo **ret_file)
{
struct cifsFileInfo *cfile;
- void *page = alloc_dentry_path();
+ void *page;
*ret_file = NULL;
+ if (inode)
+ return cifs_get_writable_file(CIFS_I(inode), flags, ret_file);
+
+ page = alloc_dentry_path();
spin_lock(&tcon->open_file_lock);
list_for_each_entry(cfile, &tcon->openFileList, tlist) {
struct cifsInodeInfo *cinode;
@@ -2726,17 +2865,7 @@ cifs_get_readable_path(struct cifs_tcon *tcon, const char *name,
cinode = CIFS_I(d_inode(cfile->dentry));
spin_unlock(&tcon->open_file_lock);
free_dentry_path(page);
- *ret_file = find_readable_file(cinode, 0);
- if (*ret_file) {
- spin_lock(&cinode->open_file_lock);
- if ((*ret_file)->status_file_deleted) {
- spin_unlock(&cinode->open_file_lock);
- cifsFileInfo_put(*ret_file);
- *ret_file = NULL;
- } else {
- spin_unlock(&cinode->open_file_lock);
- }
- }
+ *ret_file = find_readable_file(cinode, FIND_ANY);
return *ret_file ? 0 : -ENOENT;
}
@@ -2808,7 +2937,7 @@ int cifs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
}
if ((OPEN_FMODE(smbfile->f_flags) & FMODE_WRITE) == 0) {
- smbfile = find_writable_file(CIFS_I(inode), FIND_WR_ANY);
+ smbfile = find_writable_file(CIFS_I(inode), FIND_ANY);
if (smbfile) {
rc = server->ops->flush(xid, tcon, &smbfile->fid);
cifsFileInfo_put(smbfile);
@@ -2878,6 +3007,7 @@ cifs_writev(struct kiocb *iocb, struct iov_iter *from)
}
rc = netfs_buffered_write_iter_locked(iocb, from, NULL);
+ cifs_update_i_blocks_after_write(iocb, rc);
out:
up_read(&cinode->lock_sem);
@@ -2907,6 +3037,7 @@ cifs_strict_writev(struct kiocb *iocb, struct iov_iter *from)
(CIFS_UNIX_FCNTL_CAP & le64_to_cpu(tcon->fsUnixInfo.Capability)) &&
((cifs_sb_flags(cifs_sb) & CIFS_MOUNT_NOPOSIXBRL) == 0)) {
written = netfs_file_write_iter(iocb, from);
+ cifs_update_i_blocks_after_write(iocb, written);
goto out;
}
written = cifs_writev(iocb, from);
@@ -2919,6 +3050,7 @@ cifs_strict_writev(struct kiocb *iocb, struct iov_iter *from)
* these pages but not on the region from pos to ppos+len-1.
*/
written = netfs_file_write_iter(iocb, from);
+ cifs_update_i_blocks_after_write(iocb, written);
if (CIFS_CACHE_READ(cinode)) {
/*
* We have read level caching and we have just sent a write
@@ -2937,6 +3069,15 @@ out:
return written;
}
+ssize_t cifs_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+ ssize_t written;
+
+ written = netfs_file_write_iter(iocb, from);
+ cifs_update_i_blocks_after_write(iocb, written);
+ return written;
+}
+
ssize_t cifs_loose_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
ssize_t rc;
@@ -2961,6 +3102,7 @@ ssize_t cifs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
if (iocb->ki_filp->f_flags & O_DIRECT) {
written = netfs_unbuffered_write_iter(iocb, from);
+ cifs_update_i_blocks_after_write(iocb, written);
if (written > 0 && CIFS_CACHE_READ(cinode)) {
cifs_zap_mapping(inode);
cifs_dbg(FYI,
@@ -2976,6 +3118,7 @@ ssize_t cifs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
return written;
written = netfs_file_write_iter(iocb, from);
+ cifs_update_i_blocks_after_write(iocb, written);
if (!CIFS_CACHE_WRITE(CIFS_I(inode))) {
rc = filemap_fdatawrite(inode->i_mapping);
@@ -3123,13 +3266,26 @@ static int is_inode_writable(struct cifsInodeInfo *cifs_inode)
bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file,
bool from_readdir)
{
+ struct cifs_sb_info *cifs_sb;
+ unsigned long tlw;
+
if (!cifsInode)
return true;
+ cifs_sb = CIFS_SB(cifsInode);
+
if (is_inode_writable(cifsInode) ||
((cifsInode->oplock & CIFS_CACHE_RW_FLG) != 0 && from_readdir)) {
/* This inode is open for write at least once */
- struct cifs_sb_info *cifs_sb = CIFS_SB(cifsInode);
+
+ /*
+ * Readdir data is unreliable when we have writable handles or
+ * an exclusive lease -- never allow it to change i_size, even
+ * on direct-IO mounts where the server's directory metadata
+ * can still lag behind the actual file state.
+ */
+ if (from_readdir)
+ return false;
if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_DIRECT_IO) {
/* since no page cache to corrupt on directio
@@ -3141,8 +3297,40 @@ bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file,
return true;
return false;
- } else
- return true;
+ }
+
+ /*
+ * No writable handles open. Check whether we are within the attribute
+ * cache validity window of a recent local modification.
+ *
+ * For the close() path: _cifsFileInfo_put() stamps time_last_write
+ * (via smp_store_release()) before releasing open_file_lock. That
+ * spin_unlock() is a store-release that pairs with the spin_lock()
+ * (load-acquire) in is_inode_writable() above, so if
+ * is_inode_writable() returned false the smp_load_acquire() below is
+ * guaranteed to observe any time_last_write update from a concurrent
+ * close(), covering all close paths including background I/O.
+ *
+ * For the setattr/truncate paths: those callers use smp_store_release()
+ * directly; the smp_load_acquire() below pairs with that store. There
+ * is no shared lock between setattr and readdir, so this relies on
+ * acquire-release semantics alone. The store propagation latency on
+ * weakly-ordered architectures (nanoseconds) is negligible relative to
+ * the acregmax window (seconds) and the readdir RPC round-trip
+ * (milliseconds), making this a sound design choice in practice.
+ *
+ * time_last_write == 0 means the inode has never been written locally;
+ * skip the window check to avoid false positives near boot time when
+ * jiffies is still close to INITIAL_JIFFIES on 32-bit systems.
+ */
+ if (from_readdir) {
+ /* Pairs with smp_store_release() in _cifsFileInfo_put() and setattr. */
+ tlw = smp_load_acquire(&cifsInode->time_last_write);
+ if (tlw && time_before(jiffies, tlw + cifs_sb->ctx->acregmax))
+ return false;
+ }
+
+ return true;
}
void cifs_oplock_break(struct work_struct *work)
@@ -3163,18 +3351,15 @@ void cifs_oplock_break(struct work_struct *work)
__u64 persistent_fid, volatile_fid;
__u16 net_fid;
- /*
- * Hold a reference to the superblock to prevent it and its inodes from
- * being freed while we are accessing cinode. Otherwise, _cifsFileInfo_put()
- * may release the last reference to the sb and trigger inode eviction.
- */
- cifs_sb_active(sb);
wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
TASK_UNINTERRUPTIBLE);
- tlink = cifs_sb_tlink(cifs_sb);
- if (IS_ERR(tlink))
+ tlink = cifs_get_tlink(cfile->tlink);
+ if (IS_ERR_OR_NULL(tlink)) {
+ /* drop the reference taken when the break was queued */
+ _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false);
goto out;
+ }
tcon = tlink_tcon(tlink);
server = tcon->ses->server;
@@ -3253,9 +3438,40 @@ oplock_break_ack:
cifs_put_tlink(tlink);
out:
cifs_done_oplock_break(cinode);
- cifs_sb_deactive(sb);
}
+#ifdef CONFIG_SWAP
+static void cifs_swap_submit_write(struct swap_io_ctx *ctx)
+{
+ struct swap_iocb *sio = ctx->sio;
+ struct iov_iter iter;
+ int ret;
+
+ swap_fs_prepare_rw(ctx, WRITE, &iter);
+ ret = netfs_unbuffered_write_iter_locked(&sio->iocb, &iter, NULL);
+ if (ret != -EIOCBQUEUED)
+ sio->iocb.ki_complete(&sio->iocb, ret);
+}
+
+static void cifs_swap_submit_read(struct swap_io_ctx *ctx)
+{
+ struct swap_iocb *sio = ctx->sio;
+ struct iov_iter iter;
+ int ret;
+
+ swap_fs_prepare_rw(ctx, READ, &iter);
+ ret = netfs_unbuffered_read_iter_locked(&sio->iocb, &iter);
+ if (ret != -EIOCBQUEUED)
+ sio->iocb.ki_complete(&sio->iocb, ret);
+}
+
+static const struct swap_ops cifs_swap_ops = {
+ .flags = SWAP_OPS_F_REQUIRE_NOFS,
+ .submit_write = cifs_swap_submit_write,
+ .submit_read = cifs_swap_submit_read,
+ .can_merge = swap_fs_can_merge,
+};
+
static int cifs_swap_activate(struct swap_info_struct *sis,
struct file *swap_file, sector_t *span)
{
@@ -3266,7 +3482,7 @@ static int cifs_swap_activate(struct swap_info_struct *sis,
cifs_dbg(FYI, "swap activate\n");
- if (!swap_file->f_mapping->a_ops->swap_rw)
+ if (swap_file->f_mapping->a_ops != &cifs_addr_ops)
/* Cannot support swap */
return -EINVAL;
@@ -3297,9 +3513,7 @@ static int cifs_swap_activate(struct swap_info_struct *sis,
* but we could add call to grab a byte range lock to prevent others
* from reading or writing the file
*/
-
- sis->flags |= SWP_FS_OPS;
- return add_swap_extent(sis, 0, sis->max, 0);
+ return swap_fs_activate(sis, &cifs_swap_ops);
}
static void cifs_swap_deactivate(struct file *file)
@@ -3315,26 +3529,10 @@ static void cifs_swap_deactivate(struct file *file)
/* do we need to unpin (or unlock) the file */
}
-
-/**
- * cifs_swap_rw - SMB3 address space operation for swap I/O
- * @iocb: target I/O control block
- * @iter: I/O buffer
- *
- * Perform IO to the swap-file. This is much like direct IO.
- */
-static int cifs_swap_rw(struct kiocb *iocb, struct iov_iter *iter)
-{
- ssize_t ret;
-
- if (iov_iter_rw(iter) == READ)
- ret = netfs_unbuffered_read_iter_locked(iocb, iter);
- else
- ret = netfs_unbuffered_write_iter_locked(iocb, iter, NULL);
- if (ret < 0)
- return ret;
- return 0;
-}
+#else
+#define cifs_swap_activate NULL
+#define cifs_swap_deactivate NULL
+#endif /* CONFIG_SWAP */
const struct address_space_operations cifs_addr_ops = {
.read_folio = netfs_read_folio,
@@ -3351,7 +3549,6 @@ const struct address_space_operations cifs_addr_ops = {
*/
.swap_activate = cifs_swap_activate,
.swap_deactivate = cifs_swap_deactivate,
- .swap_rw = cifs_swap_rw,
};
/*
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 54090739535f..abc29500a664 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -80,7 +80,7 @@ const struct fs_parameter_spec smb3_fs_parameters[] = {
fsparam_flag_no("forcegid", Opt_forcegid),
fsparam_flag("noblocksend", Opt_noblocksend),
fsparam_flag("noautotune", Opt_noautotune),
- fsparam_flag("nolease", Opt_nolease),
+ fsparam_flag_no("lease", Opt_lease),
fsparam_flag_no("hard", Opt_hard),
fsparam_flag_no("soft", Opt_soft),
fsparam_flag_no("perm", Opt_perm),
@@ -420,7 +420,7 @@ static int parse_symlink_flavor(struct fs_context *fc, char *value,
#define DUP_CTX_STR(field) \
do { \
if (ctx->field) { \
- new_ctx->field = kstrdup(ctx->field, GFP_ATOMIC); \
+ new_ctx->field = kstrdup(ctx->field, GFP_KERNEL); \
if (new_ctx->field == NULL) { \
smb3_cleanup_fs_context_contents(new_ctx); \
return -ENOMEM; \
@@ -536,37 +536,6 @@ cifs_parse_smb_version(struct fs_context *fc, char *value, struct smb3_fs_contex
return 0;
}
-int smb3_parse_opt(const char *options, const char *key, char **val)
-{
- int rc = -ENOENT;
- char *opts, *orig, *p;
-
- orig = opts = kstrdup(options, GFP_KERNEL);
- if (!opts)
- return -ENOMEM;
-
- while ((p = strsep(&opts, ","))) {
- char *nval;
-
- if (!*p)
- continue;
- if (strncasecmp(p, key, strlen(key)))
- continue;
- nval = strchr(p, '=');
- if (nval) {
- if (nval == p)
- continue;
- *nval++ = 0;
- *val = kstrdup(nval, GFP_KERNEL);
- rc = !*val ? -ENOMEM : 0;
- goto out;
- }
- }
-out:
- kfree(orig);
- return rc;
-}
-
/*
* Remove duplicate path delimiters. Windows is supposed to do that
* but there are some bugs that prevent rename from working if there are
@@ -588,6 +557,10 @@ char *cifs_sanitize_prepath(char *prepath, gfp_t gfp)
while (IS_DELIM(*cursor1))
cursor1++;
+ /* exit in case of only delimiters */
+ if (!*cursor1)
+ return NULL;
+
/* copy the first letter */
*cursor2 = *cursor1;
@@ -658,13 +631,17 @@ smb3_parse_devname(const char *devname, struct smb3_fs_context *ctx)
/* make sure we have a valid UNC double delimiter prefix */
len = strspn(devname, delims);
- if (len != 2)
+ if (len != 2) {
+ cifs_dbg(VFS, "UNC: path must begin with // or \\\\\n");
return -EINVAL;
+ }
/* find delimiter between host and sharename */
pos = strpbrk(devname + 2, delims);
- if (!pos)
+ if (!pos) {
+ cifs_dbg(VFS, "UNC: missing delimiter between hostname and share name\n");
return -EINVAL;
+ }
/* record the server hostname */
kfree(ctx->server_hostname);
@@ -677,8 +654,10 @@ smb3_parse_devname(const char *devname, struct smb3_fs_context *ctx)
/* now go until next delimiter or end of string */
len = strcspn(pos, delims);
- if (!len)
+ if (!len) {
+ cifs_dbg(VFS, "UNC: missing share name\n");
return -EINVAL;
+ }
/* move "pos" up to delimiter or NULL */
pos += len;
@@ -714,6 +693,41 @@ static int smb3_handle_conflicting_options(struct fs_context *fc)
{
struct smb3_fs_context *ctx = smb3_fc2context(fc);
+ if (ctx->rdma && ctx->vals->protocol_id < SMB30_PROT_ID) {
+ cifs_errorf(fc, "SMB Direct requires Version >=3.0\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (ctx->multiuser && !IS_ENABLED(CONFIG_KEYS)) {
+ cifs_errorf(fc, "Multiuser mounts require kernels with CONFIG_KEYS enabled\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (ctx->multiuser && ctx->upcall_target == UPTARGET_MOUNT) {
+ cifs_errorf(fc, "multiuser mount option not supported with upcalltarget set as 'mount'\n");
+ return -EINVAL;
+ }
+
+ if (ctx->uid_specified && !ctx->forceuid_specified) {
+ ctx->override_uid = 1;
+ pr_notice("enabling forceuid mount option implicitly because uid= option is specified\n");
+ }
+
+ if (ctx->gid_specified && !ctx->forcegid_specified) {
+ ctx->override_gid = 1;
+ pr_notice("enabling forcegid mount option implicitly because gid= option is specified\n");
+ }
+
+ if (ctx->override_uid && !ctx->uid_specified) {
+ ctx->override_uid = 0;
+ pr_notice("ignoring forceuid mount option specified with no uid= option\n");
+ }
+
+ if (ctx->override_gid && !ctx->gid_specified) {
+ ctx->override_gid = 0;
+ pr_notice("ignoring forcegid mount option specified with no gid= option\n");
+ }
+
if (ctx->multichannel_specified) {
if (ctx->multichannel) {
if (!ctx->max_channels_specified) {
@@ -732,19 +746,14 @@ static int smb3_handle_conflicting_options(struct fs_context *fc)
return -EINVAL;
}
}
- } else {
- if (ctx->max_channels_specified) {
- if (ctx->max_channels > 1)
- ctx->multichannel = true;
- else
- ctx->multichannel = false;
- } else {
+ } else if (ctx->max_channels_specified) {
+ if (ctx->max_channels > 1)
+ ctx->multichannel = true;
+ else
ctx->multichannel = false;
- ctx->max_channels = 1;
- }
}
- //resetting default values as remount doesn't initialize fs_context again
+ /* clear parse-time latches so they don't persist across remounts */
ctx->multichannel_specified = false;
ctx->max_channels_specified = false;
@@ -757,7 +766,7 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
void *data);
static int smb3_get_tree(struct fs_context *fc);
-static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels);
+static void smb3_sync_ses_chan_max(struct cifs_ses *ses, size_t max_channels);
static int smb3_reconfigure(struct fs_context *fc);
static const struct fs_context_operations smb3_fs_context_ops = {
@@ -825,28 +834,23 @@ static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
if (ret < 0)
break;
}
- return ret ?: smb3_handle_conflicting_options(fc);
+ return ret;
}
/*
- * Validate the preparsed information in the config.
+ * smb3_fs_context_validate - check initial-mount-only constraints:
+ * UNC presence, address resolution, dialect warnings
+ *
+ * @fc: generic mount context
*/
static int smb3_fs_context_validate(struct fs_context *fc)
{
struct smb3_fs_context *ctx = smb3_fc2context(fc);
+ int rc;
- if (ctx->rdma && ctx->vals->protocol_id < SMB30_PROT_ID) {
- cifs_errorf(fc, "SMB Direct requires Version >=3.0\n");
- return -EOPNOTSUPP;
- }
-
-#ifndef CONFIG_KEYS
- /* Muliuser mounts require CONFIG_KEYS support */
- if (ctx->multiuser) {
- cifs_errorf(fc, "Multiuser mounts require kernels with CONFIG_KEYS enabled\n");
- return -1;
- }
-#endif
+ rc = smb3_handle_conflicting_options(fc);
+ if (rc)
+ return rc;
if (ctx->got_version == false)
pr_warn_once("No dialect specified on mount. Default has changed to a more secure dialect, SMB2.1 or later (e.g. SMB3.1.1), from CIFS (SMB1). To use the less secure SMB1 dialect to access old servers which do not support SMB3.1.1 (or even SMB3 or SMB2.1) specify vers=1.0 on mount.\n");
@@ -881,26 +885,6 @@ static int smb3_fs_context_validate(struct fs_context *fc)
/* set the port that we got earlier */
cifs_set_port((struct sockaddr *)&ctx->dstaddr, ctx->port);
- if (ctx->uid_specified && !ctx->forceuid_specified) {
- ctx->override_uid = 1;
- pr_notice("enabling forceuid mount option implicitly because uid= option is specified\n");
- }
-
- if (ctx->gid_specified && !ctx->forcegid_specified) {
- ctx->override_gid = 1;
- pr_notice("enabling forcegid mount option implicitly because gid= option is specified\n");
- }
-
- if (ctx->override_uid && !ctx->uid_specified) {
- ctx->override_uid = 0;
- pr_notice("ignoring forceuid mount option specified with no uid= option\n");
- }
-
- if (ctx->override_gid && !ctx->gid_specified) {
- ctx->override_gid = 0;
- pr_notice("ignoring forcegid mount option specified with no gid= option\n");
- }
-
return 0;
}
@@ -910,7 +894,7 @@ static int smb3_get_tree_common(struct fs_context *fc)
struct dentry *root;
int rc = 0;
- root = cifs_smb3_do_mount(fc->fs_type, 0, ctx);
+ root = cifs_smb3_do_mount(fc, ctx);
if (IS_ERR(root))
return PTR_ERR(root);
@@ -1031,25 +1015,34 @@ do { \
int smb3_sync_session_ctx_passwords(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
{
+ char *password = NULL, *password2 = NULL;
+
if (ses->password &&
cifs_sb->ctx->password &&
strcmp(ses->password, cifs_sb->ctx->password)) {
- kfree_sensitive(cifs_sb->ctx->password);
- cifs_sb->ctx->password = kstrdup(ses->password, GFP_KERNEL);
- if (!cifs_sb->ctx->password)
+ password = kstrdup(ses->password, GFP_KERNEL);
+ if (!password)
return -ENOMEM;
}
if (ses->password2 &&
cifs_sb->ctx->password2 &&
strcmp(ses->password2, cifs_sb->ctx->password2)) {
- kfree_sensitive(cifs_sb->ctx->password2);
- cifs_sb->ctx->password2 = kstrdup(ses->password2, GFP_KERNEL);
- if (!cifs_sb->ctx->password2) {
- kfree_sensitive(cifs_sb->ctx->password);
- cifs_sb->ctx->password = NULL;
+ password2 = kstrdup(ses->password2, GFP_KERNEL);
+ if (!password2) {
+ kfree_sensitive(password);
return -ENOMEM;
}
}
+
+ if (password) {
+ kfree_sensitive(cifs_sb->ctx->password);
+ cifs_sb->ctx->password = password;
+ }
+ if (password2) {
+ kfree_sensitive(cifs_sb->ctx->password2);
+ cifs_sb->ctx->password2 = password2;
+ }
+
return 0;
}
@@ -1062,7 +1055,7 @@ int smb3_sync_session_ctx_passwords(struct cifs_sb_info *cifs_sb, struct cifs_se
* with the session's channel lock. This should be called whenever the maximum
* allowed channels for a session changes (e.g., after a remount or reconfigure).
*/
-static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels)
+static void smb3_sync_ses_chan_max(struct cifs_ses *ses, size_t max_channels)
{
spin_lock(&ses->chan_lock);
ses->chan_max = max_channels;
@@ -1072,12 +1065,15 @@ static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channe
static int smb3_reconfigure(struct fs_context *fc)
{
struct smb3_fs_context *ctx = smb3_fc2context(fc);
+ struct smb3_fs_context *new_ctx = NULL;
+ struct smb3_fs_context *old_ctx = NULL;
struct dentry *root = fc->root;
struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
unsigned int rsize = ctx->rsize, wsize = ctx->wsize;
char *new_password = NULL, *new_password2 = NULL;
bool need_recon = false;
+ bool need_mchan_update;
int rc;
if (ses->expired_pwd)
@@ -1087,6 +1083,18 @@ static int smb3_reconfigure(struct fs_context *fc)
if (rc)
return rc;
+ rc = smb3_handle_conflicting_options(fc);
+ if (rc)
+ return rc;
+
+ old_ctx = kzalloc_obj(*old_ctx);
+ if (!old_ctx)
+ return -ENOMEM;
+
+ rc = smb3_fs_context_dup(old_ctx, cifs_sb->ctx);
+ if (rc)
+ goto free_old_ctx;
+
/*
* We can not change UNC/username/password/domainname/
* workstation_name/nodename/iocharset
@@ -1096,16 +1104,22 @@ static int smb3_reconfigure(struct fs_context *fc)
STEAL_STRING(cifs_sb, ctx, UNC);
STEAL_STRING(cifs_sb, ctx, source);
STEAL_STRING(cifs_sb, ctx, username);
+ STEAL_STRING(cifs_sb, ctx, domainname);
+ STEAL_STRING(cifs_sb, ctx, nodename);
+ STEAL_STRING(cifs_sb, ctx, iocharset);
- if (need_recon == false)
+ if (!need_recon) {
STEAL_STRING_SENSITIVE(cifs_sb, ctx, password);
- else {
+ } else {
if (ctx->password) {
new_password = kstrdup(ctx->password, GFP_KERNEL);
- if (!new_password)
- return -ENOMEM;
- } else
+ if (!new_password) {
+ rc = -ENOMEM;
+ goto restore_ctx;
+ }
+ } else {
STEAL_STRING_SENSITIVE(cifs_sb, ctx, password);
+ }
}
/*
@@ -1115,11 +1129,29 @@ static int smb3_reconfigure(struct fs_context *fc)
if (ctx->password2) {
new_password2 = kstrdup(ctx->password2, GFP_KERNEL);
if (!new_password2) {
- kfree_sensitive(new_password);
- return -ENOMEM;
+ rc = -ENOMEM;
+ goto restore_ctx;
}
- } else
+ } else {
STEAL_STRING_SENSITIVE(cifs_sb, ctx, password2);
+ }
+
+ /* if rsize or wsize not passed in on remount, use previous values */
+ ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
+ ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;
+
+ new_ctx = kzalloc_obj(*new_ctx);
+ if (!new_ctx) {
+ rc = -ENOMEM;
+ goto restore_ctx;
+ }
+
+ rc = smb3_fs_context_dup(new_ctx, ctx);
+ if (rc)
+ goto restore_ctx;
+
+ need_mchan_update = ctx->multichannel != cifs_sb->ctx->multichannel ||
+ ctx->max_channels != cifs_sb->ctx->max_channels;
/*
* we may update the passwords in the ses struct below. Make sure we do
@@ -1130,54 +1162,55 @@ static int smb3_reconfigure(struct fs_context *fc)
/*
* smb2_reconnect may swap password and password2 in case session setup
* failed. First get ctx passwords in sync with ses passwords. It should
- * be okay to do this even if this function were to return an error at a
- * later stage
+ * be done before committing new passwords.
*/
rc = smb3_sync_session_ctx_passwords(cifs_sb, ses);
if (rc) {
mutex_unlock(&ses->session_mutex);
- kfree_sensitive(new_password);
- kfree_sensitive(new_password2);
- return rc;
+ goto cleanup_new_ctx;
+ }
+
+ /*
+ * If multichannel or max_channels has changed, update the session's channels accordingly.
+ * This may add or remove channels to match the new configuration.
+ */
+ if (need_mchan_update) {
+ /* Prevent concurrent scaling operations */
+ spin_lock(&ses->ses_lock);
+ if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
+ spin_unlock(&ses->ses_lock);
+ mutex_unlock(&ses->session_mutex);
+ rc = -EINVAL;
+ goto cleanup_new_ctx;
+ }
+ ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
+ spin_unlock(&ses->ses_lock);
}
/*
- * now that allocations for passwords are done, commit them
+ * Commit session passwords before any channel work so newly added
+ * channels authenticate with the new credentials.
*/
if (new_password) {
kfree_sensitive(ses->password);
ses->password = new_password;
+ new_password = NULL;
}
if (new_password2) {
kfree_sensitive(ses->password2);
ses->password2 = new_password2;
+ new_password2 = NULL;
}
- /*
- * If multichannel or max_channels has changed, update the session's channels accordingly.
- * This may add or remove channels to match the new configuration.
- */
- if ((ctx->multichannel != cifs_sb->ctx->multichannel) ||
- (ctx->max_channels != cifs_sb->ctx->max_channels)) {
-
+ if (need_mchan_update) {
/* Synchronize ses->chan_max with the new mount context */
smb3_sync_ses_chan_max(ses, ctx->max_channels);
- /* Now update the session's channels to match the new configuration */
- /* Prevent concurrent scaling operations */
- spin_lock(&ses->ses_lock);
- if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
- spin_unlock(&ses->ses_lock);
- mutex_unlock(&ses->session_mutex);
- return -EINVAL;
- }
- ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
- spin_unlock(&ses->ses_lock);
mutex_unlock(&ses->session_mutex);
- rc = smb3_update_ses_channels(ses, ses->server,
- false /* from_reconnect */,
- false /* disable_mchan */);
+ smb3_update_ses_channels(ses, ses->server,
+ false /* from_reconnect */,
+ false /* disable_mchan */);
/* Clear scaling flag after operation */
spin_lock(&ses->ses_lock);
@@ -1187,16 +1220,12 @@ static int smb3_reconfigure(struct fs_context *fc)
mutex_unlock(&ses->session_mutex);
}
- STEAL_STRING(cifs_sb, ctx, domainname);
- STEAL_STRING(cifs_sb, ctx, nodename);
- STEAL_STRING(cifs_sb, ctx, iocharset);
-
- /* if rsize or wsize not passed in on remount, use previous values */
- ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
- ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;
-
smb3_cleanup_fs_context_contents(cifs_sb->ctx);
- rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
+ memcpy(cifs_sb->ctx, new_ctx, sizeof(*new_ctx));
+ kfree(new_ctx);
+ new_ctx = NULL;
+ smb3_cleanup_fs_context(old_ctx);
+ old_ctx = NULL;
smb3_update_mnt_flags(cifs_sb);
#ifdef CONFIG_CIFS_DFS_UPCALL
if (!rc)
@@ -1204,6 +1233,19 @@ static int smb3_reconfigure(struct fs_context *fc)
#endif
return rc;
+
+cleanup_new_ctx:
+ smb3_cleanup_fs_context_contents(new_ctx);
+restore_ctx:
+ kfree(new_ctx);
+ kfree_sensitive(new_password);
+ kfree_sensitive(new_password2);
+ smb3_cleanup_fs_context_contents(cifs_sb->ctx);
+ memcpy(cifs_sb->ctx, old_ctx, sizeof(*old_ctx));
+free_old_ctx:
+ kfree(old_ctx);
+
+ return rc;
}
static int smb3_fs_context_parse_param(struct fs_context *fc,
@@ -1330,8 +1372,8 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
case Opt_noautotune:
ctx->noautotune = 1;
break;
- case Opt_nolease:
- ctx->no_lease = 1;
+ case Opt_lease:
+ ctx->no_lease = result.negated;
break;
case Opt_nosparse:
ctx->no_sparse = 1;
@@ -1899,11 +1941,6 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
}
/* case Opt_ignore: - is ignored as expected ... */
- if (ctx->multiuser && ctx->upcall_target == UPTARGET_MOUNT) {
- cifs_errorf(fc, "multiuser mount option not supported with upcalltarget set as 'mount'\n");
- goto cifs_parse_mount_err;
- }
-
return 0;
cifs_parse_mount_err:
@@ -1997,7 +2034,7 @@ int smb3_init_fs_context(struct fs_context *fc)
ctx->backupuid_specified = false; /* no backup intent for a user */
ctx->backupgid_specified = false; /* no backup intent for a group */
- ctx->retrans = 1;
+ ctx->retrans = 0;
ctx->reparse_type = CIFS_REPARSE_TYPE_DEFAULT;
ctx->symlink_type = CIFS_SYMLINK_TYPE_DEFAULT;
ctx->nonativesocket = 0;
diff --git a/fs/smb/client/fs_context.h b/fs/smb/client/fs_context.h
index 0b64fcb5d302..a80a5caff23c 100644
--- a/fs/smb/client/fs_context.h
+++ b/fs/smb/client/fs_context.h
@@ -102,7 +102,7 @@ enum cifs_param {
Opt_forcegid,
Opt_noblocksend,
Opt_noautotune,
- Opt_nolease,
+ Opt_lease,
Opt_nosparse,
Opt_hard,
Opt_soft,
diff --git a/fs/smb/client/fscache.c b/fs/smb/client/fscache.c
index 01424a5cdb99..9f66bbcd3801 100644
--- a/fs/smb/client/fscache.c
+++ b/fs/smb/client/fscache.c
@@ -38,7 +38,6 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon)
struct TCP_Server_Info *server = tcon->ses->server;
struct fscache_volume *vcookie;
const struct sockaddr *sa = (struct sockaddr *)&server->dstaddr;
- size_t slen, i;
char *sharename;
char *key;
int ret = -ENOMEM;
@@ -73,10 +72,7 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon)
return PTR_ERR(sharename);
}
- slen = strlen(sharename);
- for (i = 0; i < slen; i++)
- if (sharename[i] == '/')
- sharename[i] = ';';
+ strreplace(sharename, '/', ';');
key = kasprintf(GFP_KERNEL, "cifs,%pISpc,%s", sa, sharename);
if (!key)
diff --git a/fs/smb/client/gen_smb1_mapping b/fs/smb/client/gen_smb1_mapping
new file mode 100644
index 000000000000..c2b2939a83c6
--- /dev/null
+++ b/fs/smb/client/gen_smb1_mapping
@@ -0,0 +1,124 @@
+#!/usr/bin/perl -w
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Script to generate SMB1 error mapping tables.
+#
+# Copyright (C) 2026 KylinSoft Co., Ltd. All rights reserved.
+# Author(s): Huiwen He <hehuiwen@kylinos.cn>
+# ChenXiaoSong <chenxiaosong@kylinos.cn>
+#
+use strict;
+
+if ($#ARGV != 1) {
+ print STDERR "Usage: $0 <in-file> <out-file>\n";
+ exit(2);
+}
+
+# Parse input parameters and extract filenames
+my $in_file = $ARGV[0];
+my $out_file = $ARGV[1];
+my $input_name = (split m|/|, $in_file)[-1];
+my $output_name = (split m|/|, $out_file)[-1];
+my $script_name = (split m|/|, $0)[-1];
+my @list = ();
+my %seen = ();
+my $current_class = "";
+
+# Parse annotated entries from the input file
+open(my $in, "<", $in_file) or die "Cannot open $in_file: $!";
+if ($in_file =~ /nterr\.h$/) {
+ while (<$in>) {
+ # Handle backslash line continuation
+ $_ .= <$in> while s/\\\s*\n//;
+
+ # Match #define NT_STATUS_... followed by // CLASS, CODE or /* CLASS, CODE */
+ my $re = qr{^\s*#define\s+(NT_STATUS_[A-Za-z0-9_]+)\s+(.+?)\s*} .
+ qr{(?://\s*|/\*\s*)([A-Z0-9_]+)\s*,\s*([A-Za-z0-9_]+)};
+
+ if (/$re/) {
+ my ($name, $val_str, $class, $code) = ($1, $2, $3, $4);
+
+ # Skip duplicate macro names
+ next if $seen{$name}++;
+
+ # Clean up value string (remove parens, spaces)
+ $val_str =~ s/[\s\(\)]//g;
+ my $val = 0;
+ foreach my $part (split(/\|/, $val_str)) {
+ $val |= hex($part);
+ }
+ push @list, { val => $val, name => $name, class => $class, code => $code };
+ } elsif (/^\s*#define\s+NT_STATUS_.*(?:\/\/|\/\*)/) {
+ # Error if macro has a comment (// or /*) but fails mapping format
+ die "Error: Invalid mapping comment format in $in_file: $_";
+ }
+ }
+} elsif ($in_file =~ /smberr\.h$/) {
+ while (<$in>) {
+ # Handle backslash line continuation
+ $_ .= <$in> while s/\\\s*\n//;
+
+ # Detect current error class from header comments (ERRDOS or ERRSRV)
+ if (/generated with the (\w+) error class/) {
+ $current_class = $1;
+ }
+
+ # Match #define ERR/Err_... <value> followed by // -POSIX_ERR or /* -POSIX_ERR */
+ if (/^\s*#define\s+((?:ERR|Err)[A-Za-z0-9_]+)\s+([0-9a-fA-FxX]+)\s*(?:\/\/|\/\*)\s*(-[A-Z0-9_]+)/) {
+ my ($name, $val_str, $error) = ($1, $2, $3);
+ my $val = ($val_str =~ /^0x/i) ? hex($val_str) : $val_str;
+ push @list, { val => $val, name => $name, error => $error, class => $current_class };
+ } elsif ($current_class && /^\s*#define\s+(?:ERR|Err).*?(?:\/\/|\/\*)/) {
+ # Error if macro has a comment (// or /*) but fails mapping format
+ die "Error: Invalid mapping comment format in $in_file: $_";
+ }
+ }
+}
+close($in);
+
+# Fail if no entries were found to avoid broken builds
+die "Error: No mapping entries found in $in_file\n" unless @list;
+
+# Sort entries numerically by value
+@list = sort { $a->{val} <=> $b->{val} } @list;
+
+# Generate the C mapping table output file
+open(my $out, ">", $out_file) or die "Cannot open $out_file: $!";
+print $out "/* Autogenerated from $input_name by $script_name */\n\n";
+
+if ($output_name eq "smb1_mapping_table.c") {
+ # Generate NT status -> DOS error mapping file
+
+ my $count = scalar @list;
+ my $full_names = "";
+
+ for (my $i = 0; $i < $count; $i++) {
+ my $e = $list[$i];
+ my $val = $e->{val};
+
+ $full_names .= $e->{name};
+
+ # Merge synonyms
+ if ($i < $count - 1 && $list[$i + 1]->{val} == $val) {
+ $full_names .= " or ";
+ next;
+ }
+
+ printf $out "\t{ %s, %s, 0x%08x, \"%s\" },\n", $e->{class}, $e->{code}, $val, $full_names;
+
+ $full_names = "";
+ }
+} elsif ($output_name eq "smb1_err_dos_map.c" || $output_name eq "smb1_err_srv_map.c") {
+ # Generate SMB1 error -> POSIX error mapping file
+
+ # Filtered by exact output filename
+ my $filter = ($output_name eq "smb1_err_dos_map.c") ? "ERRDOS" : "ERRSRV";
+ foreach my $e (@list) {
+ if (!$filter || $e->{class} eq $filter) {
+ printf $out "\t{%s, %s},\n", $e->{name}, $e->{error};
+ }
+ }
+} else {
+ die "Error: Unsupported output target: $output_name\n";
+}
+close($out);
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 3e844c55ab8a..1fe0ef0a95db 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -28,6 +28,23 @@
#include "cached_dir.h"
#include "reparse.h"
+static void cifs_invalidate_cached_dir(struct cifs_tcon *tcon,
+ struct dentry *parent)
+{
+ struct cached_fid *parent_cfid = NULL;
+
+ if (!tcon || !parent)
+ return;
+
+ if (!open_cached_dir_by_dentry(tcon, parent, &parent_cfid)) {
+ mutex_lock(&parent_cfid->dirents.de_mutex);
+ parent_cfid->dirents.is_valid = false;
+ parent_cfid->dirents.is_failed = true;
+ mutex_unlock(&parent_cfid->dirents.de_mutex);
+ close_cached_dir(parent_cfid);
+ }
+}
+
/*
* Set parameters for the netfs library
*/
@@ -119,7 +136,7 @@ cifs_revalidate_cache(struct inode *inode, struct cifs_fattr *fattr)
fattr->cf_mtime = timestamp_truncate(fattr->cf_mtime, inode);
mtime = inode_get_mtime(inode);
if (timespec64_equal(&mtime, &fattr->cf_mtime) &&
- cifs_i->netfs.remote_i_size == fattr->cf_eof) {
+ netfs_read_remote_i_size(inode) == fattr->cf_eof) {
cifs_dbg(FYI, "%s: inode %llu is unchanged\n",
__func__, cifs_i->uniqueid);
return;
@@ -173,12 +190,12 @@ cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr,
CIFS_I(inode)->time = 0; /* force reval */
return -ESTALE;
}
- if (inode_state_read_once(inode) & I_NEW)
- CIFS_I(inode)->netfs.zero_point = fattr->cf_eof;
-
cifs_revalidate_cache(inode, fattr);
spin_lock(&inode->i_lock);
+ if (inode_state_read_once(inode) & I_NEW)
+ netfs_write_zero_point(inode, fattr->cf_eof);
+
fattr->cf_mtime = timestamp_truncate(fattr->cf_mtime, inode);
fattr->cf_atime = timestamp_truncate(fattr->cf_atime, inode);
fattr->cf_ctime = timestamp_truncate(fattr->cf_ctime, inode);
@@ -212,20 +229,16 @@ cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr,
else
clear_bit(CIFS_INO_DELETE_PENDING, &cifs_i->flags);
- cifs_i->netfs.remote_i_size = fattr->cf_eof;
+ netfs_write_remote_i_size(inode, fattr->cf_eof);
/*
* Can't safely change the file size here if the client is writing to
* it due to potential races.
*/
if (is_size_safe_to_change(cifs_i, fattr->cf_eof, from_readdir)) {
i_size_write(inode, fattr->cf_eof);
-
- /*
- * i_blocks is not related to (i_size / i_blksize),
- * but instead 512 byte (2**9) size is required for
- * calculating num blocks.
- */
- inode->i_blocks = (512 - 1 + fattr->cf_bytes) >> 9;
+ inode->i_blocks = CIFS_INO_BLOCKS(fattr->cf_bytes);
+ } else if (from_readdir && i_size_read(inode) != fattr->cf_eof) {
+ cifs_i->time = 0;
}
if (S_ISLNK(fattr->cf_mode) && fattr->cf_symlink_target) {
@@ -838,6 +851,7 @@ static void smb311_posix_info_to_fattr(struct cifs_fattr *fattr,
struct smb311_posix_qinfo *info = &data->posix_fi;
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+ unsigned int sbflags = cifs_sb_flags(cifs_sb);
memset(fattr, 0, sizeof(*fattr));
@@ -882,8 +896,12 @@ out_reparse:
fattr->cf_symlink_target = data->symlink_target;
data->symlink_target = NULL;
}
- sid_to_id(cifs_sb, &data->posix_owner, fattr, SIDOWNER);
- sid_to_id(cifs_sb, &data->posix_group, fattr, SIDGROUP);
+ fattr->cf_uid = cifs_sb->ctx->linux_uid;
+ fattr->cf_gid = cifs_sb->ctx->linux_gid;
+ if (!(sbflags & CIFS_MOUNT_OVERR_UID))
+ sid_to_id(cifs_sb, &data->posix_owner, fattr, SIDOWNER);
+ if (!(sbflags & CIFS_MOUNT_OVERR_GID))
+ sid_to_id(cifs_sb, &data->posix_group, fattr, SIDGROUP);
cifs_dbg(FYI, "POSIX query info: mode 0x%x uniqueid 0x%llx nlink %d\n",
fattr->cf_mode, fattr->cf_uniqueid, fattr->cf_nlink);
@@ -898,6 +916,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;
@@ -1134,7 +1154,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;
}
@@ -1200,7 +1220,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
break;
case IO_REPARSE_TAG_INTERNAL:
rc = 0;
- if (le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY) {
+ if (cifs_open_data_attrs(data) & ATTR_DIRECTORY) {
cifs_create_junction_fattr(fattr, sb);
goto out;
}
@@ -1224,7 +1244,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
*/
if (rc == -EOPNOTSUPP &&
IS_REPARSE_TAG_NAME_SURROGATE(data->reparse.tag) &&
- (le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY)) {
+ (cifs_open_data_attrs(data) & ATTR_DIRECTORY)) {
rc = 0;
cifs_create_junction_fattr(fattr, sb);
goto out;
@@ -1242,13 +1262,14 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
}
if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc) {
- bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY;
+ bool directory = cifs_open_data_attrs(data) & ATTR_DIRECTORY;
+
rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb);
}
break;
}
- if (tcon->posix_extensions)
+ if (data->contains_posix_file_info)
smb311_posix_info_to_fattr(fattr, data, sb);
else
cifs_open_info_to_fattr(fattr, data, sb);
@@ -1601,7 +1622,7 @@ inode_has_hashed_dentries(struct inode *inode)
struct dentry *dentry;
spin_lock(&inode->i_lock);
- hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) {
+ for_each_alias(dentry, inode) {
if (!d_unhashed(dentry) || IS_ROOT(dentry)) {
spin_unlock(&inode->i_lock);
return true;
@@ -1631,7 +1652,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;
@@ -1697,8 +1718,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);
}
@@ -2073,6 +2095,9 @@ psx_del_no_retry:
cifs_set_file_info(inode, attrs, xid, full_path, origattr);
out_reval:
+ if (!rc && dentry->d_parent)
+ cifs_invalidate_cached_dir(tcon, dentry->d_parent);
+
if (inode) {
cifs_inode = CIFS_I(inode);
cifs_inode->time = 0; /* will force revalidate to get info
@@ -2268,6 +2293,13 @@ struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode,
const char *full_path;
void *page;
+ /*
+ * vfs_mkdir() now passes S_IFDIR in @mode, but @mode is forwarded
+ * verbatim to the server and in the past only contained permission
+ * bits. Strip the type bit until SMB is verified to deal with it.
+ */
+ mode &= ~S_IFDIR;
+
cifs_dbg(FYI, "In cifs_mkdir, mode = %04ho inode = 0x%p\n",
mode, inode);
@@ -2384,7 +2416,6 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry)
}
rc = server->ops->rmdir(xid, tcon, full_path, cifs_sb);
- cifs_put_tlink(tlink);
cifsInode = CIFS_I(d_inode(direntry));
@@ -2394,6 +2425,8 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry)
i_size_write(d_inode(direntry), 0);
clear_nlink(d_inode(direntry));
spin_unlock(&d_inode(direntry)->i_lock);
+ if (direntry->d_parent)
+ cifs_invalidate_cached_dir(tcon, direntry->d_parent);
}
/* force revalidate to go get info when needed */
@@ -2408,6 +2441,7 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry)
inode_set_ctime_current(d_inode(direntry));
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ cifs_put_tlink(tlink);
rmdir_exit:
free_dentry_path(page);
@@ -2627,11 +2661,8 @@ unlink_target:
if (d_really_is_positive(target_dentry)) {
if (!rc) {
struct inode *inode = d_inode(target_dentry);
- /*
- * Samba and ksmbd servers allow renaming a target
- * directory that is open, so make sure to update
- * ->i_nlink and then mark it as delete pending.
- */
+
+ /* Update the target link count after rename. */
if (S_ISDIR(inode->i_mode)) {
drop_cached_dir_by_name(xid, tcon, to_name, cifs_sb);
spin_lock(&inode->i_lock);
@@ -2642,6 +2673,10 @@ unlink_target:
CIFS_I(inode)->time = 0; /* force reval */
inode_set_ctime_current(inode);
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ } else {
+ cifs_mark_open_handles_for_deleted_file(inode, to_name);
+ cifs_drop_nlink(inode);
+ inode_set_ctime_current(inode);
}
} else if (rc == -EACCES || rc == -EEXIST) {
/*
@@ -2674,6 +2709,12 @@ unlink_target:
}
/* force revalidate to go get info when needed */
+ if (!rc) {
+ cifs_invalidate_cached_dir(tcon, source_dentry->d_parent);
+ if (target_dentry->d_parent != source_dentry->d_parent)
+ cifs_invalidate_cached_dir(tcon, target_dentry->d_parent);
+ }
+
CIFS_I(source_dir)->time = CIFS_I(target_dir)->time = 0;
cifs_rename_exit:
@@ -2696,7 +2737,8 @@ cifs_dentry_needs_reval(struct dentry *dentry)
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
struct cached_fid *cfid = NULL;
- if (test_bit(CIFS_INO_DELETE_PENDING, &cifs_i->flags))
+ if (test_bit(CIFS_INO_DELETE_PENDING, &cifs_i->flags) ||
+ test_bit(CIFS_INO_TMPFILE, &cifs_i->flags))
return false;
if (cifs_i->time == 0)
return true;
@@ -2777,7 +2819,9 @@ cifs_revalidate_mapping(struct inode *inode)
if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_RW_CACHE)
goto skip_invalidate;
- cifs_inode->netfs.zero_point = cifs_inode->netfs.remote_i_size;
+ spin_lock(&inode->i_lock);
+ netfs_write_zero_point(inode, netfs_inode(inode)->_remote_i_size);
+ spin_unlock(&inode->i_lock);
rc = filemap_invalidate_inode(inode, true, 0, LLONG_MAX);
if (rc) {
cifs_dbg(VFS, "%s: invalidate inode %p failed with rc %d\n",
@@ -2787,9 +2831,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;
}
@@ -2848,7 +2890,7 @@ int cifs_revalidate_dentry_attr(struct dentry *dentry)
}
cifs_dbg(FYI, "Update attributes: %s inode 0x%p count %d dentry: 0x%p d_time %ld jiffies %ld\n",
- full_path, inode, icount_read(inode),
+ full_path, inode, icount_read_once(inode),
dentry, cifs_get_time(dentry), jiffies);
again:
@@ -2955,14 +2997,14 @@ int cifs_getattr(struct mnt_idmap *idmap, const struct path *path,
stat->attributes |= STATX_ATTR_ENCRYPTED;
/*
- * If on a multiuser mount without unix extensions or cifsacl being
- * enabled, and the admin hasn't overridden them, set the ownership
- * to the fsuid/fsgid of the current process.
+ * If on a multiuser mount without unix extensions, posix extensions
+ * or cifsacl being enabled, and the admin hasn't overridden them,
+ * set the ownership to the fsuid/fsgid of the current process.
*/
sbflags = cifs_sb_flags(cifs_sb);
if ((sbflags & CIFS_MOUNT_MULTIUSER) &&
!(sbflags & CIFS_MOUNT_CIFS_ACL) &&
- !tcon->unix_ext) {
+ !tcon->unix_ext && !tcon->posix_extensions) {
if (!(sbflags & CIFS_MOUNT_OVERR_UID))
stat->uid = current_fsuid();
if (!(sbflags & CIFS_MOUNT_OVERR_GID))
@@ -2997,7 +3039,7 @@ int cifs_fiemap(struct inode *inode, struct fiemap_extent_info *fei, u64 start,
}
}
- cfile = find_readable_file(cifs_i, false);
+ cfile = find_readable_file(cifs_i, FIND_ANY);
if (cfile == NULL)
return -EINVAL;
@@ -3013,14 +3055,45 @@ int cifs_fiemap(struct inode *inode, struct fiemap_extent_info *fei, u64 start,
void cifs_setsize(struct inode *inode, loff_t offset)
{
+ loff_t old_size;
+ u64 blocks = CIFS_INO_BLOCKS(offset);
+
spin_lock(&inode->i_lock);
+ old_size = i_size_read(inode);
i_size_write(inode, offset);
+
+ /*
+ * Extending EOF does not allocate the intervening range. Only clamp
+ * i_blocks on shrink; allocation growth comes from writes or from the
+ * server-reported AllocationSize.
+ */
+ if (offset < old_size && (u64)inode->i_blocks > blocks)
+ inode->i_blocks = blocks;
spin_unlock(&inode->i_lock);
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ if (offset > old_size)
+ pagecache_isize_extended(inode, old_size, offset);
truncate_pagecache(inode, offset);
netfs_wait_for_outstanding_io(inode);
}
+void cifs_resize_file_locked(struct inode *inode, loff_t offset)
+{
+ struct fscache_cookie *cookie = cifs_inode_cookie(inode);
+
+ lockdep_assert_held_write(&inode->i_rwsem);
+
+ netfs_resize_file(netfs_inode(inode), offset, true);
+ cifs_setsize(inode, offset);
+
+ if (!cookie)
+ return;
+
+ fscache_use_cookie(cookie, true);
+ fscache_resize_cookie(cookie, offset);
+ cifs_fscache_unuse_inode_cookie(inode, true);
+}
+
int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
const char *full_path, struct cifsFileInfo *open_file,
loff_t size)
@@ -3050,7 +3123,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
size, false);
cifs_dbg(FYI, "%s: set_file_size: rc = %d\n", __func__, rc);
} else {
- open_file = find_writable_file(cifsInode, FIND_WR_FSUID_ONLY);
+ open_file = find_writable_file(cifsInode, FIND_FSUID_ONLY);
if (open_file) {
tcon = tlink_tcon(open_file->tlink);
server = tcon->ses->server;
@@ -3059,6 +3132,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
size, false);
cifs_dbg(FYI, "%s: set_file_size: rc = %d\n", __func__, rc);
cifsFileInfo_put(open_file);
+ tcon = NULL;
}
}
@@ -3084,18 +3158,8 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
cifs_put_tlink(tlink);
set_size_out:
- if (rc == 0) {
- netfs_resize_file(&cifsInode->netfs, size, true);
- cifs_setsize(inode, size);
- /*
- * i_blocks is not related to (i_size / i_blksize), but instead
- * 512 byte (2**9) size is required for calculating num blocks.
- * Until we can query the server for actual allocation size,
- * this is best estimate we have for blocks allocated for a file
- * Number of blocks must be rounded up so size 1 is not 0 blocks
- */
- inode->i_blocks = (512 - 1 + size) >> 9;
- }
+ if (rc == 0)
+ cifs_resize_file_locked(inode, size);
return rc;
}
@@ -3158,6 +3222,17 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
rc = 0;
if (attrs->ia_valid & ATTR_SIZE) {
+ if (attrs->ia_size != i_size_read(inode)) {
+ /* Stamp before RPC. On failure the stamp remains: restoring a
+ * stale snapshot could silently erase a concurrent
+ * _cifsFileInfo_put() close stamp. readdir is suppressed
+ * until the stamp expires; stat() bypasses this via the
+ * from_readdir=false path in is_size_safe_to_change() and
+ * always returns an authoritative QUERY_INFO result.
+ * Pairs with smp_load_acquire() in is_size_safe_to_change().
+ */
+ smp_store_release(&cifsInode->time_last_write, jiffies);
+ }
rc = cifs_file_set_size(xid, direntry, full_path,
open_file, attrs->ia_size);
if (rc != 0)
@@ -3170,9 +3245,13 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
attrs->ia_valid &= ~(ATTR_CTIME | ATTR_MTIME);
}
- /* skip mode change if it's just for clearing setuid/setgid */
- if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID))
- attrs->ia_valid &= ~ATTR_MODE;
+ /*
+ * This function is only called when Unix extensions are in effect,
+ * so the mode is always sent to and stored on the server. Do not
+ * skip the mode change when clearing setuid/setgid bits: dropping
+ * ATTR_MODE here would leave those bits set on the server after a
+ * write, which is a security issue.
+ */
args = kmalloc_obj(*args);
if (args == NULL) {
@@ -3219,7 +3298,7 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
open_file->fid.netfid,
open_file->pid);
} else {
- open_file = find_writable_file(cifsInode, FIND_WR_FSUID_ONLY);
+ open_file = find_writable_file(cifsInode, FIND_FSUID_ONLY);
if (open_file) {
pTcon = tlink_tcon(open_file->tlink);
rc = CIFSSMBUnixSetFileInfo(xid, pTcon, args,
@@ -3245,13 +3324,6 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
if (rc)
goto out;
- if ((attrs->ia_valid & ATTR_SIZE) &&
- attrs->ia_size != i_size_read(inode)) {
- truncate_setsize(inode, attrs->ia_size);
- netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true);
- fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size);
- }
-
setattr_copy(&nop_mnt_idmap, inode, attrs);
mark_inode_dirty(inode);
@@ -3336,6 +3408,17 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
}
if (attrs->ia_valid & ATTR_SIZE) {
+ if (attrs->ia_size != i_size_read(inode)) {
+ /* Stamp before RPC. On failure the stamp remains: restoring a
+ * stale snapshot could silently erase a concurrent
+ * _cifsFileInfo_put() close stamp. readdir is suppressed
+ * until the stamp expires; stat() bypasses this via the
+ * from_readdir=false path in is_size_safe_to_change() and
+ * always returns an authoritative QUERY_INFO result.
+ * Pairs with smp_load_acquire() in is_size_safe_to_change().
+ */
+ smp_store_release(&cifsInode->time_last_write, jiffies);
+ }
rc = cifs_file_set_size(xid, direntry, full_path,
cfile, attrs->ia_size);
if (rc != 0)
@@ -3354,7 +3437,8 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
if (attrs->ia_valid & ATTR_GID)
gid = attrs->ia_gid;
- if (sbflags & (CIFS_MOUNT_CIFS_ACL | CIFS_MOUNT_MODE_FROM_SID)) {
+ if ((sbflags & (CIFS_MOUNT_CIFS_ACL | CIFS_MOUNT_MODE_FROM_SID)) ||
+ cifs_sb_master_tcon(cifs_sb)->posix_extensions) {
if (uid_valid(uid) || gid_valid(gid)) {
mode = NO_CHANGE_64;
rc = id_mode_to_cifs_acl(inode, full_path, &mode,
@@ -3369,8 +3453,23 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
attrs->ia_valid &= ~(ATTR_UID | ATTR_GID);
}
- /* skip mode change if it's just for clearing setuid/setgid */
- if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID))
+ /*
+ * Skip the mode change if it is only being done to clear the
+ * setuid/setgid bits *and* the mode is emulated via the DOS
+ * read-only attribute (the default, non-ACL case), which cannot
+ * represent the setuid/setgid bits anyway.
+ *
+ * When the mode is instead stored on the server - i.e. with the
+ * cifsacl or modefromsid mount options (via an ACL) or with the
+ * SMB3.1.1 POSIX extensions - the cleared mode must be pushed to
+ * the server. Dropping ATTR_MODE here would leave the setuid/
+ * setgid bit set on the server after a write, which is a security
+ * issue (the bits are not stripped as they are on local
+ * filesystems).
+ */
+ if ((attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) &&
+ !((sbflags & (CIFS_MOUNT_CIFS_ACL | CIFS_MOUNT_MODE_FROM_SID)) ||
+ cifs_sb_master_tcon(cifs_sb)->posix_extensions))
attrs->ia_valid &= ~ATTR_MODE;
if (attrs->ia_valid & ATTR_MODE) {
@@ -3445,13 +3544,6 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
if (rc)
goto cifs_setattr_exit;
- if ((attrs->ia_valid & ATTR_SIZE) &&
- attrs->ia_size != i_size_read(inode)) {
- truncate_setsize(inode, attrs->ia_size);
- netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true);
- fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size);
- }
-
setattr_copy(&nop_mnt_idmap, inode, attrs);
mark_inode_dirty(inode);
diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c
index 9afab3237e54..2f152d76be7d 100644
--- a/fs/smb/client/ioctl.c
+++ b/fs/smb/client/ioctl.c
@@ -67,6 +67,115 @@ static long cifs_ioctl_query_info(unsigned int xid, struct file *filep,
return rc;
}
+static int cifs_set_compression_by_path(unsigned int xid, struct file *filep,
+ struct cifs_tcon *tcon,
+ __u16 compression_state)
+{
+ struct inode *inode = file_inode(filep);
+ struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+ struct TCP_Server_Info *server = tcon->ses->server;
+ struct cifs_open_parms oparms;
+ struct cifs_open_info_data data = {};
+ struct cifsFileInfo *tmp_cfile = NULL;
+ struct cifs_fid fid = {};
+ const char *full_path;
+ __u32 oplock = 0;
+ u64 uniqueid;
+ void *page;
+ int rc;
+
+ if (!server->ops->open || !server->ops->close ||
+ !server->ops->query_file_info)
+ return -EOPNOTSUPP;
+
+ if (!(cifs_sb_flags(cifs_sb) & CIFS_MOUNT_SERVER_INUM) ||
+ cifs_sb->mnt_cifs_serverino_autodisabled)
+ return -EOPNOTSUPP;
+
+ if (d_unhashed(filep->f_path.dentry))
+ return -ESTALE;
+
+ page = alloc_dentry_path();
+ full_path = build_path_from_dentry(filep->f_path.dentry, page);
+ if (IS_ERR(full_path)) {
+ free_dentry_path(page);
+ return PTR_ERR(full_path);
+ }
+
+ oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_WRITE_DATA |
+ FILE_READ_ATTRIBUTES,
+ FILE_OPEN, 0, ACL_NO_MODE);
+ oparms.fid = &fid;
+
+ rc = server->ops->open(xid, &oparms, &oplock, NULL);
+ if (rc)
+ goto out;
+
+ tmp_cfile = kzalloc_obj(*tmp_cfile);
+ if (!tmp_cfile) {
+ rc = -ENOMEM;
+ goto close;
+ }
+
+ tmp_cfile->fid = fid;
+ rc = server->ops->query_file_info(xid, tcon, tmp_cfile, &data);
+ if (rc)
+ goto close;
+
+ uniqueid = le64_to_cpu(data.fi.IndexNumber);
+ if (uniqueid != CIFS_I(inode)->uniqueid) {
+ rc = -ESTALE;
+ goto close;
+ }
+
+ rc = server->ops->set_compression(xid, tcon, tmp_cfile,
+ compression_state);
+
+close:
+ server->ops->close(xid, tcon, &fid);
+ kfree(tmp_cfile);
+ cifs_free_open_info(&data);
+out:
+ free_dentry_path(page);
+ return rc;
+}
+
+static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep,
+ struct cifs_tcon *tcon,
+ struct cifsFileInfo *cfile,
+ __u16 compression_state)
+{
+ struct cifsFileInfo *wfile;
+ struct cifs_tcon *wtcon;
+ struct inode *inode = file_inode(filep);
+ int rc;
+
+ if (!tcon->ses->server->ops->set_compression)
+ return -EOPNOTSUPP;
+
+ if (cfile && (cfile->fid.access & FILE_WRITE_DATA)) {
+ rc = tcon->ses->server->ops->set_compression(xid, tcon, cfile,
+ compression_state);
+ if (rc != -EACCES)
+ return rc;
+ }
+
+ rc = cifs_get_writable_file(CIFS_I(inode), FIND_FSUID_ONLY, &wfile);
+ if (!rc) {
+ wtcon = tlink_tcon(wfile->tlink);
+ rc = wtcon->ses->server->ops->set_compression(xid, wtcon, wfile,
+ compression_state);
+ cifsFileInfo_put(wfile);
+ if (rc != -EACCES)
+ return rc;
+ } else if (rc != -EBADF) {
+ return rc;
+ }
+
+ return cifs_set_compression_by_path(xid, filep, tcon,
+ compression_state);
+}
+
static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file,
unsigned long srcfd)
{
@@ -296,7 +405,7 @@ search_end:
break;
case SMB2_ENCRYPTION_AES256_CCM:
case SMB2_ENCRYPTION_AES256_GCM:
- out.session_key_length = CIFS_SESS_KEY_SIZE;
+ out.session_key_length = ses->auth_key.len;
out.server_in_key_length = out.server_out_key_length = SMB3_GCM256_CRYPTKEY_SIZE;
break;
default:
@@ -356,6 +465,8 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
struct tcon_link *tlink;
struct cifs_sb_info *cifs_sb;
__u64 ExtAttrBits = 0;
+ bool enable_compression;
+ __u16 compression_state;
#ifdef CONFIG_CIFS_POSIX
#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
__u64 caps;
@@ -392,13 +503,11 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
}
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
#endif /* CONFIG_CIFS_POSIX */
- rc = 0;
- if (CIFS_I(inode)->cifsAttrs & ATTR_COMPRESSED) {
- /* add in the compressed bit */
- ExtAttrBits = FS_COMPR_FL;
- rc = put_user(ExtAttrBits & FS_FL_USER_VISIBLE,
- (int __user *)arg);
- }
+ if (CIFS_I(inode)->cifsAttrs & FILE_ATTRIBUTE_COMPRESSED)
+ ExtAttrBits |= FS_COMPR_FL;
+
+ rc = put_user(ExtAttrBits & FS_FL_USER_VISIBLE,
+ (int __user *)arg);
break;
case FS_IOC_SETFLAGS:
if (pSMBFile == NULL)
@@ -421,16 +530,31 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
* break;
*/
- /* Currently only flag we can set is compressed flag */
- if ((ExtAttrBits & FS_COMPR_FL) == 0)
+ /* Currently only flag we can set or clear is compressed. */
+ if (ExtAttrBits & ~FS_COMPR_FL) {
+ rc = -EOPNOTSUPP;
break;
+ }
- /* Try to set compress flag */
- if (tcon->ses->server->ops->set_compression) {
- rc = tcon->ses->server->ops->set_compression(
- xid, tcon, pSMBFile);
- cifs_dbg(FYI, "set compress flag rc %d\n", rc);
+ enable_compression = ExtAttrBits & FS_COMPR_FL;
+ compression_state = enable_compression ?
+ COMPRESSION_FORMAT_DEFAULT :
+ COMPRESSION_FORMAT_NONE;
+
+ rc = cifs_ioctl_set_compression(xid, filep, tcon,
+ pSMBFile,
+ compression_state);
+ if (rc == 0) {
+ spin_lock(&inode->i_lock);
+ if (enable_compression)
+ CIFS_I(inode)->cifsAttrs |=
+ FILE_ATTRIBUTE_COMPRESSED;
+ else
+ CIFS_I(inode)->cifsAttrs &=
+ ~FILE_ATTRIBUTE_COMPRESSED;
+ spin_unlock(&inode->i_lock);
}
+ cifs_dbg(FYI, "set compress flag rc %d\n", rc);
break;
case CIFS_IOC_COPYCHUNK_FILE:
rc = cifs_ioctl_copychunk(xid, filep, arg);
diff --git a/fs/smb/client/link.c b/fs/smb/client/link.c
index 434e8fe74080..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;
}
@@ -503,6 +494,7 @@ cifs_hardlink(struct dentry *old_file, struct inode *inode,
if (d_really_is_positive(old_file)) {
cifsInode = CIFS_I(d_inode(old_file));
if (rc == 0) {
+ clear_bit(CIFS_INO_TMPFILE, &cifsInode->flags);
spin_lock(&d_inode(old_file)->i_lock);
inc_nlink(d_inode(old_file));
spin_unlock(&d_inode(old_file)->i_lock);
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index bc24c92b8b95..05168284f205 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -28,6 +28,11 @@
#include "fs_context.h"
#include "cached_dir.h"
+struct tcon_list {
+ struct list_head entry;
+ struct cifs_tcon *tcon;
+};
+
/* The xid serves as a useful identifier for each incoming vfs request,
in a similar way to the mid which is useful to track each sent smb,
and CurrentXid can also provide a running counter (although it
@@ -273,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);
@@ -285,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");
@@ -369,10 +378,11 @@ void cifs_queue_oplock_break(struct cifsFileInfo *cfile)
* open_file_lock to enforce the validity of it for the oplock
* break handler. The matching put is done at the end of the
* handler.
+ *
+ * Only take a reference if the work is actually queued.
*/
- cifsFileInfo_get(cfile);
-
- queue_work(cifsoplockd_wq, &cfile->oplock_break);
+ if (queue_work(cifsoplockd_wq, &cfile->oplock_break))
+ cifsFileInfo_get(cfile);
}
void cifs_done_oplock_break(struct cifsInodeInfo *cinode)
@@ -488,7 +498,7 @@ cifs_del_deferred_close(struct cifsFileInfo *cfile)
void
cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
{
- struct cifsFileInfo *cfile = NULL;
+ struct cifsFileInfo *cfile = NULL, *failed_cfile = NULL;
struct file_list *tmp_list, *tmp_next_list;
LIST_HEAD(file_head);
@@ -505,8 +515,10 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
tmp_list = kmalloc_obj(struct file_list,
GFP_ATOMIC);
- if (tmp_list == NULL)
+ if (tmp_list == NULL) {
+ failed_cfile = cfile;
break;
+ }
tmp_list->cfile = cfile;
list_add_tail(&tmp_list->list, &file_head);
}
@@ -514,6 +526,9 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
}
spin_unlock(&cifs_inode->open_file_lock);
+ if (failed_cfile)
+ _cifsFileInfo_put(failed_cfile, false, false);
+
list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
_cifsFileInfo_put(tmp_list->cfile, false, false);
list_del(&tmp_list->list);
@@ -524,7 +539,7 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
void
cifs_close_all_deferred_files(struct cifs_tcon *tcon)
{
- struct cifsFileInfo *cfile;
+ struct cifsFileInfo *cfile, *failed_cfile = NULL;
struct file_list *tmp_list, *tmp_next_list;
LIST_HEAD(file_head);
@@ -538,8 +553,10 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon)
tmp_list = kmalloc_obj(struct file_list,
GFP_ATOMIC);
- if (tmp_list == NULL)
+ if (tmp_list == NULL) {
+ failed_cfile = cfile;
break;
+ }
tmp_list->cfile = cfile;
list_add_tail(&tmp_list->list, &file_head);
}
@@ -547,6 +564,9 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon)
}
spin_unlock(&tcon->open_file_lock);
+ if (failed_cfile)
+ _cifsFileInfo_put(failed_cfile, true, false);
+
list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
_cifsFileInfo_put(tmp_list->cfile, true, false);
list_del(&tmp_list->list);
@@ -554,11 +574,48 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon)
}
}
+void cifs_close_all_deferred_files_sb(struct cifs_sb_info *cifs_sb)
+{
+ struct rb_root *root = &cifs_sb->tlink_tree;
+ struct rb_node *node;
+ struct cifs_tcon *tcon;
+ struct tcon_link *tlink;
+ struct tcon_list *tmp_list, *q;
+ LIST_HEAD(tcon_head);
+
+ spin_lock(&cifs_sb->tlink_tree_lock);
+ for (node = rb_first(root); node; node = rb_next(node)) {
+ tlink = rb_entry(node, struct tcon_link, tl_rbnode);
+ tcon = tlink_tcon(tlink);
+ if (IS_ERR(tcon))
+ continue;
+ tmp_list = kmalloc_obj(struct tcon_list, GFP_ATOMIC);
+ if (tmp_list == NULL)
+ break;
+ tmp_list->tcon = tcon;
+ /* Take a reference on tcon to prevent it from being freed */
+ spin_lock(&tcon->tc_lock);
+ ++tcon->tc_count;
+ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
+ netfs_trace_tcon_ref_get_close_defer_files);
+ spin_unlock(&tcon->tc_lock);
+ list_add_tail(&tmp_list->entry, &tcon_head);
+ }
+ spin_unlock(&cifs_sb->tlink_tree_lock);
+
+ list_for_each_entry_safe(tmp_list, q, &tcon_head, entry) {
+ cifs_close_all_deferred_files(tmp_list->tcon);
+ list_del(&tmp_list->entry);
+ cifs_put_tcon(tmp_list->tcon, netfs_trace_tcon_ref_put_close_defer_files);
+ kfree(tmp_list);
+ }
+}
+
void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
struct dentry *dentry)
{
struct file_list *tmp_list, *tmp_next_list;
- struct cifsFileInfo *cfile;
+ struct cifsFileInfo *cfile, *failed_cfile = NULL;
LIST_HEAD(file_head);
spin_lock(&tcon->open_file_lock);
@@ -571,14 +628,19 @@ void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
spin_unlock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
tmp_list = kmalloc_obj(struct file_list, GFP_ATOMIC);
- if (tmp_list == NULL)
+ if (tmp_list == NULL) {
+ failed_cfile = cfile;
break;
+ }
tmp_list->cfile = cfile;
list_add_tail(&tmp_list->list, &file_head);
}
}
spin_unlock(&tcon->open_file_lock);
+ if (failed_cfile)
+ _cifsFileInfo_put(failed_cfile, true, false);
+
list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
_cifsFileInfo_put(tmp_list->cfile, true, false);
list_del(&tmp_list->list);
@@ -636,6 +698,8 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
int i, rc = 0;
char *data_end;
struct dfs_referral_level_3 *ref;
+ unsigned int path_consumed;
+ size_t search_name_len;
if (rsp_size < sizeof(*rsp)) {
cifs_dbg(VFS | ONCE,
@@ -682,6 +746,7 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
rc = -ENOMEM;
goto parse_DFS_referrals_exit;
}
+ search_name_len = strlen(searchName);
/* collect necessary data from referrals */
for (i = 0; i < *num_of_nodes; i++) {
@@ -690,26 +755,47 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
struct dfs_info3_param *node = (*target_nodes)+i;
node->flags = le32_to_cpu(rsp->DFSFlags);
+ path_consumed = le16_to_cpu(rsp->PathConsumed);
if (is_unicode) {
- __le16 *tmp = kmalloc(strlen(searchName)*2 + 2,
- GFP_KERNEL);
- if (tmp == NULL) {
+ size_t search_name_utf16_len = search_name_len * 2 + 2;
+ __le16 *tmp;
+
+ if (path_consumed > search_name_utf16_len) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+
+ tmp = kmalloc(search_name_utf16_len, GFP_KERNEL);
+ if (!tmp) {
rc = -ENOMEM;
goto parse_DFS_referrals_exit;
}
- cifsConvertToUTF16((__le16 *) tmp, searchName,
+ cifsConvertToUTF16((__le16 *)tmp, searchName,
PATH_MAX, nls_codepage, remap);
- node->path_consumed = cifs_utf16_bytes(tmp,
- le16_to_cpu(rsp->PathConsumed),
- nls_codepage);
+ node->path_consumed = cifs_utf16_bytes(tmp, path_consumed,
+ nls_codepage);
kfree(tmp);
- } else
- node->path_consumed = le16_to_cpu(rsp->PathConsumed);
+ } else {
+ if (path_consumed > search_name_len) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+
+ node->path_consumed = path_consumed;
+ }
node->server_type = le16_to_cpu(ref->ServerType);
node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags);
/* copy DfsPath */
+ if (le16_to_cpu(ref->DfsPathOffset) < sizeof(*ref) ||
+ le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) {
+ cifs_dbg(VFS, "%s: DfsPathOffset %u out of range [%zu, %td]\n",
+ __func__, le16_to_cpu(ref->DfsPathOffset),
+ sizeof(*ref), 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,
@@ -720,6 +806,14 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
}
/* copy link target UNC */
+ if (le16_to_cpu(ref->NetworkAddressOffset) < sizeof(*ref) ||
+ le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) {
+ cifs_dbg(VFS, "%s: NetworkAddressOffset %u out of range [%zu, %td]\n",
+ __func__, le16_to_cpu(ref->NetworkAddressOffset),
+ sizeof(*ref), 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,
@@ -743,63 +837,6 @@ parse_DFS_referrals_exit:
return rc;
}
-/**
- * cifs_alloc_hash - allocate hash and hash context together
- * @name: The name of the crypto hash algo
- * @sdesc: SHASH descriptor where to put the pointer to the hash TFM
- *
- * The caller has to make sure @sdesc is initialized to either NULL or
- * a valid context. It can be freed via cifs_free_hash().
- */
-int
-cifs_alloc_hash(const char *name, struct shash_desc **sdesc)
-{
- int rc = 0;
- struct crypto_shash *alg = NULL;
-
- if (*sdesc)
- return 0;
-
- alg = crypto_alloc_shash(name, 0, 0);
- if (IS_ERR(alg)) {
- cifs_dbg(VFS, "Could not allocate shash TFM '%s'\n", name);
- rc = PTR_ERR(alg);
- *sdesc = NULL;
- return rc;
- }
-
- *sdesc = kmalloc(sizeof(struct shash_desc) + crypto_shash_descsize(alg), GFP_KERNEL);
- if (*sdesc == NULL) {
- cifs_dbg(VFS, "no memory left to allocate shash TFM '%s'\n", name);
- crypto_free_shash(alg);
- return -ENOMEM;
- }
-
- (*sdesc)->tfm = alg;
- return 0;
-}
-
-/**
- * cifs_free_hash - free hash and hash context together
- * @sdesc: Where to find the pointer to the hash TFM
- *
- * Freeing a NULL descriptor is safe.
- */
-void
-cifs_free_hash(struct shash_desc **sdesc)
-{
- if (unlikely(!sdesc) || !*sdesc)
- return;
-
- if ((*sdesc)->tfm) {
- crypto_free_shash((*sdesc)->tfm);
- (*sdesc)->tfm = NULL;
- }
-
- kfree_sensitive(*sdesc);
- *sdesc = NULL;
-}
-
void extract_unc_hostname(const char *unc, const char **h, size_t *len)
{
const char *end;
@@ -863,8 +900,14 @@ static void tcon_super_cb(struct super_block *sb, void *arg)
t1->ses->dfs_root_ses == t2->ses->dfs_root_ses) &&
t1->ses->server == t2->ses->server &&
t2->origin_fullpath &&
- dfs_src_pathname_equal(t2->origin_fullpath, t1->origin_fullpath))
+ dfs_src_pathname_equal(t2->origin_fullpath, t1->origin_fullpath)) {
+ /*
+ * Take the active reference while iterate_supers_type() still
+ * holds s_umount shared.
+ */
+ cifs_sb_active(sb);
sd->sb = sb;
+ }
spin_unlock(&t2->tc_lock);
}
@@ -881,15 +924,8 @@ static struct super_block *__cifs_get_super(void (*f)(struct super_block *, void
for (; *fs_type; fs_type++) {
iterate_supers_type(*fs_type, f, &sd);
- if (sd.sb) {
- /*
- * Grab an active reference in order to prevent automounts (DFS links)
- * of expiring and then freeing up our cifs superblock pointer while
- * we're doing failover.
- */
- cifs_sb_active(sd.sb);
+ if (sd.sb)
return sd.sb;
- }
}
pr_warn_once("%s: could not find dfs superblock\n", __func__);
return ERR_PTR(-EINVAL);
diff --git a/fs/smb/client/namespace.c b/fs/smb/client/namespace.c
index 52a520349cb7..52a51b032fae 100644
--- a/fs/smb/client/namespace.c
+++ b/fs/smb/client/namespace.c
@@ -294,4 +294,5 @@ struct vfsmount *cifs_d_automount(struct path *path)
}
const struct inode_operations cifs_namespace_inode_operations = {
+ .fileattr_get = cifs_fileattr_get,
};
diff --git a/fs/smb/client/netlink.c b/fs/smb/client/netlink.c
index 147d9409252c..0dd10913c37a 100644
--- a/fs/smb/client/netlink.c
+++ b/fs/smb/client/netlink.c
@@ -33,13 +33,17 @@ static const struct nla_policy cifs_genl_policy[CIFS_GENL_ATTR_MAX + 1] = {
static const struct genl_ops cifs_genl_ops[] = {
{
.cmd = CIFS_GENL_CMD_SWN_NOTIFY,
+ .flags = GENL_ADMIN_PERM,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = cifs_swn_notify,
},
};
static const struct genl_multicast_group cifs_genl_mcgrps[] = {
- [CIFS_GENL_MCGRP_SWN] = { .name = CIFS_GENL_MCGRP_SWN_NAME },
+ [CIFS_GENL_MCGRP_SWN] = {
+ .name = CIFS_GENL_MCGRP_SWN_NAME,
+ .flags = GENL_MCAST_CAP_NET_ADMIN,
+ },
};
struct genl_family cifs_genl_family = {
diff --git a/fs/smb/client/nterr.c b/fs/smb/client/nterr.c
deleted file mode 100644
index 279566cac435..000000000000
--- a/fs/smb/client/nterr.c
+++ /dev/null
@@ -1,704 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Unix SMB/Netbios implementation.
- * Version 1.9.
- * RPC Pipe client / server routines
- * Copyright (C) Luke Kenneth Casson Leighton 1997-2001.
- */
-
-/* NT error codes - see nterr.h */
-#include <linux/types.h>
-#include <linux/fs.h>
-#include "nterr.h"
-
-const struct nt_err_code_struct nt_errs[] = {
- {"NT_STATUS_OK", NT_STATUS_OK},
- {"NT_STATUS_PENDING", NT_STATUS_PENDING},
- {"NT_STATUS_NOTIFY_ENUM_DIR", NT_STATUS_NOTIFY_ENUM_DIR},
- {"NT_STATUS_MEDIA_CHANGED", NT_STATUS_MEDIA_CHANGED},
- {"NT_STATUS_END_OF_MEDIA", NT_STATUS_END_OF_MEDIA},
- {"NT_STATUS_MEDIA_CHECK", NT_STATUS_MEDIA_CHECK},
- {"NT_STATUS_NO_DATA_DETECTED", NT_STATUS_NO_DATA_DETECTED},
- {"NT_STATUS_STOPPED_ON_SYMLINK", NT_STATUS_STOPPED_ON_SYMLINK},
- {"NT_STATUS_DEVICE_REQUIRES_CLEANING", NT_STATUS_DEVICE_REQUIRES_CLEANING},
- {"NT_STATUS_DEVICE_DOOR_OPEN", NT_STATUS_DEVICE_DOOR_OPEN},
- {"NT_STATUS_UNSUCCESSFUL", NT_STATUS_UNSUCCESSFUL},
- {"NT_STATUS_NOT_IMPLEMENTED", NT_STATUS_NOT_IMPLEMENTED},
- {"NT_STATUS_INVALID_INFO_CLASS", NT_STATUS_INVALID_INFO_CLASS},
- {"NT_STATUS_INFO_LENGTH_MISMATCH", NT_STATUS_INFO_LENGTH_MISMATCH},
- {"NT_STATUS_ACCESS_VIOLATION", NT_STATUS_ACCESS_VIOLATION},
- {"NT_STATUS_BUFFER_OVERFLOW", NT_STATUS_BUFFER_OVERFLOW},
- {"NT_STATUS_IN_PAGE_ERROR", NT_STATUS_IN_PAGE_ERROR},
- {"NT_STATUS_PAGEFILE_QUOTA", NT_STATUS_PAGEFILE_QUOTA},
- {"NT_STATUS_INVALID_HANDLE", NT_STATUS_INVALID_HANDLE},
- {"NT_STATUS_BAD_INITIAL_STACK", NT_STATUS_BAD_INITIAL_STACK},
- {"NT_STATUS_BAD_INITIAL_PC", NT_STATUS_BAD_INITIAL_PC},
- {"NT_STATUS_INVALID_CID", NT_STATUS_INVALID_CID},
- {"NT_STATUS_TIMER_NOT_CANCELED", NT_STATUS_TIMER_NOT_CANCELED},
- {"NT_STATUS_INVALID_PARAMETER", NT_STATUS_INVALID_PARAMETER},
- {"NT_STATUS_NO_SUCH_DEVICE", NT_STATUS_NO_SUCH_DEVICE},
- {"NT_STATUS_NO_SUCH_FILE", NT_STATUS_NO_SUCH_FILE},
- {"NT_STATUS_INVALID_DEVICE_REQUEST",
- NT_STATUS_INVALID_DEVICE_REQUEST},
- {"NT_STATUS_END_OF_FILE", NT_STATUS_END_OF_FILE},
- {"NT_STATUS_WRONG_VOLUME", NT_STATUS_WRONG_VOLUME},
- {"NT_STATUS_NO_MEDIA_IN_DEVICE", NT_STATUS_NO_MEDIA_IN_DEVICE},
- {"NT_STATUS_UNRECOGNIZED_MEDIA", NT_STATUS_UNRECOGNIZED_MEDIA},
- {"NT_STATUS_NONEXISTENT_SECTOR", NT_STATUS_NONEXISTENT_SECTOR},
- {"NT_STATUS_MORE_PROCESSING_REQUIRED",
- NT_STATUS_MORE_PROCESSING_REQUIRED},
- {"NT_STATUS_NO_MEMORY", NT_STATUS_NO_MEMORY},
- {"NT_STATUS_CONFLICTING_ADDRESSES",
- NT_STATUS_CONFLICTING_ADDRESSES},
- {"NT_STATUS_NOT_MAPPED_VIEW", NT_STATUS_NOT_MAPPED_VIEW},
- {"NT_STATUS_UNABLE_TO_FREE_VM", NT_STATUS_UNABLE_TO_FREE_VM},
- {"NT_STATUS_UNABLE_TO_DELETE_SECTION",
- NT_STATUS_UNABLE_TO_DELETE_SECTION},
- {"NT_STATUS_INVALID_SYSTEM_SERVICE",
- NT_STATUS_INVALID_SYSTEM_SERVICE},
- {"NT_STATUS_ILLEGAL_INSTRUCTION", NT_STATUS_ILLEGAL_INSTRUCTION},
- {"NT_STATUS_INVALID_LOCK_SEQUENCE",
- NT_STATUS_INVALID_LOCK_SEQUENCE},
- {"NT_STATUS_INVALID_VIEW_SIZE", NT_STATUS_INVALID_VIEW_SIZE},
- {"NT_STATUS_INVALID_FILE_FOR_SECTION",
- NT_STATUS_INVALID_FILE_FOR_SECTION},
- {"NT_STATUS_ALREADY_COMMITTED", NT_STATUS_ALREADY_COMMITTED},
- {"NT_STATUS_ACCESS_DENIED", NT_STATUS_ACCESS_DENIED},
- {"NT_STATUS_BUFFER_TOO_SMALL", NT_STATUS_BUFFER_TOO_SMALL},
- {"NT_STATUS_OBJECT_TYPE_MISMATCH", NT_STATUS_OBJECT_TYPE_MISMATCH},
- {"NT_STATUS_NONCONTINUABLE_EXCEPTION",
- NT_STATUS_NONCONTINUABLE_EXCEPTION},
- {"NT_STATUS_INVALID_DISPOSITION", NT_STATUS_INVALID_DISPOSITION},
- {"NT_STATUS_UNWIND", NT_STATUS_UNWIND},
- {"NT_STATUS_BAD_STACK", NT_STATUS_BAD_STACK},
- {"NT_STATUS_INVALID_UNWIND_TARGET",
- NT_STATUS_INVALID_UNWIND_TARGET},
- {"NT_STATUS_NOT_LOCKED", NT_STATUS_NOT_LOCKED},
- {"NT_STATUS_PARITY_ERROR", NT_STATUS_PARITY_ERROR},
- {"NT_STATUS_UNABLE_TO_DECOMMIT_VM",
- NT_STATUS_UNABLE_TO_DECOMMIT_VM},
- {"NT_STATUS_NOT_COMMITTED", NT_STATUS_NOT_COMMITTED},
- {"NT_STATUS_INVALID_PORT_ATTRIBUTES",
- NT_STATUS_INVALID_PORT_ATTRIBUTES},
- {"NT_STATUS_PORT_MESSAGE_TOO_LONG",
- NT_STATUS_PORT_MESSAGE_TOO_LONG},
- {"NT_STATUS_INVALID_PARAMETER_MIX",
- NT_STATUS_INVALID_PARAMETER_MIX},
- {"NT_STATUS_INVALID_QUOTA_LOWER", NT_STATUS_INVALID_QUOTA_LOWER},
- {"NT_STATUS_DISK_CORRUPT_ERROR", NT_STATUS_DISK_CORRUPT_ERROR},
- {"NT_STATUS_OBJECT_NAME_INVALID", NT_STATUS_OBJECT_NAME_INVALID},
- {"NT_STATUS_OBJECT_NAME_NOT_FOUND",
- NT_STATUS_OBJECT_NAME_NOT_FOUND},
- {"NT_STATUS_OBJECT_NAME_COLLISION",
- NT_STATUS_OBJECT_NAME_COLLISION},
- {"NT_STATUS_HANDLE_NOT_WAITABLE", NT_STATUS_HANDLE_NOT_WAITABLE},
- {"NT_STATUS_PORT_DISCONNECTED", NT_STATUS_PORT_DISCONNECTED},
- {"NT_STATUS_DEVICE_ALREADY_ATTACHED",
- NT_STATUS_DEVICE_ALREADY_ATTACHED},
- {"NT_STATUS_OBJECT_PATH_INVALID", NT_STATUS_OBJECT_PATH_INVALID},
- {"NT_STATUS_OBJECT_PATH_NOT_FOUND",
- NT_STATUS_OBJECT_PATH_NOT_FOUND},
- {"NT_STATUS_OBJECT_PATH_SYNTAX_BAD",
- NT_STATUS_OBJECT_PATH_SYNTAX_BAD},
- {"NT_STATUS_DATA_OVERRUN", NT_STATUS_DATA_OVERRUN},
- {"NT_STATUS_DATA_LATE_ERROR", NT_STATUS_DATA_LATE_ERROR},
- {"NT_STATUS_DATA_ERROR", NT_STATUS_DATA_ERROR},
- {"NT_STATUS_CRC_ERROR", NT_STATUS_CRC_ERROR},
- {"NT_STATUS_SECTION_TOO_BIG", NT_STATUS_SECTION_TOO_BIG},
- {"NT_STATUS_PORT_CONNECTION_REFUSED",
- NT_STATUS_PORT_CONNECTION_REFUSED},
- {"NT_STATUS_INVALID_PORT_HANDLE", NT_STATUS_INVALID_PORT_HANDLE},
- {"NT_STATUS_SHARING_VIOLATION", NT_STATUS_SHARING_VIOLATION},
- {"NT_STATUS_QUOTA_EXCEEDED", NT_STATUS_QUOTA_EXCEEDED},
- {"NT_STATUS_INVALID_PAGE_PROTECTION",
- NT_STATUS_INVALID_PAGE_PROTECTION},
- {"NT_STATUS_MUTANT_NOT_OWNED", NT_STATUS_MUTANT_NOT_OWNED},
- {"NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED",
- NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED},
- {"NT_STATUS_PORT_ALREADY_SET", NT_STATUS_PORT_ALREADY_SET},
- {"NT_STATUS_SECTION_NOT_IMAGE", NT_STATUS_SECTION_NOT_IMAGE},
- {"NT_STATUS_SUSPEND_COUNT_EXCEEDED",
- NT_STATUS_SUSPEND_COUNT_EXCEEDED},
- {"NT_STATUS_THREAD_IS_TERMINATING",
- NT_STATUS_THREAD_IS_TERMINATING},
- {"NT_STATUS_BAD_WORKING_SET_LIMIT",
- NT_STATUS_BAD_WORKING_SET_LIMIT},
- {"NT_STATUS_INCOMPATIBLE_FILE_MAP",
- NT_STATUS_INCOMPATIBLE_FILE_MAP},
- {"NT_STATUS_SECTION_PROTECTION", NT_STATUS_SECTION_PROTECTION},
- {"NT_STATUS_EAS_NOT_SUPPORTED", NT_STATUS_EAS_NOT_SUPPORTED},
- {"NT_STATUS_EA_TOO_LARGE", NT_STATUS_EA_TOO_LARGE},
- {"NT_STATUS_NONEXISTENT_EA_ENTRY", NT_STATUS_NONEXISTENT_EA_ENTRY},
- {"NT_STATUS_NO_EAS_ON_FILE", NT_STATUS_NO_EAS_ON_FILE},
- {"NT_STATUS_EA_CORRUPT_ERROR", NT_STATUS_EA_CORRUPT_ERROR},
- {"NT_STATUS_FILE_LOCK_CONFLICT", NT_STATUS_FILE_LOCK_CONFLICT},
- {"NT_STATUS_LOCK_NOT_GRANTED", NT_STATUS_LOCK_NOT_GRANTED},
- {"NT_STATUS_DELETE_PENDING", NT_STATUS_DELETE_PENDING},
- {"NT_STATUS_CTL_FILE_NOT_SUPPORTED",
- NT_STATUS_CTL_FILE_NOT_SUPPORTED},
- {"NT_STATUS_UNKNOWN_REVISION", NT_STATUS_UNKNOWN_REVISION},
- {"NT_STATUS_REVISION_MISMATCH", NT_STATUS_REVISION_MISMATCH},
- {"NT_STATUS_INVALID_OWNER", NT_STATUS_INVALID_OWNER},
- {"NT_STATUS_INVALID_PRIMARY_GROUP",
- NT_STATUS_INVALID_PRIMARY_GROUP},
- {"NT_STATUS_NO_IMPERSONATION_TOKEN",
- NT_STATUS_NO_IMPERSONATION_TOKEN},
- {"NT_STATUS_CANT_DISABLE_MANDATORY",
- NT_STATUS_CANT_DISABLE_MANDATORY},
- {"NT_STATUS_NO_LOGON_SERVERS", NT_STATUS_NO_LOGON_SERVERS},
- {"NT_STATUS_NO_SUCH_LOGON_SESSION",
- NT_STATUS_NO_SUCH_LOGON_SESSION},
- {"NT_STATUS_NO_SUCH_PRIVILEGE", NT_STATUS_NO_SUCH_PRIVILEGE},
- {"NT_STATUS_PRIVILEGE_NOT_HELD", NT_STATUS_PRIVILEGE_NOT_HELD},
- {"NT_STATUS_INVALID_ACCOUNT_NAME", NT_STATUS_INVALID_ACCOUNT_NAME},
- {"NT_STATUS_USER_EXISTS", NT_STATUS_USER_EXISTS},
- {"NT_STATUS_NO_SUCH_USER", NT_STATUS_NO_SUCH_USER},
- {"NT_STATUS_GROUP_EXISTS", NT_STATUS_GROUP_EXISTS},
- {"NT_STATUS_NO_SUCH_GROUP", NT_STATUS_NO_SUCH_GROUP},
- {"NT_STATUS_MEMBER_IN_GROUP", NT_STATUS_MEMBER_IN_GROUP},
- {"NT_STATUS_MEMBER_NOT_IN_GROUP", NT_STATUS_MEMBER_NOT_IN_GROUP},
- {"NT_STATUS_LAST_ADMIN", NT_STATUS_LAST_ADMIN},
- {"NT_STATUS_WRONG_PASSWORD", NT_STATUS_WRONG_PASSWORD},
- {"NT_STATUS_ILL_FORMED_PASSWORD", NT_STATUS_ILL_FORMED_PASSWORD},
- {"NT_STATUS_PASSWORD_RESTRICTION", NT_STATUS_PASSWORD_RESTRICTION},
- {"NT_STATUS_LOGON_FAILURE", NT_STATUS_LOGON_FAILURE},
- {"NT_STATUS_ACCOUNT_RESTRICTION", NT_STATUS_ACCOUNT_RESTRICTION},
- {"NT_STATUS_INVALID_LOGON_HOURS", NT_STATUS_INVALID_LOGON_HOURS},
- {"NT_STATUS_INVALID_WORKSTATION", NT_STATUS_INVALID_WORKSTATION},
- {"NT_STATUS_PASSWORD_EXPIRED", NT_STATUS_PASSWORD_EXPIRED},
- {"NT_STATUS_ACCOUNT_DISABLED", NT_STATUS_ACCOUNT_DISABLED},
- {"NT_STATUS_NONE_MAPPED", NT_STATUS_NONE_MAPPED},
- {"NT_STATUS_TOO_MANY_LUIDS_REQUESTED",
- NT_STATUS_TOO_MANY_LUIDS_REQUESTED},
- {"NT_STATUS_LUIDS_EXHAUSTED", NT_STATUS_LUIDS_EXHAUSTED},
- {"NT_STATUS_INVALID_SUB_AUTHORITY",
- NT_STATUS_INVALID_SUB_AUTHORITY},
- {"NT_STATUS_INVALID_ACL", NT_STATUS_INVALID_ACL},
- {"NT_STATUS_INVALID_SID", NT_STATUS_INVALID_SID},
- {"NT_STATUS_INVALID_SECURITY_DESCR",
- NT_STATUS_INVALID_SECURITY_DESCR},
- {"NT_STATUS_PROCEDURE_NOT_FOUND", NT_STATUS_PROCEDURE_NOT_FOUND},
- {"NT_STATUS_INVALID_IMAGE_FORMAT", NT_STATUS_INVALID_IMAGE_FORMAT},
- {"NT_STATUS_NO_TOKEN", NT_STATUS_NO_TOKEN},
- {"NT_STATUS_BAD_INHERITANCE_ACL", NT_STATUS_BAD_INHERITANCE_ACL},
- {"NT_STATUS_RANGE_NOT_LOCKED", NT_STATUS_RANGE_NOT_LOCKED},
- {"NT_STATUS_DISK_FULL", NT_STATUS_DISK_FULL},
- {"NT_STATUS_SERVER_DISABLED", NT_STATUS_SERVER_DISABLED},
- {"NT_STATUS_SERVER_NOT_DISABLED", NT_STATUS_SERVER_NOT_DISABLED},
- {"NT_STATUS_TOO_MANY_GUIDS_REQUESTED",
- NT_STATUS_TOO_MANY_GUIDS_REQUESTED},
- {"NT_STATUS_GUIDS_EXHAUSTED", NT_STATUS_GUIDS_EXHAUSTED},
- {"NT_STATUS_INVALID_ID_AUTHORITY", NT_STATUS_INVALID_ID_AUTHORITY},
- {"NT_STATUS_AGENTS_EXHAUSTED", NT_STATUS_AGENTS_EXHAUSTED},
- {"NT_STATUS_INVALID_VOLUME_LABEL", NT_STATUS_INVALID_VOLUME_LABEL},
- {"NT_STATUS_SECTION_NOT_EXTENDED", NT_STATUS_SECTION_NOT_EXTENDED},
- {"NT_STATUS_NOT_MAPPED_DATA", NT_STATUS_NOT_MAPPED_DATA},
- {"NT_STATUS_RESOURCE_DATA_NOT_FOUND",
- NT_STATUS_RESOURCE_DATA_NOT_FOUND},
- {"NT_STATUS_RESOURCE_TYPE_NOT_FOUND",
- NT_STATUS_RESOURCE_TYPE_NOT_FOUND},
- {"NT_STATUS_RESOURCE_NAME_NOT_FOUND",
- NT_STATUS_RESOURCE_NAME_NOT_FOUND},
- {"NT_STATUS_ARRAY_BOUNDS_EXCEEDED",
- NT_STATUS_ARRAY_BOUNDS_EXCEEDED},
- {"NT_STATUS_FLOAT_DENORMAL_OPERAND",
- NT_STATUS_FLOAT_DENORMAL_OPERAND},
- {"NT_STATUS_FLOAT_DIVIDE_BY_ZERO", NT_STATUS_FLOAT_DIVIDE_BY_ZERO},
- {"NT_STATUS_FLOAT_INEXACT_RESULT", NT_STATUS_FLOAT_INEXACT_RESULT},
- {"NT_STATUS_FLOAT_INVALID_OPERATION",
- NT_STATUS_FLOAT_INVALID_OPERATION},
- {"NT_STATUS_FLOAT_OVERFLOW", NT_STATUS_FLOAT_OVERFLOW},
- {"NT_STATUS_FLOAT_STACK_CHECK", NT_STATUS_FLOAT_STACK_CHECK},
- {"NT_STATUS_FLOAT_UNDERFLOW", NT_STATUS_FLOAT_UNDERFLOW},
- {"NT_STATUS_INTEGER_DIVIDE_BY_ZERO",
- NT_STATUS_INTEGER_DIVIDE_BY_ZERO},
- {"NT_STATUS_INTEGER_OVERFLOW", NT_STATUS_INTEGER_OVERFLOW},
- {"NT_STATUS_PRIVILEGED_INSTRUCTION",
- NT_STATUS_PRIVILEGED_INSTRUCTION},
- {"NT_STATUS_TOO_MANY_PAGING_FILES",
- NT_STATUS_TOO_MANY_PAGING_FILES},
- {"NT_STATUS_FILE_INVALID", NT_STATUS_FILE_INVALID},
- {"NT_STATUS_ALLOTTED_SPACE_EXCEEDED",
- NT_STATUS_ALLOTTED_SPACE_EXCEEDED},
- {"NT_STATUS_INSUFFICIENT_RESOURCES",
- NT_STATUS_INSUFFICIENT_RESOURCES},
- {"NT_STATUS_DFS_EXIT_PATH_FOUND", NT_STATUS_DFS_EXIT_PATH_FOUND},
- {"NT_STATUS_DEVICE_DATA_ERROR", NT_STATUS_DEVICE_DATA_ERROR},
- {"NT_STATUS_DEVICE_NOT_CONNECTED", NT_STATUS_DEVICE_NOT_CONNECTED},
- {"NT_STATUS_DEVICE_POWER_FAILURE", NT_STATUS_DEVICE_POWER_FAILURE},
- {"NT_STATUS_FREE_VM_NOT_AT_BASE", NT_STATUS_FREE_VM_NOT_AT_BASE},
- {"NT_STATUS_MEMORY_NOT_ALLOCATED", NT_STATUS_MEMORY_NOT_ALLOCATED},
- {"NT_STATUS_WORKING_SET_QUOTA", NT_STATUS_WORKING_SET_QUOTA},
- {"NT_STATUS_MEDIA_WRITE_PROTECTED",
- NT_STATUS_MEDIA_WRITE_PROTECTED},
- {"NT_STATUS_DEVICE_NOT_READY", NT_STATUS_DEVICE_NOT_READY},
- {"NT_STATUS_INVALID_GROUP_ATTRIBUTES",
- NT_STATUS_INVALID_GROUP_ATTRIBUTES},
- {"NT_STATUS_BAD_IMPERSONATION_LEVEL",
- NT_STATUS_BAD_IMPERSONATION_LEVEL},
- {"NT_STATUS_CANT_OPEN_ANONYMOUS", NT_STATUS_CANT_OPEN_ANONYMOUS},
- {"NT_STATUS_BAD_VALIDATION_CLASS", NT_STATUS_BAD_VALIDATION_CLASS},
- {"NT_STATUS_BAD_TOKEN_TYPE", NT_STATUS_BAD_TOKEN_TYPE},
- {"NT_STATUS_BAD_MASTER_BOOT_RECORD",
- NT_STATUS_BAD_MASTER_BOOT_RECORD},
- {"NT_STATUS_INSTRUCTION_MISALIGNMENT",
- NT_STATUS_INSTRUCTION_MISALIGNMENT},
- {"NT_STATUS_INSTANCE_NOT_AVAILABLE",
- NT_STATUS_INSTANCE_NOT_AVAILABLE},
- {"NT_STATUS_PIPE_NOT_AVAILABLE", NT_STATUS_PIPE_NOT_AVAILABLE},
- {"NT_STATUS_INVALID_PIPE_STATE", NT_STATUS_INVALID_PIPE_STATE},
- {"NT_STATUS_PIPE_BUSY", NT_STATUS_PIPE_BUSY},
- {"NT_STATUS_ILLEGAL_FUNCTION", NT_STATUS_ILLEGAL_FUNCTION},
- {"NT_STATUS_PIPE_DISCONNECTED", NT_STATUS_PIPE_DISCONNECTED},
- {"NT_STATUS_PIPE_CLOSING", NT_STATUS_PIPE_CLOSING},
- {"NT_STATUS_PIPE_CONNECTED", NT_STATUS_PIPE_CONNECTED},
- {"NT_STATUS_PIPE_LISTENING", NT_STATUS_PIPE_LISTENING},
- {"NT_STATUS_INVALID_READ_MODE", NT_STATUS_INVALID_READ_MODE},
- {"NT_STATUS_IO_TIMEOUT", NT_STATUS_IO_TIMEOUT},
- {"NT_STATUS_FILE_FORCED_CLOSED", NT_STATUS_FILE_FORCED_CLOSED},
- {"NT_STATUS_PROFILING_NOT_STARTED",
- NT_STATUS_PROFILING_NOT_STARTED},
- {"NT_STATUS_PROFILING_NOT_STOPPED",
- NT_STATUS_PROFILING_NOT_STOPPED},
- {"NT_STATUS_COULD_NOT_INTERPRET", NT_STATUS_COULD_NOT_INTERPRET},
- {"NT_STATUS_FILE_IS_A_DIRECTORY", NT_STATUS_FILE_IS_A_DIRECTORY},
- {"NT_STATUS_NOT_SUPPORTED", NT_STATUS_NOT_SUPPORTED},
- {"NT_STATUS_REMOTE_NOT_LISTENING", NT_STATUS_REMOTE_NOT_LISTENING},
- {"NT_STATUS_DUPLICATE_NAME", NT_STATUS_DUPLICATE_NAME},
- {"NT_STATUS_BAD_NETWORK_PATH", NT_STATUS_BAD_NETWORK_PATH},
- {"NT_STATUS_NETWORK_BUSY", NT_STATUS_NETWORK_BUSY},
- {"NT_STATUS_DEVICE_DOES_NOT_EXIST",
- NT_STATUS_DEVICE_DOES_NOT_EXIST},
- {"NT_STATUS_TOO_MANY_COMMANDS", NT_STATUS_TOO_MANY_COMMANDS},
- {"NT_STATUS_ADAPTER_HARDWARE_ERROR",
- NT_STATUS_ADAPTER_HARDWARE_ERROR},
- {"NT_STATUS_INVALID_NETWORK_RESPONSE",
- NT_STATUS_INVALID_NETWORK_RESPONSE},
- {"NT_STATUS_UNEXPECTED_NETWORK_ERROR",
- NT_STATUS_UNEXPECTED_NETWORK_ERROR},
- {"NT_STATUS_BAD_REMOTE_ADAPTER", NT_STATUS_BAD_REMOTE_ADAPTER},
- {"NT_STATUS_PRINT_QUEUE_FULL", NT_STATUS_PRINT_QUEUE_FULL},
- {"NT_STATUS_NO_SPOOL_SPACE", NT_STATUS_NO_SPOOL_SPACE},
- {"NT_STATUS_PRINT_CANCELLED", NT_STATUS_PRINT_CANCELLED},
- {"NT_STATUS_NETWORK_NAME_DELETED", NT_STATUS_NETWORK_NAME_DELETED},
- {"NT_STATUS_NETWORK_ACCESS_DENIED",
- NT_STATUS_NETWORK_ACCESS_DENIED},
- {"NT_STATUS_BAD_DEVICE_TYPE", NT_STATUS_BAD_DEVICE_TYPE},
- {"NT_STATUS_BAD_NETWORK_NAME", NT_STATUS_BAD_NETWORK_NAME},
- {"NT_STATUS_TOO_MANY_NAMES", NT_STATUS_TOO_MANY_NAMES},
- {"NT_STATUS_TOO_MANY_SESSIONS", NT_STATUS_TOO_MANY_SESSIONS},
- {"NT_STATUS_SHARING_PAUSED", NT_STATUS_SHARING_PAUSED},
- {"NT_STATUS_REQUEST_NOT_ACCEPTED", NT_STATUS_REQUEST_NOT_ACCEPTED},
- {"NT_STATUS_REDIRECTOR_PAUSED", NT_STATUS_REDIRECTOR_PAUSED},
- {"NT_STATUS_NET_WRITE_FAULT", NT_STATUS_NET_WRITE_FAULT},
- {"NT_STATUS_PROFILING_AT_LIMIT", NT_STATUS_PROFILING_AT_LIMIT},
- {"NT_STATUS_NOT_SAME_DEVICE", NT_STATUS_NOT_SAME_DEVICE},
- {"NT_STATUS_FILE_RENAMED", NT_STATUS_FILE_RENAMED},
- {"NT_STATUS_VIRTUAL_CIRCUIT_CLOSED",
- NT_STATUS_VIRTUAL_CIRCUIT_CLOSED},
- {"NT_STATUS_NO_SECURITY_ON_OBJECT",
- NT_STATUS_NO_SECURITY_ON_OBJECT},
- {"NT_STATUS_CANT_WAIT", NT_STATUS_CANT_WAIT},
- {"NT_STATUS_PIPE_EMPTY", NT_STATUS_PIPE_EMPTY},
- {"NT_STATUS_CANT_ACCESS_DOMAIN_INFO",
- NT_STATUS_CANT_ACCESS_DOMAIN_INFO},
- {"NT_STATUS_CANT_TERMINATE_SELF", NT_STATUS_CANT_TERMINATE_SELF},
- {"NT_STATUS_INVALID_SERVER_STATE", NT_STATUS_INVALID_SERVER_STATE},
- {"NT_STATUS_INVALID_DOMAIN_STATE", NT_STATUS_INVALID_DOMAIN_STATE},
- {"NT_STATUS_INVALID_DOMAIN_ROLE", NT_STATUS_INVALID_DOMAIN_ROLE},
- {"NT_STATUS_NO_SUCH_DOMAIN", NT_STATUS_NO_SUCH_DOMAIN},
- {"NT_STATUS_DOMAIN_EXISTS", NT_STATUS_DOMAIN_EXISTS},
- {"NT_STATUS_DOMAIN_LIMIT_EXCEEDED",
- NT_STATUS_DOMAIN_LIMIT_EXCEEDED},
- {"NT_STATUS_OPLOCK_NOT_GRANTED", NT_STATUS_OPLOCK_NOT_GRANTED},
- {"NT_STATUS_INVALID_OPLOCK_PROTOCOL",
- NT_STATUS_INVALID_OPLOCK_PROTOCOL},
- {"NT_STATUS_INTERNAL_DB_CORRUPTION",
- NT_STATUS_INTERNAL_DB_CORRUPTION},
- {"NT_STATUS_INTERNAL_ERROR", NT_STATUS_INTERNAL_ERROR},
- {"NT_STATUS_GENERIC_NOT_MAPPED", NT_STATUS_GENERIC_NOT_MAPPED},
- {"NT_STATUS_BAD_DESCRIPTOR_FORMAT",
- NT_STATUS_BAD_DESCRIPTOR_FORMAT},
- {"NT_STATUS_INVALID_USER_BUFFER", NT_STATUS_INVALID_USER_BUFFER},
- {"NT_STATUS_UNEXPECTED_IO_ERROR", NT_STATUS_UNEXPECTED_IO_ERROR},
- {"NT_STATUS_UNEXPECTED_MM_CREATE_ERR",
- NT_STATUS_UNEXPECTED_MM_CREATE_ERR},
- {"NT_STATUS_UNEXPECTED_MM_MAP_ERROR",
- NT_STATUS_UNEXPECTED_MM_MAP_ERROR},
- {"NT_STATUS_UNEXPECTED_MM_EXTEND_ERR",
- NT_STATUS_UNEXPECTED_MM_EXTEND_ERR},
- {"NT_STATUS_NOT_LOGON_PROCESS", NT_STATUS_NOT_LOGON_PROCESS},
- {"NT_STATUS_LOGON_SESSION_EXISTS", NT_STATUS_LOGON_SESSION_EXISTS},
- {"NT_STATUS_INVALID_PARAMETER_1", NT_STATUS_INVALID_PARAMETER_1},
- {"NT_STATUS_INVALID_PARAMETER_2", NT_STATUS_INVALID_PARAMETER_2},
- {"NT_STATUS_INVALID_PARAMETER_3", NT_STATUS_INVALID_PARAMETER_3},
- {"NT_STATUS_INVALID_PARAMETER_4", NT_STATUS_INVALID_PARAMETER_4},
- {"NT_STATUS_INVALID_PARAMETER_5", NT_STATUS_INVALID_PARAMETER_5},
- {"NT_STATUS_INVALID_PARAMETER_6", NT_STATUS_INVALID_PARAMETER_6},
- {"NT_STATUS_INVALID_PARAMETER_7", NT_STATUS_INVALID_PARAMETER_7},
- {"NT_STATUS_INVALID_PARAMETER_8", NT_STATUS_INVALID_PARAMETER_8},
- {"NT_STATUS_INVALID_PARAMETER_9", NT_STATUS_INVALID_PARAMETER_9},
- {"NT_STATUS_INVALID_PARAMETER_10", NT_STATUS_INVALID_PARAMETER_10},
- {"NT_STATUS_INVALID_PARAMETER_11", NT_STATUS_INVALID_PARAMETER_11},
- {"NT_STATUS_INVALID_PARAMETER_12", NT_STATUS_INVALID_PARAMETER_12},
- {"NT_STATUS_REDIRECTOR_NOT_STARTED",
- NT_STATUS_REDIRECTOR_NOT_STARTED},
- {"NT_STATUS_REDIRECTOR_STARTED", NT_STATUS_REDIRECTOR_STARTED},
- {"NT_STATUS_STACK_OVERFLOW", NT_STATUS_STACK_OVERFLOW},
- {"NT_STATUS_NO_SUCH_PACKAGE", NT_STATUS_NO_SUCH_PACKAGE},
- {"NT_STATUS_BAD_FUNCTION_TABLE", NT_STATUS_BAD_FUNCTION_TABLE},
- {"NT_STATUS_VARIABLE_NOT_FOUND", NT_STATUS_VARIABLE_NOT_FOUND},
- {"NT_STATUS_DIRECTORY_NOT_EMPTY", NT_STATUS_DIRECTORY_NOT_EMPTY},
- {"NT_STATUS_FILE_CORRUPT_ERROR", NT_STATUS_FILE_CORRUPT_ERROR},
- {"NT_STATUS_NOT_A_DIRECTORY", NT_STATUS_NOT_A_DIRECTORY},
- {"NT_STATUS_BAD_LOGON_SESSION_STATE",
- NT_STATUS_BAD_LOGON_SESSION_STATE},
- {"NT_STATUS_LOGON_SESSION_COLLISION",
- NT_STATUS_LOGON_SESSION_COLLISION},
- {"NT_STATUS_NAME_TOO_LONG", NT_STATUS_NAME_TOO_LONG},
- {"NT_STATUS_FILES_OPEN", NT_STATUS_FILES_OPEN},
- {"NT_STATUS_CONNECTION_IN_USE", NT_STATUS_CONNECTION_IN_USE},
- {"NT_STATUS_MESSAGE_NOT_FOUND", NT_STATUS_MESSAGE_NOT_FOUND},
- {"NT_STATUS_PROCESS_IS_TERMINATING",
- NT_STATUS_PROCESS_IS_TERMINATING},
- {"NT_STATUS_INVALID_LOGON_TYPE", NT_STATUS_INVALID_LOGON_TYPE},
- {"NT_STATUS_NO_GUID_TRANSLATION", NT_STATUS_NO_GUID_TRANSLATION},
- {"NT_STATUS_CANNOT_IMPERSONATE", NT_STATUS_CANNOT_IMPERSONATE},
- {"NT_STATUS_IMAGE_ALREADY_LOADED", NT_STATUS_IMAGE_ALREADY_LOADED},
- {"NT_STATUS_ABIOS_NOT_PRESENT", NT_STATUS_ABIOS_NOT_PRESENT},
- {"NT_STATUS_ABIOS_LID_NOT_EXIST", NT_STATUS_ABIOS_LID_NOT_EXIST},
- {"NT_STATUS_ABIOS_LID_ALREADY_OWNED",
- NT_STATUS_ABIOS_LID_ALREADY_OWNED},
- {"NT_STATUS_ABIOS_NOT_LID_OWNER", NT_STATUS_ABIOS_NOT_LID_OWNER},
- {"NT_STATUS_ABIOS_INVALID_COMMAND",
- NT_STATUS_ABIOS_INVALID_COMMAND},
- {"NT_STATUS_ABIOS_INVALID_LID", NT_STATUS_ABIOS_INVALID_LID},
- {"NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE",
- NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE},
- {"NT_STATUS_ABIOS_INVALID_SELECTOR",
- NT_STATUS_ABIOS_INVALID_SELECTOR},
- {"NT_STATUS_NO_LDT", NT_STATUS_NO_LDT},
- {"NT_STATUS_INVALID_LDT_SIZE", NT_STATUS_INVALID_LDT_SIZE},
- {"NT_STATUS_INVALID_LDT_OFFSET", NT_STATUS_INVALID_LDT_OFFSET},
- {"NT_STATUS_INVALID_LDT_DESCRIPTOR",
- NT_STATUS_INVALID_LDT_DESCRIPTOR},
- {"NT_STATUS_INVALID_IMAGE_NE_FORMAT",
- NT_STATUS_INVALID_IMAGE_NE_FORMAT},
- {"NT_STATUS_RXACT_INVALID_STATE", NT_STATUS_RXACT_INVALID_STATE},
- {"NT_STATUS_RXACT_COMMIT_FAILURE", NT_STATUS_RXACT_COMMIT_FAILURE},
- {"NT_STATUS_MAPPED_FILE_SIZE_ZERO",
- NT_STATUS_MAPPED_FILE_SIZE_ZERO},
- {"NT_STATUS_TOO_MANY_OPENED_FILES",
- NT_STATUS_TOO_MANY_OPENED_FILES},
- {"NT_STATUS_CANCELLED", NT_STATUS_CANCELLED},
- {"NT_STATUS_CANNOT_DELETE", NT_STATUS_CANNOT_DELETE},
- {"NT_STATUS_INVALID_COMPUTER_NAME",
- NT_STATUS_INVALID_COMPUTER_NAME},
- {"NT_STATUS_FILE_DELETED", NT_STATUS_FILE_DELETED},
- {"NT_STATUS_SPECIAL_ACCOUNT", NT_STATUS_SPECIAL_ACCOUNT},
- {"NT_STATUS_SPECIAL_GROUP", NT_STATUS_SPECIAL_GROUP},
- {"NT_STATUS_SPECIAL_USER", NT_STATUS_SPECIAL_USER},
- {"NT_STATUS_MEMBERS_PRIMARY_GROUP",
- NT_STATUS_MEMBERS_PRIMARY_GROUP},
- {"NT_STATUS_FILE_CLOSED", NT_STATUS_FILE_CLOSED},
- {"NT_STATUS_TOO_MANY_THREADS", NT_STATUS_TOO_MANY_THREADS},
- {"NT_STATUS_THREAD_NOT_IN_PROCESS",
- NT_STATUS_THREAD_NOT_IN_PROCESS},
- {"NT_STATUS_TOKEN_ALREADY_IN_USE", NT_STATUS_TOKEN_ALREADY_IN_USE},
- {"NT_STATUS_PAGEFILE_QUOTA_EXCEEDED",
- NT_STATUS_PAGEFILE_QUOTA_EXCEEDED},
- {"NT_STATUS_COMMITMENT_LIMIT", NT_STATUS_COMMITMENT_LIMIT},
- {"NT_STATUS_INVALID_IMAGE_LE_FORMAT",
- NT_STATUS_INVALID_IMAGE_LE_FORMAT},
- {"NT_STATUS_INVALID_IMAGE_NOT_MZ", NT_STATUS_INVALID_IMAGE_NOT_MZ},
- {"NT_STATUS_INVALID_IMAGE_PROTECT",
- NT_STATUS_INVALID_IMAGE_PROTECT},
- {"NT_STATUS_INVALID_IMAGE_WIN_16", NT_STATUS_INVALID_IMAGE_WIN_16},
- {"NT_STATUS_LOGON_SERVER_CONFLICT",
- NT_STATUS_LOGON_SERVER_CONFLICT},
- {"NT_STATUS_TIME_DIFFERENCE_AT_DC",
- NT_STATUS_TIME_DIFFERENCE_AT_DC},
- {"NT_STATUS_SYNCHRONIZATION_REQUIRED",
- NT_STATUS_SYNCHRONIZATION_REQUIRED},
- {"NT_STATUS_DLL_NOT_FOUND", NT_STATUS_DLL_NOT_FOUND},
- {"NT_STATUS_OPEN_FAILED", NT_STATUS_OPEN_FAILED},
- {"NT_STATUS_IO_PRIVILEGE_FAILED", NT_STATUS_IO_PRIVILEGE_FAILED},
- {"NT_STATUS_ORDINAL_NOT_FOUND", NT_STATUS_ORDINAL_NOT_FOUND},
- {"NT_STATUS_ENTRYPOINT_NOT_FOUND", NT_STATUS_ENTRYPOINT_NOT_FOUND},
- {"NT_STATUS_CONTROL_C_EXIT", NT_STATUS_CONTROL_C_EXIT},
- {"NT_STATUS_LOCAL_DISCONNECT", NT_STATUS_LOCAL_DISCONNECT},
- {"NT_STATUS_REMOTE_DISCONNECT", NT_STATUS_REMOTE_DISCONNECT},
- {"NT_STATUS_REMOTE_RESOURCES", NT_STATUS_REMOTE_RESOURCES},
- {"NT_STATUS_LINK_FAILED", NT_STATUS_LINK_FAILED},
- {"NT_STATUS_LINK_TIMEOUT", NT_STATUS_LINK_TIMEOUT},
- {"NT_STATUS_INVALID_CONNECTION", NT_STATUS_INVALID_CONNECTION},
- {"NT_STATUS_INVALID_ADDRESS", NT_STATUS_INVALID_ADDRESS},
- {"NT_STATUS_DLL_INIT_FAILED", NT_STATUS_DLL_INIT_FAILED},
- {"NT_STATUS_MISSING_SYSTEMFILE", NT_STATUS_MISSING_SYSTEMFILE},
- {"NT_STATUS_UNHANDLED_EXCEPTION", NT_STATUS_UNHANDLED_EXCEPTION},
- {"NT_STATUS_APP_INIT_FAILURE", NT_STATUS_APP_INIT_FAILURE},
- {"NT_STATUS_PAGEFILE_CREATE_FAILED",
- NT_STATUS_PAGEFILE_CREATE_FAILED},
- {"NT_STATUS_NO_PAGEFILE", NT_STATUS_NO_PAGEFILE},
- {"NT_STATUS_INVALID_LEVEL", NT_STATUS_INVALID_LEVEL},
- {"NT_STATUS_WRONG_PASSWORD_CORE", NT_STATUS_WRONG_PASSWORD_CORE},
- {"NT_STATUS_ILLEGAL_FLOAT_CONTEXT",
- NT_STATUS_ILLEGAL_FLOAT_CONTEXT},
- {"NT_STATUS_PIPE_BROKEN", NT_STATUS_PIPE_BROKEN},
- {"NT_STATUS_REGISTRY_CORRUPT", NT_STATUS_REGISTRY_CORRUPT},
- {"NT_STATUS_REGISTRY_IO_FAILED", NT_STATUS_REGISTRY_IO_FAILED},
- {"NT_STATUS_NO_EVENT_PAIR", NT_STATUS_NO_EVENT_PAIR},
- {"NT_STATUS_UNRECOGNIZED_VOLUME", NT_STATUS_UNRECOGNIZED_VOLUME},
- {"NT_STATUS_SERIAL_NO_DEVICE_INITED",
- NT_STATUS_SERIAL_NO_DEVICE_INITED},
- {"NT_STATUS_NO_SUCH_ALIAS", NT_STATUS_NO_SUCH_ALIAS},
- {"NT_STATUS_MEMBER_NOT_IN_ALIAS", NT_STATUS_MEMBER_NOT_IN_ALIAS},
- {"NT_STATUS_MEMBER_IN_ALIAS", NT_STATUS_MEMBER_IN_ALIAS},
- {"NT_STATUS_ALIAS_EXISTS", NT_STATUS_ALIAS_EXISTS},
- {"NT_STATUS_LOGON_NOT_GRANTED", NT_STATUS_LOGON_NOT_GRANTED},
- {"NT_STATUS_TOO_MANY_SECRETS", NT_STATUS_TOO_MANY_SECRETS},
- {"NT_STATUS_SECRET_TOO_LONG", NT_STATUS_SECRET_TOO_LONG},
- {"NT_STATUS_INTERNAL_DB_ERROR", NT_STATUS_INTERNAL_DB_ERROR},
- {"NT_STATUS_FULLSCREEN_MODE", NT_STATUS_FULLSCREEN_MODE},
- {"NT_STATUS_TOO_MANY_CONTEXT_IDS", NT_STATUS_TOO_MANY_CONTEXT_IDS},
- {"NT_STATUS_LOGON_TYPE_NOT_GRANTED",
- NT_STATUS_LOGON_TYPE_NOT_GRANTED},
- {"NT_STATUS_NOT_REGISTRY_FILE", NT_STATUS_NOT_REGISTRY_FILE},
- {"NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED",
- NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED},
- {"NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR",
- NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR},
- {"NT_STATUS_FT_MISSING_MEMBER", NT_STATUS_FT_MISSING_MEMBER},
- {"NT_STATUS_ILL_FORMED_SERVICE_ENTRY",
- NT_STATUS_ILL_FORMED_SERVICE_ENTRY},
- {"NT_STATUS_ILLEGAL_CHARACTER", NT_STATUS_ILLEGAL_CHARACTER},
- {"NT_STATUS_UNMAPPABLE_CHARACTER", NT_STATUS_UNMAPPABLE_CHARACTER},
- {"NT_STATUS_UNDEFINED_CHARACTER", NT_STATUS_UNDEFINED_CHARACTER},
- {"NT_STATUS_FLOPPY_VOLUME", NT_STATUS_FLOPPY_VOLUME},
- {"NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND",
- NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND},
- {"NT_STATUS_FLOPPY_WRONG_CYLINDER",
- NT_STATUS_FLOPPY_WRONG_CYLINDER},
- {"NT_STATUS_FLOPPY_UNKNOWN_ERROR", NT_STATUS_FLOPPY_UNKNOWN_ERROR},
- {"NT_STATUS_FLOPPY_BAD_REGISTERS", NT_STATUS_FLOPPY_BAD_REGISTERS},
- {"NT_STATUS_DISK_RECALIBRATE_FAILED",
- NT_STATUS_DISK_RECALIBRATE_FAILED},
- {"NT_STATUS_DISK_OPERATION_FAILED",
- NT_STATUS_DISK_OPERATION_FAILED},
- {"NT_STATUS_DISK_RESET_FAILED", NT_STATUS_DISK_RESET_FAILED},
- {"NT_STATUS_SHARED_IRQ_BUSY", NT_STATUS_SHARED_IRQ_BUSY},
- {"NT_STATUS_FT_ORPHANING", NT_STATUS_FT_ORPHANING},
- {"NT_STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT",
- NT_STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT},
- {"NT_STATUS_PARTITION_FAILURE", NT_STATUS_PARTITION_FAILURE},
- {"NT_STATUS_INVALID_BLOCK_LENGTH", NT_STATUS_INVALID_BLOCK_LENGTH},
- {"NT_STATUS_DEVICE_NOT_PARTITIONED",
- NT_STATUS_DEVICE_NOT_PARTITIONED},
- {"NT_STATUS_UNABLE_TO_LOCK_MEDIA", NT_STATUS_UNABLE_TO_LOCK_MEDIA},
- {"NT_STATUS_UNABLE_TO_UNLOAD_MEDIA",
- NT_STATUS_UNABLE_TO_UNLOAD_MEDIA},
- {"NT_STATUS_EOM_OVERFLOW", NT_STATUS_EOM_OVERFLOW},
- {"NT_STATUS_NO_MEDIA", NT_STATUS_NO_MEDIA},
- {"NT_STATUS_NO_SUCH_MEMBER", NT_STATUS_NO_SUCH_MEMBER},
- {"NT_STATUS_INVALID_MEMBER", NT_STATUS_INVALID_MEMBER},
- {"NT_STATUS_KEY_DELETED", NT_STATUS_KEY_DELETED},
- {"NT_STATUS_NO_LOG_SPACE", NT_STATUS_NO_LOG_SPACE},
- {"NT_STATUS_TOO_MANY_SIDS", NT_STATUS_TOO_MANY_SIDS},
- {"NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED",
- NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED},
- {"NT_STATUS_KEY_HAS_CHILDREN", NT_STATUS_KEY_HAS_CHILDREN},
- {"NT_STATUS_CHILD_MUST_BE_VOLATILE",
- NT_STATUS_CHILD_MUST_BE_VOLATILE},
- {"NT_STATUS_DEVICE_CONFIGURATION_ERROR",
- NT_STATUS_DEVICE_CONFIGURATION_ERROR},
- {"NT_STATUS_DRIVER_INTERNAL_ERROR",
- NT_STATUS_DRIVER_INTERNAL_ERROR},
- {"NT_STATUS_INVALID_DEVICE_STATE", NT_STATUS_INVALID_DEVICE_STATE},
- {"NT_STATUS_IO_DEVICE_ERROR", NT_STATUS_IO_DEVICE_ERROR},
- {"NT_STATUS_DEVICE_PROTOCOL_ERROR",
- NT_STATUS_DEVICE_PROTOCOL_ERROR},
- {"NT_STATUS_BACKUP_CONTROLLER", NT_STATUS_BACKUP_CONTROLLER},
- {"NT_STATUS_LOG_FILE_FULL", NT_STATUS_LOG_FILE_FULL},
- {"NT_STATUS_TOO_LATE", NT_STATUS_TOO_LATE},
- {"NT_STATUS_NO_TRUST_LSA_SECRET", NT_STATUS_NO_TRUST_LSA_SECRET},
- {"NT_STATUS_NO_TRUST_SAM_ACCOUNT", NT_STATUS_NO_TRUST_SAM_ACCOUNT},
- {"NT_STATUS_TRUSTED_DOMAIN_FAILURE",
- NT_STATUS_TRUSTED_DOMAIN_FAILURE},
- {"NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE",
- NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE},
- {"NT_STATUS_EVENTLOG_FILE_CORRUPT",
- NT_STATUS_EVENTLOG_FILE_CORRUPT},
- {"NT_STATUS_EVENTLOG_CANT_START", NT_STATUS_EVENTLOG_CANT_START},
- {"NT_STATUS_TRUST_FAILURE", NT_STATUS_TRUST_FAILURE},
- {"NT_STATUS_MUTANT_LIMIT_EXCEEDED",
- NT_STATUS_MUTANT_LIMIT_EXCEEDED},
- {"NT_STATUS_NETLOGON_NOT_STARTED", NT_STATUS_NETLOGON_NOT_STARTED},
- {"NT_STATUS_ACCOUNT_EXPIRED", NT_STATUS_ACCOUNT_EXPIRED},
- {"NT_STATUS_POSSIBLE_DEADLOCK", NT_STATUS_POSSIBLE_DEADLOCK},
- {"NT_STATUS_NETWORK_CREDENTIAL_CONFLICT",
- NT_STATUS_NETWORK_CREDENTIAL_CONFLICT},
- {"NT_STATUS_REMOTE_SESSION_LIMIT", NT_STATUS_REMOTE_SESSION_LIMIT},
- {"NT_STATUS_EVENTLOG_FILE_CHANGED",
- NT_STATUS_EVENTLOG_FILE_CHANGED},
- {"NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT",
- NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT},
- {"NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT",
- NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT},
- {"NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT",
- NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT},
- {"NT_STATUS_DOMAIN_TRUST_INCONSISTENT",
- NT_STATUS_DOMAIN_TRUST_INCONSISTENT},
- {"NT_STATUS_FS_DRIVER_REQUIRED", NT_STATUS_FS_DRIVER_REQUIRED},
- {"NT_STATUS_INVALID_LOCK_RANGE", NT_STATUS_INVALID_LOCK_RANGE},
- {"NT_STATUS_NO_USER_SESSION_KEY", NT_STATUS_NO_USER_SESSION_KEY},
- {"NT_STATUS_USER_SESSION_DELETED", NT_STATUS_USER_SESSION_DELETED},
- {"NT_STATUS_RESOURCE_LANG_NOT_FOUND",
- NT_STATUS_RESOURCE_LANG_NOT_FOUND},
- {"NT_STATUS_INSUFF_SERVER_RESOURCES",
- NT_STATUS_INSUFF_SERVER_RESOURCES},
- {"NT_STATUS_INVALID_BUFFER_SIZE", NT_STATUS_INVALID_BUFFER_SIZE},
- {"NT_STATUS_INVALID_ADDRESS_COMPONENT",
- NT_STATUS_INVALID_ADDRESS_COMPONENT},
- {"NT_STATUS_INVALID_ADDRESS_WILDCARD",
- NT_STATUS_INVALID_ADDRESS_WILDCARD},
- {"NT_STATUS_TOO_MANY_ADDRESSES", NT_STATUS_TOO_MANY_ADDRESSES},
- {"NT_STATUS_ADDRESS_ALREADY_EXISTS",
- NT_STATUS_ADDRESS_ALREADY_EXISTS},
- {"NT_STATUS_ADDRESS_CLOSED", NT_STATUS_ADDRESS_CLOSED},
- {"NT_STATUS_CONNECTION_DISCONNECTED",
- NT_STATUS_CONNECTION_DISCONNECTED},
- {"NT_STATUS_CONNECTION_RESET", NT_STATUS_CONNECTION_RESET},
- {"NT_STATUS_TOO_MANY_NODES", NT_STATUS_TOO_MANY_NODES},
- {"NT_STATUS_TRANSACTION_ABORTED", NT_STATUS_TRANSACTION_ABORTED},
- {"NT_STATUS_TRANSACTION_TIMED_OUT",
- NT_STATUS_TRANSACTION_TIMED_OUT},
- {"NT_STATUS_TRANSACTION_NO_RELEASE",
- NT_STATUS_TRANSACTION_NO_RELEASE},
- {"NT_STATUS_TRANSACTION_NO_MATCH", NT_STATUS_TRANSACTION_NO_MATCH},
- {"NT_STATUS_TRANSACTION_RESPONDED",
- NT_STATUS_TRANSACTION_RESPONDED},
- {"NT_STATUS_TRANSACTION_INVALID_ID",
- NT_STATUS_TRANSACTION_INVALID_ID},
- {"NT_STATUS_TRANSACTION_INVALID_TYPE",
- NT_STATUS_TRANSACTION_INVALID_TYPE},
- {"NT_STATUS_NOT_SERVER_SESSION", NT_STATUS_NOT_SERVER_SESSION},
- {"NT_STATUS_NOT_CLIENT_SESSION", NT_STATUS_NOT_CLIENT_SESSION},
- {"NT_STATUS_CANNOT_LOAD_REGISTRY_FILE",
- NT_STATUS_CANNOT_LOAD_REGISTRY_FILE},
- {"NT_STATUS_DEBUG_ATTACH_FAILED", NT_STATUS_DEBUG_ATTACH_FAILED},
- {"NT_STATUS_SYSTEM_PROCESS_TERMINATED",
- NT_STATUS_SYSTEM_PROCESS_TERMINATED},
- {"NT_STATUS_DATA_NOT_ACCEPTED", NT_STATUS_DATA_NOT_ACCEPTED},
- {"NT_STATUS_NO_BROWSER_SERVERS_FOUND",
- NT_STATUS_NO_BROWSER_SERVERS_FOUND},
- {"NT_STATUS_VDM_HARD_ERROR", NT_STATUS_VDM_HARD_ERROR},
- {"NT_STATUS_DRIVER_CANCEL_TIMEOUT",
- NT_STATUS_DRIVER_CANCEL_TIMEOUT},
- {"NT_STATUS_REPLY_MESSAGE_MISMATCH",
- NT_STATUS_REPLY_MESSAGE_MISMATCH},
- {"NT_STATUS_MAPPED_ALIGNMENT", NT_STATUS_MAPPED_ALIGNMENT},
- {"NT_STATUS_IMAGE_CHECKSUM_MISMATCH",
- NT_STATUS_IMAGE_CHECKSUM_MISMATCH},
- {"NT_STATUS_LOST_WRITEBEHIND_DATA",
- NT_STATUS_LOST_WRITEBEHIND_DATA},
- {"NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID",
- NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID},
- {"NT_STATUS_PASSWORD_MUST_CHANGE", NT_STATUS_PASSWORD_MUST_CHANGE},
- {"NT_STATUS_NOT_FOUND", NT_STATUS_NOT_FOUND},
- {"NT_STATUS_NOT_TINY_STREAM", NT_STATUS_NOT_TINY_STREAM},
- {"NT_STATUS_RECOVERY_FAILURE", NT_STATUS_RECOVERY_FAILURE},
- {"NT_STATUS_STACK_OVERFLOW_READ", NT_STATUS_STACK_OVERFLOW_READ},
- {"NT_STATUS_FAIL_CHECK", NT_STATUS_FAIL_CHECK},
- {"NT_STATUS_DUPLICATE_OBJECTID", NT_STATUS_DUPLICATE_OBJECTID},
- {"NT_STATUS_OBJECTID_EXISTS", NT_STATUS_OBJECTID_EXISTS},
- {"NT_STATUS_CONVERT_TO_LARGE", NT_STATUS_CONVERT_TO_LARGE},
- {"NT_STATUS_RETRY", NT_STATUS_RETRY},
- {"NT_STATUS_FOUND_OUT_OF_SCOPE", NT_STATUS_FOUND_OUT_OF_SCOPE},
- {"NT_STATUS_ALLOCATE_BUCKET", NT_STATUS_ALLOCATE_BUCKET},
- {"NT_STATUS_PROPSET_NOT_FOUND", NT_STATUS_PROPSET_NOT_FOUND},
- {"NT_STATUS_MARSHALL_OVERFLOW", NT_STATUS_MARSHALL_OVERFLOW},
- {"NT_STATUS_INVALID_VARIANT", NT_STATUS_INVALID_VARIANT},
- {"NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND",
- NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND},
- {"NT_STATUS_ACCOUNT_LOCKED_OUT", NT_STATUS_ACCOUNT_LOCKED_OUT},
- {"NT_STATUS_HANDLE_NOT_CLOSABLE", NT_STATUS_HANDLE_NOT_CLOSABLE},
- {"NT_STATUS_CONNECTION_REFUSED", NT_STATUS_CONNECTION_REFUSED},
- {"NT_STATUS_GRACEFUL_DISCONNECT", NT_STATUS_GRACEFUL_DISCONNECT},
- {"NT_STATUS_ADDRESS_ALREADY_ASSOCIATED",
- NT_STATUS_ADDRESS_ALREADY_ASSOCIATED},
- {"NT_STATUS_ADDRESS_NOT_ASSOCIATED",
- NT_STATUS_ADDRESS_NOT_ASSOCIATED},
- {"NT_STATUS_CONNECTION_INVALID", NT_STATUS_CONNECTION_INVALID},
- {"NT_STATUS_CONNECTION_ACTIVE", NT_STATUS_CONNECTION_ACTIVE},
- {"NT_STATUS_NETWORK_UNREACHABLE", NT_STATUS_NETWORK_UNREACHABLE},
- {"NT_STATUS_HOST_UNREACHABLE", NT_STATUS_HOST_UNREACHABLE},
- {"NT_STATUS_PROTOCOL_UNREACHABLE", NT_STATUS_PROTOCOL_UNREACHABLE},
- {"NT_STATUS_PORT_UNREACHABLE", NT_STATUS_PORT_UNREACHABLE},
- {"NT_STATUS_REQUEST_ABORTED", NT_STATUS_REQUEST_ABORTED},
- {"NT_STATUS_CONNECTION_ABORTED", NT_STATUS_CONNECTION_ABORTED},
- {"NT_STATUS_BAD_COMPRESSION_BUFFER",
- NT_STATUS_BAD_COMPRESSION_BUFFER},
- {"NT_STATUS_USER_MAPPED_FILE", NT_STATUS_USER_MAPPED_FILE},
- {"NT_STATUS_AUDIT_FAILED", NT_STATUS_AUDIT_FAILED},
- {"NT_STATUS_TIMER_RESOLUTION_NOT_SET",
- NT_STATUS_TIMER_RESOLUTION_NOT_SET},
- {"NT_STATUS_CONNECTION_COUNT_LIMIT",
- NT_STATUS_CONNECTION_COUNT_LIMIT},
- {"NT_STATUS_LOGIN_TIME_RESTRICTION",
- NT_STATUS_LOGIN_TIME_RESTRICTION},
- {"NT_STATUS_LOGIN_WKSTA_RESTRICTION",
- NT_STATUS_LOGIN_WKSTA_RESTRICTION},
- {"NT_STATUS_IMAGE_MP_UP_MISMATCH", NT_STATUS_IMAGE_MP_UP_MISMATCH},
- {"NT_STATUS_INSUFFICIENT_LOGON_INFO",
- NT_STATUS_INSUFFICIENT_LOGON_INFO},
- {"NT_STATUS_BAD_DLL_ENTRYPOINT", NT_STATUS_BAD_DLL_ENTRYPOINT},
- {"NT_STATUS_BAD_SERVICE_ENTRYPOINT",
- NT_STATUS_BAD_SERVICE_ENTRYPOINT},
- {"NT_STATUS_LPC_REPLY_LOST", NT_STATUS_LPC_REPLY_LOST},
- {"NT_STATUS_IP_ADDRESS_CONFLICT1", NT_STATUS_IP_ADDRESS_CONFLICT1},
- {"NT_STATUS_IP_ADDRESS_CONFLICT2", NT_STATUS_IP_ADDRESS_CONFLICT2},
- {"NT_STATUS_REGISTRY_QUOTA_LIMIT", NT_STATUS_REGISTRY_QUOTA_LIMIT},
- {"NT_STATUS_PATH_NOT_COVERED", NT_STATUS_PATH_NOT_COVERED},
- {"NT_STATUS_NO_CALLBACK_ACTIVE", NT_STATUS_NO_CALLBACK_ACTIVE},
- {"NT_STATUS_LICENSE_QUOTA_EXCEEDED",
- NT_STATUS_LICENSE_QUOTA_EXCEEDED},
- {"NT_STATUS_PWD_TOO_SHORT", NT_STATUS_PWD_TOO_SHORT},
- {"NT_STATUS_PWD_TOO_RECENT", NT_STATUS_PWD_TOO_RECENT},
- {"NT_STATUS_PWD_HISTORY_CONFLICT", NT_STATUS_PWD_HISTORY_CONFLICT},
- {"NT_STATUS_PLUGPLAY_NO_DEVICE", NT_STATUS_PLUGPLAY_NO_DEVICE},
- {"NT_STATUS_UNSUPPORTED_COMPRESSION",
- NT_STATUS_UNSUPPORTED_COMPRESSION},
- {"NT_STATUS_INVALID_HW_PROFILE", NT_STATUS_INVALID_HW_PROFILE},
- {"NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH",
- NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH},
- {"NT_STATUS_DRIVER_ORDINAL_NOT_FOUND",
- NT_STATUS_DRIVER_ORDINAL_NOT_FOUND},
- {"NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND",
- NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND},
- {"NT_STATUS_RESOURCE_NOT_OWNED", NT_STATUS_RESOURCE_NOT_OWNED},
- {"NT_STATUS_TOO_MANY_LINKS", NT_STATUS_TOO_MANY_LINKS},
- {"NT_STATUS_QUOTA_LIST_INCONSISTENT",
- NT_STATUS_QUOTA_LIST_INCONSISTENT},
- {"NT_STATUS_FILE_IS_OFFLINE", NT_STATUS_FILE_IS_OFFLINE},
- {"NT_STATUS_VOLUME_DISMOUNTED", NT_STATUS_VOLUME_DISMOUNTED},
- {"NT_STATUS_NOT_A_REPARSE_POINT", NT_STATUS_NOT_A_REPARSE_POINT},
- {"NT_STATUS_DIRECTORY_IS_A_REPARSE_POINT",
- NT_STATUS_DIRECTORY_IS_A_REPARSE_POINT},
- {"NT_STATUS_ENCRYPTION_FAILED", NT_STATUS_ENCRYPTION_FAILED},
- {"NT_STATUS_DECRYPTION_FAILED", NT_STATUS_DECRYPTION_FAILED},
- {"NT_STATUS_RANGE_NOT_FOUND", NT_STATUS_RANGE_NOT_FOUND},
- {"NT_STATUS_NO_RECOVERY_POLICY", NT_STATUS_NO_RECOVERY_POLICY},
- {"NT_STATUS_NO_EFS", NT_STATUS_NO_EFS},
- {"NT_STATUS_WRONG_EFS", NT_STATUS_WRONG_EFS},
- {"NT_STATUS_NO_USER_KEYS", NT_STATUS_NO_USER_KEYS},
- {"NT_STATUS_VOLUME_NOT_UPGRADED", NT_STATUS_VOLUME_NOT_UPGRADED},
- {"NT_STATUS_NETWORK_SESSION_EXPIRED", NT_STATUS_NETWORK_SESSION_EXPIRED},
- {"NT_STATUS_NO_MORE_ENTRIES", NT_STATUS_NO_MORE_ENTRIES},
- {"NT_STATUS_MORE_ENTRIES", NT_STATUS_MORE_ENTRIES},
- {"NT_STATUS_SOME_NOT_MAPPED", NT_STATUS_SOME_NOT_MAPPED},
- {"NT_STATUS_NO_SUCH_JOB", NT_STATUS_NO_SUCH_JOB},
- {"NT_STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP",
- NT_STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP},
- {"NT_STATUS_OS2_INVALID_LEVEL", NT_STATUS_OS2_INVALID_LEVEL},
- {NULL, 0}
-};
diff --git a/fs/smb/client/nterr.h b/fs/smb/client/nterr.h
index 9a4a450c6571..a542029c6d34 100644
--- a/fs/smb/client/nterr.h
+++ b/fs/smb/client/nterr.h
@@ -15,13 +15,14 @@
#ifndef _NTERR_H
#define _NTERR_H
-struct nt_err_code_struct {
- char *nt_errstr;
- __u32 nt_errcode;
+/* NT status -> dos error map */
+struct ntstatus_to_dos_err {
+ __u8 dos_class;
+ __u16 dos_code;
+ __u32 ntstatus;
+ const char *nt_errstr;
};
-extern const struct nt_err_code_struct nt_errs[];
-
/* Win32 Error Codes. */
#define NT_ERROR_INVALID_PARAMETER 0x0057
#define NT_ERROR_INSUFFICIENT_BUFFER 0x007a
@@ -30,540 +31,545 @@ extern const struct nt_err_code_struct nt_errs[];
/*
* NTSTATUS Values extracted using a loop in smbclient then printing a netmon
* sniff to a file.
+ *
+ * The comment at the end of each definition indicates `dos_class`
+ * and `dos_code` fields of the `ntstatus_to_dos_map` array; it is
+ * used to generate the `smb1_mapping_table.c` file.
*/
-#define NT_STATUS_OK 0x0000
-#define NT_STATUS_PENDING 0x0103
-#define NT_STATUS_MORE_ENTRIES 0x0105
-#define NT_STATUS_SOME_NOT_MAPPED 0x0107
-#define NT_STATUS_NOTIFY_ENUM_DIR 0x010c
-#define NT_STATUS_BUFFER_OVERFLOW 0x80000005
-#define NT_STATUS_NO_MORE_ENTRIES 0x8000001a
-#define NT_STATUS_MEDIA_CHANGED 0x8000001c
-#define NT_STATUS_END_OF_MEDIA 0x8000001e
-#define NT_STATUS_MEDIA_CHECK 0x80000020
-#define NT_STATUS_NO_DATA_DETECTED 0x80000022
-#define NT_STATUS_STOPPED_ON_SYMLINK 0x8000002d
-#define NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288
-#define NT_STATUS_DEVICE_DOOR_OPEN 0x80000289
-#define NT_STATUS_UNSUCCESSFUL (0xC0000000 | 0x0001)
-#define NT_STATUS_NOT_IMPLEMENTED (0xC0000000 | 0x0002)
-#define NT_STATUS_INVALID_INFO_CLASS (0xC0000000 | 0x0003)
-#define NT_STATUS_INFO_LENGTH_MISMATCH (0xC0000000 | 0x0004)
-#define NT_STATUS_ACCESS_VIOLATION (0xC0000000 | 0x0005)
-#define NT_STATUS_IN_PAGE_ERROR (0xC0000000 | 0x0006)
-#define NT_STATUS_PAGEFILE_QUOTA (0xC0000000 | 0x0007)
-#define NT_STATUS_INVALID_HANDLE (0xC0000000 | 0x0008)
-#define NT_STATUS_BAD_INITIAL_STACK (0xC0000000 | 0x0009)
-#define NT_STATUS_BAD_INITIAL_PC (0xC0000000 | 0x000a)
-#define NT_STATUS_INVALID_CID (0xC0000000 | 0x000b)
-#define NT_STATUS_TIMER_NOT_CANCELED (0xC0000000 | 0x000c)
-#define NT_STATUS_INVALID_PARAMETER (0xC0000000 | 0x000d)
-#define NT_STATUS_NO_SUCH_DEVICE (0xC0000000 | 0x000e)
-#define NT_STATUS_NO_SUCH_FILE (0xC0000000 | 0x000f)
-#define NT_STATUS_INVALID_DEVICE_REQUEST (0xC0000000 | 0x0010)
-#define NT_STATUS_END_OF_FILE (0xC0000000 | 0x0011)
-#define NT_STATUS_WRONG_VOLUME (0xC0000000 | 0x0012)
-#define NT_STATUS_NO_MEDIA_IN_DEVICE (0xC0000000 | 0x0013)
-#define NT_STATUS_UNRECOGNIZED_MEDIA (0xC0000000 | 0x0014)
-#define NT_STATUS_NONEXISTENT_SECTOR (0xC0000000 | 0x0015)
-#define NT_STATUS_MORE_PROCESSING_REQUIRED (0xC0000000 | 0x0016)
-#define NT_STATUS_NO_MEMORY (0xC0000000 | 0x0017)
-#define NT_STATUS_CONFLICTING_ADDRESSES (0xC0000000 | 0x0018)
-#define NT_STATUS_NOT_MAPPED_VIEW (0xC0000000 | 0x0019)
-#define NT_STATUS_UNABLE_TO_FREE_VM (0xC0000000 | 0x001a)
-#define NT_STATUS_UNABLE_TO_DELETE_SECTION (0xC0000000 | 0x001b)
-#define NT_STATUS_INVALID_SYSTEM_SERVICE (0xC0000000 | 0x001c)
-#define NT_STATUS_ILLEGAL_INSTRUCTION (0xC0000000 | 0x001d)
-#define NT_STATUS_INVALID_LOCK_SEQUENCE (0xC0000000 | 0x001e)
-#define NT_STATUS_INVALID_VIEW_SIZE (0xC0000000 | 0x001f)
-#define NT_STATUS_INVALID_FILE_FOR_SECTION (0xC0000000 | 0x0020)
-#define NT_STATUS_ALREADY_COMMITTED (0xC0000000 | 0x0021)
-#define NT_STATUS_ACCESS_DENIED (0xC0000000 | 0x0022)
-#define NT_STATUS_BUFFER_TOO_SMALL (0xC0000000 | 0x0023)
-#define NT_STATUS_OBJECT_TYPE_MISMATCH (0xC0000000 | 0x0024)
-#define NT_STATUS_NONCONTINUABLE_EXCEPTION (0xC0000000 | 0x0025)
-#define NT_STATUS_INVALID_DISPOSITION (0xC0000000 | 0x0026)
-#define NT_STATUS_UNWIND (0xC0000000 | 0x0027)
-#define NT_STATUS_BAD_STACK (0xC0000000 | 0x0028)
-#define NT_STATUS_INVALID_UNWIND_TARGET (0xC0000000 | 0x0029)
-#define NT_STATUS_NOT_LOCKED (0xC0000000 | 0x002a)
-#define NT_STATUS_PARITY_ERROR (0xC0000000 | 0x002b)
-#define NT_STATUS_UNABLE_TO_DECOMMIT_VM (0xC0000000 | 0x002c)
-#define NT_STATUS_NOT_COMMITTED (0xC0000000 | 0x002d)
-#define NT_STATUS_INVALID_PORT_ATTRIBUTES (0xC0000000 | 0x002e)
-#define NT_STATUS_PORT_MESSAGE_TOO_LONG (0xC0000000 | 0x002f)
-#define NT_STATUS_INVALID_PARAMETER_MIX (0xC0000000 | 0x0030)
-#define NT_STATUS_INVALID_QUOTA_LOWER (0xC0000000 | 0x0031)
-#define NT_STATUS_DISK_CORRUPT_ERROR (0xC0000000 | 0x0032)
-#define NT_STATUS_OBJECT_NAME_INVALID (0xC0000000 | 0x0033)
-#define NT_STATUS_OBJECT_NAME_NOT_FOUND (0xC0000000 | 0x0034)
-#define NT_STATUS_OBJECT_NAME_COLLISION (0xC0000000 | 0x0035)
-#define NT_STATUS_HANDLE_NOT_WAITABLE (0xC0000000 | 0x0036)
-#define NT_STATUS_PORT_DISCONNECTED (0xC0000000 | 0x0037)
-#define NT_STATUS_DEVICE_ALREADY_ATTACHED (0xC0000000 | 0x0038)
-#define NT_STATUS_OBJECT_PATH_INVALID (0xC0000000 | 0x0039)
-#define NT_STATUS_OBJECT_PATH_NOT_FOUND (0xC0000000 | 0x003a)
-#define NT_STATUS_OBJECT_PATH_SYNTAX_BAD (0xC0000000 | 0x003b)
-#define NT_STATUS_DATA_OVERRUN (0xC0000000 | 0x003c)
-#define NT_STATUS_DATA_LATE_ERROR (0xC0000000 | 0x003d)
-#define NT_STATUS_DATA_ERROR (0xC0000000 | 0x003e)
-#define NT_STATUS_CRC_ERROR (0xC0000000 | 0x003f)
-#define NT_STATUS_SECTION_TOO_BIG (0xC0000000 | 0x0040)
-#define NT_STATUS_PORT_CONNECTION_REFUSED (0xC0000000 | 0x0041)
-#define NT_STATUS_INVALID_PORT_HANDLE (0xC0000000 | 0x0042)
-#define NT_STATUS_SHARING_VIOLATION (0xC0000000 | 0x0043)
-#define NT_STATUS_QUOTA_EXCEEDED (0xC0000000 | 0x0044)
-#define NT_STATUS_INVALID_PAGE_PROTECTION (0xC0000000 | 0x0045)
-#define NT_STATUS_MUTANT_NOT_OWNED (0xC0000000 | 0x0046)
-#define NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED (0xC0000000 | 0x0047)
-#define NT_STATUS_PORT_ALREADY_SET (0xC0000000 | 0x0048)
-#define NT_STATUS_SECTION_NOT_IMAGE (0xC0000000 | 0x0049)
-#define NT_STATUS_SUSPEND_COUNT_EXCEEDED (0xC0000000 | 0x004a)
-#define NT_STATUS_THREAD_IS_TERMINATING (0xC0000000 | 0x004b)
-#define NT_STATUS_BAD_WORKING_SET_LIMIT (0xC0000000 | 0x004c)
-#define NT_STATUS_INCOMPATIBLE_FILE_MAP (0xC0000000 | 0x004d)
-#define NT_STATUS_SECTION_PROTECTION (0xC0000000 | 0x004e)
-#define NT_STATUS_EAS_NOT_SUPPORTED (0xC0000000 | 0x004f)
-#define NT_STATUS_EA_TOO_LARGE (0xC0000000 | 0x0050)
-#define NT_STATUS_NONEXISTENT_EA_ENTRY (0xC0000000 | 0x0051)
-#define NT_STATUS_NO_EAS_ON_FILE (0xC0000000 | 0x0052)
-#define NT_STATUS_EA_CORRUPT_ERROR (0xC0000000 | 0x0053)
-#define NT_STATUS_FILE_LOCK_CONFLICT (0xC0000000 | 0x0054)
-#define NT_STATUS_LOCK_NOT_GRANTED (0xC0000000 | 0x0055)
-#define NT_STATUS_DELETE_PENDING (0xC0000000 | 0x0056)
-#define NT_STATUS_CTL_FILE_NOT_SUPPORTED (0xC0000000 | 0x0057)
-#define NT_STATUS_UNKNOWN_REVISION (0xC0000000 | 0x0058)
-#define NT_STATUS_REVISION_MISMATCH (0xC0000000 | 0x0059)
-#define NT_STATUS_INVALID_OWNER (0xC0000000 | 0x005a)
-#define NT_STATUS_INVALID_PRIMARY_GROUP (0xC0000000 | 0x005b)
-#define NT_STATUS_NO_IMPERSONATION_TOKEN (0xC0000000 | 0x005c)
-#define NT_STATUS_CANT_DISABLE_MANDATORY (0xC0000000 | 0x005d)
-#define NT_STATUS_NO_LOGON_SERVERS (0xC0000000 | 0x005e)
-#define NT_STATUS_NO_SUCH_LOGON_SESSION (0xC0000000 | 0x005f)
-#define NT_STATUS_NO_SUCH_PRIVILEGE (0xC0000000 | 0x0060)
-#define NT_STATUS_PRIVILEGE_NOT_HELD (0xC0000000 | 0x0061)
-#define NT_STATUS_INVALID_ACCOUNT_NAME (0xC0000000 | 0x0062)
-#define NT_STATUS_USER_EXISTS (0xC0000000 | 0x0063)
-#define NT_STATUS_NO_SUCH_USER (0xC0000000 | 0x0064)
-#define NT_STATUS_GROUP_EXISTS (0xC0000000 | 0x0065)
-#define NT_STATUS_NO_SUCH_GROUP (0xC0000000 | 0x0066)
-#define NT_STATUS_MEMBER_IN_GROUP (0xC0000000 | 0x0067)
-#define NT_STATUS_MEMBER_NOT_IN_GROUP (0xC0000000 | 0x0068)
-#define NT_STATUS_LAST_ADMIN (0xC0000000 | 0x0069)
-#define NT_STATUS_WRONG_PASSWORD (0xC0000000 | 0x006a)
-#define NT_STATUS_ILL_FORMED_PASSWORD (0xC0000000 | 0x006b)
-#define NT_STATUS_PASSWORD_RESTRICTION (0xC0000000 | 0x006c)
-#define NT_STATUS_LOGON_FAILURE (0xC0000000 | 0x006d)
-#define NT_STATUS_ACCOUNT_RESTRICTION (0xC0000000 | 0x006e)
-#define NT_STATUS_INVALID_LOGON_HOURS (0xC0000000 | 0x006f)
-#define NT_STATUS_INVALID_WORKSTATION (0xC0000000 | 0x0070)
-#define NT_STATUS_PASSWORD_EXPIRED (0xC0000000 | 0x0071)
-#define NT_STATUS_ACCOUNT_DISABLED (0xC0000000 | 0x0072)
-#define NT_STATUS_NONE_MAPPED (0xC0000000 | 0x0073)
-#define NT_STATUS_TOO_MANY_LUIDS_REQUESTED (0xC0000000 | 0x0074)
-#define NT_STATUS_LUIDS_EXHAUSTED (0xC0000000 | 0x0075)
-#define NT_STATUS_INVALID_SUB_AUTHORITY (0xC0000000 | 0x0076)
-#define NT_STATUS_INVALID_ACL (0xC0000000 | 0x0077)
-#define NT_STATUS_INVALID_SID (0xC0000000 | 0x0078)
-#define NT_STATUS_INVALID_SECURITY_DESCR (0xC0000000 | 0x0079)
-#define NT_STATUS_PROCEDURE_NOT_FOUND (0xC0000000 | 0x007a)
-#define NT_STATUS_INVALID_IMAGE_FORMAT (0xC0000000 | 0x007b)
-#define NT_STATUS_NO_TOKEN (0xC0000000 | 0x007c)
-#define NT_STATUS_BAD_INHERITANCE_ACL (0xC0000000 | 0x007d)
-#define NT_STATUS_RANGE_NOT_LOCKED (0xC0000000 | 0x007e)
-#define NT_STATUS_DISK_FULL (0xC0000000 | 0x007f)
-#define NT_STATUS_SERVER_DISABLED (0xC0000000 | 0x0080)
-#define NT_STATUS_SERVER_NOT_DISABLED (0xC0000000 | 0x0081)
-#define NT_STATUS_TOO_MANY_GUIDS_REQUESTED (0xC0000000 | 0x0082)
-#define NT_STATUS_GUIDS_EXHAUSTED (0xC0000000 | 0x0083)
-#define NT_STATUS_INVALID_ID_AUTHORITY (0xC0000000 | 0x0084)
-#define NT_STATUS_AGENTS_EXHAUSTED (0xC0000000 | 0x0085)
-#define NT_STATUS_INVALID_VOLUME_LABEL (0xC0000000 | 0x0086)
-#define NT_STATUS_SECTION_NOT_EXTENDED (0xC0000000 | 0x0087)
-#define NT_STATUS_NOT_MAPPED_DATA (0xC0000000 | 0x0088)
-#define NT_STATUS_RESOURCE_DATA_NOT_FOUND (0xC0000000 | 0x0089)
-#define NT_STATUS_RESOURCE_TYPE_NOT_FOUND (0xC0000000 | 0x008a)
-#define NT_STATUS_RESOURCE_NAME_NOT_FOUND (0xC0000000 | 0x008b)
-#define NT_STATUS_ARRAY_BOUNDS_EXCEEDED (0xC0000000 | 0x008c)
-#define NT_STATUS_FLOAT_DENORMAL_OPERAND (0xC0000000 | 0x008d)
-#define NT_STATUS_FLOAT_DIVIDE_BY_ZERO (0xC0000000 | 0x008e)
-#define NT_STATUS_FLOAT_INEXACT_RESULT (0xC0000000 | 0x008f)
-#define NT_STATUS_FLOAT_INVALID_OPERATION (0xC0000000 | 0x0090)
-#define NT_STATUS_FLOAT_OVERFLOW (0xC0000000 | 0x0091)
-#define NT_STATUS_FLOAT_STACK_CHECK (0xC0000000 | 0x0092)
-#define NT_STATUS_FLOAT_UNDERFLOW (0xC0000000 | 0x0093)
-#define NT_STATUS_INTEGER_DIVIDE_BY_ZERO (0xC0000000 | 0x0094)
-#define NT_STATUS_INTEGER_OVERFLOW (0xC0000000 | 0x0095)
-#define NT_STATUS_PRIVILEGED_INSTRUCTION (0xC0000000 | 0x0096)
-#define NT_STATUS_TOO_MANY_PAGING_FILES (0xC0000000 | 0x0097)
-#define NT_STATUS_FILE_INVALID (0xC0000000 | 0x0098)
-#define NT_STATUS_ALLOTTED_SPACE_EXCEEDED (0xC0000000 | 0x0099)
-#define NT_STATUS_INSUFFICIENT_RESOURCES (0xC0000000 | 0x009a)
-#define NT_STATUS_DFS_EXIT_PATH_FOUND (0xC0000000 | 0x009b)
-#define NT_STATUS_DEVICE_DATA_ERROR (0xC0000000 | 0x009c)
-#define NT_STATUS_DEVICE_NOT_CONNECTED (0xC0000000 | 0x009d)
-#define NT_STATUS_DEVICE_POWER_FAILURE (0xC0000000 | 0x009e)
-#define NT_STATUS_FREE_VM_NOT_AT_BASE (0xC0000000 | 0x009f)
-#define NT_STATUS_MEMORY_NOT_ALLOCATED (0xC0000000 | 0x00a0)
-#define NT_STATUS_WORKING_SET_QUOTA (0xC0000000 | 0x00a1)
-#define NT_STATUS_MEDIA_WRITE_PROTECTED (0xC0000000 | 0x00a2)
-#define NT_STATUS_DEVICE_NOT_READY (0xC0000000 | 0x00a3)
-#define NT_STATUS_INVALID_GROUP_ATTRIBUTES (0xC0000000 | 0x00a4)
-#define NT_STATUS_BAD_IMPERSONATION_LEVEL (0xC0000000 | 0x00a5)
-#define NT_STATUS_CANT_OPEN_ANONYMOUS (0xC0000000 | 0x00a6)
-#define NT_STATUS_BAD_VALIDATION_CLASS (0xC0000000 | 0x00a7)
-#define NT_STATUS_BAD_TOKEN_TYPE (0xC0000000 | 0x00a8)
-#define NT_STATUS_BAD_MASTER_BOOT_RECORD (0xC0000000 | 0x00a9)
-#define NT_STATUS_INSTRUCTION_MISALIGNMENT (0xC0000000 | 0x00aa)
-#define NT_STATUS_INSTANCE_NOT_AVAILABLE (0xC0000000 | 0x00ab)
-#define NT_STATUS_PIPE_NOT_AVAILABLE (0xC0000000 | 0x00ac)
-#define NT_STATUS_INVALID_PIPE_STATE (0xC0000000 | 0x00ad)
-#define NT_STATUS_PIPE_BUSY (0xC0000000 | 0x00ae)
-#define NT_STATUS_ILLEGAL_FUNCTION (0xC0000000 | 0x00af)
-#define NT_STATUS_PIPE_DISCONNECTED (0xC0000000 | 0x00b0)
-#define NT_STATUS_PIPE_CLOSING (0xC0000000 | 0x00b1)
-#define NT_STATUS_PIPE_CONNECTED (0xC0000000 | 0x00b2)
-#define NT_STATUS_PIPE_LISTENING (0xC0000000 | 0x00b3)
-#define NT_STATUS_INVALID_READ_MODE (0xC0000000 | 0x00b4)
-#define NT_STATUS_IO_TIMEOUT (0xC0000000 | 0x00b5)
-#define NT_STATUS_FILE_FORCED_CLOSED (0xC0000000 | 0x00b6)
-#define NT_STATUS_PROFILING_NOT_STARTED (0xC0000000 | 0x00b7)
-#define NT_STATUS_PROFILING_NOT_STOPPED (0xC0000000 | 0x00b8)
-#define NT_STATUS_COULD_NOT_INTERPRET (0xC0000000 | 0x00b9)
-#define NT_STATUS_FILE_IS_A_DIRECTORY (0xC0000000 | 0x00ba)
-#define NT_STATUS_NOT_SUPPORTED (0xC0000000 | 0x00bb)
-#define NT_STATUS_REMOTE_NOT_LISTENING (0xC0000000 | 0x00bc)
-#define NT_STATUS_DUPLICATE_NAME (0xC0000000 | 0x00bd)
-#define NT_STATUS_BAD_NETWORK_PATH (0xC0000000 | 0x00be)
-#define NT_STATUS_NETWORK_BUSY (0xC0000000 | 0x00bf)
-#define NT_STATUS_DEVICE_DOES_NOT_EXIST (0xC0000000 | 0x00c0)
-#define NT_STATUS_TOO_MANY_COMMANDS (0xC0000000 | 0x00c1)
-#define NT_STATUS_ADAPTER_HARDWARE_ERROR (0xC0000000 | 0x00c2)
-#define NT_STATUS_INVALID_NETWORK_RESPONSE (0xC0000000 | 0x00c3)
-#define NT_STATUS_UNEXPECTED_NETWORK_ERROR (0xC0000000 | 0x00c4)
-#define NT_STATUS_BAD_REMOTE_ADAPTER (0xC0000000 | 0x00c5)
-#define NT_STATUS_PRINT_QUEUE_FULL (0xC0000000 | 0x00c6)
-#define NT_STATUS_NO_SPOOL_SPACE (0xC0000000 | 0x00c7)
-#define NT_STATUS_PRINT_CANCELLED (0xC0000000 | 0x00c8)
-#define NT_STATUS_NETWORK_NAME_DELETED (0xC0000000 | 0x00c9)
-#define NT_STATUS_NETWORK_ACCESS_DENIED (0xC0000000 | 0x00ca)
-#define NT_STATUS_BAD_DEVICE_TYPE (0xC0000000 | 0x00cb)
-#define NT_STATUS_BAD_NETWORK_NAME (0xC0000000 | 0x00cc)
-#define NT_STATUS_TOO_MANY_NAMES (0xC0000000 | 0x00cd)
-#define NT_STATUS_TOO_MANY_SESSIONS (0xC0000000 | 0x00ce)
-#define NT_STATUS_SHARING_PAUSED (0xC0000000 | 0x00cf)
-#define NT_STATUS_REQUEST_NOT_ACCEPTED (0xC0000000 | 0x00d0)
-#define NT_STATUS_REDIRECTOR_PAUSED (0xC0000000 | 0x00d1)
-#define NT_STATUS_NET_WRITE_FAULT (0xC0000000 | 0x00d2)
-#define NT_STATUS_PROFILING_AT_LIMIT (0xC0000000 | 0x00d3)
-#define NT_STATUS_NOT_SAME_DEVICE (0xC0000000 | 0x00d4)
-#define NT_STATUS_FILE_RENAMED (0xC0000000 | 0x00d5)
-#define NT_STATUS_VIRTUAL_CIRCUIT_CLOSED (0xC0000000 | 0x00d6)
-#define NT_STATUS_NO_SECURITY_ON_OBJECT (0xC0000000 | 0x00d7)
-#define NT_STATUS_CANT_WAIT (0xC0000000 | 0x00d8)
-#define NT_STATUS_PIPE_EMPTY (0xC0000000 | 0x00d9)
-#define NT_STATUS_CANT_ACCESS_DOMAIN_INFO (0xC0000000 | 0x00da)
-#define NT_STATUS_CANT_TERMINATE_SELF (0xC0000000 | 0x00db)
-#define NT_STATUS_INVALID_SERVER_STATE (0xC0000000 | 0x00dc)
-#define NT_STATUS_INVALID_DOMAIN_STATE (0xC0000000 | 0x00dd)
-#define NT_STATUS_INVALID_DOMAIN_ROLE (0xC0000000 | 0x00de)
-#define NT_STATUS_NO_SUCH_DOMAIN (0xC0000000 | 0x00df)
-#define NT_STATUS_DOMAIN_EXISTS (0xC0000000 | 0x00e0)
-#define NT_STATUS_DOMAIN_LIMIT_EXCEEDED (0xC0000000 | 0x00e1)
-#define NT_STATUS_OPLOCK_NOT_GRANTED (0xC0000000 | 0x00e2)
-#define NT_STATUS_INVALID_OPLOCK_PROTOCOL (0xC0000000 | 0x00e3)
-#define NT_STATUS_INTERNAL_DB_CORRUPTION (0xC0000000 | 0x00e4)
-#define NT_STATUS_INTERNAL_ERROR (0xC0000000 | 0x00e5)
-#define NT_STATUS_GENERIC_NOT_MAPPED (0xC0000000 | 0x00e6)
-#define NT_STATUS_BAD_DESCRIPTOR_FORMAT (0xC0000000 | 0x00e7)
-#define NT_STATUS_INVALID_USER_BUFFER (0xC0000000 | 0x00e8)
-#define NT_STATUS_UNEXPECTED_IO_ERROR (0xC0000000 | 0x00e9)
-#define NT_STATUS_UNEXPECTED_MM_CREATE_ERR (0xC0000000 | 0x00ea)
-#define NT_STATUS_UNEXPECTED_MM_MAP_ERROR (0xC0000000 | 0x00eb)
-#define NT_STATUS_UNEXPECTED_MM_EXTEND_ERR (0xC0000000 | 0x00ec)
-#define NT_STATUS_NOT_LOGON_PROCESS (0xC0000000 | 0x00ed)
-#define NT_STATUS_LOGON_SESSION_EXISTS (0xC0000000 | 0x00ee)
-#define NT_STATUS_INVALID_PARAMETER_1 (0xC0000000 | 0x00ef)
-#define NT_STATUS_INVALID_PARAMETER_2 (0xC0000000 | 0x00f0)
-#define NT_STATUS_INVALID_PARAMETER_3 (0xC0000000 | 0x00f1)
-#define NT_STATUS_INVALID_PARAMETER_4 (0xC0000000 | 0x00f2)
-#define NT_STATUS_INVALID_PARAMETER_5 (0xC0000000 | 0x00f3)
-#define NT_STATUS_INVALID_PARAMETER_6 (0xC0000000 | 0x00f4)
-#define NT_STATUS_INVALID_PARAMETER_7 (0xC0000000 | 0x00f5)
-#define NT_STATUS_INVALID_PARAMETER_8 (0xC0000000 | 0x00f6)
-#define NT_STATUS_INVALID_PARAMETER_9 (0xC0000000 | 0x00f7)
-#define NT_STATUS_INVALID_PARAMETER_10 (0xC0000000 | 0x00f8)
-#define NT_STATUS_INVALID_PARAMETER_11 (0xC0000000 | 0x00f9)
-#define NT_STATUS_INVALID_PARAMETER_12 (0xC0000000 | 0x00fa)
-#define NT_STATUS_REDIRECTOR_NOT_STARTED (0xC0000000 | 0x00fb)
-#define NT_STATUS_REDIRECTOR_STARTED (0xC0000000 | 0x00fc)
-#define NT_STATUS_STACK_OVERFLOW (0xC0000000 | 0x00fd)
-#define NT_STATUS_NO_SUCH_PACKAGE (0xC0000000 | 0x00fe)
-#define NT_STATUS_BAD_FUNCTION_TABLE (0xC0000000 | 0x00ff)
-#define NT_STATUS_VARIABLE_NOT_FOUND (0xC0000000 | 0x0100)
-#define NT_STATUS_DIRECTORY_NOT_EMPTY (0xC0000000 | 0x0101)
-#define NT_STATUS_FILE_CORRUPT_ERROR (0xC0000000 | 0x0102)
-#define NT_STATUS_NOT_A_DIRECTORY (0xC0000000 | 0x0103)
-#define NT_STATUS_BAD_LOGON_SESSION_STATE (0xC0000000 | 0x0104)
-#define NT_STATUS_LOGON_SESSION_COLLISION (0xC0000000 | 0x0105)
-#define NT_STATUS_NAME_TOO_LONG (0xC0000000 | 0x0106)
-#define NT_STATUS_FILES_OPEN (0xC0000000 | 0x0107)
-#define NT_STATUS_CONNECTION_IN_USE (0xC0000000 | 0x0108)
-#define NT_STATUS_MESSAGE_NOT_FOUND (0xC0000000 | 0x0109)
-#define NT_STATUS_PROCESS_IS_TERMINATING (0xC0000000 | 0x010a)
-#define NT_STATUS_INVALID_LOGON_TYPE (0xC0000000 | 0x010b)
-#define NT_STATUS_NO_GUID_TRANSLATION (0xC0000000 | 0x010c)
-#define NT_STATUS_CANNOT_IMPERSONATE (0xC0000000 | 0x010d)
-#define NT_STATUS_IMAGE_ALREADY_LOADED (0xC0000000 | 0x010e)
-#define NT_STATUS_ABIOS_NOT_PRESENT (0xC0000000 | 0x010f)
-#define NT_STATUS_ABIOS_LID_NOT_EXIST (0xC0000000 | 0x0110)
-#define NT_STATUS_ABIOS_LID_ALREADY_OWNED (0xC0000000 | 0x0111)
-#define NT_STATUS_ABIOS_NOT_LID_OWNER (0xC0000000 | 0x0112)
-#define NT_STATUS_ABIOS_INVALID_COMMAND (0xC0000000 | 0x0113)
-#define NT_STATUS_ABIOS_INVALID_LID (0xC0000000 | 0x0114)
-#define NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE (0xC0000000 | 0x0115)
-#define NT_STATUS_ABIOS_INVALID_SELECTOR (0xC0000000 | 0x0116)
-#define NT_STATUS_NO_LDT (0xC0000000 | 0x0117)
-#define NT_STATUS_INVALID_LDT_SIZE (0xC0000000 | 0x0118)
-#define NT_STATUS_INVALID_LDT_OFFSET (0xC0000000 | 0x0119)
-#define NT_STATUS_INVALID_LDT_DESCRIPTOR (0xC0000000 | 0x011a)
-#define NT_STATUS_INVALID_IMAGE_NE_FORMAT (0xC0000000 | 0x011b)
-#define NT_STATUS_RXACT_INVALID_STATE (0xC0000000 | 0x011c)
-#define NT_STATUS_RXACT_COMMIT_FAILURE (0xC0000000 | 0x011d)
-#define NT_STATUS_MAPPED_FILE_SIZE_ZERO (0xC0000000 | 0x011e)
-#define NT_STATUS_TOO_MANY_OPENED_FILES (0xC0000000 | 0x011f)
-#define NT_STATUS_CANCELLED (0xC0000000 | 0x0120)
-#define NT_STATUS_CANNOT_DELETE (0xC0000000 | 0x0121)
-#define NT_STATUS_INVALID_COMPUTER_NAME (0xC0000000 | 0x0122)
-#define NT_STATUS_FILE_DELETED (0xC0000000 | 0x0123)
-#define NT_STATUS_SPECIAL_ACCOUNT (0xC0000000 | 0x0124)
-#define NT_STATUS_SPECIAL_GROUP (0xC0000000 | 0x0125)
-#define NT_STATUS_SPECIAL_USER (0xC0000000 | 0x0126)
-#define NT_STATUS_MEMBERS_PRIMARY_GROUP (0xC0000000 | 0x0127)
-#define NT_STATUS_FILE_CLOSED (0xC0000000 | 0x0128)
-#define NT_STATUS_TOO_MANY_THREADS (0xC0000000 | 0x0129)
-#define NT_STATUS_THREAD_NOT_IN_PROCESS (0xC0000000 | 0x012a)
-#define NT_STATUS_TOKEN_ALREADY_IN_USE (0xC0000000 | 0x012b)
-#define NT_STATUS_PAGEFILE_QUOTA_EXCEEDED (0xC0000000 | 0x012c)
-#define NT_STATUS_COMMITMENT_LIMIT (0xC0000000 | 0x012d)
-#define NT_STATUS_INVALID_IMAGE_LE_FORMAT (0xC0000000 | 0x012e)
-#define NT_STATUS_INVALID_IMAGE_NOT_MZ (0xC0000000 | 0x012f)
-#define NT_STATUS_INVALID_IMAGE_PROTECT (0xC0000000 | 0x0130)
-#define NT_STATUS_INVALID_IMAGE_WIN_16 (0xC0000000 | 0x0131)
-#define NT_STATUS_LOGON_SERVER_CONFLICT (0xC0000000 | 0x0132)
-#define NT_STATUS_TIME_DIFFERENCE_AT_DC (0xC0000000 | 0x0133)
-#define NT_STATUS_SYNCHRONIZATION_REQUIRED (0xC0000000 | 0x0134)
-#define NT_STATUS_DLL_NOT_FOUND (0xC0000000 | 0x0135)
-#define NT_STATUS_OPEN_FAILED (0xC0000000 | 0x0136)
-#define NT_STATUS_IO_PRIVILEGE_FAILED (0xC0000000 | 0x0137)
-#define NT_STATUS_ORDINAL_NOT_FOUND (0xC0000000 | 0x0138)
-#define NT_STATUS_ENTRYPOINT_NOT_FOUND (0xC0000000 | 0x0139)
-#define NT_STATUS_CONTROL_C_EXIT (0xC0000000 | 0x013a)
-#define NT_STATUS_LOCAL_DISCONNECT (0xC0000000 | 0x013b)
-#define NT_STATUS_REMOTE_DISCONNECT (0xC0000000 | 0x013c)
-#define NT_STATUS_REMOTE_RESOURCES (0xC0000000 | 0x013d)
-#define NT_STATUS_LINK_FAILED (0xC0000000 | 0x013e)
-#define NT_STATUS_LINK_TIMEOUT (0xC0000000 | 0x013f)
-#define NT_STATUS_INVALID_CONNECTION (0xC0000000 | 0x0140)
-#define NT_STATUS_INVALID_ADDRESS (0xC0000000 | 0x0141)
-#define NT_STATUS_DLL_INIT_FAILED (0xC0000000 | 0x0142)
-#define NT_STATUS_MISSING_SYSTEMFILE (0xC0000000 | 0x0143)
-#define NT_STATUS_UNHANDLED_EXCEPTION (0xC0000000 | 0x0144)
-#define NT_STATUS_APP_INIT_FAILURE (0xC0000000 | 0x0145)
-#define NT_STATUS_PAGEFILE_CREATE_FAILED (0xC0000000 | 0x0146)
-#define NT_STATUS_NO_PAGEFILE (0xC0000000 | 0x0147)
-#define NT_STATUS_INVALID_LEVEL (0xC0000000 | 0x0148)
-#define NT_STATUS_WRONG_PASSWORD_CORE (0xC0000000 | 0x0149)
-#define NT_STATUS_ILLEGAL_FLOAT_CONTEXT (0xC0000000 | 0x014a)
-#define NT_STATUS_PIPE_BROKEN (0xC0000000 | 0x014b)
-#define NT_STATUS_REGISTRY_CORRUPT (0xC0000000 | 0x014c)
-#define NT_STATUS_REGISTRY_IO_FAILED (0xC0000000 | 0x014d)
-#define NT_STATUS_NO_EVENT_PAIR (0xC0000000 | 0x014e)
-#define NT_STATUS_UNRECOGNIZED_VOLUME (0xC0000000 | 0x014f)
-#define NT_STATUS_SERIAL_NO_DEVICE_INITED (0xC0000000 | 0x0150)
-#define NT_STATUS_NO_SUCH_ALIAS (0xC0000000 | 0x0151)
-#define NT_STATUS_MEMBER_NOT_IN_ALIAS (0xC0000000 | 0x0152)
-#define NT_STATUS_MEMBER_IN_ALIAS (0xC0000000 | 0x0153)
-#define NT_STATUS_ALIAS_EXISTS (0xC0000000 | 0x0154)
-#define NT_STATUS_LOGON_NOT_GRANTED (0xC0000000 | 0x0155)
-#define NT_STATUS_TOO_MANY_SECRETS (0xC0000000 | 0x0156)
-#define NT_STATUS_SECRET_TOO_LONG (0xC0000000 | 0x0157)
-#define NT_STATUS_INTERNAL_DB_ERROR (0xC0000000 | 0x0158)
-#define NT_STATUS_FULLSCREEN_MODE (0xC0000000 | 0x0159)
-#define NT_STATUS_TOO_MANY_CONTEXT_IDS (0xC0000000 | 0x015a)
-#define NT_STATUS_LOGON_TYPE_NOT_GRANTED (0xC0000000 | 0x015b)
-#define NT_STATUS_NOT_REGISTRY_FILE (0xC0000000 | 0x015c)
-#define NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED (0xC0000000 | 0x015d)
-#define NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR (0xC0000000 | 0x015e)
-#define NT_STATUS_FT_MISSING_MEMBER (0xC0000000 | 0x015f)
-#define NT_STATUS_ILL_FORMED_SERVICE_ENTRY (0xC0000000 | 0x0160)
-#define NT_STATUS_ILLEGAL_CHARACTER (0xC0000000 | 0x0161)
-#define NT_STATUS_UNMAPPABLE_CHARACTER (0xC0000000 | 0x0162)
-#define NT_STATUS_UNDEFINED_CHARACTER (0xC0000000 | 0x0163)
-#define NT_STATUS_FLOPPY_VOLUME (0xC0000000 | 0x0164)
-#define NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND (0xC0000000 | 0x0165)
-#define NT_STATUS_FLOPPY_WRONG_CYLINDER (0xC0000000 | 0x0166)
-#define NT_STATUS_FLOPPY_UNKNOWN_ERROR (0xC0000000 | 0x0167)
-#define NT_STATUS_FLOPPY_BAD_REGISTERS (0xC0000000 | 0x0168)
-#define NT_STATUS_DISK_RECALIBRATE_FAILED (0xC0000000 | 0x0169)
-#define NT_STATUS_DISK_OPERATION_FAILED (0xC0000000 | 0x016a)
-#define NT_STATUS_DISK_RESET_FAILED (0xC0000000 | 0x016b)
-#define NT_STATUS_SHARED_IRQ_BUSY (0xC0000000 | 0x016c)
-#define NT_STATUS_FT_ORPHANING (0xC0000000 | 0x016d)
-#define NT_STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT (0xC0000000 | 0x016e)
-#define NT_STATUS_PARTITION_FAILURE (0xC0000000 | 0x0172)
-#define NT_STATUS_INVALID_BLOCK_LENGTH (0xC0000000 | 0x0173)
-#define NT_STATUS_DEVICE_NOT_PARTITIONED (0xC0000000 | 0x0174)
-#define NT_STATUS_UNABLE_TO_LOCK_MEDIA (0xC0000000 | 0x0175)
-#define NT_STATUS_UNABLE_TO_UNLOAD_MEDIA (0xC0000000 | 0x0176)
-#define NT_STATUS_EOM_OVERFLOW (0xC0000000 | 0x0177)
-#define NT_STATUS_NO_MEDIA (0xC0000000 | 0x0178)
-#define NT_STATUS_NO_SUCH_MEMBER (0xC0000000 | 0x017a)
-#define NT_STATUS_INVALID_MEMBER (0xC0000000 | 0x017b)
-#define NT_STATUS_KEY_DELETED (0xC0000000 | 0x017c)
-#define NT_STATUS_NO_LOG_SPACE (0xC0000000 | 0x017d)
-#define NT_STATUS_TOO_MANY_SIDS (0xC0000000 | 0x017e)
-#define NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED (0xC0000000 | 0x017f)
-#define NT_STATUS_KEY_HAS_CHILDREN (0xC0000000 | 0x0180)
-#define NT_STATUS_CHILD_MUST_BE_VOLATILE (0xC0000000 | 0x0181)
-#define NT_STATUS_DEVICE_CONFIGURATION_ERROR (0xC0000000 | 0x0182)
-#define NT_STATUS_DRIVER_INTERNAL_ERROR (0xC0000000 | 0x0183)
-#define NT_STATUS_INVALID_DEVICE_STATE (0xC0000000 | 0x0184)
-#define NT_STATUS_IO_DEVICE_ERROR (0xC0000000 | 0x0185)
-#define NT_STATUS_DEVICE_PROTOCOL_ERROR (0xC0000000 | 0x0186)
-#define NT_STATUS_BACKUP_CONTROLLER (0xC0000000 | 0x0187)
-#define NT_STATUS_LOG_FILE_FULL (0xC0000000 | 0x0188)
-#define NT_STATUS_TOO_LATE (0xC0000000 | 0x0189)
-#define NT_STATUS_NO_TRUST_LSA_SECRET (0xC0000000 | 0x018a)
-#define NT_STATUS_NO_TRUST_SAM_ACCOUNT (0xC0000000 | 0x018b)
-#define NT_STATUS_TRUSTED_DOMAIN_FAILURE (0xC0000000 | 0x018c)
-#define NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE (0xC0000000 | 0x018d)
-#define NT_STATUS_EVENTLOG_FILE_CORRUPT (0xC0000000 | 0x018e)
-#define NT_STATUS_EVENTLOG_CANT_START (0xC0000000 | 0x018f)
-#define NT_STATUS_TRUST_FAILURE (0xC0000000 | 0x0190)
-#define NT_STATUS_MUTANT_LIMIT_EXCEEDED (0xC0000000 | 0x0191)
-#define NT_STATUS_NETLOGON_NOT_STARTED (0xC0000000 | 0x0192)
-#define NT_STATUS_ACCOUNT_EXPIRED (0xC0000000 | 0x0193)
-#define NT_STATUS_POSSIBLE_DEADLOCK (0xC0000000 | 0x0194)
-#define NT_STATUS_NETWORK_CREDENTIAL_CONFLICT (0xC0000000 | 0x0195)
-#define NT_STATUS_REMOTE_SESSION_LIMIT (0xC0000000 | 0x0196)
-#define NT_STATUS_EVENTLOG_FILE_CHANGED (0xC0000000 | 0x0197)
-#define NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT (0xC0000000 | 0x0198)
-#define NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT (0xC0000000 | 0x0199)
-#define NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT (0xC0000000 | 0x019a)
-#define NT_STATUS_DOMAIN_TRUST_INCONSISTENT (0xC0000000 | 0x019b)
-#define NT_STATUS_FS_DRIVER_REQUIRED (0xC0000000 | 0x019c)
-#define NT_STATUS_INVALID_LOCK_RANGE (0xC0000000 | 0x01a1)
-#define NT_STATUS_NO_USER_SESSION_KEY (0xC0000000 | 0x0202)
-#define NT_STATUS_USER_SESSION_DELETED (0xC0000000 | 0x0203)
-#define NT_STATUS_RESOURCE_LANG_NOT_FOUND (0xC0000000 | 0x0204)
-#define NT_STATUS_INSUFF_SERVER_RESOURCES (0xC0000000 | 0x0205)
-#define NT_STATUS_INVALID_BUFFER_SIZE (0xC0000000 | 0x0206)
-#define NT_STATUS_INVALID_ADDRESS_COMPONENT (0xC0000000 | 0x0207)
-#define NT_STATUS_INVALID_ADDRESS_WILDCARD (0xC0000000 | 0x0208)
-#define NT_STATUS_TOO_MANY_ADDRESSES (0xC0000000 | 0x0209)
-#define NT_STATUS_ADDRESS_ALREADY_EXISTS (0xC0000000 | 0x020a)
-#define NT_STATUS_ADDRESS_CLOSED (0xC0000000 | 0x020b)
-#define NT_STATUS_CONNECTION_DISCONNECTED (0xC0000000 | 0x020c)
-#define NT_STATUS_CONNECTION_RESET (0xC0000000 | 0x020d)
-#define NT_STATUS_TOO_MANY_NODES (0xC0000000 | 0x020e)
-#define NT_STATUS_TRANSACTION_ABORTED (0xC0000000 | 0x020f)
-#define NT_STATUS_TRANSACTION_TIMED_OUT (0xC0000000 | 0x0210)
-#define NT_STATUS_TRANSACTION_NO_RELEASE (0xC0000000 | 0x0211)
-#define NT_STATUS_TRANSACTION_NO_MATCH (0xC0000000 | 0x0212)
-#define NT_STATUS_TRANSACTION_RESPONDED (0xC0000000 | 0x0213)
-#define NT_STATUS_TRANSACTION_INVALID_ID (0xC0000000 | 0x0214)
-#define NT_STATUS_TRANSACTION_INVALID_TYPE (0xC0000000 | 0x0215)
-#define NT_STATUS_NOT_SERVER_SESSION (0xC0000000 | 0x0216)
-#define NT_STATUS_NOT_CLIENT_SESSION (0xC0000000 | 0x0217)
-#define NT_STATUS_CANNOT_LOAD_REGISTRY_FILE (0xC0000000 | 0x0218)
-#define NT_STATUS_DEBUG_ATTACH_FAILED (0xC0000000 | 0x0219)
-#define NT_STATUS_SYSTEM_PROCESS_TERMINATED (0xC0000000 | 0x021a)
-#define NT_STATUS_DATA_NOT_ACCEPTED (0xC0000000 | 0x021b)
-#define NT_STATUS_NO_BROWSER_SERVERS_FOUND (0xC0000000 | 0x021c)
-#define NT_STATUS_VDM_HARD_ERROR (0xC0000000 | 0x021d)
-#define NT_STATUS_DRIVER_CANCEL_TIMEOUT (0xC0000000 | 0x021e)
-#define NT_STATUS_REPLY_MESSAGE_MISMATCH (0xC0000000 | 0x021f)
-#define NT_STATUS_MAPPED_ALIGNMENT (0xC0000000 | 0x0220)
-#define NT_STATUS_IMAGE_CHECKSUM_MISMATCH (0xC0000000 | 0x0221)
-#define NT_STATUS_LOST_WRITEBEHIND_DATA (0xC0000000 | 0x0222)
-#define NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID (0xC0000000 | 0x0223)
-#define NT_STATUS_PASSWORD_MUST_CHANGE (0xC0000000 | 0x0224)
-#define NT_STATUS_NOT_FOUND (0xC0000000 | 0x0225)
-#define NT_STATUS_NOT_TINY_STREAM (0xC0000000 | 0x0226)
-#define NT_STATUS_RECOVERY_FAILURE (0xC0000000 | 0x0227)
-#define NT_STATUS_STACK_OVERFLOW_READ (0xC0000000 | 0x0228)
-#define NT_STATUS_FAIL_CHECK (0xC0000000 | 0x0229)
-#define NT_STATUS_DUPLICATE_OBJECTID (0xC0000000 | 0x022a)
-#define NT_STATUS_OBJECTID_EXISTS (0xC0000000 | 0x022b)
-#define NT_STATUS_CONVERT_TO_LARGE (0xC0000000 | 0x022c)
-#define NT_STATUS_RETRY (0xC0000000 | 0x022d)
-#define NT_STATUS_FOUND_OUT_OF_SCOPE (0xC0000000 | 0x022e)
-#define NT_STATUS_ALLOCATE_BUCKET (0xC0000000 | 0x022f)
-#define NT_STATUS_PROPSET_NOT_FOUND (0xC0000000 | 0x0230)
-#define NT_STATUS_MARSHALL_OVERFLOW (0xC0000000 | 0x0231)
-#define NT_STATUS_INVALID_VARIANT (0xC0000000 | 0x0232)
-#define NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND (0xC0000000 | 0x0233)
-#define NT_STATUS_ACCOUNT_LOCKED_OUT (0xC0000000 | 0x0234)
-#define NT_STATUS_HANDLE_NOT_CLOSABLE (0xC0000000 | 0x0235)
-#define NT_STATUS_CONNECTION_REFUSED (0xC0000000 | 0x0236)
-#define NT_STATUS_GRACEFUL_DISCONNECT (0xC0000000 | 0x0237)
-#define NT_STATUS_ADDRESS_ALREADY_ASSOCIATED (0xC0000000 | 0x0238)
-#define NT_STATUS_ADDRESS_NOT_ASSOCIATED (0xC0000000 | 0x0239)
-#define NT_STATUS_CONNECTION_INVALID (0xC0000000 | 0x023a)
-#define NT_STATUS_CONNECTION_ACTIVE (0xC0000000 | 0x023b)
-#define NT_STATUS_NETWORK_UNREACHABLE (0xC0000000 | 0x023c)
-#define NT_STATUS_HOST_UNREACHABLE (0xC0000000 | 0x023d)
-#define NT_STATUS_PROTOCOL_UNREACHABLE (0xC0000000 | 0x023e)
-#define NT_STATUS_PORT_UNREACHABLE (0xC0000000 | 0x023f)
-#define NT_STATUS_REQUEST_ABORTED (0xC0000000 | 0x0240)
-#define NT_STATUS_CONNECTION_ABORTED (0xC0000000 | 0x0241)
-#define NT_STATUS_BAD_COMPRESSION_BUFFER (0xC0000000 | 0x0242)
-#define NT_STATUS_USER_MAPPED_FILE (0xC0000000 | 0x0243)
-#define NT_STATUS_AUDIT_FAILED (0xC0000000 | 0x0244)
-#define NT_STATUS_TIMER_RESOLUTION_NOT_SET (0xC0000000 | 0x0245)
-#define NT_STATUS_CONNECTION_COUNT_LIMIT (0xC0000000 | 0x0246)
-#define NT_STATUS_LOGIN_TIME_RESTRICTION (0xC0000000 | 0x0247)
-#define NT_STATUS_LOGIN_WKSTA_RESTRICTION (0xC0000000 | 0x0248)
-#define NT_STATUS_IMAGE_MP_UP_MISMATCH (0xC0000000 | 0x0249)
-#define NT_STATUS_INSUFFICIENT_LOGON_INFO (0xC0000000 | 0x0250)
-#define NT_STATUS_BAD_DLL_ENTRYPOINT (0xC0000000 | 0x0251)
-#define NT_STATUS_BAD_SERVICE_ENTRYPOINT (0xC0000000 | 0x0252)
-#define NT_STATUS_LPC_REPLY_LOST (0xC0000000 | 0x0253)
-#define NT_STATUS_IP_ADDRESS_CONFLICT1 (0xC0000000 | 0x0254)
-#define NT_STATUS_IP_ADDRESS_CONFLICT2 (0xC0000000 | 0x0255)
-#define NT_STATUS_REGISTRY_QUOTA_LIMIT (0xC0000000 | 0x0256)
-#define NT_STATUS_PATH_NOT_COVERED (0xC0000000 | 0x0257)
-#define NT_STATUS_NO_CALLBACK_ACTIVE (0xC0000000 | 0x0258)
-#define NT_STATUS_LICENSE_QUOTA_EXCEEDED (0xC0000000 | 0x0259)
-#define NT_STATUS_PWD_TOO_SHORT (0xC0000000 | 0x025a)
-#define NT_STATUS_PWD_TOO_RECENT (0xC0000000 | 0x025b)
-#define NT_STATUS_PWD_HISTORY_CONFLICT (0xC0000000 | 0x025c)
-#define NT_STATUS_PLUGPLAY_NO_DEVICE (0xC0000000 | 0x025e)
-#define NT_STATUS_UNSUPPORTED_COMPRESSION (0xC0000000 | 0x025f)
-#define NT_STATUS_INVALID_HW_PROFILE (0xC0000000 | 0x0260)
-#define NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH (0xC0000000 | 0x0261)
-#define NT_STATUS_DRIVER_ORDINAL_NOT_FOUND (0xC0000000 | 0x0262)
-#define NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND (0xC0000000 | 0x0263)
-#define NT_STATUS_RESOURCE_NOT_OWNED (0xC0000000 | 0x0264)
-#define NT_STATUS_TOO_MANY_LINKS (0xC0000000 | 0x0265)
-#define NT_STATUS_QUOTA_LIST_INCONSISTENT (0xC0000000 | 0x0266)
-#define NT_STATUS_FILE_IS_OFFLINE (0xC0000000 | 0x0267)
-#define NT_STATUS_VOLUME_DISMOUNTED (0xC0000000 | 0x026e)
-#define NT_STATUS_NOT_A_REPARSE_POINT (0xC0000000 | 0x0275)
-#define NT_STATUS_DIRECTORY_IS_A_REPARSE_POINT (0xC0000000 | 0x0281)
-#define NT_STATUS_ENCRYPTION_FAILED (0xC0000000 | 0x028a)
-#define NT_STATUS_DECRYPTION_FAILED (0xC0000000 | 0x028b)
-#define NT_STATUS_RANGE_NOT_FOUND (0xC0000000 | 0x028c)
-#define NT_STATUS_NO_RECOVERY_POLICY (0xC0000000 | 0x028d)
-#define NT_STATUS_NO_EFS (0xC0000000 | 0x028e)
-#define NT_STATUS_WRONG_EFS (0xC0000000 | 0x028f)
-#define NT_STATUS_NO_USER_KEYS (0xC0000000 | 0x0290)
-#define NT_STATUS_VOLUME_NOT_UPGRADED (0xC0000000 | 0x029c)
-#define NT_STATUS_NETWORK_SESSION_EXPIRED (0xC0000000 | 0x035c)
-#define NT_STATUS_NO_SUCH_JOB (0xC0000000 | 0xEDE) /* scheduler */
-#define NT_STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP (0xC0000000 | 0x5D0000)
-#define NT_STATUS_OS2_INVALID_LEVEL 0x007c0001
+#define NT_STATUS_OK 0x0000 // SUCCESS, 0
+#define NT_STATUS_PENDING 0x0103 // ERRHRD, ERRgeneral
+#define NT_STATUS_MORE_ENTRIES 0x0105 // ERRHRD, ERRgeneral
+#define NT_STATUS_SOME_NOT_MAPPED 0x0107 // ERRHRD, ERRgeneral
+#define NT_STATUS_NOTIFY_ENUM_DIR 0x010c // ERRSRV, ERR_NOTIFY_ENUM_DIR
+#define NT_STATUS_BUFFER_OVERFLOW 0x80000005 // ERRDOS, ERRmoredata
+#define NT_STATUS_NO_MORE_ENTRIES 0x8000001a // ERRHRD, ERRgeneral
+#define NT_STATUS_MEDIA_CHANGED 0x8000001c // ERRHRD, ERRgeneral
+#define NT_STATUS_END_OF_MEDIA 0x8000001e // ERRHRD, ERRgeneral
+#define NT_STATUS_MEDIA_CHECK 0x80000020 // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_DATA_DETECTED 0x80000022 // ERRHRD, ERRgeneral
+#define NT_STATUS_STOPPED_ON_SYMLINK 0x8000002d // ERRDOS, ERRsymlink
+#define NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288 // ERRHRD, ERRgeneral
+#define NT_STATUS_DEVICE_DOOR_OPEN 0x80000289 // ERRHRD, ERRgeneral
+#define NT_STATUS_UNSUCCESSFUL (0xC0000000 | 0x0001) // ERRDOS, ERRgeneral
+#define NT_STATUS_NOT_IMPLEMENTED (0xC0000000 | 0x0002) // ERRDOS, ERRbadfunc
+#define NT_STATUS_INVALID_INFO_CLASS (0xC0000000 | 0x0003) // ERRDOS, ERRbadpipe
+#define NT_STATUS_INFO_LENGTH_MISMATCH (0xC0000000 | 0x0004) // ERRDOS, 24
+#define NT_STATUS_ACCESS_VIOLATION (0xC0000000 | 0x0005) // ERRHRD, ERRgeneral
+#define NT_STATUS_IN_PAGE_ERROR (0xC0000000 | 0x0006) // ERRHRD, ERRgeneral
+#define NT_STATUS_PAGEFILE_QUOTA (0xC0000000 | 0x0007) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_HANDLE (0xC0000000 | 0x0008) // ERRDOS, ERRbadfid
+#define NT_STATUS_BAD_INITIAL_STACK (0xC0000000 | 0x0009) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_INITIAL_PC (0xC0000000 | 0x000a) // ERRDOS, 193
+#define NT_STATUS_INVALID_CID (0xC0000000 | 0x000b) // ERRDOS, 87
+#define NT_STATUS_TIMER_NOT_CANCELED (0xC0000000 | 0x000c) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_PARAMETER (0xC0000000 | 0x000d) // ERRDOS, 87
+#define NT_STATUS_NO_SUCH_DEVICE (0xC0000000 | 0x000e) // ERRDOS, ERRbadfile
+#define NT_STATUS_NO_SUCH_FILE (0xC0000000 | 0x000f) // ERRDOS, ERRbadfile
+#define NT_STATUS_INVALID_DEVICE_REQUEST (0xC0000000 | 0x0010) // ERRDOS, ERRbadfunc
+#define NT_STATUS_END_OF_FILE (0xC0000000 | 0x0011) // ERRDOS, 38
+#define NT_STATUS_WRONG_VOLUME (0xC0000000 | 0x0012) // ERRDOS, 34
+#define NT_STATUS_NO_MEDIA_IN_DEVICE (0xC0000000 | 0x0013) // ERRDOS, 21
+#define NT_STATUS_UNRECOGNIZED_MEDIA (0xC0000000 | 0x0014) // ERRHRD, ERRgeneral
+#define NT_STATUS_NONEXISTENT_SECTOR (0xC0000000 | 0x0015) // ERRDOS, 27
+#define NT_STATUS_MORE_PROCESSING_REQUIRED (0xC0000000 | 0x0016) // ERRDOS, ERRmoredata
+#define NT_STATUS_NO_MEMORY (0xC0000000 | 0x0017) // ERRDOS, ERRnomem
+#define NT_STATUS_CONFLICTING_ADDRESSES (0xC0000000 | 0x0018) // ERRDOS, 487
+#define NT_STATUS_NOT_MAPPED_VIEW (0xC0000000 | 0x0019) // ERRDOS, 487
+#define NT_STATUS_UNABLE_TO_FREE_VM (0xC0000000 | 0x001a) // ERRDOS, 87
+#define NT_STATUS_UNABLE_TO_DELETE_SECTION (0xC0000000 | 0x001b) // ERRDOS, 87
+#define NT_STATUS_INVALID_SYSTEM_SERVICE (0xC0000000 | 0x001c) // ERRDOS, 2142
+#define NT_STATUS_ILLEGAL_INSTRUCTION (0xC0000000 | 0x001d) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_LOCK_SEQUENCE (0xC0000000 | 0x001e) // ERRDOS, ERRnoaccess
+#define NT_STATUS_INVALID_VIEW_SIZE (0xC0000000 | 0x001f) // ERRDOS, ERRnoaccess
+#define NT_STATUS_INVALID_FILE_FOR_SECTION (0xC0000000 | 0x0020) // ERRDOS, 193
+#define NT_STATUS_ALREADY_COMMITTED (0xC0000000 | 0x0021) // ERRDOS, ERRnoaccess
+#define NT_STATUS_ACCESS_DENIED (0xC0000000 | 0x0022) // ERRDOS, ERRnoaccess
+#define NT_STATUS_BUFFER_TOO_SMALL (0xC0000000 | 0x0023) // ERRDOS, 111
+#define NT_STATUS_OBJECT_TYPE_MISMATCH (0xC0000000 | 0x0024) // ERRDOS, ERRbadfid
+#define NT_STATUS_NONCONTINUABLE_EXCEPTION (0xC0000000 | 0x0025) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_DISPOSITION (0xC0000000 | 0x0026) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNWIND (0xC0000000 | 0x0027) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_STACK (0xC0000000 | 0x0028) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_UNWIND_TARGET (0xC0000000 | 0x0029) // ERRHRD, ERRgeneral
+#define NT_STATUS_NOT_LOCKED (0xC0000000 | 0x002a) // ERRDOS, 158
+#define NT_STATUS_PARITY_ERROR (0xC0000000 | 0x002b) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNABLE_TO_DECOMMIT_VM (0xC0000000 | 0x002c) // ERRDOS, 487
+#define NT_STATUS_NOT_COMMITTED (0xC0000000 | 0x002d) // ERRDOS, 487
+#define NT_STATUS_INVALID_PORT_ATTRIBUTES (0xC0000000 | 0x002e) // ERRHRD, ERRgeneral
+#define NT_STATUS_PORT_MESSAGE_TOO_LONG (0xC0000000 | 0x002f) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_PARAMETER_MIX (0xC0000000 | 0x0030) // ERRDOS, 87
+#define NT_STATUS_INVALID_QUOTA_LOWER (0xC0000000 | 0x0031) // ERRHRD, ERRgeneral
+#define NT_STATUS_DISK_CORRUPT_ERROR (0xC0000000 | 0x0032) // ERRHRD, ERRgeneral
+#define NT_STATUS_OBJECT_NAME_INVALID (0xC0000000 | 0x0033) // ERRDOS, ERRbadfile
+#define NT_STATUS_OBJECT_NAME_NOT_FOUND (0xC0000000 | 0x0034) // ERRDOS, ERRbadfile
+#define NT_STATUS_OBJECT_NAME_COLLISION (0xC0000000 | 0x0035) // ERRDOS, ERRalreadyexists
+#define NT_STATUS_HANDLE_NOT_WAITABLE (0xC0000000 | 0x0036) // ERRHRD, ERRgeneral
+#define NT_STATUS_PORT_DISCONNECTED (0xC0000000 | 0x0037) // ERRDOS, ERRbadfid
+#define NT_STATUS_DEVICE_ALREADY_ATTACHED (0xC0000000 | 0x0038) // ERRHRD, ERRgeneral
+#define NT_STATUS_OBJECT_PATH_INVALID (0xC0000000 | 0x0039) // ERRDOS, 161
+#define NT_STATUS_OBJECT_PATH_NOT_FOUND (0xC0000000 | 0x003a) // ERRDOS, ERRbadpath
+#define NT_STATUS_OBJECT_PATH_SYNTAX_BAD (0xC0000000 | 0x003b) // ERRDOS, 161
+#define NT_STATUS_DATA_OVERRUN (0xC0000000 | 0x003c) // ERRHRD, ERRgeneral
+#define NT_STATUS_DATA_LATE_ERROR (0xC0000000 | 0x003d) // ERRHRD, ERRgeneral
+#define NT_STATUS_DATA_ERROR (0xC0000000 | 0x003e) // ERRDOS, 23
+#define NT_STATUS_CRC_ERROR (0xC0000000 | 0x003f) // ERRDOS, 23
+#define NT_STATUS_SECTION_TOO_BIG (0xC0000000 | 0x0040) // ERRDOS, ERRnomem
+#define NT_STATUS_PORT_CONNECTION_REFUSED (0xC0000000 | 0x0041) // ERRDOS, ERRnoaccess
+#define NT_STATUS_INVALID_PORT_HANDLE (0xC0000000 | 0x0042) // ERRDOS, ERRbadfid
+#define NT_STATUS_SHARING_VIOLATION (0xC0000000 | 0x0043) // ERRDOS, ERRbadshare
+#define NT_STATUS_QUOTA_EXCEEDED (0xC0000000 | 0x0044) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_PAGE_PROTECTION (0xC0000000 | 0x0045) // ERRDOS, 87
+#define NT_STATUS_MUTANT_NOT_OWNED (0xC0000000 | 0x0046) // ERRDOS, 288
+#define NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED (0xC0000000 | 0x0047) // ERRDOS, 298
+#define NT_STATUS_PORT_ALREADY_SET (0xC0000000 | 0x0048) // ERRDOS, 87
+#define NT_STATUS_SECTION_NOT_IMAGE (0xC0000000 | 0x0049) // ERRDOS, 87
+#define NT_STATUS_SUSPEND_COUNT_EXCEEDED (0xC0000000 | 0x004a) // ERRDOS, 156
+#define NT_STATUS_THREAD_IS_TERMINATING (0xC0000000 | 0x004b) // ERRDOS, ERRnoaccess
+#define NT_STATUS_BAD_WORKING_SET_LIMIT (0xC0000000 | 0x004c) // ERRDOS, 87
+#define NT_STATUS_INCOMPATIBLE_FILE_MAP (0xC0000000 | 0x004d) // ERRDOS, 87
+#define NT_STATUS_SECTION_PROTECTION (0xC0000000 | 0x004e) // ERRDOS, 87
+#define NT_STATUS_EAS_NOT_SUPPORTED (0xC0000000 | 0x004f) // ERRDOS, ERReasnotsupported
+#define NT_STATUS_EA_TOO_LARGE (0xC0000000 | 0x0050) // ERRDOS, 255
+#define NT_STATUS_NONEXISTENT_EA_ENTRY (0xC0000000 | 0x0051) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_EAS_ON_FILE (0xC0000000 | 0x0052) // ERRHRD, ERRgeneral
+#define NT_STATUS_EA_CORRUPT_ERROR (0xC0000000 | 0x0053) // ERRHRD, ERRgeneral
+#define NT_STATUS_FILE_LOCK_CONFLICT (0xC0000000 | 0x0054) // ERRDOS, ERRlock
+#define NT_STATUS_LOCK_NOT_GRANTED (0xC0000000 | 0x0055) // ERRDOS, ERRlock
+#define NT_STATUS_DELETE_PENDING (0xC0000000 | 0x0056) // ERRDOS, ERRbadfile
+#define NT_STATUS_CTL_FILE_NOT_SUPPORTED (0xC0000000 | 0x0057) // ERRDOS, ERRunsup
+#define NT_STATUS_UNKNOWN_REVISION (0xC0000000 | 0x0058) // ERRHRD, ERRgeneral
+#define NT_STATUS_REVISION_MISMATCH (0xC0000000 | 0x0059) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_OWNER (0xC0000000 | 0x005a) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_PRIMARY_GROUP (0xC0000000 | 0x005b) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_IMPERSONATION_TOKEN (0xC0000000 | 0x005c) // ERRHRD, ERRgeneral
+#define NT_STATUS_CANT_DISABLE_MANDATORY (0xC0000000 | 0x005d) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_LOGON_SERVERS (0xC0000000 | 0x005e) // ERRDOS, 2215
+#define NT_STATUS_NO_SUCH_LOGON_SESSION (0xC0000000 | 0x005f) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_SUCH_PRIVILEGE (0xC0000000 | 0x0060) // ERRHRD, ERRgeneral
+#define NT_STATUS_PRIVILEGE_NOT_HELD (0xC0000000 | 0x0061) // ERRDOS, ERRnoaccess
+#define NT_STATUS_INVALID_ACCOUNT_NAME (0xC0000000 | 0x0062) // ERRHRD, ERRgeneral
+#define NT_STATUS_USER_EXISTS (0xC0000000 | 0x0063) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_SUCH_USER (0xC0000000 | 0x0064) // ERRDOS, ERRnoaccess
+#define NT_STATUS_GROUP_EXISTS (0xC0000000 | 0x0065) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_SUCH_GROUP (0xC0000000 | 0x0066) // ERRHRD, ERRgeneral
+#define NT_STATUS_MEMBER_IN_GROUP (0xC0000000 | 0x0067) // ERRHRD, ERRgeneral
+#define NT_STATUS_MEMBER_NOT_IN_GROUP (0xC0000000 | 0x0068) // ERRHRD, ERRgeneral
+#define NT_STATUS_LAST_ADMIN (0xC0000000 | 0x0069) // ERRHRD, ERRgeneral
+#define NT_STATUS_WRONG_PASSWORD (0xC0000000 | 0x006a) // ERRSRV, ERRbadpw
+#define NT_STATUS_ILL_FORMED_PASSWORD (0xC0000000 | 0x006b) // ERRHRD, ERRgeneral
+#define NT_STATUS_PASSWORD_RESTRICTION (0xC0000000 | 0x006c) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOGON_FAILURE (0xC0000000 | 0x006d) // ERRDOS, ERRnoaccess
+#define NT_STATUS_ACCOUNT_RESTRICTION (0xC0000000 | 0x006e) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_LOGON_HOURS (0xC0000000 | 0x006f) // ERRSRV, ERRbadLogonTime
+#define NT_STATUS_INVALID_WORKSTATION (0xC0000000 | 0x0070) // ERRSRV, ERRbadclient
+#define NT_STATUS_PASSWORD_EXPIRED (0xC0000000 | 0x0071) // ERRSRV, ERRpasswordExpired
+#define NT_STATUS_ACCOUNT_DISABLED (0xC0000000 | 0x0072) // ERRSRV, ERRaccountexpired
+#define NT_STATUS_NONE_MAPPED (0xC0000000 | 0x0073) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_MANY_LUIDS_REQUESTED (0xC0000000 | 0x0074) // ERRHRD, ERRgeneral
+#define NT_STATUS_LUIDS_EXHAUSTED (0xC0000000 | 0x0075) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_SUB_AUTHORITY (0xC0000000 | 0x0076) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_ACL (0xC0000000 | 0x0077) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_SID (0xC0000000 | 0x0078) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_SECURITY_DESCR (0xC0000000 | 0x0079) // ERRHRD, ERRgeneral
+#define NT_STATUS_PROCEDURE_NOT_FOUND (0xC0000000 | 0x007a) // ERRDOS, 127
+#define NT_STATUS_INVALID_IMAGE_FORMAT (0xC0000000 | 0x007b) // ERRDOS, 193
+#define NT_STATUS_NO_TOKEN (0xC0000000 | 0x007c) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_INHERITANCE_ACL (0xC0000000 | 0x007d) // ERRHRD, ERRgeneral
+#define NT_STATUS_RANGE_NOT_LOCKED (0xC0000000 | 0x007e) // ERRDOS, 158
+#define NT_STATUS_DISK_FULL (0xC0000000 | 0x007f) // ERRDOS, 112
+#define NT_STATUS_SERVER_DISABLED (0xC0000000 | 0x0080) // ERRHRD, ERRgeneral
+#define NT_STATUS_SERVER_NOT_DISABLED (0xC0000000 | 0x0081) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_MANY_GUIDS_REQUESTED (0xC0000000 | 0x0082) // ERRDOS, 68
+#define NT_STATUS_GUIDS_EXHAUSTED (0xC0000000 | 0x0083) // ERRDOS, 259
+#define NT_STATUS_INVALID_ID_AUTHORITY (0xC0000000 | 0x0084) // ERRHRD, ERRgeneral
+#define NT_STATUS_AGENTS_EXHAUSTED (0xC0000000 | 0x0085) // ERRDOS, 259
+#define NT_STATUS_INVALID_VOLUME_LABEL (0xC0000000 | 0x0086) // ERRDOS, 154
+#define NT_STATUS_SECTION_NOT_EXTENDED (0xC0000000 | 0x0087) // ERRDOS, 14
+#define NT_STATUS_NOT_MAPPED_DATA (0xC0000000 | 0x0088) // ERRDOS, 487
+#define NT_STATUS_RESOURCE_DATA_NOT_FOUND (0xC0000000 | 0x0089) // ERRHRD, ERRgeneral
+#define NT_STATUS_RESOURCE_TYPE_NOT_FOUND (0xC0000000 | 0x008a) // ERRHRD, ERRgeneral
+#define NT_STATUS_RESOURCE_NAME_NOT_FOUND (0xC0000000 | 0x008b) // ERRHRD, ERRgeneral
+#define NT_STATUS_ARRAY_BOUNDS_EXCEEDED (0xC0000000 | 0x008c) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOAT_DENORMAL_OPERAND (0xC0000000 | 0x008d) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOAT_DIVIDE_BY_ZERO (0xC0000000 | 0x008e) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOAT_INEXACT_RESULT (0xC0000000 | 0x008f) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOAT_INVALID_OPERATION (0xC0000000 | 0x0090) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOAT_OVERFLOW (0xC0000000 | 0x0091) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOAT_STACK_CHECK (0xC0000000 | 0x0092) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOAT_UNDERFLOW (0xC0000000 | 0x0093) // ERRHRD, ERRgeneral
+#define NT_STATUS_INTEGER_DIVIDE_BY_ZERO (0xC0000000 | 0x0094) // ERRHRD, ERRgeneral
+#define NT_STATUS_INTEGER_OVERFLOW (0xC0000000 | 0x0095) // ERRDOS, 534
+#define NT_STATUS_PRIVILEGED_INSTRUCTION (0xC0000000 | 0x0096) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_MANY_PAGING_FILES (0xC0000000 | 0x0097) // ERRDOS, ERRnomem
+#define NT_STATUS_FILE_INVALID (0xC0000000 | 0x0098) // ERRHRD, ERRgeneral
+#define NT_STATUS_ALLOTTED_SPACE_EXCEEDED (0xC0000000 | 0x0099) // ERRHRD, ERRgeneral
+#define NT_STATUS_INSUFFICIENT_RESOURCES (0xC0000000 | 0x009a) // ERRDOS, ERRnoresource
+#define NT_STATUS_DFS_EXIT_PATH_FOUND (0xC0000000 | 0x009b) // ERRDOS, ERRbadpath
+#define NT_STATUS_DEVICE_DATA_ERROR (0xC0000000 | 0x009c) // ERRDOS, 23
+#define NT_STATUS_DEVICE_NOT_CONNECTED (0xC0000000 | 0x009d) // ERRHRD, ERRgeneral
+#define NT_STATUS_DEVICE_POWER_FAILURE (0xC0000000 | 0x009e) // ERRDOS, 21
+#define NT_STATUS_FREE_VM_NOT_AT_BASE (0xC0000000 | 0x009f) // ERRDOS, 487
+#define NT_STATUS_MEMORY_NOT_ALLOCATED (0xC0000000 | 0x00a0) // ERRDOS, 487
+#define NT_STATUS_WORKING_SET_QUOTA (0xC0000000 | 0x00a1) // ERRHRD, ERRgeneral
+#define NT_STATUS_MEDIA_WRITE_PROTECTED (0xC0000000 | 0x00a2) // ERRDOS, 19
+#define NT_STATUS_DEVICE_NOT_READY (0xC0000000 | 0x00a3) // ERRDOS, 21
+#define NT_STATUS_INVALID_GROUP_ATTRIBUTES (0xC0000000 | 0x00a4) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_IMPERSONATION_LEVEL (0xC0000000 | 0x00a5) // ERRHRD, ERRgeneral
+#define NT_STATUS_CANT_OPEN_ANONYMOUS (0xC0000000 | 0x00a6) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_VALIDATION_CLASS (0xC0000000 | 0x00a7) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_TOKEN_TYPE (0xC0000000 | 0x00a8) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_MASTER_BOOT_RECORD (0xC0000000 | 0x00a9) // ERRDOS, 87
+#define NT_STATUS_INSTRUCTION_MISALIGNMENT (0xC0000000 | 0x00aa) // ERRHRD, ERRgeneral
+#define NT_STATUS_INSTANCE_NOT_AVAILABLE (0xC0000000 | 0x00ab) // ERRDOS, ERRpipebusy
+#define NT_STATUS_PIPE_NOT_AVAILABLE (0xC0000000 | 0x00ac) // ERRDOS, ERRpipebusy
+#define NT_STATUS_INVALID_PIPE_STATE (0xC0000000 | 0x00ad) // ERRDOS, ERRbadpipe
+#define NT_STATUS_PIPE_BUSY (0xC0000000 | 0x00ae) // ERRDOS, ERRpipebusy
+#define NT_STATUS_ILLEGAL_FUNCTION (0xC0000000 | 0x00af) // ERRDOS, ERRbadfunc
+#define NT_STATUS_PIPE_DISCONNECTED (0xC0000000 | 0x00b0) // ERRDOS, ERRnotconnected
+#define NT_STATUS_PIPE_CLOSING (0xC0000000 | 0x00b1) // ERRDOS, ERRpipeclosing
+#define NT_STATUS_PIPE_CONNECTED (0xC0000000 | 0x00b2) // ERRHRD, ERRgeneral
+#define NT_STATUS_PIPE_LISTENING (0xC0000000 | 0x00b3) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_READ_MODE (0xC0000000 | 0x00b4) // ERRDOS, ERRbadpipe
+#define NT_STATUS_IO_TIMEOUT (0xC0000000 | 0x00b5) // ERRDOS, 121
+#define NT_STATUS_FILE_FORCED_CLOSED (0xC0000000 | 0x00b6) // ERRDOS, 38
+#define NT_STATUS_PROFILING_NOT_STARTED (0xC0000000 | 0x00b7) // ERRHRD, ERRgeneral
+#define NT_STATUS_PROFILING_NOT_STOPPED (0xC0000000 | 0x00b8) // ERRHRD, ERRgeneral
+#define NT_STATUS_COULD_NOT_INTERPRET (0xC0000000 | 0x00b9) // ERRHRD, ERRgeneral
+#define NT_STATUS_FILE_IS_A_DIRECTORY (0xC0000000 | 0x00ba) // ERRDOS, ERRnoaccess
+#define NT_STATUS_NOT_SUPPORTED (0xC0000000 | 0x00bb) // ERRDOS, ERRunsup
+#define NT_STATUS_REMOTE_NOT_LISTENING (0xC0000000 | 0x00bc) // ERRDOS, 51
+#define NT_STATUS_DUPLICATE_NAME (0xC0000000 | 0x00bd) // ERRDOS, 52
+#define NT_STATUS_BAD_NETWORK_PATH (0xC0000000 | 0x00be) // ERRDOS, 53
+#define NT_STATUS_NETWORK_BUSY (0xC0000000 | 0x00bf) // ERRDOS, 54
+#define NT_STATUS_DEVICE_DOES_NOT_EXIST (0xC0000000 | 0x00c0) // ERRDOS, 55
+#define NT_STATUS_TOO_MANY_COMMANDS (0xC0000000 | 0x00c1) // ERRDOS, 56
+#define NT_STATUS_ADAPTER_HARDWARE_ERROR (0xC0000000 | 0x00c2) // ERRDOS, 57
+#define NT_STATUS_INVALID_NETWORK_RESPONSE (0xC0000000 | 0x00c3) // ERRDOS, 58
+#define NT_STATUS_UNEXPECTED_NETWORK_ERROR (0xC0000000 | 0x00c4) // ERRDOS, 59
+#define NT_STATUS_BAD_REMOTE_ADAPTER (0xC0000000 | 0x00c5) // ERRDOS, 60
+#define NT_STATUS_PRINT_QUEUE_FULL (0xC0000000 | 0x00c6) // ERRDOS, 61
+#define NT_STATUS_NO_SPOOL_SPACE (0xC0000000 | 0x00c7) // ERRDOS, 62
+#define NT_STATUS_PRINT_CANCELLED (0xC0000000 | 0x00c8) // ERRDOS, 63
+#define NT_STATUS_NETWORK_NAME_DELETED (0xC0000000 | 0x00c9) // ERRDOS, 64
+#define NT_STATUS_NETWORK_ACCESS_DENIED (0xC0000000 | 0x00ca) // ERRDOS, 65
+#define NT_STATUS_BAD_DEVICE_TYPE (0xC0000000 | 0x00cb) // ERRDOS, 66
+#define NT_STATUS_BAD_NETWORK_NAME (0xC0000000 | 0x00cc) // ERRDOS, ERRnosuchshare
+#define NT_STATUS_TOO_MANY_NAMES (0xC0000000 | 0x00cd) // ERRDOS, 68
+#define NT_STATUS_TOO_MANY_SESSIONS (0xC0000000 | 0x00ce) // ERRDOS, 69
+#define NT_STATUS_SHARING_PAUSED (0xC0000000 | 0x00cf) // ERRDOS, 70
+#define NT_STATUS_REQUEST_NOT_ACCEPTED (0xC0000000 | 0x00d0) // ERRDOS, 71
+#define NT_STATUS_REDIRECTOR_PAUSED (0xC0000000 | 0x00d1) // ERRDOS, 72
+#define NT_STATUS_NET_WRITE_FAULT (0xC0000000 | 0x00d2) // ERRDOS, 88
+#define NT_STATUS_PROFILING_AT_LIMIT (0xC0000000 | 0x00d3) // ERRHRD, ERRgeneral
+#define NT_STATUS_NOT_SAME_DEVICE (0xC0000000 | 0x00d4) // ERRDOS, ERRdiffdevice
+#define NT_STATUS_FILE_RENAMED (0xC0000000 | 0x00d5) // ERRDOS, ERRnoaccess
+#define NT_STATUS_VIRTUAL_CIRCUIT_CLOSED (0xC0000000 | 0x00d6) // ERRDOS, 240
+#define NT_STATUS_NO_SECURITY_ON_OBJECT (0xC0000000 | 0x00d7) // ERRHRD, ERRgeneral
+#define NT_STATUS_CANT_WAIT (0xC0000000 | 0x00d8) // ERRHRD, ERRgeneral
+#define NT_STATUS_PIPE_EMPTY (0xC0000000 | 0x00d9) // ERRDOS, ERRpipeclosing
+#define NT_STATUS_CANT_ACCESS_DOMAIN_INFO (0xC0000000 | 0x00da) // ERRHRD, ERRgeneral
+#define NT_STATUS_CANT_TERMINATE_SELF (0xC0000000 | 0x00db) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_SERVER_STATE (0xC0000000 | 0x00dc) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_DOMAIN_STATE (0xC0000000 | 0x00dd) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_DOMAIN_ROLE (0xC0000000 | 0x00de) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_SUCH_DOMAIN (0xC0000000 | 0x00df) // ERRHRD, ERRgeneral
+#define NT_STATUS_DOMAIN_EXISTS (0xC0000000 | 0x00e0) // ERRHRD, ERRgeneral
+#define NT_STATUS_DOMAIN_LIMIT_EXCEEDED (0xC0000000 | 0x00e1) // ERRHRD, ERRgeneral
+#define NT_STATUS_OPLOCK_NOT_GRANTED (0xC0000000 | 0x00e2) // ERRDOS, 300
+#define NT_STATUS_INVALID_OPLOCK_PROTOCOL (0xC0000000 | 0x00e3) // ERRDOS, 301
+#define NT_STATUS_INTERNAL_DB_CORRUPTION (0xC0000000 | 0x00e4) // ERRHRD, ERRgeneral
+#define NT_STATUS_INTERNAL_ERROR (0xC0000000 | 0x00e5) // ERRHRD, ERRgeneral
+#define NT_STATUS_GENERIC_NOT_MAPPED (0xC0000000 | 0x00e6) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_DESCRIPTOR_FORMAT (0xC0000000 | 0x00e7) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_USER_BUFFER (0xC0000000 | 0x00e8) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNEXPECTED_IO_ERROR (0xC0000000 | 0x00e9) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNEXPECTED_MM_CREATE_ERR (0xC0000000 | 0x00ea) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNEXPECTED_MM_MAP_ERROR (0xC0000000 | 0x00eb) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNEXPECTED_MM_EXTEND_ERR (0xC0000000 | 0x00ec) // ERRHRD, ERRgeneral
+#define NT_STATUS_NOT_LOGON_PROCESS (0xC0000000 | 0x00ed) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOGON_SESSION_EXISTS (0xC0000000 | 0x00ee) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_PARAMETER_1 (0xC0000000 | 0x00ef) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_2 (0xC0000000 | 0x00f0) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_3 (0xC0000000 | 0x00f1) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_4 (0xC0000000 | 0x00f2) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_5 (0xC0000000 | 0x00f3) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_6 (0xC0000000 | 0x00f4) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_7 (0xC0000000 | 0x00f5) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_8 (0xC0000000 | 0x00f6) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_9 (0xC0000000 | 0x00f7) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_10 (0xC0000000 | 0x00f8) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_11 (0xC0000000 | 0x00f9) // ERRDOS, 87
+#define NT_STATUS_INVALID_PARAMETER_12 (0xC0000000 | 0x00fa) // ERRDOS, 87
+#define NT_STATUS_REDIRECTOR_NOT_STARTED (0xC0000000 | 0x00fb) // ERRDOS, ERRbadpath
+#define NT_STATUS_REDIRECTOR_STARTED (0xC0000000 | 0x00fc) // ERRHRD, ERRgeneral
+#define NT_STATUS_STACK_OVERFLOW (0xC0000000 | 0x00fd) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_SUCH_PACKAGE (0xC0000000 | 0x00fe) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_FUNCTION_TABLE (0xC0000000 | 0x00ff) // ERRHRD, ERRgeneral
+#define NT_STATUS_VARIABLE_NOT_FOUND (0xC0000000 | 0x0100) // ERRDOS, 203
+#define NT_STATUS_DIRECTORY_NOT_EMPTY (0xC0000000 | 0x0101) // ERRDOS, 145
+#define NT_STATUS_FILE_CORRUPT_ERROR (0xC0000000 | 0x0102) // ERRHRD, ERRgeneral
+#define NT_STATUS_NOT_A_DIRECTORY (0xC0000000 | 0x0103) // ERRDOS, 267
+#define NT_STATUS_BAD_LOGON_SESSION_STATE (0xC0000000 | 0x0104) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOGON_SESSION_COLLISION (0xC0000000 | 0x0105) // ERRHRD, ERRgeneral
+#define NT_STATUS_NAME_TOO_LONG (0xC0000000 | 0x0106) // ERRDOS, 206
+#define NT_STATUS_FILES_OPEN (0xC0000000 | 0x0107) // ERRDOS, 2401
+#define NT_STATUS_CONNECTION_IN_USE (0xC0000000 | 0x0108) // ERRDOS, 2404
+#define NT_STATUS_MESSAGE_NOT_FOUND (0xC0000000 | 0x0109) // ERRHRD, ERRgeneral
+#define NT_STATUS_PROCESS_IS_TERMINATING (0xC0000000 | 0x010a) // ERRDOS, ERRnoaccess
+#define NT_STATUS_INVALID_LOGON_TYPE (0xC0000000 | 0x010b) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_GUID_TRANSLATION (0xC0000000 | 0x010c) // ERRHRD, ERRgeneral
+#define NT_STATUS_CANNOT_IMPERSONATE (0xC0000000 | 0x010d) // ERRHRD, ERRgeneral
+#define NT_STATUS_IMAGE_ALREADY_LOADED (0xC0000000 | 0x010e) // ERRHRD, ERRgeneral
+#define NT_STATUS_ABIOS_NOT_PRESENT (0xC0000000 | 0x010f) // ERRHRD, ERRgeneral
+#define NT_STATUS_ABIOS_LID_NOT_EXIST (0xC0000000 | 0x0110) // ERRHRD, ERRgeneral
+#define NT_STATUS_ABIOS_LID_ALREADY_OWNED (0xC0000000 | 0x0111) // ERRHRD, ERRgeneral
+#define NT_STATUS_ABIOS_NOT_LID_OWNER (0xC0000000 | 0x0112) // ERRHRD, ERRgeneral
+#define NT_STATUS_ABIOS_INVALID_COMMAND (0xC0000000 | 0x0113) // ERRHRD, ERRgeneral
+#define NT_STATUS_ABIOS_INVALID_LID (0xC0000000 | 0x0114) // ERRHRD, ERRgeneral
+#define NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE (0xC0000000 | 0x0115) // ERRHRD, ERRgeneral
+#define NT_STATUS_ABIOS_INVALID_SELECTOR (0xC0000000 | 0x0116) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_LDT (0xC0000000 | 0x0117) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_LDT_SIZE (0xC0000000 | 0x0118) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_LDT_OFFSET (0xC0000000 | 0x0119) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_LDT_DESCRIPTOR (0xC0000000 | 0x011a) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_IMAGE_NE_FORMAT (0xC0000000 | 0x011b) // ERRDOS, 193
+#define NT_STATUS_RXACT_INVALID_STATE (0xC0000000 | 0x011c) // ERRHRD, ERRgeneral
+#define NT_STATUS_RXACT_COMMIT_FAILURE (0xC0000000 | 0x011d) // ERRHRD, ERRgeneral
+#define NT_STATUS_MAPPED_FILE_SIZE_ZERO (0xC0000000 | 0x011e) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_MANY_OPENED_FILES (0xC0000000 | 0x011f) // ERRDOS, ERRnofids
+#define NT_STATUS_CANCELLED (0xC0000000 | 0x0120) // ERRHRD, ERRgeneral
+#define NT_STATUS_CANNOT_DELETE (0xC0000000 | 0x0121) // ERRDOS, ERRnoaccess
+#define NT_STATUS_INVALID_COMPUTER_NAME (0xC0000000 | 0x0122) // ERRHRD, ERRgeneral
+#define NT_STATUS_FILE_DELETED (0xC0000000 | 0x0123) // ERRDOS, ERRnoaccess
+#define NT_STATUS_SPECIAL_ACCOUNT (0xC0000000 | 0x0124) // ERRHRD, ERRgeneral
+#define NT_STATUS_SPECIAL_GROUP (0xC0000000 | 0x0125) // ERRHRD, ERRgeneral
+#define NT_STATUS_SPECIAL_USER (0xC0000000 | 0x0126) // ERRHRD, ERRgeneral
+#define NT_STATUS_MEMBERS_PRIMARY_GROUP (0xC0000000 | 0x0127) // ERRHRD, ERRgeneral
+#define NT_STATUS_FILE_CLOSED (0xC0000000 | 0x0128) // ERRDOS, ERRbadfid
+#define NT_STATUS_TOO_MANY_THREADS (0xC0000000 | 0x0129) // ERRHRD, ERRgeneral
+#define NT_STATUS_THREAD_NOT_IN_PROCESS (0xC0000000 | 0x012a) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOKEN_ALREADY_IN_USE (0xC0000000 | 0x012b) // ERRHRD, ERRgeneral
+#define NT_STATUS_PAGEFILE_QUOTA_EXCEEDED (0xC0000000 | 0x012c) // ERRHRD, ERRgeneral
+#define NT_STATUS_COMMITMENT_LIMIT (0xC0000000 | 0x012d) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_IMAGE_LE_FORMAT (0xC0000000 | 0x012e) // ERRDOS, 193
+#define NT_STATUS_INVALID_IMAGE_NOT_MZ (0xC0000000 | 0x012f) // ERRDOS, 193
+#define NT_STATUS_INVALID_IMAGE_PROTECT (0xC0000000 | 0x0130) // ERRDOS, 193
+#define NT_STATUS_INVALID_IMAGE_WIN_16 (0xC0000000 | 0x0131) // ERRDOS, 193
+#define NT_STATUS_LOGON_SERVER_CONFLICT (0xC0000000 | 0x0132) // ERRHRD, ERRgeneral
+#define NT_STATUS_TIME_DIFFERENCE_AT_DC (0xC0000000 | 0x0133) // ERRHRD, ERRgeneral
+#define NT_STATUS_SYNCHRONIZATION_REQUIRED (0xC0000000 | 0x0134) // ERRHRD, ERRgeneral
+#define NT_STATUS_DLL_NOT_FOUND (0xC0000000 | 0x0135) // ERRDOS, 126
+#define NT_STATUS_OPEN_FAILED (0xC0000000 | 0x0136) // ERRHRD, ERRgeneral
+#define NT_STATUS_IO_PRIVILEGE_FAILED (0xC0000000 | 0x0137) // ERRHRD, ERRgeneral
+#define NT_STATUS_ORDINAL_NOT_FOUND (0xC0000000 | 0x0138) // ERRDOS, 182
+#define NT_STATUS_ENTRYPOINT_NOT_FOUND (0xC0000000 | 0x0139) // ERRDOS, 127
+#define NT_STATUS_CONTROL_C_EXIT (0xC0000000 | 0x013a) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOCAL_DISCONNECT (0xC0000000 | 0x013b) // ERRDOS, 64
+#define NT_STATUS_REMOTE_DISCONNECT (0xC0000000 | 0x013c) // ERRDOS, 64
+#define NT_STATUS_REMOTE_RESOURCES (0xC0000000 | 0x013d) // ERRDOS, 51
+#define NT_STATUS_LINK_FAILED (0xC0000000 | 0x013e) // ERRDOS, 59
+#define NT_STATUS_LINK_TIMEOUT (0xC0000000 | 0x013f) // ERRDOS, 59
+#define NT_STATUS_INVALID_CONNECTION (0xC0000000 | 0x0140) // ERRDOS, 59
+#define NT_STATUS_INVALID_ADDRESS (0xC0000000 | 0x0141) // ERRDOS, 59
+#define NT_STATUS_DLL_INIT_FAILED (0xC0000000 | 0x0142) // ERRHRD, ERRgeneral
+#define NT_STATUS_MISSING_SYSTEMFILE (0xC0000000 | 0x0143) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNHANDLED_EXCEPTION (0xC0000000 | 0x0144) // ERRHRD, ERRgeneral
+#define NT_STATUS_APP_INIT_FAILURE (0xC0000000 | 0x0145) // ERRHRD, ERRgeneral
+#define NT_STATUS_PAGEFILE_CREATE_FAILED (0xC0000000 | 0x0146) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_PAGEFILE (0xC0000000 | 0x0147) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_LEVEL (0xC0000000 | 0x0148) // ERRDOS, 124
+#define NT_STATUS_WRONG_PASSWORD_CORE (0xC0000000 | 0x0149) // ERRDOS, 86
+#define NT_STATUS_ILLEGAL_FLOAT_CONTEXT (0xC0000000 | 0x014a) // ERRHRD, ERRgeneral
+#define NT_STATUS_PIPE_BROKEN (0xC0000000 | 0x014b) // ERRDOS, 109
+#define NT_STATUS_REGISTRY_CORRUPT (0xC0000000 | 0x014c) // ERRHRD, ERRgeneral
+#define NT_STATUS_REGISTRY_IO_FAILED (0xC0000000 | 0x014d) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_EVENT_PAIR (0xC0000000 | 0x014e) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNRECOGNIZED_VOLUME (0xC0000000 | 0x014f) // ERRHRD, ERRgeneral
+#define NT_STATUS_SERIAL_NO_DEVICE_INITED (0xC0000000 | 0x0150) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_SUCH_ALIAS (0xC0000000 | 0x0151) // ERRHRD, ERRgeneral
+#define NT_STATUS_MEMBER_NOT_IN_ALIAS (0xC0000000 | 0x0152) // ERRHRD, ERRgeneral
+#define NT_STATUS_MEMBER_IN_ALIAS (0xC0000000 | 0x0153) // ERRHRD, ERRgeneral
+#define NT_STATUS_ALIAS_EXISTS (0xC0000000 | 0x0154) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOGON_NOT_GRANTED (0xC0000000 | 0x0155) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_MANY_SECRETS (0xC0000000 | 0x0156) // ERRHRD, ERRgeneral
+#define NT_STATUS_SECRET_TOO_LONG (0xC0000000 | 0x0157) // ERRHRD, ERRgeneral
+#define NT_STATUS_INTERNAL_DB_ERROR (0xC0000000 | 0x0158) // ERRHRD, ERRgeneral
+#define NT_STATUS_FULLSCREEN_MODE (0xC0000000 | 0x0159) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_MANY_CONTEXT_IDS (0xC0000000 | 0x015a) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOGON_TYPE_NOT_GRANTED (0xC0000000 | 0x015b) // ERRDOS, ERRnoaccess
+#define NT_STATUS_NOT_REGISTRY_FILE (0xC0000000 | 0x015c) // ERRHRD, ERRgeneral
+#define NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED (0xC0000000 | 0x015d) // ERRHRD, ERRgeneral
+#define NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR (0xC0000000 | 0x015e) // ERRHRD, ERRgeneral
+#define NT_STATUS_FT_MISSING_MEMBER (0xC0000000 | 0x015f) // ERRHRD, ERRgeneral
+#define NT_STATUS_ILL_FORMED_SERVICE_ENTRY (0xC0000000 | 0x0160) // ERRHRD, ERRgeneral
+#define NT_STATUS_ILLEGAL_CHARACTER (0xC0000000 | 0x0161) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNMAPPABLE_CHARACTER (0xC0000000 | 0x0162) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNDEFINED_CHARACTER (0xC0000000 | 0x0163) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOPPY_VOLUME (0xC0000000 | 0x0164) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND (0xC0000000 | 0x0165) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOPPY_WRONG_CYLINDER (0xC0000000 | 0x0166) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOPPY_UNKNOWN_ERROR (0xC0000000 | 0x0167) // ERRHRD, ERRgeneral
+#define NT_STATUS_FLOPPY_BAD_REGISTERS (0xC0000000 | 0x0168) // ERRHRD, ERRgeneral
+#define NT_STATUS_DISK_RECALIBRATE_FAILED (0xC0000000 | 0x0169) // ERRHRD, ERRgeneral
+#define NT_STATUS_DISK_OPERATION_FAILED (0xC0000000 | 0x016a) // ERRHRD, ERRgeneral
+#define NT_STATUS_DISK_RESET_FAILED (0xC0000000 | 0x016b) // ERRHRD, ERRgeneral
+#define NT_STATUS_SHARED_IRQ_BUSY (0xC0000000 | 0x016c) // ERRHRD, ERRgeneral
+#define NT_STATUS_FT_ORPHANING (0xC0000000 | 0x016d) // ERRHRD, ERRgeneral
+#define NT_STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT (0xC0000000 | 0x016e) // ERRHRD, ERRgeneral
+#define NT_STATUS_PARTITION_FAILURE (0xC0000000 | 0x0172) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_BLOCK_LENGTH (0xC0000000 | 0x0173) // ERRHRD, ERRgeneral
+#define NT_STATUS_DEVICE_NOT_PARTITIONED (0xC0000000 | 0x0174) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNABLE_TO_LOCK_MEDIA (0xC0000000 | 0x0175) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNABLE_TO_UNLOAD_MEDIA (0xC0000000 | 0x0176) // ERRHRD, ERRgeneral
+#define NT_STATUS_EOM_OVERFLOW (0xC0000000 | 0x0177) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_MEDIA (0xC0000000 | 0x0178) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_SUCH_MEMBER (0xC0000000 | 0x017a) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_MEMBER (0xC0000000 | 0x017b) // ERRHRD, ERRgeneral
+#define NT_STATUS_KEY_DELETED (0xC0000000 | 0x017c) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_LOG_SPACE (0xC0000000 | 0x017d) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_MANY_SIDS (0xC0000000 | 0x017e) // ERRHRD, ERRgeneral
+#define NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED (0xC0000000 | 0x017f) // ERRHRD, ERRgeneral
+#define NT_STATUS_KEY_HAS_CHILDREN (0xC0000000 | 0x0180) // ERRHRD, ERRgeneral
+#define NT_STATUS_CHILD_MUST_BE_VOLATILE (0xC0000000 | 0x0181) // ERRHRD, ERRgeneral
+#define NT_STATUS_DEVICE_CONFIGURATION_ERROR (0xC0000000 | 0x0182) // ERRDOS, 87
+#define NT_STATUS_DRIVER_INTERNAL_ERROR (0xC0000000 | 0x0183) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_DEVICE_STATE (0xC0000000 | 0x0184) // ERRDOS, 22
+#define NT_STATUS_IO_DEVICE_ERROR (0xC0000000 | 0x0185) // ERRHRD, ERRgeneral
+#define NT_STATUS_DEVICE_PROTOCOL_ERROR (0xC0000000 | 0x0186) // ERRHRD, ERRgeneral
+#define NT_STATUS_BACKUP_CONTROLLER (0xC0000000 | 0x0187) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOG_FILE_FULL (0xC0000000 | 0x0188) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_LATE (0xC0000000 | 0x0189) // ERRDOS, 19
+#define NT_STATUS_NO_TRUST_LSA_SECRET (0xC0000000 | 0x018a) // ERRDOS, ERRnoaccess
+#define NT_STATUS_NO_TRUST_SAM_ACCOUNT (0xC0000000 | 0x018b) // ERRDOS, ERRnoaccess
+#define NT_STATUS_TRUSTED_DOMAIN_FAILURE (0xC0000000 | 0x018c) // ERRDOS, ERRnoaccess
+#define NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE (0xC0000000 | 0x018d) // ERRDOS, ERRnoaccess
+#define NT_STATUS_EVENTLOG_FILE_CORRUPT (0xC0000000 | 0x018e) // ERRHRD, ERRgeneral
+#define NT_STATUS_EVENTLOG_CANT_START (0xC0000000 | 0x018f) // ERRHRD, ERRgeneral
+#define NT_STATUS_TRUST_FAILURE (0xC0000000 | 0x0190) // ERRDOS, ERRnoaccess
+#define NT_STATUS_MUTANT_LIMIT_EXCEEDED (0xC0000000 | 0x0191) // ERRHRD, ERRgeneral
+#define NT_STATUS_NETLOGON_NOT_STARTED (0xC0000000 | 0x0192) // ERRDOS, ERRnetlogonNotStarted
+#define NT_STATUS_ACCOUNT_EXPIRED (0xC0000000 | 0x0193) // ERRSRV, ERRaccountexpired
+#define NT_STATUS_POSSIBLE_DEADLOCK (0xC0000000 | 0x0194) // ERRHRD, ERRgeneral
+#define NT_STATUS_NETWORK_CREDENTIAL_CONFLICT (0xC0000000 | 0x0195) // ERRHRD, ERRgeneral
+#define NT_STATUS_REMOTE_SESSION_LIMIT (0xC0000000 | 0x0196) // ERRHRD, ERRgeneral
+#define NT_STATUS_EVENTLOG_FILE_CHANGED (0xC0000000 | 0x0197) // ERRHRD, ERRgeneral
+#define NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT (0xC0000000 | 0x0198) // ERRDOS, ERRnoaccess
+#define NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT (0xC0000000 | 0x0199) // ERRDOS, ERRnoaccess
+#define NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT (0xC0000000 | 0x019a) // ERRDOS, ERRnoaccess
+#define NT_STATUS_DOMAIN_TRUST_INCONSISTENT (0xC0000000 | 0x019b) // ERRDOS, ERRnoaccess
+#define NT_STATUS_FS_DRIVER_REQUIRED (0xC0000000 | 0x019c) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_LOCK_RANGE (0xC0000000 | 0x01a1) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_USER_SESSION_KEY (0xC0000000 | 0x0202) // ERRHRD, ERRgeneral
+#define NT_STATUS_USER_SESSION_DELETED (0xC0000000 | 0x0203) // ERRDOS, 59
+#define NT_STATUS_RESOURCE_LANG_NOT_FOUND (0xC0000000 | 0x0204) // ERRHRD, ERRgeneral
+#define NT_STATUS_INSUFF_SERVER_RESOURCES (0xC0000000 | 0x0205) // ERRDOS, ERRnoresource
+#define NT_STATUS_INVALID_BUFFER_SIZE (0xC0000000 | 0x0206) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_ADDRESS_COMPONENT (0xC0000000 | 0x0207) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_ADDRESS_WILDCARD (0xC0000000 | 0x0208) // ERRHRD, ERRgeneral
+#define NT_STATUS_TOO_MANY_ADDRESSES (0xC0000000 | 0x0209) // ERRDOS, 68
+#define NT_STATUS_ADDRESS_ALREADY_EXISTS (0xC0000000 | 0x020a) // ERRDOS, 52
+#define NT_STATUS_ADDRESS_CLOSED (0xC0000000 | 0x020b) // ERRDOS, 64
+#define NT_STATUS_CONNECTION_DISCONNECTED (0xC0000000 | 0x020c) // ERRDOS, 64
+#define NT_STATUS_CONNECTION_RESET (0xC0000000 | 0x020d) // ERRDOS, 64
+#define NT_STATUS_TOO_MANY_NODES (0xC0000000 | 0x020e) // ERRDOS, 68
+#define NT_STATUS_TRANSACTION_ABORTED (0xC0000000 | 0x020f) // ERRDOS, 59
+#define NT_STATUS_TRANSACTION_TIMED_OUT (0xC0000000 | 0x0210) // ERRDOS, 59
+#define NT_STATUS_TRANSACTION_NO_RELEASE (0xC0000000 | 0x0211) // ERRDOS, 59
+#define NT_STATUS_TRANSACTION_NO_MATCH (0xC0000000 | 0x0212) // ERRDOS, 59
+#define NT_STATUS_TRANSACTION_RESPONDED (0xC0000000 | 0x0213) // ERRDOS, 59
+#define NT_STATUS_TRANSACTION_INVALID_ID (0xC0000000 | 0x0214) // ERRDOS, 59
+#define NT_STATUS_TRANSACTION_INVALID_TYPE (0xC0000000 | 0x0215) // ERRDOS, 59
+#define NT_STATUS_NOT_SERVER_SESSION (0xC0000000 | 0x0216) // ERRDOS, ERRunsup
+#define NT_STATUS_NOT_CLIENT_SESSION (0xC0000000 | 0x0217) // ERRDOS, ERRunsup
+#define NT_STATUS_CANNOT_LOAD_REGISTRY_FILE (0xC0000000 | 0x0218) // ERRHRD, ERRgeneral
+#define NT_STATUS_DEBUG_ATTACH_FAILED (0xC0000000 | 0x0219) // ERRHRD, ERRgeneral
+#define NT_STATUS_SYSTEM_PROCESS_TERMINATED (0xC0000000 | 0x021a) // ERRHRD, ERRgeneral
+#define NT_STATUS_DATA_NOT_ACCEPTED (0xC0000000 | 0x021b) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_BROWSER_SERVERS_FOUND (0xC0000000 | 0x021c) // ERRHRD, ERRgeneral
+#define NT_STATUS_VDM_HARD_ERROR (0xC0000000 | 0x021d) // ERRHRD, ERRgeneral
+#define NT_STATUS_DRIVER_CANCEL_TIMEOUT (0xC0000000 | 0x021e) // ERRHRD, ERRgeneral
+#define NT_STATUS_REPLY_MESSAGE_MISMATCH (0xC0000000 | 0x021f) // ERRHRD, ERRgeneral
+#define NT_STATUS_MAPPED_ALIGNMENT (0xC0000000 | 0x0220) // ERRHRD, ERRgeneral
+#define NT_STATUS_IMAGE_CHECKSUM_MISMATCH (0xC0000000 | 0x0221) // ERRDOS, 193
+#define NT_STATUS_LOST_WRITEBEHIND_DATA (0xC0000000 | 0x0222) // ERRHRD, ERRgeneral
+#define NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID (0xC0000000 | 0x0223) // ERRHRD, ERRgeneral
+#define NT_STATUS_PASSWORD_MUST_CHANGE (0xC0000000 | 0x0224) // ERRSRV, ERRpasswordExpired
+#define NT_STATUS_NOT_FOUND (0xC0000000 | 0x0225) // ERRHRD, ERRgeneral
+#define NT_STATUS_NOT_TINY_STREAM (0xC0000000 | 0x0226) // ERRHRD, ERRgeneral
+#define NT_STATUS_RECOVERY_FAILURE (0xC0000000 | 0x0227) // ERRHRD, ERRgeneral
+#define NT_STATUS_STACK_OVERFLOW_READ (0xC0000000 | 0x0228) // ERRHRD, ERRgeneral
+#define NT_STATUS_FAIL_CHECK (0xC0000000 | 0x0229) // ERRHRD, ERRgeneral
+#define NT_STATUS_DUPLICATE_OBJECTID (0xC0000000 | 0x022a) // ERRHRD, ERRgeneral
+#define NT_STATUS_OBJECTID_EXISTS (0xC0000000 | 0x022b) // ERRHRD, ERRgeneral
+#define NT_STATUS_CONVERT_TO_LARGE (0xC0000000 | 0x022c) // ERRHRD, ERRgeneral
+#define NT_STATUS_RETRY (0xC0000000 | 0x022d) // ERRHRD, ERRgeneral
+#define NT_STATUS_FOUND_OUT_OF_SCOPE (0xC0000000 | 0x022e) // ERRHRD, ERRgeneral
+#define NT_STATUS_ALLOCATE_BUCKET (0xC0000000 | 0x022f) // ERRHRD, ERRgeneral
+#define NT_STATUS_PROPSET_NOT_FOUND (0xC0000000 | 0x0230) // ERRHRD, ERRgeneral
+#define NT_STATUS_MARSHALL_OVERFLOW (0xC0000000 | 0x0231) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_VARIANT (0xC0000000 | 0x0232) // ERRHRD, ERRgeneral
+#define NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND (0xC0000000 | 0x0233) // ERRHRD, ERRgeneral
+#define NT_STATUS_ACCOUNT_LOCKED_OUT (0xC0000000 | 0x0234) // ERRDOS, ERRnoaccess
+#define NT_STATUS_HANDLE_NOT_CLOSABLE (0xC0000000 | 0x0235) // ERRDOS, ERRbadfid
+#define NT_STATUS_CONNECTION_REFUSED (0xC0000000 | 0x0236) // ERRHRD, ERRgeneral
+#define NT_STATUS_GRACEFUL_DISCONNECT (0xC0000000 | 0x0237) // ERRHRD, ERRgeneral
+#define NT_STATUS_ADDRESS_ALREADY_ASSOCIATED (0xC0000000 | 0x0238) // ERRHRD, ERRgeneral
+#define NT_STATUS_ADDRESS_NOT_ASSOCIATED (0xC0000000 | 0x0239) // ERRHRD, ERRgeneral
+#define NT_STATUS_CONNECTION_INVALID (0xC0000000 | 0x023a) // ERRHRD, ERRgeneral
+#define NT_STATUS_CONNECTION_ACTIVE (0xC0000000 | 0x023b) // ERRHRD, ERRgeneral
+#define NT_STATUS_NETWORK_UNREACHABLE (0xC0000000 | 0x023c) // ERRHRD, ERRgeneral
+#define NT_STATUS_HOST_UNREACHABLE (0xC0000000 | 0x023d) // ERRHRD, ERRgeneral
+#define NT_STATUS_PROTOCOL_UNREACHABLE (0xC0000000 | 0x023e) // ERRHRD, ERRgeneral
+#define NT_STATUS_PORT_UNREACHABLE (0xC0000000 | 0x023f) // ERRHRD, ERRgeneral
+#define NT_STATUS_REQUEST_ABORTED (0xC0000000 | 0x0240) // ERRHRD, ERRgeneral
+#define NT_STATUS_CONNECTION_ABORTED (0xC0000000 | 0x0241) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_COMPRESSION_BUFFER (0xC0000000 | 0x0242) // ERRHRD, ERRgeneral
+#define NT_STATUS_USER_MAPPED_FILE (0xC0000000 | 0x0243) // ERRHRD, ERRgeneral
+#define NT_STATUS_AUDIT_FAILED (0xC0000000 | 0x0244) // ERRHRD, ERRgeneral
+#define NT_STATUS_TIMER_RESOLUTION_NOT_SET (0xC0000000 | 0x0245) // ERRHRD, ERRgeneral
+#define NT_STATUS_CONNECTION_COUNT_LIMIT (0xC0000000 | 0x0246) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOGIN_TIME_RESTRICTION (0xC0000000 | 0x0247) // ERRHRD, ERRgeneral
+#define NT_STATUS_LOGIN_WKSTA_RESTRICTION (0xC0000000 | 0x0248) // ERRHRD, ERRgeneral
+#define NT_STATUS_IMAGE_MP_UP_MISMATCH (0xC0000000 | 0x0249) // ERRDOS, 193
+#define NT_STATUS_INSUFFICIENT_LOGON_INFO (0xC0000000 | 0x0250) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_DLL_ENTRYPOINT (0xC0000000 | 0x0251) // ERRHRD, ERRgeneral
+#define NT_STATUS_BAD_SERVICE_ENTRYPOINT (0xC0000000 | 0x0252) // ERRHRD, ERRgeneral
+#define NT_STATUS_LPC_REPLY_LOST (0xC0000000 | 0x0253) // ERRHRD, ERRgeneral
+#define NT_STATUS_IP_ADDRESS_CONFLICT1 (0xC0000000 | 0x0254) // ERRHRD, ERRgeneral
+#define NT_STATUS_IP_ADDRESS_CONFLICT2 (0xC0000000 | 0x0255) // ERRHRD, ERRgeneral
+#define NT_STATUS_REGISTRY_QUOTA_LIMIT (0xC0000000 | 0x0256) // ERRHRD, ERRgeneral
+#define NT_STATUS_PATH_NOT_COVERED (0xC0000000 | 0x0257) // ERRSRV, 3
+#define NT_STATUS_NO_CALLBACK_ACTIVE (0xC0000000 | 0x0258) // ERRHRD, ERRgeneral
+#define NT_STATUS_LICENSE_QUOTA_EXCEEDED (0xC0000000 | 0x0259) // ERRHRD, ERRgeneral
+#define NT_STATUS_PWD_TOO_SHORT (0xC0000000 | 0x025a) // ERRHRD, ERRgeneral
+#define NT_STATUS_PWD_TOO_RECENT (0xC0000000 | 0x025b) // ERRHRD, ERRgeneral
+#define NT_STATUS_PWD_HISTORY_CONFLICT (0xC0000000 | 0x025c) // ERRHRD, ERRgeneral
+#define NT_STATUS_PLUGPLAY_NO_DEVICE (0xC0000000 | 0x025e) // ERRHRD, ERRgeneral
+#define NT_STATUS_UNSUPPORTED_COMPRESSION (0xC0000000 | 0x025f) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_HW_PROFILE (0xC0000000 | 0x0260) // ERRHRD, ERRgeneral
+#define NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH (0xC0000000 | 0x0261) // ERRHRD, ERRgeneral
+#define NT_STATUS_DRIVER_ORDINAL_NOT_FOUND (0xC0000000 | 0x0262) // ERRDOS, 182
+#define NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND (0xC0000000 | 0x0263) // ERRDOS, 127
+#define NT_STATUS_RESOURCE_NOT_OWNED (0xC0000000 | 0x0264) // ERRDOS, 288
+#define NT_STATUS_TOO_MANY_LINKS (0xC0000000 | 0x0265) // ERRDOS, ErrTooManyLinks
+#define NT_STATUS_QUOTA_LIST_INCONSISTENT (0xC0000000 | 0x0266) // ERRHRD, ERRgeneral
+#define NT_STATUS_FILE_IS_OFFLINE (0xC0000000 | 0x0267) // ERRHRD, ERRgeneral
+#define NT_STATUS_VOLUME_DISMOUNTED (0xC0000000 | 0x026e) // ERRDOS, 21
+#define NT_STATUS_NOT_A_REPARSE_POINT (0xC0000000 | 0x0275) // ERRHRD, ERRgeneral
+#define NT_STATUS_DIRECTORY_IS_A_REPARSE_POINT (0xC0000000 | 0x0281) // ERRDOS, 161
+#define NT_STATUS_ENCRYPTION_FAILED (0xC0000000 | 0x028a) // ERRDOS, ERRnoaccess
+#define NT_STATUS_DECRYPTION_FAILED (0xC0000000 | 0x028b) // ERRDOS, ERRnoaccess
+#define NT_STATUS_RANGE_NOT_FOUND (0xC0000000 | 0x028c) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_RECOVERY_POLICY (0xC0000000 | 0x028d) // ERRDOS, ERRnoaccess
+#define NT_STATUS_NO_EFS (0xC0000000 | 0x028e) // ERRDOS, ERRnoaccess
+#define NT_STATUS_WRONG_EFS (0xC0000000 | 0x028f) // ERRDOS, ERRnoaccess
+#define NT_STATUS_NO_USER_KEYS (0xC0000000 | 0x0290) // ERRDOS, ERRnoaccess
+#define NT_STATUS_VOLUME_NOT_UPGRADED (0xC0000000 | 0x029c) // ERRDOS, ERRbadfunc
+#define NT_STATUS_NETWORK_SESSION_EXPIRED (0xC0000000 | 0x035c) // ERRHRD, ERRgeneral
+/* scheduler */
+#define NT_STATUS_NO_SUCH_JOB (0xC0000000 | 0xEDE) // ERRHRD, ERRgeneral
+#define NT_STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP (0xC0000000 | 0x5D0000) // ERRHRD, ERRgeneral
+#define NT_STATUS_OS2_INVALID_LEVEL 0x007c0001 // ERRDOS, ERRunknownlevel
#endif /* _NTERR_H */
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index be22bbc4a65a..9530e5b01564 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -73,7 +73,6 @@ cifs_prime_dcache(struct dentry *parent, struct qstr *name,
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
bool posix = cifs_sb_master_tcon(cifs_sb)->posix_extensions;
bool reparse_need_reval = false;
- DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
int rc;
cifs_dbg(FYI, "%s: for %s\n", __func__, name->name);
@@ -105,7 +104,7 @@ retry:
(fattr->cf_flags & CIFS_FATTR_NEED_REVAL))
return;
- dentry = d_alloc_parallel(parent, name, &wq);
+ dentry = d_alloc_parallel(parent, name);
}
if (IS_ERR(dentry))
return;
@@ -143,7 +142,8 @@ retry:
fattr->cf_rdev = inode->i_rdev;
fattr->cf_uid = inode->i_uid;
fattr->cf_gid = inode->i_gid;
- fattr->cf_eof = CIFS_I(inode)->netfs.remote_i_size;
+ fattr->cf_eof =
+ netfs_read_remote_i_size(inode);
fattr->cf_symlink_target = NULL;
} else {
CIFS_I(inode)->time = 0;
@@ -242,9 +242,11 @@ static void
cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info,
struct cifs_sb_info *cifs_sb)
{
+ unsigned int sbflags = cifs_sb_flags(cifs_sb);
struct smb2_posix_info_parsed parsed;
+ int rc;
- posix_info_parse(info, NULL, &parsed);
+ rc = posix_info_parse(info, NULL, &parsed);
memset(fattr, 0, sizeof(*fattr));
fattr->cf_uniqueid = le64_to_cpu(info->Inode);
@@ -281,8 +283,17 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info,
le32_to_cpu(info->ReparseTag),
le32_to_cpu(info->Mode));
- sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER);
- sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP);
+ fattr->cf_uid = cifs_sb->ctx->linux_uid;
+ fattr->cf_gid = cifs_sb->ctx->linux_gid;
+ if (rc < 0) {
+ cifs_dbg(VFS, "%s: failed to parse SIDs: %d\n",
+ __func__, rc);
+ } else {
+ if (!(sbflags & CIFS_MOUNT_OVERR_UID))
+ sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER);
+ if (!(sbflags & CIFS_MOUNT_OVERR_GID))
+ sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP);
+ }
}
static void __dir_info_to_fattr(struct cifs_fattr *fattr, const void *info)
@@ -415,7 +426,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:
@@ -732,6 +743,8 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
if (cfile->srch_inf.smallBuf)
cifs_small_buf_release(cfile->srch_inf.
ntwrk_buf_start);
+ else if (cfile->srch_inf.is_dynamic_buf)
+ kfree(cfile->srch_inf.ntwrk_buf_start);
else
cifs_buf_release(cfile->srch_inf.
ntwrk_buf_start);
@@ -950,7 +963,7 @@ static bool cifs_dir_emit(struct dir_context *ctx,
static int cifs_filldir(char *find_entry, struct file *file,
struct dir_context *ctx,
char *scratch_buf, unsigned int max_len,
- struct cached_fid *cfid)
+ char *end_of_smb, struct cached_fid *cfid)
{
struct cifsFileInfo *file_info = file->private_data;
struct super_block *sb = file_inode(file)->i_sb;
@@ -972,6 +985,11 @@ static int cifs_filldir(char *find_entry, struct file *file,
return -EINVAL;
}
+ if (de.name + de.namelen > end_of_smb) {
+ cifs_dbg(VFS, "search entry name extends past end of SMB\n");
+ return -EINVAL;
+ }
+
/* skip . and .. since we added them first */
if (cifs_entry_is_dot(&de, file_info->srch_inf.unicode))
return 0;
@@ -1027,7 +1045,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))
@@ -1192,7 +1210,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
*/
*tmp_buf = 0;
rc = cifs_filldir(current_entry, file, ctx,
- tmp_buf, max_len, cfid);
+ tmp_buf, max_len, end_of_smb, cfid);
if (rc) {
if (rc > 0)
rc = 0;
diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c
index cd1e1eaee67a..3a27773186ae 100644
--- a/fs/smb/client/reparse.c
+++ b/fs/smb/client/reparse.c
@@ -3,6 +3,7 @@
* Copyright (c) 2024 Paulo Alcantara <pc@manguebit.com>
*/
+#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/slab.h>
@@ -67,6 +68,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)
@@ -158,17 +160,40 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,
convert_delimiter(sym, sep);
/*
- * For absolute NT symlinks it is required to pass also leading
- * backslash and to not mangle NT object prefix "\\??\\" and not to
- * mangle colon in drive letter. But cifs_convert_path_to_utf16()
- * removes leading backslash and replaces '?' and ':'. So temporary
- * mask these characters in NT object prefix by '_' and then change
- * them back.
+ * Absolute NT symlinks must retain the leading backslash, "\\??\\"
+ * prefix and drive-letter colon. cifs_convert_path_to_utf16() strips
+ * the leading backslash and maps '?' and ':', so temporarily mask
+ * these characters with '_' and restore them after conversion.
+ *
+ * When symlinkroot is unset, sym comes directly from the caller.
+ * Validate the complete "\\??\\X:" prefix before using fixed offsets
+ * or subtracting the NT prefix length below. Require an ASCII drive
+ * letter so the prefix occupies six characters in UTF-16 too.
*/
- if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/')
+ if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') {
+ if (!strstarts(sym, "\\??\\") || !isascii(sym[4]) ||
+ !isalpha(sym[4]) || sym[5] != ':') {
+ rc = -EINVAL;
+ goto out;
+ }
sym[0] = sym[1] = sym[2] = sym[5] = '_';
+ }
+
+ /*
+ * 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);
- path = cifs_convert_path_to_utf16(sym, cifs_sb);
if (!path) {
rc = -ENOMEM;
goto out;
@@ -956,7 +981,8 @@ globalroot:
linux_target[i*3 + 1] = '.';
linux_target[i*3 + 2] = sep;
}
- memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */
+ /* +1 to skip leading sep */
+ memcpy(linux_target + levels*3, smb_target+1, smb_target_len-1);
} else {
/*
* This is either an absolute symlink in POSIX-style format
@@ -1122,25 +1148,31 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data,
struct cifs_sb_info *cifs_sb,
u32 tag, struct cifs_fattr *fattr)
{
+ unsigned int sbflags = cifs_sb_flags(cifs_sb);
+ kuid_t uid = cifs_sb->ctx->linux_uid;
+ kgid_t gid = cifs_sb->ctx->linux_gid;
struct smb2_file_full_ea_info *ea;
bool have_xattr_dev = false;
+ dev_t rdev = 0;
+ umode_t mode;
u32 next = 0;
+ mode = fattr->cf_mode & ~S_IFMT;
switch (tag) {
case IO_REPARSE_TAG_LX_SYMLINK:
- fattr->cf_mode |= S_IFLNK;
+ mode |= S_IFLNK;
break;
case IO_REPARSE_TAG_LX_FIFO:
- fattr->cf_mode |= S_IFIFO;
+ mode |= S_IFIFO;
break;
case IO_REPARSE_TAG_AF_UNIX:
- fattr->cf_mode |= S_IFSOCK;
+ mode |= S_IFSOCK;
break;
case IO_REPARSE_TAG_LX_CHR:
- fattr->cf_mode |= S_IFCHR;
+ mode |= S_IFCHR;
break;
case IO_REPARSE_TAG_LX_BLK:
- fattr->cf_mode |= S_IFBLK;
+ mode |= S_IFBLK;
break;
}
@@ -1162,26 +1194,31 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data,
nlen = ea->ea_name_length;
v = (void *)((u8 *)ea->ea_data + ea->ea_name_length + 1);
- if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen))
- fattr->cf_uid = wsl_make_kuid(cifs_sb, v);
- else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen))
- fattr->cf_gid = wsl_make_kgid(cifs_sb, v);
- else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) {
+ if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) {
+ if (!(sbflags & CIFS_MOUNT_OVERR_UID))
+ uid = wsl_make_kuid(cifs_sb, v);
+ } else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) {
+ if (!(sbflags & CIFS_MOUNT_OVERR_GID))
+ gid = wsl_make_kgid(cifs_sb, v);
+ } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) {
/* File type in reparse point tag and in xattr mode must match. */
- if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v)))
+ if (S_DT(mode) != S_DT(get_unaligned_le32(v)))
return false;
- fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v);
+ mode = get_unaligned_le32(v);
} else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) {
- fattr->cf_rdev = reparse_mkdev(v);
+ rdev = reparse_mkdev(v);
have_xattr_dev = true;
}
} while (next);
out:
-
/* Major and minor numbers for char and block devices are mandatory. */
if (!have_xattr_dev && (tag == IO_REPARSE_TAG_LX_CHR || tag == IO_REPARSE_TAG_LX_BLK))
return false;
+ fattr->cf_uid = uid;
+ fattr->cf_gid = gid;
+ fattr->cf_mode = mode;
+ fattr->cf_rdev = rdev;
return true;
}
@@ -1190,6 +1227,7 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb,
struct cifs_open_info_data *data)
{
struct reparse_nfs_data_buffer *buf = (struct reparse_nfs_data_buffer *)data->reparse.buf;
+ umode_t ftype;
if (buf == NULL)
return true;
@@ -1205,7 +1243,7 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb,
WARN_ON_ONCE(1);
return false;
}
- fattr->cf_mode |= S_IFCHR;
+ ftype = S_IFCHR;
fattr->cf_rdev = reparse_mkdev(buf->DataBuffer);
break;
case NFS_SPECFILE_BLK:
@@ -1213,22 +1251,23 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb,
WARN_ON_ONCE(1);
return false;
}
- fattr->cf_mode |= S_IFBLK;
+ ftype = S_IFBLK;
fattr->cf_rdev = reparse_mkdev(buf->DataBuffer);
break;
case NFS_SPECFILE_FIFO:
- fattr->cf_mode |= S_IFIFO;
+ ftype = S_IFIFO;
break;
case NFS_SPECFILE_SOCK:
- fattr->cf_mode |= S_IFSOCK;
+ ftype = S_IFSOCK;
break;
case NFS_SPECFILE_LNK:
- fattr->cf_mode |= S_IFLNK;
+ ftype = S_IFLNK;
break;
default:
WARN_ON_ONCE(1);
return false;
}
+ fattr->cf_mode = (fattr->cf_mode & ~S_IFMT) | ftype;
return true;
}
@@ -1256,6 +1295,7 @@ bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
break;
case 0: /* SMB1 symlink */
case IO_REPARSE_TAG_SYMLINK:
+ fattr->cf_mode &= ~S_IFMT;
fattr->cf_mode |= S_IFLNK;
break;
default:
diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h
index 0164dc47bdfd..05b2cecb4495 100644
--- a/fs/smb/client/reparse.h
+++ b/fs/smb/client/reparse.h
@@ -9,6 +9,7 @@
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/uidgid.h>
+#include <linux/unaligned.h>
#include "fs_context.h"
#include "cifsglob.h"
#include "../common/smbfsctl.h"
@@ -23,7 +24,7 @@
static inline dev_t reparse_mkdev(void *ptr)
{
- u64 v = le64_to_cpu(*(__le64 *)ptr);
+ u64 v = get_unaligned_le64(ptr);
return MKDEV(v & 0xffffffff, v >> 32);
}
@@ -31,7 +32,7 @@ static inline dev_t reparse_mkdev(void *ptr)
static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb,
void *ptr)
{
- u32 uid = le32_to_cpu(*(__le32 *)ptr);
+ u32 uid = get_unaligned_le32(ptr);
if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_UID)
return cifs_sb->ctx->linux_uid;
@@ -41,7 +42,7 @@ static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb,
static inline kgid_t wsl_make_kgid(struct cifs_sb_info *cifs_sb,
void *ptr)
{
- u32 gid = le32_to_cpu(*(__le32 *)ptr);
+ u32 gid = get_unaligned_le32(ptr);
if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_GID)
return cifs_sb->ctx->linux_gid;
@@ -98,15 +99,21 @@ static inline bool reparse_inode_match(struct inode *inode,
timespec64_equal(&ctime, &fattr->cf_ctime);
}
+static inline u32 cifs_open_data_attrs(const struct cifs_open_info_data *data)
+{
+ if (data->contains_posix_file_info)
+ return le32_to_cpu(data->posix_fi.DosAttributes);
+
+ return le32_to_cpu(data->fi.Attributes);
+}
+
static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data)
{
- u32 attrs;
- bool ret;
+ u32 attrs = cifs_open_data_attrs(data);
if (data->contains_posix_file_info) {
struct smb311_posix_qinfo *fi = &data->posix_fi;
- attrs = le32_to_cpu(fi->DosAttributes);
if (data->reparse_point) {
attrs |= ATTR_REPARSE_POINT;
fi->DosAttributes = cpu_to_le32(attrs);
@@ -115,16 +122,13 @@ static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data)
} else {
struct smb2_file_all_info *fi = &data->fi;
- attrs = le32_to_cpu(fi->Attributes);
if (data->reparse_point) {
attrs |= ATTR_REPARSE_POINT;
fi->Attributes = cpu_to_le32(attrs);
}
}
- ret = attrs & ATTR_REPARSE_POINT;
-
- return ret;
+ return attrs & ATTR_REPARSE_POINT;
}
bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c
index 698bd27119ae..e095f41b5882 100644
--- a/fs/smb/client/sess.c
+++ b/fs/smb/client/sess.c
@@ -149,9 +149,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
int old_chan_count, new_chan_count;
int left;
int rc = 0;
- int tries = 0;
+ int tries = 0, attempts;
size_t iface_weight = 0, iface_min_speed = 0;
- struct cifs_server_iface *iface = NULL, *niface = NULL;
+ struct cifs_server_iface *iface = NULL, *candidate = NULL;
struct cifs_server_iface *last_iface = NULL;
spin_lock(&ses->chan_lock);
@@ -197,67 +197,89 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
break;
}
- if (!iface)
- iface = list_first_entry(&ses->iface_list, struct cifs_server_iface,
- iface_head);
last_iface = list_last_entry(&ses->iface_list, struct cifs_server_iface,
iface_head);
iface_min_speed = last_iface->speed;
+ spin_unlock(&ses->iface_lock);
- list_for_each_entry_safe_from(iface, niface, &ses->iface_list,
- iface_head) {
- /* do not mix rdma and non-rdma interfaces */
- if (iface->rdma_capable != ses->server->rdma)
- continue;
-
- /* skip ifaces that are unusable */
- if (!iface->is_active ||
- (is_ses_using_iface(ses, iface) &&
- !iface->rss_capable))
- continue;
+ attempts = 0;
+ while (left > 0) {
+ spin_lock(&ses->iface_lock);
- /* check if we already allocated enough channels */
- iface_weight = iface->speed / iface_min_speed;
+ /*
+ * iface_lock must be dropped while opening a channel,
+ * and a concurrent interface refresh may remove and
+ * free entries during that window, so no list entry
+ * may be kept across it without a reference. Scan
+ * the list from the beginning each time and only pass
+ * a referenced candidate to cifs_ses_add_channel();
+ * weight_fulfilled tracks the progress so that no
+ * iface is selected beyond its weight.
+ */
+ candidate = NULL;
+ list_for_each_entry(iface, &ses->iface_list, iface_head) {
+ /* do not mix rdma and non-rdma interfaces */
+ if (iface->rdma_capable != ses->server->rdma)
+ continue;
+
+ /* skip ifaces that are unusable */
+ if (!iface->is_active ||
+ (is_ses_using_iface(ses, iface) &&
+ !iface->rss_capable))
+ continue;
+
+ /* check if we already allocated enough channels */
+ iface_weight = iface->speed / iface_min_speed;
+
+ if (iface->weight_fulfilled >= iface_weight)
+ continue;
+
+ /* take ref before unlock */
+ kref_get(&iface->refcount);
+ candidate = iface;
+ break;
+ }
- if (iface->weight_fulfilled >= iface_weight)
- continue;
+ if (!candidate) {
+ /* no usable iface. reset weight_fulfilled and start over */
+ list_for_each_entry(iface, &ses->iface_list, iface_head)
+ iface->weight_fulfilled = 0;
+ spin_unlock(&ses->iface_lock);
+ break;
+ }
- /* take ref before unlock */
- kref_get(&iface->refcount);
+ attempts++;
+ if (attempts > 3 * ses->chan_max) {
+ kref_put(&candidate->refcount, release_iface);
+ spin_unlock(&ses->iface_lock);
+ break;
+ }
spin_unlock(&ses->iface_lock);
- rc = cifs_ses_add_channel(ses, iface);
+ rc = cifs_ses_add_channel(ses, candidate);
spin_lock(&ses->iface_lock);
if (rc) {
cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n",
- &iface->sockaddr,
+ &candidate->sockaddr,
rc);
- kref_put(&iface->refcount, release_iface);
/* failure to add chan should increase weight */
- iface->weight_fulfilled++;
+ candidate->weight_fulfilled++;
+ kref_put(&candidate->refcount, release_iface);
+ spin_unlock(&ses->iface_lock);
continue;
}
- iface->num_channels++;
- iface->weight_fulfilled++;
+ candidate->num_channels++;
+ candidate->weight_fulfilled++;
cifs_info("successfully opened new channel on iface:%pIS\n",
- &iface->sockaddr);
- break;
- }
-
- /* reached end of list. reset weight_fulfilled and start over */
- if (list_entry_is_head(iface, &ses->iface_list, iface_head)) {
- list_for_each_entry(iface, &ses->iface_list, iface_head)
- iface->weight_fulfilled = 0;
+ &candidate->sockaddr);
spin_unlock(&ses->iface_lock);
- iface = NULL;
- continue;
- }
- spin_unlock(&ses->iface_lock);
- left--;
- new_chan_count++;
+ left--;
+ new_chan_count++;
+ break;
+ }
}
return new_chan_count - old_chan_count;
@@ -595,17 +617,6 @@ cifs_ses_add_channel(struct cifs_ses *ses,
spin_unlock(&ses->chan_lock);
mutex_lock(&ses->session_mutex);
- /*
- * We need to allocate the server crypto now as we will need
- * to sign packets before we generate the channel signing key
- * (we sign with the session key)
- */
- rc = smb3_crypto_shash_allocate(chan->server);
- if (rc) {
- cifs_dbg(VFS, "%s: crypto alloc failed\n", __func__);
- mutex_unlock(&ses->session_mutex);
- goto out;
- }
rc = cifs_negotiate_protocol(xid, ses, chan->server);
if (!rc)
diff --git a/fs/smb/client/smb1encrypt.c b/fs/smb/client/smb1encrypt.c
index 0dbbce2431ff..c9eb68f04e7b 100644
--- a/fs/smb/client/smb1encrypt.c
+++ b/fs/smb/client/smb1encrypt.c
@@ -11,6 +11,7 @@
#include <linux/fips.h>
#include <crypto/md5.h>
+#include <crypto/utils.h>
#include "cifsproto.h"
#include "smb1proto.h"
#include "cifs_debug.h"
@@ -80,6 +81,7 @@ int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server,
else
memcpy(cifs_pdu->Signature.SecuritySignature, smb_signature, 8);
+ memzero_explicit(smb_signature, sizeof(smb_signature));
return rc;
}
@@ -125,15 +127,13 @@ int cifs_verify_signature(struct smb_rqst *rqst,
rc = cifs_calc_signature(rqst, server, what_we_think_sig_should_be);
cifs_server_unlock(server);
- if (rc)
- return rc;
-
-/* cifs_dump_mem("what we think it should be: ",
- what_we_think_sig_should_be, 16); */
-
- if (memcmp(server_response_sig, what_we_think_sig_should_be, 8))
- return -EACCES;
- else
- return 0;
+ if (!rc) {
+ if (crypto_memneq(server_response_sig,
+ what_we_think_sig_should_be, 8))
+ rc = -EACCES;
+ }
+ memzero_explicit(what_we_think_sig_should_be,
+ sizeof(what_we_think_sig_should_be));
+ return rc;
}
diff --git a/fs/smb/client/smb1maperror.c b/fs/smb/client/smb1maperror.c
index e1964fd2b85e..395299f9121b 100644
--- a/fs/smb/client/smb1maperror.c
+++ b/fs/smb/client/smb1maperror.c
@@ -6,709 +6,107 @@
*
* Error mapping routines from Samba libsmb/errormap.c
* Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Luke Kenneth Casson Leighton 1997-2001.
*/
+#include <linux/bsearch.h>
+
+#include <kunit/visibility.h>
+
#include "cifsproto.h"
#include "smb1proto.h"
#include "smberr.h"
#include "nterr.h"
#include "cifs_debug.h"
-struct smb_to_posix_error {
- __u16 smb_err;
- int posix_code;
-};
+static __always_inline int smb1_posix_error_cmp(const void *_key, const void *_pivot)
+{
+ __u16 key = *(__u16 *)_key;
+ const struct smb_to_posix_error *pivot = _pivot;
+
+ if (key < pivot->smb_err)
+ return -1;
+ if (key > pivot->smb_err)
+ return 1;
+ return 0;
+}
static const struct smb_to_posix_error mapping_table_ERRDOS[] = {
- {ERRbadfunc, -EINVAL},
- {ERRbadfile, -ENOENT},
- {ERRbadpath, -ENOTDIR},
- {ERRnofids, -EMFILE},
- {ERRnoaccess, -EACCES},
- {ERRbadfid, -EBADF},
- {ERRbadmcb, -EIO},
- {ERRnomem, -EREMOTEIO},
- {ERRbadmem, -EFAULT},
- {ERRbadenv, -EFAULT},
- {ERRbadformat, -EINVAL},
- {ERRbadaccess, -EACCES},
- {ERRbaddata, -EIO},
- {ERRbaddrive, -ENXIO},
- {ERRremcd, -EACCES},
- {ERRdiffdevice, -EXDEV},
- {ERRnofiles, -ENOENT},
- {ERRwriteprot, -EROFS},
- {ERRbadshare, -EBUSY},
- {ERRlock, -EACCES},
- {ERRunsup, -EINVAL},
- {ERRnosuchshare, -ENXIO},
- {ERRfilexists, -EEXIST},
- {ERRinvparm, -EINVAL},
- {ERRdiskfull, -ENOSPC},
- {ERRinvname, -ENOENT},
- {ERRunknownlevel, -EOPNOTSUPP},
- {ERRdirnotempty, -ENOTEMPTY},
- {ERRnotlocked, -ENOLCK},
- {ERRcancelviolation, -ENOLCK},
- {ERRalreadyexists, -EEXIST},
- {ERRmoredata, -EOVERFLOW},
- {ERReasnotsupported, -EOPNOTSUPP},
- {ErrQuota, -EDQUOT},
- {ErrNotALink, -ENOLINK},
- {ERRnetlogonNotStarted, -ENOPROTOOPT},
- {ERRsymlink, -EOPNOTSUPP},
- {ErrTooManyLinks, -EMLINK},
- {0, 0}
+/*
+ * Automatically generated by the `gen_smb1_mapping` script,
+ * sorted by DOS error code (ascending).
+ */
+#include "smb1_err_dos_map.c"
};
static const struct smb_to_posix_error mapping_table_ERRSRV[] = {
- {ERRerror, -EIO},
- {ERRbadpw, -EACCES}, /* was EPERM */
- {ERRbadtype, -EREMOTE},
- {ERRaccess, -EACCES},
- {ERRinvtid, -ENXIO},
- {ERRinvnetname, -ENXIO},
- {ERRinvdevice, -ENXIO},
- {ERRqfull, -ENOSPC},
- {ERRqtoobig, -ENOSPC},
- {ERRqeof, -EIO},
- {ERRinvpfid, -EBADF},
- {ERRsmbcmd, -EBADRQC},
- {ERRsrverror, -EIO},
- {ERRbadBID, -EIO},
- {ERRfilespecs, -EINVAL},
- {ERRbadLink, -EIO},
- {ERRbadpermits, -EINVAL},
- {ERRbadPID, -ESRCH},
- {ERRsetattrmode, -EINVAL},
- {ERRpaused, -EHOSTDOWN},
- {ERRmsgoff, -EHOSTDOWN},
- {ERRnoroom, -ENOSPC},
- {ERRrmuns, -EUSERS},
- {ERRtimeout, -ETIME},
- {ERRnoresource, -EREMOTEIO},
- {ERRtoomanyuids, -EUSERS},
- {ERRbaduid, -EACCES},
- {ERRusempx, -EIO},
- {ERRusestd, -EIO},
- {ERR_NOTIFY_ENUM_DIR, -ENOBUFS},
- {ERRnoSuchUser, -EACCES},
- {ERRaccountexpired, -EKEYEXPIRED},
- {ERRbadclient, -EACCES},
- {ERRbadLogonTime, -EACCES},
- {ERRpasswordExpired, -EKEYEXPIRED},
-
- {ERRnosupport, -EINVAL},
- {0, 0}
+/*
+ * Automatically generated by the `gen_smb1_mapping` script,
+ * sorted by SRV error code (ascending).
+ */
+#include "smb1_err_srv_map.c"
};
/*****************************************************************************
*convert a NT status code to a dos class/code
*****************************************************************************/
+
+static __always_inline int ntstatus_to_dos_cmp(const void *_key, const void *_pivot)
+{
+ __u32 key = *(__u32 *)_key;
+ const struct ntstatus_to_dos_err *pivot = _pivot;
+
+ if (key < pivot->ntstatus)
+ return -1;
+ if (key > pivot->ntstatus)
+ return 1;
+ return 0;
+}
+
/* NT status -> dos error map */
-static const struct {
- __u8 dos_class;
- __u16 dos_code;
- __u32 ntstatus;
-} ntstatus_to_dos_map[] = {
- {
- ERRSRV, ERR_NOTIFY_ENUM_DIR, NT_STATUS_NOTIFY_ENUM_DIR}, {
- ERRDOS, ERRmoredata, NT_STATUS_BUFFER_OVERFLOW}, {
- ERRDOS, ERRmoredata, NT_STATUS_MORE_PROCESSING_REQUIRED}, {
- ERRDOS, ERRnoaccess, NT_STATUS_PRIVILEGE_NOT_HELD}, {
- ERRDOS, ERRgeneral, NT_STATUS_UNSUCCESSFUL}, {
- ERRDOS, ERRbadfunc, NT_STATUS_NOT_IMPLEMENTED}, {
- ERRDOS, ERRbadpipe, NT_STATUS_INVALID_INFO_CLASS}, {
- ERRDOS, 24, NT_STATUS_INFO_LENGTH_MISMATCH}, {
- ERRHRD, ERRgeneral, NT_STATUS_ACCESS_VIOLATION}, {
- ERRHRD, ERRgeneral, NT_STATUS_IN_PAGE_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_PAGEFILE_QUOTA}, {
- ERRDOS, ERRbadfid, NT_STATUS_INVALID_HANDLE}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_INITIAL_STACK}, {
- ERRDOS, 193, NT_STATUS_BAD_INITIAL_PC}, {
- ERRDOS, 87, NT_STATUS_INVALID_CID}, {
- ERRHRD, ERRgeneral, NT_STATUS_TIMER_NOT_CANCELED}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER}, {
- ERRDOS, ERRbadfile, NT_STATUS_NO_SUCH_DEVICE}, {
- ERRDOS, ERRbadfile, NT_STATUS_NO_SUCH_FILE}, {
- ERRDOS, ERRbadfunc, NT_STATUS_INVALID_DEVICE_REQUEST}, {
- ERRDOS, 38, NT_STATUS_END_OF_FILE}, {
- ERRDOS, 34, NT_STATUS_WRONG_VOLUME}, {
- ERRDOS, 21, NT_STATUS_NO_MEDIA_IN_DEVICE}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNRECOGNIZED_MEDIA}, {
- ERRDOS, 27, NT_STATUS_NONEXISTENT_SECTOR},
-/* { This NT error code was 'sqashed'
- from NT_STATUS_MORE_PROCESSING_REQUIRED to NT_STATUS_OK
- during the session setup } */
- {
- ERRDOS, ERRnomem, NT_STATUS_NO_MEMORY}, {
- ERRDOS, 487, NT_STATUS_CONFLICTING_ADDRESSES}, {
- ERRDOS, 487, NT_STATUS_NOT_MAPPED_VIEW}, {
- ERRDOS, 87, NT_STATUS_UNABLE_TO_FREE_VM}, {
- ERRDOS, 87, NT_STATUS_UNABLE_TO_DELETE_SECTION}, {
- ERRDOS, 2142, NT_STATUS_INVALID_SYSTEM_SERVICE}, {
- ERRHRD, ERRgeneral, NT_STATUS_ILLEGAL_INSTRUCTION}, {
- ERRDOS, ERRnoaccess, NT_STATUS_INVALID_LOCK_SEQUENCE}, {
- ERRDOS, ERRnoaccess, NT_STATUS_INVALID_VIEW_SIZE}, {
- ERRDOS, 193, NT_STATUS_INVALID_FILE_FOR_SECTION}, {
- ERRDOS, ERRnoaccess, NT_STATUS_ALREADY_COMMITTED},
-/* { This NT error code was 'sqashed'
- from NT_STATUS_ACCESS_DENIED to NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE
- during the session setup } */
- {
- ERRDOS, ERRnoaccess, NT_STATUS_ACCESS_DENIED}, {
- ERRDOS, 111, NT_STATUS_BUFFER_TOO_SMALL}, {
- ERRDOS, ERRbadfid, NT_STATUS_OBJECT_TYPE_MISMATCH}, {
- ERRHRD, ERRgeneral, NT_STATUS_NONCONTINUABLE_EXCEPTION}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_DISPOSITION}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNWIND}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_STACK}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_UNWIND_TARGET}, {
- ERRDOS, 158, NT_STATUS_NOT_LOCKED}, {
- ERRHRD, ERRgeneral, NT_STATUS_PARITY_ERROR}, {
- ERRDOS, 487, NT_STATUS_UNABLE_TO_DECOMMIT_VM}, {
- ERRDOS, 487, NT_STATUS_NOT_COMMITTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_PORT_ATTRIBUTES}, {
- ERRHRD, ERRgeneral, NT_STATUS_PORT_MESSAGE_TOO_LONG}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_MIX}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_QUOTA_LOWER}, {
- ERRHRD, ERRgeneral, NT_STATUS_DISK_CORRUPT_ERROR}, {
- /* mapping changed since shell does lookup on * expects FileNotFound */
- ERRDOS, ERRbadfile, NT_STATUS_OBJECT_NAME_INVALID}, {
- ERRDOS, ERRbadfile, NT_STATUS_OBJECT_NAME_NOT_FOUND}, {
- ERRDOS, ERRalreadyexists, NT_STATUS_OBJECT_NAME_COLLISION}, {
- ERRHRD, ERRgeneral, NT_STATUS_HANDLE_NOT_WAITABLE}, {
- ERRDOS, ERRbadfid, NT_STATUS_PORT_DISCONNECTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_DEVICE_ALREADY_ATTACHED}, {
- ERRDOS, 161, NT_STATUS_OBJECT_PATH_INVALID}, {
- ERRDOS, ERRbadpath, NT_STATUS_OBJECT_PATH_NOT_FOUND}, {
- ERRDOS, 161, NT_STATUS_OBJECT_PATH_SYNTAX_BAD}, {
- ERRHRD, ERRgeneral, NT_STATUS_DATA_OVERRUN}, {
- ERRHRD, ERRgeneral, NT_STATUS_DATA_LATE_ERROR}, {
- ERRDOS, 23, NT_STATUS_DATA_ERROR}, {
- ERRDOS, 23, NT_STATUS_CRC_ERROR}, {
- ERRDOS, ERRnomem, NT_STATUS_SECTION_TOO_BIG}, {
- ERRDOS, ERRnoaccess, NT_STATUS_PORT_CONNECTION_REFUSED}, {
- ERRDOS, ERRbadfid, NT_STATUS_INVALID_PORT_HANDLE}, {
- ERRDOS, ERRbadshare, NT_STATUS_SHARING_VIOLATION}, {
- ERRHRD, ERRgeneral, NT_STATUS_QUOTA_EXCEEDED}, {
- ERRDOS, 87, NT_STATUS_INVALID_PAGE_PROTECTION}, {
- ERRDOS, 288, NT_STATUS_MUTANT_NOT_OWNED}, {
- ERRDOS, 298, NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED}, {
- ERRDOS, 87, NT_STATUS_PORT_ALREADY_SET}, {
- ERRDOS, 87, NT_STATUS_SECTION_NOT_IMAGE}, {
- ERRDOS, 156, NT_STATUS_SUSPEND_COUNT_EXCEEDED}, {
- ERRDOS, ERRnoaccess, NT_STATUS_THREAD_IS_TERMINATING}, {
- ERRDOS, 87, NT_STATUS_BAD_WORKING_SET_LIMIT}, {
- ERRDOS, 87, NT_STATUS_INCOMPATIBLE_FILE_MAP}, {
- ERRDOS, 87, NT_STATUS_SECTION_PROTECTION}, {
- ERRDOS, ERReasnotsupported, NT_STATUS_EAS_NOT_SUPPORTED}, {
- ERRDOS, 255, NT_STATUS_EA_TOO_LARGE}, {
- ERRHRD, ERRgeneral, NT_STATUS_NONEXISTENT_EA_ENTRY}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_EAS_ON_FILE}, {
- ERRHRD, ERRgeneral, NT_STATUS_EA_CORRUPT_ERROR}, {
- ERRDOS, ERRlock, NT_STATUS_FILE_LOCK_CONFLICT}, {
- ERRDOS, ERRlock, NT_STATUS_LOCK_NOT_GRANTED}, {
- ERRDOS, ERRbadfile, NT_STATUS_DELETE_PENDING}, {
- ERRDOS, ERRunsup, NT_STATUS_CTL_FILE_NOT_SUPPORTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNKNOWN_REVISION}, {
- ERRHRD, ERRgeneral, NT_STATUS_REVISION_MISMATCH}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_OWNER}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_PRIMARY_GROUP}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_IMPERSONATION_TOKEN}, {
- ERRHRD, ERRgeneral, NT_STATUS_CANT_DISABLE_MANDATORY}, {
- ERRDOS, 2215, NT_STATUS_NO_LOGON_SERVERS}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_LOGON_SESSION}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_PRIVILEGE}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_ACCOUNT_NAME}, {
- ERRHRD, ERRgeneral, NT_STATUS_USER_EXISTS},
-/* { This NT error code was 'sqashed'
- from NT_STATUS_NO_SUCH_USER to NT_STATUS_LOGON_FAILURE
- during the session setup } */
- {
- ERRDOS, ERRnoaccess, NT_STATUS_NO_SUCH_USER}, { /* could map to 2238 */
- ERRHRD, ERRgeneral, NT_STATUS_GROUP_EXISTS}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_GROUP}, {
- ERRHRD, ERRgeneral, NT_STATUS_MEMBER_IN_GROUP}, {
- ERRHRD, ERRgeneral, NT_STATUS_MEMBER_NOT_IN_GROUP}, {
- ERRHRD, ERRgeneral, NT_STATUS_LAST_ADMIN},
-/* { This NT error code was 'sqashed'
- from NT_STATUS_WRONG_PASSWORD to NT_STATUS_LOGON_FAILURE
- during the session setup } */
- {
- ERRSRV, ERRbadpw, NT_STATUS_WRONG_PASSWORD}, {
- ERRHRD, ERRgeneral, NT_STATUS_ILL_FORMED_PASSWORD}, {
- ERRHRD, ERRgeneral, NT_STATUS_PASSWORD_RESTRICTION}, {
- ERRDOS, ERRnoaccess, NT_STATUS_LOGON_FAILURE}, {
- ERRHRD, ERRgeneral, NT_STATUS_ACCOUNT_RESTRICTION}, {
- ERRSRV, ERRbadLogonTime, NT_STATUS_INVALID_LOGON_HOURS}, {
- ERRSRV, ERRbadclient, NT_STATUS_INVALID_WORKSTATION}, {
- ERRSRV, ERRpasswordExpired, NT_STATUS_PASSWORD_EXPIRED}, {
- ERRSRV, ERRaccountexpired, NT_STATUS_ACCOUNT_DISABLED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NONE_MAPPED}, {
- ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_LUIDS_REQUESTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_LUIDS_EXHAUSTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_SUB_AUTHORITY}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_ACL}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_SID}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_SECURITY_DESCR}, {
- ERRDOS, 127, NT_STATUS_PROCEDURE_NOT_FOUND}, {
- ERRDOS, 193, NT_STATUS_INVALID_IMAGE_FORMAT}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_TOKEN}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_INHERITANCE_ACL}, {
- ERRDOS, 158, NT_STATUS_RANGE_NOT_LOCKED}, {
- ERRDOS, 112, NT_STATUS_DISK_FULL}, {
- ERRHRD, ERRgeneral, NT_STATUS_SERVER_DISABLED}, {
- ERRHRD, ERRgeneral, NT_STATUS_SERVER_NOT_DISABLED}, {
- ERRDOS, 68, NT_STATUS_TOO_MANY_GUIDS_REQUESTED}, {
- ERRDOS, 259, NT_STATUS_GUIDS_EXHAUSTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_ID_AUTHORITY}, {
- ERRDOS, 259, NT_STATUS_AGENTS_EXHAUSTED}, {
- ERRDOS, 154, NT_STATUS_INVALID_VOLUME_LABEL}, {
- ERRDOS, 14, NT_STATUS_SECTION_NOT_EXTENDED}, {
- ERRDOS, 487, NT_STATUS_NOT_MAPPED_DATA}, {
- ERRHRD, ERRgeneral, NT_STATUS_RESOURCE_DATA_NOT_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_RESOURCE_TYPE_NOT_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_RESOURCE_NAME_NOT_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_ARRAY_BOUNDS_EXCEEDED}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOAT_DENORMAL_OPERAND}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOAT_DIVIDE_BY_ZERO}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOAT_INEXACT_RESULT}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOAT_INVALID_OPERATION}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOAT_OVERFLOW}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOAT_STACK_CHECK}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOAT_UNDERFLOW}, {
- ERRHRD, ERRgeneral, NT_STATUS_INTEGER_DIVIDE_BY_ZERO}, {
- ERRDOS, 534, NT_STATUS_INTEGER_OVERFLOW}, {
- ERRHRD, ERRgeneral, NT_STATUS_PRIVILEGED_INSTRUCTION}, {
- ERRDOS, ERRnomem, NT_STATUS_TOO_MANY_PAGING_FILES}, {
- ERRHRD, ERRgeneral, NT_STATUS_FILE_INVALID}, {
- ERRHRD, ERRgeneral, NT_STATUS_ALLOTTED_SPACE_EXCEEDED},
-/* { This NT error code was 'sqashed'
- from NT_STATUS_INSUFFICIENT_RESOURCES to
- NT_STATUS_INSUFF_SERVER_RESOURCES during the session setup } */
- {
- ERRDOS, ERRnoresource, NT_STATUS_INSUFFICIENT_RESOURCES}, {
- ERRDOS, ERRbadpath, NT_STATUS_DFS_EXIT_PATH_FOUND}, {
- ERRDOS, 23, NT_STATUS_DEVICE_DATA_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_DEVICE_NOT_CONNECTED}, {
- ERRDOS, 21, NT_STATUS_DEVICE_POWER_FAILURE}, {
- ERRDOS, 487, NT_STATUS_FREE_VM_NOT_AT_BASE}, {
- ERRDOS, 487, NT_STATUS_MEMORY_NOT_ALLOCATED}, {
- ERRHRD, ERRgeneral, NT_STATUS_WORKING_SET_QUOTA}, {
- ERRDOS, 19, NT_STATUS_MEDIA_WRITE_PROTECTED}, {
- ERRDOS, 21, NT_STATUS_DEVICE_NOT_READY}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_GROUP_ATTRIBUTES}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_IMPERSONATION_LEVEL}, {
- ERRHRD, ERRgeneral, NT_STATUS_CANT_OPEN_ANONYMOUS}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_VALIDATION_CLASS}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_TOKEN_TYPE}, {
- ERRDOS, 87, NT_STATUS_BAD_MASTER_BOOT_RECORD}, {
- ERRHRD, ERRgeneral, NT_STATUS_INSTRUCTION_MISALIGNMENT}, {
- ERRDOS, ERRpipebusy, NT_STATUS_INSTANCE_NOT_AVAILABLE}, {
- ERRDOS, ERRpipebusy, NT_STATUS_PIPE_NOT_AVAILABLE}, {
- ERRDOS, ERRbadpipe, NT_STATUS_INVALID_PIPE_STATE}, {
- ERRDOS, ERRpipebusy, NT_STATUS_PIPE_BUSY}, {
- ERRDOS, ERRbadfunc, NT_STATUS_ILLEGAL_FUNCTION}, {
- ERRDOS, ERRnotconnected, NT_STATUS_PIPE_DISCONNECTED}, {
- ERRDOS, ERRpipeclosing, NT_STATUS_PIPE_CLOSING}, {
- ERRHRD, ERRgeneral, NT_STATUS_PIPE_CONNECTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_PIPE_LISTENING}, {
- ERRDOS, ERRbadpipe, NT_STATUS_INVALID_READ_MODE}, {
- ERRDOS, 121, NT_STATUS_IO_TIMEOUT}, {
- ERRDOS, 38, NT_STATUS_FILE_FORCED_CLOSED}, {
- ERRHRD, ERRgeneral, NT_STATUS_PROFILING_NOT_STARTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_PROFILING_NOT_STOPPED}, {
- ERRHRD, ERRgeneral, NT_STATUS_COULD_NOT_INTERPRET}, {
- ERRDOS, ERRnoaccess, NT_STATUS_FILE_IS_A_DIRECTORY}, {
- ERRDOS, ERRunsup, NT_STATUS_NOT_SUPPORTED}, {
- ERRDOS, 51, NT_STATUS_REMOTE_NOT_LISTENING}, {
- ERRDOS, 52, NT_STATUS_DUPLICATE_NAME}, {
- ERRDOS, 53, NT_STATUS_BAD_NETWORK_PATH}, {
- ERRDOS, 54, NT_STATUS_NETWORK_BUSY}, {
- ERRDOS, 55, NT_STATUS_DEVICE_DOES_NOT_EXIST}, {
- ERRDOS, 56, NT_STATUS_TOO_MANY_COMMANDS}, {
- ERRDOS, 57, NT_STATUS_ADAPTER_HARDWARE_ERROR}, {
- ERRDOS, 58, NT_STATUS_INVALID_NETWORK_RESPONSE}, {
- ERRDOS, 59, NT_STATUS_UNEXPECTED_NETWORK_ERROR}, {
- ERRDOS, 60, NT_STATUS_BAD_REMOTE_ADAPTER}, {
- ERRDOS, 61, NT_STATUS_PRINT_QUEUE_FULL}, {
- ERRDOS, 62, NT_STATUS_NO_SPOOL_SPACE}, {
- ERRDOS, 63, NT_STATUS_PRINT_CANCELLED}, {
- ERRDOS, 64, NT_STATUS_NETWORK_NAME_DELETED}, {
- ERRDOS, 65, NT_STATUS_NETWORK_ACCESS_DENIED}, {
- ERRDOS, 66, NT_STATUS_BAD_DEVICE_TYPE}, {
- ERRDOS, ERRnosuchshare, NT_STATUS_BAD_NETWORK_NAME}, {
- ERRDOS, 68, NT_STATUS_TOO_MANY_NAMES}, {
- ERRDOS, 69, NT_STATUS_TOO_MANY_SESSIONS}, {
- ERRDOS, 70, NT_STATUS_SHARING_PAUSED}, {
- ERRDOS, 71, NT_STATUS_REQUEST_NOT_ACCEPTED}, {
- ERRDOS, 72, NT_STATUS_REDIRECTOR_PAUSED}, {
- ERRDOS, 88, NT_STATUS_NET_WRITE_FAULT}, {
- ERRHRD, ERRgeneral, NT_STATUS_PROFILING_AT_LIMIT}, {
- ERRDOS, ERRdiffdevice, NT_STATUS_NOT_SAME_DEVICE}, {
- ERRDOS, ERRnoaccess, NT_STATUS_FILE_RENAMED}, {
- ERRDOS, 240, NT_STATUS_VIRTUAL_CIRCUIT_CLOSED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_SECURITY_ON_OBJECT}, {
- ERRHRD, ERRgeneral, NT_STATUS_CANT_WAIT}, {
- ERRDOS, ERRpipeclosing, NT_STATUS_PIPE_EMPTY}, {
- ERRHRD, ERRgeneral, NT_STATUS_CANT_ACCESS_DOMAIN_INFO}, {
- ERRHRD, ERRgeneral, NT_STATUS_CANT_TERMINATE_SELF}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_SERVER_STATE}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_DOMAIN_STATE}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_DOMAIN_ROLE}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_DOMAIN}, {
- ERRHRD, ERRgeneral, NT_STATUS_DOMAIN_EXISTS}, {
- ERRHRD, ERRgeneral, NT_STATUS_DOMAIN_LIMIT_EXCEEDED}, {
- ERRDOS, 300, NT_STATUS_OPLOCK_NOT_GRANTED}, {
- ERRDOS, 301, NT_STATUS_INVALID_OPLOCK_PROTOCOL}, {
- ERRHRD, ERRgeneral, NT_STATUS_INTERNAL_DB_CORRUPTION}, {
- ERRHRD, ERRgeneral, NT_STATUS_INTERNAL_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_GENERIC_NOT_MAPPED}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_DESCRIPTOR_FORMAT}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_USER_BUFFER}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNEXPECTED_IO_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNEXPECTED_MM_CREATE_ERR}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNEXPECTED_MM_MAP_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNEXPECTED_MM_EXTEND_ERR}, {
- ERRHRD, ERRgeneral, NT_STATUS_NOT_LOGON_PROCESS}, {
- ERRHRD, ERRgeneral, NT_STATUS_LOGON_SESSION_EXISTS}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_1}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_2}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_3}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_4}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_5}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_6}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_7}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_8}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_9}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_10}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_11}, {
- ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_12}, {
- ERRDOS, ERRbadpath, NT_STATUS_REDIRECTOR_NOT_STARTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_REDIRECTOR_STARTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_STACK_OVERFLOW}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_PACKAGE}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_FUNCTION_TABLE}, {
- ERRDOS, 203, NT_STATUS_VARIABLE_NOT_FOUND}, {
- ERRDOS, 145, NT_STATUS_DIRECTORY_NOT_EMPTY}, {
- ERRHRD, ERRgeneral, NT_STATUS_FILE_CORRUPT_ERROR}, {
- ERRDOS, 267, NT_STATUS_NOT_A_DIRECTORY}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_LOGON_SESSION_STATE}, {
- ERRHRD, ERRgeneral, NT_STATUS_LOGON_SESSION_COLLISION}, {
- ERRDOS, 206, NT_STATUS_NAME_TOO_LONG}, {
- ERRDOS, 2401, NT_STATUS_FILES_OPEN}, {
- ERRDOS, 2404, NT_STATUS_CONNECTION_IN_USE}, {
- ERRHRD, ERRgeneral, NT_STATUS_MESSAGE_NOT_FOUND}, {
- ERRDOS, ERRnoaccess, NT_STATUS_PROCESS_IS_TERMINATING}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_LOGON_TYPE}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_GUID_TRANSLATION}, {
- ERRHRD, ERRgeneral, NT_STATUS_CANNOT_IMPERSONATE}, {
- ERRHRD, ERRgeneral, NT_STATUS_IMAGE_ALREADY_LOADED}, {
- ERRHRD, ERRgeneral, NT_STATUS_ABIOS_NOT_PRESENT}, {
- ERRHRD, ERRgeneral, NT_STATUS_ABIOS_LID_NOT_EXIST}, {
- ERRHRD, ERRgeneral, NT_STATUS_ABIOS_LID_ALREADY_OWNED}, {
- ERRHRD, ERRgeneral, NT_STATUS_ABIOS_NOT_LID_OWNER}, {
- ERRHRD, ERRgeneral, NT_STATUS_ABIOS_INVALID_COMMAND}, {
- ERRHRD, ERRgeneral, NT_STATUS_ABIOS_INVALID_LID}, {
- ERRHRD, ERRgeneral, NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE}, {
- ERRHRD, ERRgeneral, NT_STATUS_ABIOS_INVALID_SELECTOR}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_LDT}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_LDT_SIZE}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_LDT_OFFSET}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_LDT_DESCRIPTOR}, {
- ERRDOS, 193, NT_STATUS_INVALID_IMAGE_NE_FORMAT}, {
- ERRHRD, ERRgeneral, NT_STATUS_RXACT_INVALID_STATE}, {
- ERRHRD, ERRgeneral, NT_STATUS_RXACT_COMMIT_FAILURE}, {
- ERRHRD, ERRgeneral, NT_STATUS_MAPPED_FILE_SIZE_ZERO}, {
- ERRDOS, ERRnofids, NT_STATUS_TOO_MANY_OPENED_FILES}, {
- ERRHRD, ERRgeneral, NT_STATUS_CANCELLED}, {
- ERRDOS, ERRnoaccess, NT_STATUS_CANNOT_DELETE}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_COMPUTER_NAME}, {
- ERRDOS, ERRnoaccess, NT_STATUS_FILE_DELETED}, {
- ERRHRD, ERRgeneral, NT_STATUS_SPECIAL_ACCOUNT}, {
- ERRHRD, ERRgeneral, NT_STATUS_SPECIAL_GROUP}, {
- ERRHRD, ERRgeneral, NT_STATUS_SPECIAL_USER}, {
- ERRHRD, ERRgeneral, NT_STATUS_MEMBERS_PRIMARY_GROUP}, {
- ERRDOS, ERRbadfid, NT_STATUS_FILE_CLOSED}, {
- ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_THREADS}, {
- ERRHRD, ERRgeneral, NT_STATUS_THREAD_NOT_IN_PROCESS}, {
- ERRHRD, ERRgeneral, NT_STATUS_TOKEN_ALREADY_IN_USE}, {
- ERRHRD, ERRgeneral, NT_STATUS_PAGEFILE_QUOTA_EXCEEDED}, {
- ERRHRD, ERRgeneral, NT_STATUS_COMMITMENT_LIMIT}, {
- ERRDOS, 193, NT_STATUS_INVALID_IMAGE_LE_FORMAT}, {
- ERRDOS, 193, NT_STATUS_INVALID_IMAGE_NOT_MZ}, {
- ERRDOS, 193, NT_STATUS_INVALID_IMAGE_PROTECT}, {
- ERRDOS, 193, NT_STATUS_INVALID_IMAGE_WIN_16}, {
- ERRHRD, ERRgeneral, NT_STATUS_LOGON_SERVER_CONFLICT}, {
- ERRHRD, ERRgeneral, NT_STATUS_TIME_DIFFERENCE_AT_DC}, {
- ERRHRD, ERRgeneral, NT_STATUS_SYNCHRONIZATION_REQUIRED}, {
- ERRDOS, 126, NT_STATUS_DLL_NOT_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_OPEN_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_IO_PRIVILEGE_FAILED}, {
- ERRDOS, 182, NT_STATUS_ORDINAL_NOT_FOUND}, {
- ERRDOS, 127, NT_STATUS_ENTRYPOINT_NOT_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_CONTROL_C_EXIT}, {
- ERRDOS, 64, NT_STATUS_LOCAL_DISCONNECT}, {
- ERRDOS, 64, NT_STATUS_REMOTE_DISCONNECT}, {
- ERRDOS, 51, NT_STATUS_REMOTE_RESOURCES}, {
- ERRDOS, 59, NT_STATUS_LINK_FAILED}, {
- ERRDOS, 59, NT_STATUS_LINK_TIMEOUT}, {
- ERRDOS, 59, NT_STATUS_INVALID_CONNECTION}, {
- ERRDOS, 59, NT_STATUS_INVALID_ADDRESS}, {
- ERRHRD, ERRgeneral, NT_STATUS_DLL_INIT_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_MISSING_SYSTEMFILE}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNHANDLED_EXCEPTION}, {
- ERRHRD, ERRgeneral, NT_STATUS_APP_INIT_FAILURE}, {
- ERRHRD, ERRgeneral, NT_STATUS_PAGEFILE_CREATE_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_PAGEFILE}, {
- ERRDOS, 124, NT_STATUS_INVALID_LEVEL}, {
- ERRDOS, 86, NT_STATUS_WRONG_PASSWORD_CORE}, {
- ERRHRD, ERRgeneral, NT_STATUS_ILLEGAL_FLOAT_CONTEXT}, {
- ERRDOS, 109, NT_STATUS_PIPE_BROKEN}, {
- ERRHRD, ERRgeneral, NT_STATUS_REGISTRY_CORRUPT}, {
- ERRHRD, ERRgeneral, NT_STATUS_REGISTRY_IO_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_EVENT_PAIR}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNRECOGNIZED_VOLUME}, {
- ERRHRD, ERRgeneral, NT_STATUS_SERIAL_NO_DEVICE_INITED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_ALIAS}, {
- ERRHRD, ERRgeneral, NT_STATUS_MEMBER_NOT_IN_ALIAS}, {
- ERRHRD, ERRgeneral, NT_STATUS_MEMBER_IN_ALIAS}, {
- ERRHRD, ERRgeneral, NT_STATUS_ALIAS_EXISTS}, {
- ERRHRD, ERRgeneral, NT_STATUS_LOGON_NOT_GRANTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_SECRETS}, {
- ERRHRD, ERRgeneral, NT_STATUS_SECRET_TOO_LONG}, {
- ERRHRD, ERRgeneral, NT_STATUS_INTERNAL_DB_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_FULLSCREEN_MODE}, {
- ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_CONTEXT_IDS}, {
- ERRDOS, ERRnoaccess, NT_STATUS_LOGON_TYPE_NOT_GRANTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NOT_REGISTRY_FILE}, {
- ERRHRD, ERRgeneral, NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED}, {
- ERRHRD, ERRgeneral, NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_FT_MISSING_MEMBER}, {
- ERRHRD, ERRgeneral, NT_STATUS_ILL_FORMED_SERVICE_ENTRY}, {
- ERRHRD, ERRgeneral, NT_STATUS_ILLEGAL_CHARACTER}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNMAPPABLE_CHARACTER}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNDEFINED_CHARACTER}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_VOLUME}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_WRONG_CYLINDER}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_UNKNOWN_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_BAD_REGISTERS}, {
- ERRHRD, ERRgeneral, NT_STATUS_DISK_RECALIBRATE_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_DISK_OPERATION_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_DISK_RESET_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_SHARED_IRQ_BUSY}, {
- ERRHRD, ERRgeneral, NT_STATUS_FT_ORPHANING}, {
- ERRHRD, ERRgeneral, NT_STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT}, {
- ERRHRD, ERRgeneral, NT_STATUS_PARTITION_FAILURE}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_BLOCK_LENGTH}, {
- ERRHRD, ERRgeneral, NT_STATUS_DEVICE_NOT_PARTITIONED}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNABLE_TO_LOCK_MEDIA}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNABLE_TO_UNLOAD_MEDIA}, {
- ERRHRD, ERRgeneral, NT_STATUS_EOM_OVERFLOW}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_MEDIA}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_MEMBER}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_MEMBER}, {
- ERRHRD, ERRgeneral, NT_STATUS_KEY_DELETED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_LOG_SPACE}, {
- ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_SIDS}, {
- ERRHRD, ERRgeneral, NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED}, {
- ERRHRD, ERRgeneral, NT_STATUS_KEY_HAS_CHILDREN}, {
- ERRHRD, ERRgeneral, NT_STATUS_CHILD_MUST_BE_VOLATILE}, {
- ERRDOS, 87, NT_STATUS_DEVICE_CONFIGURATION_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_DRIVER_INTERNAL_ERROR}, {
- ERRDOS, 22, NT_STATUS_INVALID_DEVICE_STATE}, {
- ERRHRD, ERRgeneral, NT_STATUS_IO_DEVICE_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_DEVICE_PROTOCOL_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_BACKUP_CONTROLLER}, {
- ERRHRD, ERRgeneral, NT_STATUS_LOG_FILE_FULL}, {
- ERRDOS, 19, NT_STATUS_TOO_LATE}, {
- ERRDOS, ERRnoaccess, NT_STATUS_NO_TRUST_LSA_SECRET},
-/* { This NT error code was 'sqashed'
- from NT_STATUS_NO_TRUST_SAM_ACCOUNT to
- NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE during the session setup } */
- {
- ERRDOS, ERRnoaccess, NT_STATUS_NO_TRUST_SAM_ACCOUNT}, {
- ERRDOS, ERRnoaccess, NT_STATUS_TRUSTED_DOMAIN_FAILURE}, {
- ERRDOS, ERRnoaccess, NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE}, {
- ERRHRD, ERRgeneral, NT_STATUS_EVENTLOG_FILE_CORRUPT}, {
- ERRHRD, ERRgeneral, NT_STATUS_EVENTLOG_CANT_START}, {
- ERRDOS, ERRnoaccess, NT_STATUS_TRUST_FAILURE}, {
- ERRHRD, ERRgeneral, NT_STATUS_MUTANT_LIMIT_EXCEEDED}, {
- ERRDOS, ERRnetlogonNotStarted, NT_STATUS_NETLOGON_NOT_STARTED}, {
- ERRSRV, ERRaccountexpired, NT_STATUS_ACCOUNT_EXPIRED}, {
- ERRHRD, ERRgeneral, NT_STATUS_POSSIBLE_DEADLOCK}, {
- ERRHRD, ERRgeneral, NT_STATUS_NETWORK_CREDENTIAL_CONFLICT}, {
- ERRHRD, ERRgeneral, NT_STATUS_REMOTE_SESSION_LIMIT}, {
- ERRHRD, ERRgeneral, NT_STATUS_EVENTLOG_FILE_CHANGED}, {
- ERRDOS, ERRnoaccess, NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT}, {
- ERRDOS, ERRnoaccess, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT}, {
- ERRDOS, ERRnoaccess, NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT},
-/* { This NT error code was 'sqashed'
- from NT_STATUS_DOMAIN_TRUST_INCONSISTENT to NT_STATUS_LOGON_FAILURE
- during the session setup } */
- {
- ERRDOS, ERRnoaccess, NT_STATUS_DOMAIN_TRUST_INCONSISTENT}, {
- ERRHRD, ERRgeneral, NT_STATUS_FS_DRIVER_REQUIRED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_USER_SESSION_KEY}, {
- ERRDOS, 59, NT_STATUS_USER_SESSION_DELETED}, {
- ERRHRD, ERRgeneral, NT_STATUS_RESOURCE_LANG_NOT_FOUND}, {
- ERRDOS, ERRnoresource, NT_STATUS_INSUFF_SERVER_RESOURCES}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_BUFFER_SIZE}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_ADDRESS_COMPONENT}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_ADDRESS_WILDCARD}, {
- ERRDOS, 68, NT_STATUS_TOO_MANY_ADDRESSES}, {
- ERRDOS, 52, NT_STATUS_ADDRESS_ALREADY_EXISTS}, {
- ERRDOS, 64, NT_STATUS_ADDRESS_CLOSED}, {
- ERRDOS, 64, NT_STATUS_CONNECTION_DISCONNECTED}, {
- ERRDOS, 64, NT_STATUS_CONNECTION_RESET}, {
- ERRDOS, 68, NT_STATUS_TOO_MANY_NODES}, {
- ERRDOS, 59, NT_STATUS_TRANSACTION_ABORTED}, {
- ERRDOS, 59, NT_STATUS_TRANSACTION_TIMED_OUT}, {
- ERRDOS, 59, NT_STATUS_TRANSACTION_NO_RELEASE}, {
- ERRDOS, 59, NT_STATUS_TRANSACTION_NO_MATCH}, {
- ERRDOS, 59, NT_STATUS_TRANSACTION_RESPONDED}, {
- ERRDOS, 59, NT_STATUS_TRANSACTION_INVALID_ID}, {
- ERRDOS, 59, NT_STATUS_TRANSACTION_INVALID_TYPE}, {
- ERRDOS, ERRunsup, NT_STATUS_NOT_SERVER_SESSION}, {
- ERRDOS, ERRunsup, NT_STATUS_NOT_CLIENT_SESSION}, {
- ERRHRD, ERRgeneral, NT_STATUS_CANNOT_LOAD_REGISTRY_FILE}, {
- ERRHRD, ERRgeneral, NT_STATUS_DEBUG_ATTACH_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_SYSTEM_PROCESS_TERMINATED}, {
- ERRHRD, ERRgeneral, NT_STATUS_DATA_NOT_ACCEPTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_BROWSER_SERVERS_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_VDM_HARD_ERROR}, {
- ERRHRD, ERRgeneral, NT_STATUS_DRIVER_CANCEL_TIMEOUT}, {
- ERRHRD, ERRgeneral, NT_STATUS_REPLY_MESSAGE_MISMATCH}, {
- ERRHRD, ERRgeneral, NT_STATUS_MAPPED_ALIGNMENT}, {
- ERRDOS, 193, NT_STATUS_IMAGE_CHECKSUM_MISMATCH}, {
- ERRHRD, ERRgeneral, NT_STATUS_LOST_WRITEBEHIND_DATA}, {
- ERRHRD, ERRgeneral, NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID}, {
- ERRSRV, ERRpasswordExpired, NT_STATUS_PASSWORD_MUST_CHANGE}, {
- ERRHRD, ERRgeneral, NT_STATUS_NOT_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_NOT_TINY_STREAM}, {
- ERRHRD, ERRgeneral, NT_STATUS_RECOVERY_FAILURE}, {
- ERRHRD, ERRgeneral, NT_STATUS_STACK_OVERFLOW_READ}, {
- ERRHRD, ERRgeneral, NT_STATUS_FAIL_CHECK}, {
- ERRHRD, ERRgeneral, NT_STATUS_DUPLICATE_OBJECTID}, {
- ERRHRD, ERRgeneral, NT_STATUS_OBJECTID_EXISTS}, {
- ERRHRD, ERRgeneral, NT_STATUS_CONVERT_TO_LARGE}, {
- ERRHRD, ERRgeneral, NT_STATUS_RETRY}, {
- ERRHRD, ERRgeneral, NT_STATUS_FOUND_OUT_OF_SCOPE}, {
- ERRHRD, ERRgeneral, NT_STATUS_ALLOCATE_BUCKET}, {
- ERRHRD, ERRgeneral, NT_STATUS_PROPSET_NOT_FOUND}, {
- ERRHRD, ERRgeneral, NT_STATUS_MARSHALL_OVERFLOW}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_VARIANT}, {
- ERRHRD, ERRgeneral, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND}, {
- ERRDOS, ERRnoaccess, NT_STATUS_ACCOUNT_LOCKED_OUT}, {
- ERRDOS, ERRbadfid, NT_STATUS_HANDLE_NOT_CLOSABLE}, {
- ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_REFUSED}, {
- ERRHRD, ERRgeneral, NT_STATUS_GRACEFUL_DISCONNECT}, {
- ERRHRD, ERRgeneral, NT_STATUS_ADDRESS_ALREADY_ASSOCIATED}, {
- ERRHRD, ERRgeneral, NT_STATUS_ADDRESS_NOT_ASSOCIATED}, {
- ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_INVALID}, {
- ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_ACTIVE}, {
- ERRHRD, ERRgeneral, NT_STATUS_NETWORK_UNREACHABLE}, {
- ERRHRD, ERRgeneral, NT_STATUS_HOST_UNREACHABLE}, {
- ERRHRD, ERRgeneral, NT_STATUS_PROTOCOL_UNREACHABLE}, {
- ERRHRD, ERRgeneral, NT_STATUS_PORT_UNREACHABLE}, {
- ERRHRD, ERRgeneral, NT_STATUS_REQUEST_ABORTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_ABORTED}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_COMPRESSION_BUFFER}, {
- ERRHRD, ERRgeneral, NT_STATUS_USER_MAPPED_FILE}, {
- ERRHRD, ERRgeneral, NT_STATUS_AUDIT_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_TIMER_RESOLUTION_NOT_SET}, {
- ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_COUNT_LIMIT}, {
- ERRHRD, ERRgeneral, NT_STATUS_LOGIN_TIME_RESTRICTION}, {
- ERRHRD, ERRgeneral, NT_STATUS_LOGIN_WKSTA_RESTRICTION}, {
- ERRDOS, 193, NT_STATUS_IMAGE_MP_UP_MISMATCH}, {
- ERRHRD, ERRgeneral, NT_STATUS_INSUFFICIENT_LOGON_INFO}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_DLL_ENTRYPOINT}, {
- ERRHRD, ERRgeneral, NT_STATUS_BAD_SERVICE_ENTRYPOINT}, {
- ERRHRD, ERRgeneral, NT_STATUS_LPC_REPLY_LOST}, {
- ERRHRD, ERRgeneral, NT_STATUS_IP_ADDRESS_CONFLICT1}, {
- ERRHRD, ERRgeneral, NT_STATUS_IP_ADDRESS_CONFLICT2}, {
- ERRHRD, ERRgeneral, NT_STATUS_REGISTRY_QUOTA_LIMIT}, {
- ERRSRV, 3, NT_STATUS_PATH_NOT_COVERED}, {
- ERRHRD, ERRgeneral, NT_STATUS_NO_CALLBACK_ACTIVE}, {
- ERRHRD, ERRgeneral, NT_STATUS_LICENSE_QUOTA_EXCEEDED}, {
- ERRHRD, ERRgeneral, NT_STATUS_PWD_TOO_SHORT}, {
- ERRHRD, ERRgeneral, NT_STATUS_PWD_TOO_RECENT}, {
- ERRHRD, ERRgeneral, NT_STATUS_PWD_HISTORY_CONFLICT}, {
- ERRHRD, ERRgeneral, NT_STATUS_PLUGPLAY_NO_DEVICE}, {
- ERRHRD, ERRgeneral, NT_STATUS_UNSUPPORTED_COMPRESSION}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_HW_PROFILE}, {
- ERRHRD, ERRgeneral, NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH}, {
- ERRDOS, 182, NT_STATUS_DRIVER_ORDINAL_NOT_FOUND}, {
- ERRDOS, 127, NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND}, {
- ERRDOS, 288, NT_STATUS_RESOURCE_NOT_OWNED}, {
- ERRDOS, ErrTooManyLinks, NT_STATUS_TOO_MANY_LINKS}, {
- ERRHRD, ERRgeneral, NT_STATUS_QUOTA_LIST_INCONSISTENT}, {
- ERRHRD, ERRgeneral, NT_STATUS_FILE_IS_OFFLINE}, {
- ERRDOS, 21, NT_STATUS_VOLUME_DISMOUNTED}, {
- ERRDOS, 161, NT_STATUS_DIRECTORY_IS_A_REPARSE_POINT}, {
- ERRDOS, ERRnoaccess, NT_STATUS_ENCRYPTION_FAILED}, {
- ERRDOS, ERRnoaccess, NT_STATUS_DECRYPTION_FAILED}, {
- ERRHRD, ERRgeneral, NT_STATUS_RANGE_NOT_FOUND}, {
- ERRDOS, ERRnoaccess, NT_STATUS_NO_RECOVERY_POLICY}, {
- ERRDOS, ERRnoaccess, NT_STATUS_NO_EFS}, {
- ERRDOS, ERRnoaccess, NT_STATUS_WRONG_EFS}, {
- ERRDOS, ERRnoaccess, NT_STATUS_NO_USER_KEYS}, {
- ERRDOS, ERRbadfunc, NT_STATUS_VOLUME_NOT_UPGRADED}, {
- ERRDOS, ERRsymlink, NT_STATUS_STOPPED_ON_SYMLINK}, {
- ERRDOS, ERRunknownlevel, NT_STATUS_OS2_INVALID_LEVEL}, {
- 0, 0, 0 }
+static const struct ntstatus_to_dos_err ntstatus_to_dos_map[] = {
+/*
+ * Automatically generated by the `gen_smb1_mapping` script,
+ * sorted by NT status code (ascending).
+ */
+#include "smb1_mapping_table.c"
};
-/*****************************************************************************
- Print an error message from the status code
- *****************************************************************************/
-static void
-cifs_print_status(__u32 status_code)
+static const struct ntstatus_to_dos_err *
+search_ntstatus_to_dos_map(__u32 ntstatus)
{
- int idx = 0;
-
- while (nt_errs[idx].nt_errstr != NULL) {
- if (nt_errs[idx].nt_errcode == status_code) {
- pr_notice("Status code returned 0x%08x %s\n",
- status_code, nt_errs[idx].nt_errstr);
- return;
- }
- idx++;
- }
- return;
+ return __inline_bsearch(&ntstatus, ntstatus_to_dos_map,
+ ARRAY_SIZE(ntstatus_to_dos_map),
+ sizeof(struct ntstatus_to_dos_err),
+ ntstatus_to_dos_cmp);
}
+static const struct smb_to_posix_error *
+search_mapping_table_ERRDOS(__u16 smb_err)
+{
+ return __inline_bsearch(&smb_err, mapping_table_ERRDOS,
+ ARRAY_SIZE(mapping_table_ERRDOS),
+ sizeof(struct smb_to_posix_error),
+ smb1_posix_error_cmp);
+}
-static void
-ntstatus_to_dos(__u32 ntstatus, __u8 *eclass, __u16 *ecode)
+static const struct smb_to_posix_error *
+search_mapping_table_ERRSRV(__u16 smb_err)
{
- int i;
- if (ntstatus == 0) {
- *eclass = 0;
- *ecode = 0;
- return;
- }
- for (i = 0; ntstatus_to_dos_map[i].ntstatus; i++) {
- if (ntstatus == ntstatus_to_dos_map[i].ntstatus) {
- *eclass = ntstatus_to_dos_map[i].dos_class;
- *ecode = ntstatus_to_dos_map[i].dos_code;
- return;
- }
- }
- *eclass = ERRHRD;
- *ecode = ERRgeneral;
+ return __inline_bsearch(&smb_err, mapping_table_ERRSRV,
+ ARRAY_SIZE(mapping_table_ERRSRV),
+ sizeof(struct smb_to_posix_error),
+ smb1_posix_error_cmp);
}
int
map_smb_to_linux_error(char *buf, bool logErr)
{
struct smb_hdr *smb = (struct smb_hdr *)buf;
- unsigned int i;
int rc = -EIO; /* if transport error smb error may not be set */
__u8 smberrclass;
__u16 smberrcode;
+ const struct smb_to_posix_error *err_map = NULL;
/* BB if NT Status codes - map NT BB */
@@ -720,11 +118,20 @@ map_smb_to_linux_error(char *buf, bool logErr)
/* translate the newer STATUS codes to old style SMB errors
* and then to POSIX errors */
__u32 err = le32_to_cpu(smb->Status.CifsError);
- if (logErr && (err != (NT_STATUS_MORE_PROCESSING_REQUIRED)))
- cifs_print_status(err);
- else if (cifsFYI & CIFS_RC)
- cifs_print_status(err);
- ntstatus_to_dos(err, &smberrclass, &smberrcode);
+ const struct ntstatus_to_dos_err *map = search_ntstatus_to_dos_map(err);
+
+ if (map) {
+ if ((logErr && err != NT_STATUS_MORE_PROCESSING_REQUIRED) ||
+ (cifsFYI & CIFS_RC))
+ pr_notice("Status code returned 0x%08x %s\n",
+ map->ntstatus, map->nt_errstr);
+
+ smberrclass = map->dos_class;
+ smberrcode = map->dos_code;
+ } else {
+ smberrclass = ERRHRD;
+ smberrcode = ERRgeneral;
+ }
} else {
smberrclass = smb->Status.DosError.ErrorClass;
smberrcode = le16_to_cpu(smb->Status.DosError.Error);
@@ -732,38 +139,16 @@ map_smb_to_linux_error(char *buf, bool logErr)
/* old style errors */
- /* DOS class smb error codes - map DOS */
if (smberrclass == ERRDOS) {
+ /* DOS class smb error codes - map DOS */
/* 1 byte field no need to byte reverse */
- for (i = 0;
- i <
- sizeof(mapping_table_ERRDOS) /
- sizeof(struct smb_to_posix_error); i++) {
- if (mapping_table_ERRDOS[i].smb_err == 0)
- break;
- else if (mapping_table_ERRDOS[i].smb_err ==
- smberrcode) {
- rc = mapping_table_ERRDOS[i].posix_code;
- break;
- }
- /* else try next error mapping one to see if match */
- }
+ err_map = search_mapping_table_ERRDOS(smberrcode);
} else if (smberrclass == ERRSRV) {
/* server class of error codes */
- for (i = 0;
- i <
- sizeof(mapping_table_ERRSRV) /
- sizeof(struct smb_to_posix_error); i++) {
- if (mapping_table_ERRSRV[i].smb_err == 0)
- break;
- else if (mapping_table_ERRSRV[i].smb_err ==
- smberrcode) {
- rc = mapping_table_ERRSRV[i].posix_code;
- break;
- }
- /* else try next error mapping to see if match */
- }
+ err_map = search_mapping_table_ERRSRV(smberrcode);
}
+ if (err_map)
+ rc = err_map->posix_code;
/* else ERRHRD class errors or junk - return EIO */
/* special cases for NT status codes which cannot be translated to DOS codes */
@@ -811,3 +196,87 @@ map_and_check_smb_error(struct TCP_Server_Info *server,
return rc;
}
+
+#define DEFINE_CHECK_SORT_FUNC(__array, __field) \
+static int __init __array ## _is_sorted(void) \
+{ \
+ unsigned int i; \
+ \
+ /* Check whether the array is sorted in ascending order */ \
+ for (i = 1; i < ARRAY_SIZE(__array); i++) { \
+ if (__array[i].__field >= \
+ __array[i - 1].__field) \
+ continue; \
+ \
+ pr_err(#__array " array order is incorrect\n"); \
+ return -EINVAL; \
+ } \
+ \
+ return 0; \
+}
+
+/* ntstatus_to_dos_map_is_sorted */
+DEFINE_CHECK_SORT_FUNC(ntstatus_to_dos_map, ntstatus);
+/* mapping_table_ERRDOS_is_sorted */
+DEFINE_CHECK_SORT_FUNC(mapping_table_ERRDOS, smb_err);
+/* mapping_table_ERRSRV_is_sorted */
+DEFINE_CHECK_SORT_FUNC(mapping_table_ERRSRV, smb_err);
+
+int __init smb1_init_maperror(void)
+{
+ int rc;
+
+ rc = ntstatus_to_dos_map_is_sorted();
+ if (rc)
+ return rc;
+
+ rc = mapping_table_ERRDOS_is_sorted();
+ if (rc)
+ return rc;
+
+ return mapping_table_ERRSRV_is_sorted();
+}
+
+#if IS_ENABLED(CONFIG_SMB1_KUNIT_TESTS)
+const struct ntstatus_to_dos_err *
+search_ntstatus_to_dos_map_test(__u32 ntstatus)
+{
+ return search_ntstatus_to_dos_map(ntstatus);
+}
+EXPORT_SYMBOL_IF_KUNIT(search_ntstatus_to_dos_map_test);
+
+const struct ntstatus_to_dos_err *
+ntstatus_to_dos_map_test = ntstatus_to_dos_map;
+EXPORT_SYMBOL_IF_KUNIT(ntstatus_to_dos_map_test);
+
+unsigned int ntstatus_to_dos_num = ARRAY_SIZE(ntstatus_to_dos_map);
+EXPORT_SYMBOL_IF_KUNIT(ntstatus_to_dos_num);
+
+const struct smb_to_posix_error *
+search_mapping_table_ERRDOS_test(__u16 smb_err)
+{
+ return search_mapping_table_ERRDOS(smb_err);
+}
+EXPORT_SYMBOL_IF_KUNIT(search_mapping_table_ERRDOS_test);
+
+const struct smb_to_posix_error *
+mapping_table_ERRDOS_test = mapping_table_ERRDOS;
+EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRDOS_test);
+
+unsigned int mapping_table_ERRDOS_num = ARRAY_SIZE(mapping_table_ERRDOS);
+EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRDOS_num);
+
+const struct smb_to_posix_error *
+search_mapping_table_ERRSRV_test(__u16 smb_err)
+{
+ return search_mapping_table_ERRSRV(smb_err);
+}
+EXPORT_SYMBOL_IF_KUNIT(search_mapping_table_ERRSRV_test);
+
+const struct smb_to_posix_error *
+mapping_table_ERRSRV_test = mapping_table_ERRSRV;
+EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRSRV_test);
+
+unsigned int mapping_table_ERRSRV_num = ARRAY_SIZE(mapping_table_ERRSRV);
+EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRSRV_num);
+#endif
diff --git a/fs/smb/client/smb1maperror_test.c b/fs/smb/client/smb1maperror_test.c
new file mode 100644
index 000000000000..903c46f71291
--- /dev/null
+++ b/fs/smb/client/smb1maperror_test.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * KUnit tests of SMB1 maperror
+ *
+ * Copyright (C) 2026 KylinSoft Co., Ltd. All rights reserved.
+ * Author(s): Youling Tang <tangyouling@kylinos.cn>
+ * ChenXiaoSong <chenxiaosong@kylinos.cn>
+ *
+ */
+
+#include <kunit/test.h>
+#include "smb1proto.h"
+#include "nterr.h"
+#include "smberr.h"
+
+#define DEFINE_CHECK_SEARCH_FUNC(__struct_name, __field, \
+ __array, __num) \
+static void check_search_ ## __array(struct kunit *test) \
+{ \
+ unsigned int i; \
+ const struct __struct_name *expect, *result; \
+ \
+ for (i = 0; i < __num; i++) { \
+ expect = &__array ## _test[i]; \
+ result = search_ ## __array ## _test(expect->__field); \
+ KUNIT_ASSERT_NOT_NULL(test, result); \
+ test_cmp_ ## __struct_name(test, expect, result); \
+ } \
+}
+
+static void
+test_cmp_ntstatus_to_dos_err(struct kunit *test,
+ const struct ntstatus_to_dos_err *expect,
+ const struct ntstatus_to_dos_err *result)
+{
+ KUNIT_EXPECT_EQ(test, expect->dos_class, result->dos_class);
+ KUNIT_EXPECT_EQ(test, expect->dos_code, result->dos_code);
+ KUNIT_EXPECT_EQ(test, expect->ntstatus, result->ntstatus);
+ KUNIT_EXPECT_STREQ(test, expect->nt_errstr, result->nt_errstr);
+}
+
+static void
+test_cmp_smb_to_posix_error(struct kunit *test,
+ const struct smb_to_posix_error *expect,
+ const struct smb_to_posix_error *result)
+{
+ KUNIT_EXPECT_EQ(test, expect->smb_err, result->smb_err);
+ KUNIT_EXPECT_EQ(test, expect->posix_code, result->posix_code);
+}
+
+/* check_search_ntstatus_to_dos_map */
+DEFINE_CHECK_SEARCH_FUNC(ntstatus_to_dos_err, ntstatus, ntstatus_to_dos_map,
+ ntstatus_to_dos_num);
+/* check_search_mapping_table_ERRDOS */
+DEFINE_CHECK_SEARCH_FUNC(smb_to_posix_error, smb_err, mapping_table_ERRDOS,
+ mapping_table_ERRDOS_num);
+/* check_search_mapping_table_ERRSRV */
+DEFINE_CHECK_SEARCH_FUNC(smb_to_posix_error, smb_err, mapping_table_ERRSRV,
+ mapping_table_ERRSRV_num);
+
+static struct kunit_case maperror_test_cases[] = {
+ KUNIT_CASE(check_search_ntstatus_to_dos_map),
+ KUNIT_CASE(check_search_mapping_table_ERRDOS),
+ KUNIT_CASE(check_search_mapping_table_ERRSRV),
+ {}
+};
+
+static struct kunit_suite maperror_suite = {
+ .name = "smb1_maperror",
+ .test_cases = maperror_test_cases,
+};
+
+kunit_test_suite(maperror_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit tests of SMB1 maperror");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/fs/smb/client/smb1misc.c b/fs/smb/client/smb1misc.c
index ba56023010d8..cdfbbff24b72 100644
--- a/fs/smb/client/smb1misc.c
+++ b/fs/smb/client/smb1misc.c
@@ -80,7 +80,8 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
(struct smb_com_transaction_change_notify_rsp *)buf;
struct file_notify_information *pnotify;
__u32 data_offset = 0;
- size_t len = srv->total_read - srv->pdu_size;
+ /* total_read excludes the RFC1002 preamble */
+ size_t len = srv->total_read;
if (get_bcc(buf) > sizeof(struct file_notify_information)) {
data_offset = le32_to_cpu(pSMBr->DataOffset);
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index 9643eca0cb70..3ac4126267f6 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -49,7 +49,6 @@ void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon,
if (!CIFSSMBQFSUnixInfo(xid, tcon)) {
__u64 cap = le64_to_cpu(tcon->fsUnixInfo.Capability);
- unsigned int sbflags;
cifs_dbg(FYI, "unix caps which server supports %lld\n", cap);
/*
@@ -76,29 +75,27 @@ void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon,
if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)
cifs_dbg(VFS, "per-share encryption not supported yet\n");
- if (cifs_sb)
- sbflags = cifs_sb_flags(cifs_sb);
-
cap &= CIFS_UNIX_CAP_MASK;
if (ctx && ctx->no_psx_acl)
cap &= ~CIFS_UNIX_POSIX_ACL_CAP;
else if (CIFS_UNIX_POSIX_ACL_CAP & cap) {
cifs_dbg(FYI, "negotiated posix acl support\n");
- if (cifs_sb)
- sbflags |= CIFS_MOUNT_POSIXACL;
+ if (cifs_sb) {
+ atomic_or(CIFS_MOUNT_POSIXACL,
+ &cifs_sb->mnt_cifs_flags);
+ }
}
if (ctx && ctx->posix_paths == 0)
cap &= ~CIFS_UNIX_POSIX_PATHNAMES_CAP;
else if (cap & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
cifs_dbg(FYI, "negotiate posix pathnames\n");
- if (cifs_sb)
- sbflags |= CIFS_MOUNT_POSIX_PATHS;
+ if (cifs_sb) {
+ atomic_or(CIFS_MOUNT_POSIX_PATHS,
+ &cifs_sb->mnt_cifs_flags);
+ }
}
- if (cifs_sb)
- atomic_set(&cifs_sb->mnt_cifs_flags, sbflags);
-
cifs_dbg(FYI, "Negotiate caps 0x%x\n", (int)cap);
#ifdef CONFIG_CIFS_DEBUG2
if (cap & CIFS_UNIX_FCNTL_CAP)
@@ -508,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;
}
@@ -539,6 +542,7 @@ static int cifs_query_path_info(const unsigned int xid,
data->reparse_point = false;
data->adjust_tz = false;
+ data->unknown_nlink = false;
/*
* First try CIFSSMBQPathInfo() function which returns more info
@@ -605,6 +609,7 @@ static int cifs_query_path_info(const unsigned int xid,
fi.EASize = di->EaSize;
}
fi.NumberOfLinks = cpu_to_le32(1);
+ data->unknown_nlink = true;
fi.DeletePending = 0;
fi.Directory = !!(le32_to_cpu(fi.Attributes) & ATTR_DIRECTORY);
cifs_buf_release(search_info.ntwrk_buf_start);
@@ -627,6 +632,8 @@ static int cifs_query_path_info(const unsigned int xid,
rc = SMBQueryInformation(xid, tcon, full_path, &fi, cifs_sb->local_nls,
cifs_remap(cifs_sb));
data->adjust_tz = true;
+ if (!rc)
+ data->unknown_nlink = true;
} else if ((rc == -EOPNOTSUPP || rc == -EINVAL) && non_unicode_wildcard) {
/* Path with non-UNICODE wildcard character cannot exist. */
rc = -ENOENT;
@@ -714,7 +721,7 @@ static int cifs_query_path_info(const unsigned int xid,
ea->ea_value_length = cpu_to_le16(SMB2_WSL_XATTR_DEV_SIZE);
memcpy(&ea->ea_data[0], SMB2_WSL_XATTR_DEV, SMB2_WSL_XATTR_NAME_LEN + 1);
data->wsl.eas_len += ALIGN(sizeof(*ea) + SMB2_WSL_XATTR_NAME_LEN + 1 +
- SMB2_WSL_XATTR_MODE_SIZE, 4);
+ SMB2_WSL_XATTR_DEV_SIZE, 4);
rc = 0;
} else if (rc >= 0) {
/* It is an error if EA $LXDEV has wrong size. */
@@ -890,8 +897,10 @@ static int cifs_open_file(const unsigned int xid, struct cifs_open_parms *oparms
else
rc = CIFS_open(xid, oparms, oplock, &fi);
- if (!rc && data)
+ if (!rc && data) {
move_cifs_info_to_smb2(&data->fi, &fi);
+ data->unknown_nlink = true;
+ }
return rc;
}
@@ -952,7 +961,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);
@@ -960,7 +969,7 @@ smb_set_file_info(struct inode *inode, const char *full_path,
struct cifs_tcon *tcon;
/* if the file is already open for write, just use that fileid */
- open_file = find_writable_file(cinode, FIND_WR_FSUID_ONLY);
+ open_file = find_writable_file(cinode, FIND_FSUID_ONLY);
if (open_file) {
fid.netfid = open_file->fid.netfid;
@@ -1113,9 +1122,10 @@ out:
static int
cifs_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
- struct cifsFileInfo *cfile)
+ struct cifsFileInfo *cfile, __u16 compression_state)
{
- return CIFSSMB_set_compression(xid, tcon, cfile->fid.netfid);
+ return CIFSSMB_set_compression(xid, tcon, cfile->fid.netfid,
+ compression_state);
}
static int
diff --git a/fs/smb/client/smb1pdu.h b/fs/smb/client/smb1pdu.h
index 97f7e1244a8b..0870949144ab 100644
--- a/fs/smb/client/smb1pdu.h
+++ b/fs/smb/client/smb1pdu.h
@@ -1211,11 +1211,6 @@ typedef struct smb_com_transaction_compr_ioctl_req {
__le16 compression_state; /* See below for valid flags */
} __packed TRANSACT_COMPR_IOCTL_REQ;
-/* compression state flags */
-#define COMPRESSION_FORMAT_NONE 0x0000
-#define COMPRESSION_FORMAT_DEFAULT 0x0001
-#define COMPRESSION_FORMAT_LZNT1 0x0002
-
typedef struct smb_com_transaction_ioctl_rsp {
struct smb_hdr hdr; /* wct = 19 */
__u8 Reserved[3];
@@ -2061,15 +2056,6 @@ typedef struct {
__le32 EASize;
} __packed FILE_INFO_STANDARD; /* level 1 SetPath/FileInfo */
-typedef struct {
- __le64 CreationTime;
- __le64 LastAccessTime;
- __le64 LastWriteTime;
- __le64 ChangeTime;
- __le32 Attributes;
- __u32 Pad;
-} __packed FILE_BASIC_INFO; /* size info, level 0x101 */
-
struct file_allocation_info {
__le64 AllocationSize; /* Note old Samba srvr rounds this up too much */
} __packed; /* size used on disk, for level 0x103 for set, 0x105 for query */
diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h
index 42569bbcf6fd..80eaeb3dd2ec 100644
--- a/fs/smb/client/smb1proto.h
+++ b/fs/smb/client/smb1proto.h
@@ -117,7 +117,7 @@ struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data,
struct kvec *reparse_iov,
struct kvec *xattr_iov);
int CIFSSMB_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
- __u16 fid);
+ __u16 fid, __u16 compression_state);
int cifs_do_get_acl(const unsigned int xid, struct cifs_tcon *tcon,
const unsigned char *searchName, struct posix_acl **acl,
const int acl_type, const struct nls_table *nls_codepage,
@@ -234,8 +234,23 @@ int cifs_verify_signature(struct smb_rqst *rqst,
* smb1maperror.c
*/
int map_smb_to_linux_error(char *buf, bool logErr);
+int smb1_init_maperror(void);
int map_and_check_smb_error(struct TCP_Server_Info *server,
struct mid_q_entry *mid, bool logErr);
+#if IS_ENABLED(CONFIG_SMB1_KUNIT_TESTS)
+extern const struct ntstatus_to_dos_err *ntstatus_to_dos_map_test;
+extern unsigned int ntstatus_to_dos_num;
+const struct ntstatus_to_dos_err *
+search_ntstatus_to_dos_map_test(__u32 ntstatus);
+extern const struct smb_to_posix_error *mapping_table_ERRDOS_test;
+extern unsigned int mapping_table_ERRDOS_num;
+const struct smb_to_posix_error *
+search_mapping_table_ERRDOS_test(__u16 smb_err);
+extern const struct smb_to_posix_error *mapping_table_ERRSRV_test;
+extern unsigned int mapping_table_ERRSRV_num;
+const struct smb_to_posix_error *
+search_mapping_table_ERRSRV_test(__u16 smb_err);
+#endif
/*
* smb1misc.c
diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c
index 38d6d5538b96..42e95cc1bd89 100644
--- a/fs/smb/client/smb1transport.c
+++ b/fs/smb/client/smb1transport.c
@@ -260,9 +260,23 @@ SendReceive(const unsigned int xid, struct cifs_ses *ses,
goto out;
if (out_buf) {
- *pbytes_returned = resp_iov.iov_len;
- if (resp_iov.iov_len)
- memcpy(out_buf, resp_iov.iov_base, resp_iov.iov_len);
+ /* Use smbCalcSize() for both single- and multi-part T2 responses,
+ * both here and in coalesce_t2().
+ */
+ unsigned int copy_len;
+ if (WARN_ON_ONCE(!resp_iov.iov_base)) {
+ rc = -EIO;
+ goto out;
+ }
+ copy_len = smbCalcSize(resp_iov.iov_base);
+ if (copy_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) {
+ cifs_dbg(VFS, "response size %u exceeds buffer\n",
+ copy_len);
+ rc = -ENOBUFS;
+ goto out;
+ }
+ *pbytes_returned = copy_len;
+ memcpy(out_buf, resp_iov.iov_base, copy_len);
}
out:
@@ -361,12 +375,31 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr, unsigned int *pdu_len)
data_area_of_tgt = (char *)&pSMBt->hdr.Protocol +
get_unaligned_le16(&pSMBt->t2_rsp.DataOffset);
- /* validate target area */
data_area_of_src = (char *)&pSMBs->hdr.Protocol +
get_unaligned_le16(&pSMBs->t2_rsp.DataOffset);
data_area_of_tgt += total_in_tgt;
+ /*
+ * DataOffset fields are server-supplied and not validated against
+ * buffer bounds; check both data pointers before mutating the
+ * target header.
+ */
+ if (data_area_of_tgt < (char *)target_hdr +
+ sizeof(struct smb_t2_rsp) + sizeof(__le16) ||
+ data_area_of_tgt + total_in_src >
+ (char *)target_hdr + CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) {
+ cifs_dbg(VFS, "%s: target data area out of bounds\n", __func__);
+ return -EPROTO;
+ }
+ if (data_area_of_src < second_buf +
+ sizeof(struct smb_t2_rsp) + sizeof(__le16) ||
+ data_area_of_src + total_in_src >
+ second_buf + smbCalcSize((struct smb_hdr *)second_buf)) {
+ cifs_dbg(VFS, "%s: secondary data area out of bounds\n", __func__);
+ return -EPROTO;
+ }
+
total_in_tgt += total_in_src;
/* is the result too big for the field? */
if (total_in_tgt > USHRT_MAX) {
@@ -386,11 +419,13 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr, unsigned int *pdu_len)
}
put_bcc(byte_count, target_hdr);
- byte_count = *pdu_len;
- byte_count += total_in_src;
+ /* use smbCalcSize() rather than *pdu_len: the demux loop resets
+ * *pdu_len to each secondary's pdu_length, making it unreliable.
+ */
+ byte_count = smbCalcSize(target_hdr);
/* don't allow buffer to overflow */
if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) {
- cifs_dbg(FYI, "coalesced BCC exceeds buffer size (%u)\n",
+ cifs_dbg(FYI, "coalesced size exceeds buffer size (%u)\n",
byte_count);
return -ENOBUFS;
}
@@ -414,10 +449,18 @@ bool
cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server,
char *buf, int malformed)
{
- if (malformed)
- return false;
- if (check2ndT2(buf) <= 0)
+ if (malformed || check2ndT2(buf) <= 0) {
+ /* mid->multiRsp blocks the server buf detach in handle_mid();
+ * returning false here would leak resp_buf and leave a dangling
+ * server->smallbuf/bigbuf after the user thread frees resp_buf.
+ */
+ if (mid->multiRsp) {
+ mid->multiEnd = true;
+ dequeue_mid(server, mid, true);
+ return true;
+ }
return false;
+ }
mid->multiRsp = true;
if (mid->resp_buf) {
/* merge response - fix up 1st*/
@@ -460,7 +503,7 @@ check_smb_hdr(struct smb_hdr *smb)
return 0;
/*
- * Windows NT server returns error resposne (e.g. STATUS_DELETE_PENDING
+ * Windows NT server returns error response (e.g. STATUS_DELETE_PENDING
* or STATUS_OBJECT_NAME_NOT_FOUND or ERRDOS/ERRbadfile or any other)
* for some TRANS2 requests without the RESPONSE flag set in header.
*/
diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c
index ed651c946251..fb2fccbe8667 100644
--- a/fs/smb/client/smb2file.c
+++ b/fs/smb/client/smb2file.c
@@ -27,10 +27,24 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
{
struct smb2_err_rsp *err = iov->iov_base;
struct smb2_symlink_err_rsp *sym = ERR_PTR(-EINVAL);
+ u8 *end = (u8 *)err + iov->iov_len;
u32 len;
+ /*
+ * Per [MS-SMB2] section 2.2.2, a STATUS_STOPPED_ON_SYMLINK response has to
+ * carry a Symbolic Link Error Response, so ByteCount cannot be zero. Some
+ * servers (e.g. the macOS built-in SMB server) violate this and return an
+ * empty error response, with both ErrorContextCount and ByteCount set to
+ * zero, i.e. without the symlink target. Detect this and return -ENODATA
+ * so that callers can tell "server did not send the target" apart from a
+ * malformed response, and retrieve the target with FSCTL_GET_REPARSE_POINT
+ * instead.
+ */
+ if (!err->ErrorContextCount && !le32_to_cpu(err->ByteCount))
+ return ERR_PTR(-ENODATA);
+
if (err->ErrorContextCount) {
- struct smb2_error_context_rsp *p, *end;
+ struct smb2_error_context_rsp *p;
len = (u32)err->ErrorContextCount * (offsetof(struct smb2_error_context_rsp,
ErrorContextData) +
@@ -39,8 +53,7 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
return ERR_PTR(-EINVAL);
p = (struct smb2_error_context_rsp *)err->ErrorData;
- end = (struct smb2_error_context_rsp *)((u8 *)err + iov->iov_len);
- do {
+ while ((u8 *)p + sizeof(*p) <= end) {
if (le32_to_cpu(p->ErrorId) == SMB2_ERROR_ID_DEFAULT) {
sym = (struct smb2_symlink_err_rsp *)p->ErrorContextData;
break;
@@ -48,16 +61,24 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
cifs_dbg(FYI, "%s: skipping unhandled error context: 0x%x\n",
__func__, le32_to_cpu(p->ErrorId));
- len = ALIGN(le32_to_cpu(p->ErrorDataLength), 8);
+ len = le32_to_cpu(p->ErrorDataLength);
+ if (len > end - ((u8 *)p + sizeof(*p)))
+ return ERR_PTR(-EINVAL);
+ len = ALIGN(len, 8);
+ if (len > end - ((u8 *)p + sizeof(*p)))
+ return ERR_PTR(-EINVAL);
+
p = (struct smb2_error_context_rsp *)(p->ErrorContextData + len);
- } while (p < end);
+ }
} else if (le32_to_cpu(err->ByteCount) >= sizeof(*sym) &&
iov->iov_len >= SMB2_SYMLINK_STRUCT_SIZE) {
sym = (struct smb2_symlink_err_rsp *)err->ErrorData;
}
- if (!IS_ERR(sym) && (le32_to_cpu(sym->SymLinkErrorTag) != SYMLINK_ERROR_TAG ||
- le32_to_cpu(sym->ReparseTag) != IO_REPARSE_TAG_SYMLINK))
+ if (!IS_ERR(sym) &&
+ ((u8 *)sym + sizeof(*sym) > end ||
+ le32_to_cpu(sym->SymLinkErrorTag) != SYMLINK_ERROR_TAG ||
+ le32_to_cpu(sym->ReparseTag) != IO_REPARSE_TAG_SYMLINK))
sym = ERR_PTR(-EINVAL);
return sym;
@@ -128,8 +149,10 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec
print_len = le16_to_cpu(sym->PrintNameLength);
print_offs = le16_to_cpu(sym->PrintNameOffset);
- if (iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + sub_offs + sub_len ||
- iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len)
+ if ((char *)sym->PathBuffer + sub_offs + sub_len >
+ (char *)iov->iov_base + iov->iov_len ||
+ (char *)sym->PathBuffer + print_offs + print_len >
+ (char *)iov->iov_base + iov->iov_len)
return -EINVAL;
return smb2_parse_native_symlink(path,
@@ -147,8 +170,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;
@@ -175,14 +196,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) {
@@ -194,10 +215,18 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov,
oparms->path,
&data->symlink_target);
+ /*
+ * If smb2_parse_symlink_response returned -ENODATA then the
+ * symlink_target was not sent. Treat this as if the SMB2_open()
+ * failed with STATUS_IO_REPARSE_TAG_NOT_HANDLED status, which is
+ * indicated by the -EIO errno.
+ */
+ if (rc == -ENODATA)
+ rc = -EIO;
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;
}
@@ -231,23 +260,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/smb2glob.h b/fs/smb/client/smb2glob.h
index e56e4d402f13..19da74b1edab 100644
--- a/fs/smb/client/smb2glob.h
+++ b/fs/smb/client/smb2glob.h
@@ -46,4 +46,16 @@ enum smb2_compound_ops {
#define END_OF_CHAIN 4
#define RELATED_REQUEST 8
+/*
+ *****************************************************************
+ * Struct definitions go here
+ *****************************************************************
+ */
+
+struct status_to_posix_error {
+ __u32 smb2_status;
+ int posix_error;
+ char *status_string;
+};
+
#endif /* _SMB2_GLOB_H */
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index 195a38fd61e8..13fe8e3b48f3 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -22,6 +22,7 @@
#include "smb2glob.h"
#include "smb2proto.h"
#include "cached_dir.h"
+#include "reparse.h"
#include "../common/smb2status.h"
#include "../common/smbfsctl.h"
@@ -40,9 +41,11 @@ static struct reparse_data_buffer *reparse_buf_ptr(struct kvec *iov)
buf = (struct reparse_data_buffer *)((u8 *)io + off);
len = sizeof(*buf);
- rdlen = le16_to_cpu(buf->ReparseDataLength);
+ if (count < len)
+ return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, 0));
- if (count < len || count < rdlen + len)
+ rdlen = le16_to_cpu(buf->ReparseDataLength);
+ if (count < rdlen + len)
return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, rdlen));
return buf;
}
@@ -74,6 +77,17 @@ static int parse_posix_sids(struct cifs_open_info_data *data,
sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len;
sidsbuf_end = sidsbuf + out_len - qi_len;
+ if (sidsbuf_end < sidsbuf) {
+ cifs_dbg(VFS, "%s: server-supplied out_len %u caused pointer wraparound\n",
+ __func__, out_len);
+ return -EINVAL;
+ }
+ if (sidsbuf_end > (u8 *)rsp_iov->iov_base + rsp_iov->iov_len) {
+ cifs_dbg(VFS, "%s: server-supplied out_len %u overruns iov by %td bytes\n",
+ __func__, out_len,
+ sidsbuf_end - ((u8 *)rsp_iov->iov_base + rsp_iov->iov_len));
+ return -EINVAL;
+ }
owner_len = posix_info_sid_size(sidsbuf, sidsbuf_end);
if (owner_len == -1)
@@ -111,7 +125,7 @@ static int check_wsl_eas(struct kvec *rsp_iov)
u32 outlen, next;
u16 vlen;
u8 nlen;
- u8 *end;
+ u8 *ea_end, *iov_end;
outlen = le32_to_cpu(rsp->OutputBufferLength);
if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE ||
@@ -120,15 +134,19 @@ static int check_wsl_eas(struct kvec *rsp_iov)
ea = (void *)((u8 *)rsp_iov->iov_base +
le16_to_cpu(rsp->OutputBufferOffset));
- end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len;
+ ea_end = (u8 *)ea + outlen;
+ iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len;
+ if (ea_end > iov_end)
+ return -EINVAL;
+
for (;;) {
- if ((u8 *)ea > end - sizeof(*ea))
+ if ((u8 *)ea > ea_end - sizeof(*ea))
return -EINVAL;
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 > ea_end)
return -EINVAL;
switch (vlen) {
@@ -165,6 +183,27 @@ static int check_wsl_eas(struct kvec *rsp_iov)
}
/*
+ * If @cfile is NULL, then need to account for trailing CLOSE request in the
+ * compound chain.
+ */
+static void set_next_compound(struct cifs_tcon *tcon,
+ struct cifsFileInfo *cfile,
+ int i, int num_cmds,
+ struct smb_rqst *rqst, int *num_rqst)
+{
+ int k = !cfile ? 1 : 0;
+
+ if (i + 1 < num_cmds + k)
+ smb2_set_next_command(tcon, &rqst[*num_rqst]);
+ if (i + k > 0)
+ smb2_set_related(&rqst[*num_rqst]);
+ (*num_rqst)++;
+}
+
+#define COMP_PID(cfile) ((cfile) ? (cfile)->fid.persistent_fid : COMPOUND_FID)
+#define COMP_VID(cfile) ((cfile) ? (cfile)->fid.volatile_fid : COMPOUND_FID)
+
+/*
* note: If cfile is passed, the reference to it is dropped here.
* So make sure that you do not reuse cfile after return from this func.
*
@@ -209,7 +248,7 @@ replay_again:
num_rqst = 0;
server = cifs_pick_channel(ses);
- vars = kzalloc_obj(*vars, GFP_ATOMIC);
+ vars = kzalloc_obj(*vars);
if (vars == NULL) {
rc = -ENOMEM;
goto out;
@@ -284,32 +323,16 @@ replay_again:
rqst[num_rqst].rq_iov = &vars->qi_iov;
rqst[num_rqst].rq_nvec = 1;
- if (cfile) {
- rc = SMB2_query_info_init(tcon, server,
- &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid,
- FILE_ALL_INFORMATION,
- SMB2_O_INFO_FILE, 0,
- sizeof(struct smb2_file_all_info) +
- PATH_MAX * 2, 0, NULL);
- } else {
- rc = SMB2_query_info_init(tcon, server,
- &rqst[num_rqst],
- COMPOUND_FID,
- COMPOUND_FID,
- FILE_ALL_INFORMATION,
- SMB2_O_INFO_FILE, 0,
- sizeof(struct smb2_file_all_info) +
- PATH_MAX * 2, 0, NULL);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ rc = SMB2_query_info_init(tcon, server,
+ &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ FILE_ALL_INFORMATION,
+ SMB2_O_INFO_FILE, 0,
+ sizeof(struct smb2_file_all_info) +
+ PATH_MAX * 2, 0, NULL);
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_query_info_compound_enter(xid, tcon->tid,
ses->Suid, full_path);
break;
@@ -317,35 +340,18 @@ replay_again:
rqst[num_rqst].rq_iov = &vars->qi_iov;
rqst[num_rqst].rq_nvec = 1;
- if (cfile) {
- /* TBD: fix following to allow for longer SIDs */
- rc = SMB2_query_info_init(tcon, server,
- &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid,
- SMB_FIND_FILE_POSIX_INFO,
- SMB2_O_INFO_FILE, 0,
- sizeof(struct smb311_posix_qinfo *) +
- (PATH_MAX * 2) +
- (sizeof(struct smb_sid) * 2), 0, NULL);
- } else {
- rc = SMB2_query_info_init(tcon, server,
- &rqst[num_rqst],
- COMPOUND_FID,
- COMPOUND_FID,
- SMB_FIND_FILE_POSIX_INFO,
- SMB2_O_INFO_FILE, 0,
- sizeof(struct smb311_posix_qinfo *) +
- (PATH_MAX * 2) +
- (sizeof(struct smb_sid) * 2), 0, NULL);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ /* TBD: fix following to allow for longer SIDs */
+ rc = SMB2_query_info_init(tcon, server,
+ &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ SMB_FIND_FILE_POSIX_INFO,
+ SMB2_O_INFO_FILE, 0,
+ sizeof(struct smb311_posix_qinfo) +
+ (PATH_MAX * 2) +
+ (sizeof(struct smb_sid) * 2), 0, NULL);
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_posix_query_info_compound_enter(xid, tcon->tid,
ses->Suid, full_path);
break;
@@ -363,32 +369,15 @@ replay_again:
size[0] = 1; /* sizeof __u8 See MS-FSCC section 2.4.11 */
data[0] = &delete_pending[0];
- if (cfile) {
- rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid,
- current->tgid,
- FILE_DISPOSITION_INFORMATION,
- SMB2_O_INFO_FILE, 0,
- data, size);
- } else {
- rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst],
- COMPOUND_FID,
- COMPOUND_FID,
- current->tgid,
- FILE_DISPOSITION_INFORMATION,
- SMB2_O_INFO_FILE, 0,
- data, size);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ rc = SMB2_set_info_init(tcon, server,
+ &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ current->tgid, FILE_DISPOSITION_INFORMATION,
+ SMB2_O_INFO_FILE, 0,
+ data, size);
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_unlink_enter(xid, tcon->tid, ses->Suid, full_path);
break;
case SMB2_OP_SET_EOF:
@@ -398,32 +387,15 @@ replay_again:
size[0] = in_iov[i].iov_len;
data[0] = in_iov[i].iov_base;
- if (cfile) {
- rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid,
- current->tgid,
- FILE_END_OF_FILE_INFORMATION,
- SMB2_O_INFO_FILE, 0,
- data, size);
- } else {
- rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst],
- COMPOUND_FID,
- COMPOUND_FID,
- current->tgid,
- FILE_END_OF_FILE_INFORMATION,
- SMB2_O_INFO_FILE, 0,
- data, size);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ rc = SMB2_set_info_init(tcon, server,
+ &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ current->tgid, FILE_END_OF_FILE_INFORMATION,
+ SMB2_O_INFO_FILE, 0,
+ data, size);
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_set_eof_enter(xid, tcon->tid, ses->Suid, full_path);
break;
case SMB2_OP_SET_INFO:
@@ -433,28 +405,14 @@ replay_again:
size[0] = in_iov[i].iov_len;
data[0] = in_iov[i].iov_base;
- if (cfile) {
- rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid, current->tgid,
- FILE_BASIC_INFORMATION,
- SMB2_O_INFO_FILE, 0, data, size);
- } else {
- rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst],
- COMPOUND_FID,
- COMPOUND_FID, current->tgid,
- FILE_BASIC_INFORMATION,
- SMB2_O_INFO_FILE, 0, data, size);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ rc = SMB2_set_info_init(tcon, server,
+ &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ current->tgid, FILE_BASIC_INFORMATION,
+ SMB2_O_INFO_FILE, 0, data, size);
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_set_info_compound_enter(xid, tcon->tid,
ses->Suid, full_path);
break;
@@ -474,31 +432,19 @@ replay_again:
size[1] = len + 2 /* null */;
data[1] = in_iov[i].iov_base;
- if (cfile) {
- rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid,
- current->tgid, FILE_RENAME_INFORMATION,
- SMB2_O_INFO_FILE, 0, data, size);
- } else {
- rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst],
- COMPOUND_FID, COMPOUND_FID,
- current->tgid, FILE_RENAME_INFORMATION,
- SMB2_O_INFO_FILE, 0, data, size);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ rc = SMB2_set_info_init(tcon, server,
+ &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ current->tgid, FILE_RENAME_INFORMATION,
+ SMB2_O_INFO_FILE, 0, data, size);
+
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_rename_enter(xid, tcon->tid, ses->Suid, full_path);
break;
case SMB2_OP_HARDLINK:
- rqst[num_rqst].rq_iov = &vars->si_iov[0];
+ rqst[num_rqst].rq_iov = vars->hl_iov;
rqst[num_rqst].rq_nvec = 2;
len = in_iov[i].iov_len;
@@ -514,41 +460,27 @@ replay_again:
data[1] = in_iov[i].iov_base;
rc = SMB2_set_info_init(tcon, server,
- &rqst[num_rqst], COMPOUND_FID,
- COMPOUND_FID, current->tgid,
- FILE_LINK_INFORMATION,
+ &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ current->tgid, FILE_LINK_INFORMATION,
SMB2_O_INFO_FILE, 0, data, size);
if (rc)
goto finished;
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst++]);
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_hardlink_enter(xid, tcon->tid, ses->Suid, full_path);
break;
case SMB2_OP_SET_REPARSE:
rqst[num_rqst].rq_iov = vars->io_iov;
rqst[num_rqst].rq_nvec = ARRAY_SIZE(vars->io_iov);
- if (cfile) {
- rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid,
- FSCTL_SET_REPARSE_POINT,
- in_iov[i].iov_base,
- in_iov[i].iov_len, 0);
- } else {
- rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst],
- COMPOUND_FID, COMPOUND_FID,
- FSCTL_SET_REPARSE_POINT,
- in_iov[i].iov_base,
- in_iov[i].iov_len, 0);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ FSCTL_SET_REPARSE_POINT,
+ in_iov[i].iov_base,
+ in_iov[i].iov_len, 0);
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_set_reparse_compound_enter(xid, tcon->tid,
ses->Suid, full_path);
break;
@@ -556,25 +488,13 @@ replay_again:
rqst[num_rqst].rq_iov = vars->io_iov;
rqst[num_rqst].rq_nvec = ARRAY_SIZE(vars->io_iov);
- if (cfile) {
- rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid,
- FSCTL_GET_REPARSE_POINT,
- NULL, 0, CIFSMaxBufSize);
- } else {
- rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst],
- COMPOUND_FID, COMPOUND_FID,
- FSCTL_GET_REPARSE_POINT,
- NULL, 0, CIFSMaxBufSize);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ FSCTL_GET_REPARSE_POINT,
+ NULL, 0, CIFSMaxBufSize);
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_get_reparse_compound_enter(xid, tcon->tid,
ses->Suid, full_path);
break;
@@ -582,34 +502,17 @@ replay_again:
rqst[num_rqst].rq_iov = &vars->ea_iov;
rqst[num_rqst].rq_nvec = 1;
- if (cfile) {
- rc = SMB2_query_info_init(tcon, server,
- &rqst[num_rqst],
- cfile->fid.persistent_fid,
- cfile->fid.volatile_fid,
- FILE_FULL_EA_INFORMATION,
- SMB2_O_INFO_FILE, 0,
- SMB2_WSL_MAX_QUERY_EA_RESP_SIZE,
- sizeof(wsl_query_eas),
- (void *)wsl_query_eas);
- } else {
- rc = SMB2_query_info_init(tcon, server,
- &rqst[num_rqst],
- COMPOUND_FID,
- COMPOUND_FID,
- FILE_FULL_EA_INFORMATION,
- SMB2_O_INFO_FILE, 0,
- SMB2_WSL_MAX_QUERY_EA_RESP_SIZE,
- sizeof(wsl_query_eas),
- (void *)wsl_query_eas);
- }
- if (!rc && (!cfile || num_rqst > 1)) {
- smb2_set_next_command(tcon, &rqst[num_rqst]);
- smb2_set_related(&rqst[num_rqst]);
- } else if (rc) {
+ rc = SMB2_query_info_init(tcon, server,
+ &rqst[num_rqst],
+ COMP_PID(cfile), COMP_VID(cfile),
+ FILE_FULL_EA_INFORMATION,
+ SMB2_O_INFO_FILE, 0,
+ SMB2_WSL_MAX_QUERY_EA_RESP_SIZE,
+ sizeof(wsl_query_eas),
+ (void *)wsl_query_eas);
+ if (rc)
goto finished;
- }
- num_rqst++;
+ set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst);
trace_smb3_query_wsl_ea_compound_enter(xid, tcon->tid,
ses->Suid, full_path);
break;
@@ -685,8 +588,10 @@ finished:
idata->fi.Attributes = create_rsp->FileAttributes;
idata->fi.AllocationSize = create_rsp->AllocationSize;
idata->fi.EndOfFile = create_rsp->EndofFile;
+ idata->contains_posix_file_info = false;
if (le32_to_cpu(idata->fi.NumberOfLinks) == 0)
idata->fi.NumberOfLinks = cpu_to_le32(1); /* dummy value */
+ idata->unknown_nlink = true;
idata->fi.DeletePending = 0; /* successful open = not delete pending */
idata->fi.Directory = !!(le32_to_cpu(create_rsp->FileAttributes) & ATTR_DIRECTORY);
@@ -707,7 +612,6 @@ finished:
switch (cmds[i]) {
case SMB2_OP_QUERY_INFO:
idata = in_iov[i].iov_base;
- idata->contains_posix_file_info = false;
if (rc == 0 && cfile && cfile->symlink_target) {
idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL);
if (!idata->symlink_target)
@@ -720,6 +624,8 @@ finished:
le16_to_cpu(qi_rsp->OutputBufferOffset),
le32_to_cpu(qi_rsp->OutputBufferLength),
&rsp_iov[i + 1], sizeof(idata->fi), (char *)&idata->fi);
+ if (!rc)
+ idata->contains_posix_file_info = false;
}
SMB2_query_info_free(&rqst[num_rqst++]);
if (rc)
@@ -731,7 +637,6 @@ finished:
break;
case SMB2_OP_POSIX_QUERY_INFO:
idata = in_iov[i].iov_base;
- idata->contains_posix_file_info = true;
if (rc == 0 && cfile && cfile->symlink_target) {
idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL);
if (!idata->symlink_target)
@@ -745,6 +650,8 @@ finished:
le32_to_cpu(qi_rsp->OutputBufferLength),
&rsp_iov[i + 1], sizeof(idata->posix_fi) /* add SIDs */,
(char *)&idata->posix_fi);
+ if (!rc)
+ idata->contains_posix_file_info = true;
}
if (rc == 0)
rc = parse_posix_sids(idata, &rsp_iov[i + 1]);
@@ -816,7 +723,6 @@ finished:
idata = in_iov[i].iov_base;
idata->reparse.io.iov = *iov;
idata->reparse.io.buftype = resp_buftype[i + 1];
- idata->contains_posix_file_info = false; /* BB VERIFY */
rbuf = reparse_buf_ptr(iov);
if (IS_ERR(rbuf)) {
rc = PTR_ERR(rbuf);
@@ -838,7 +744,6 @@ finished:
case SMB2_OP_QUERY_WSL_EA:
if (!rc) {
idata = in_iov[i].iov_base;
- idata->contains_posix_file_info = false;
qi_rsp = rsp_iov[i + 1].iov_base;
data[0] = (u8 *)qi_rsp + le16_to_cpu(qi_rsp->OutputBufferOffset);
size[0] = le32_to_cpu(qi_rsp->OutputBufferLength);
@@ -903,9 +808,19 @@ static int parse_create_response(struct cifs_open_info_data *data,
rc = smb2_parse_symlink_response(cifs_sb, iov,
full_path,
&data->symlink_target);
- if (rc)
+ if (rc != 0 && rc != -ENODATA)
return rc;
- tag = IO_REPARSE_TAG_SYMLINK;
+ /*
+ * -ENODATA means that the response was parsed but did not contain
+ * the symlink target at all (see symlink_data()). Treat it like
+ * STATUS_IO_REPARSE_TAG_NOT_HANDLED, which does not contain it
+ * either: leave the tag unset and clear rc, so that the caller
+ * retrieves the target with SMB2_OP_GET_REPARSE.
+ */
+ if (rc == -ENODATA)
+ rc = 0;
+ else
+ tag = IO_REPARSE_TAG_SYMLINK;
reparse_point = true;
break;
case STATUS_SUCCESS:
@@ -1098,8 +1013,16 @@ int smb2_query_path_info(const unsigned int xid,
rc = -EOPNOTSUPP;
}
- if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc) {
- bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY;
+ /*
+ * If the symlink was already parsed in create response then it is needed to fix
+ * its type now (after the second call with OPEN_REPARSE_POINT which filled the
+ * metadata attributes). If the symlink was not parsed in create response then
+ * the data->symlink_target was not filled yet and then the type will be fixed
+ * later after data->symlink_target is filled.
+ */
+ if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc && data->symlink_target) {
+ bool directory = cifs_open_data_attrs(data) & ATTR_DIRECTORY;
+
rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb);
}
break;
@@ -1156,7 +1079,7 @@ smb2_mkdir_setinfo(struct inode *inode, const char *name,
cifs_i = CIFS_I(inode);
dosattrs = cifs_i->cifsAttrs | ATTR_READONLY;
data.Attributes = cpu_to_le32(dosattrs);
- cifs_get_writable_path(tcon, name, FIND_WR_ANY, &cfile);
+ cifs_get_writable_path(tcon, name, inode, FIND_ANY, &cfile);
oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES,
FILE_CREATE, CREATE_NOT_FILE, ACL_NO_MODE);
tmprc = smb2_compound_op(xid, tcon, cifs_sb, name,
@@ -1216,6 +1139,7 @@ again:
memset(resp_buftype, 0, sizeof(resp_buftype));
memset(rsp_iov, 0, sizeof(rsp_iov));
+ memset(open_iov, 0, sizeof(open_iov));
rqst[0].rq_iov = open_iov;
rqst[0].rq_nvec = ARRAY_SIZE(open_iov);
@@ -1240,14 +1164,15 @@ again:
creq = rqst[0].rq_iov[0].iov_base;
creq->ShareAccess = FILE_SHARE_DELETE_LE;
+ memset(&close_iov, 0, sizeof(close_iov));
rqst[1].rq_iov = &close_iov;
rqst[1].rq_nvec = 1;
rc = SMB2_close_init(tcon, server, &rqst[1],
COMPOUND_FID, COMPOUND_FID, false);
- smb2_set_related(&rqst[1]);
if (rc)
goto err_free;
+ smb2_set_related(&rqst[1]);
if (retries) {
/* Back-off before retry */
@@ -1330,35 +1255,62 @@ int smb2_rename_path(const unsigned int xid,
const char *from_name, const char *to_name,
struct cifs_sb_info *cifs_sb)
{
+ struct inode *inode = source_dentry ? d_inode(source_dentry) : NULL;
struct cifsFileInfo *cfile;
__u32 co = file_create_options(source_dentry);
drop_cached_dir_by_name(xid, tcon, from_name, cifs_sb);
- cifs_get_writable_path(tcon, from_name, FIND_WR_WITH_DELETE, &cfile);
+ cifs_get_writable_path(tcon, from_name, inode,
+ FIND_WITH_DELETE, &cfile);
int rc = smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb,
co, DELETE, SMB2_OP_RENAME, cfile, source_dentry);
if (rc == -EINVAL) {
cifs_dbg(FYI, "invalid lease key, resending request without lease");
- cifs_get_writable_path(tcon, from_name,
- FIND_WR_WITH_DELETE, &cfile);
+ cifs_get_writable_path(tcon, from_name, inode,
+ FIND_WITH_DELETE, &cfile);
rc = smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb,
co, DELETE, SMB2_OP_RENAME, cfile, NULL);
}
return rc;
}
+static int clear_tmpfile_attr(const unsigned int xid, struct cifs_tcon *tcon,
+ struct inode *inode, const char *full_path)
+{
+ struct TCP_Server_Info *server = cifs_pick_channel(tcon->ses);
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ FILE_BASIC_INFO fi;
+
+ cinode->cifsAttrs &= ~(ATTR_TEMPORARY | ATTR_HIDDEN);
+ fi = (FILE_BASIC_INFO) {
+ .Attributes = cpu_to_le32(cinode->cifsAttrs),
+ };
+ return server->ops->set_file_info(inode, full_path, &fi, xid);
+}
+
int smb2_create_hardlink(const unsigned int xid,
struct cifs_tcon *tcon,
struct dentry *source_dentry,
const char *from_name, const char *to_name,
struct cifs_sb_info *cifs_sb)
{
+ struct inode *inode = source_dentry ? d_inode(source_dentry) : NULL;
__u32 co = file_create_options(source_dentry);
+ struct cifsFileInfo *cfile;
+ int rc;
+
+ if (inode && test_bit(CIFS_INO_TMPFILE, &CIFS_I(inode)->flags)) {
+ rc = clear_tmpfile_attr(xid, tcon, inode, from_name);
+ if (rc)
+ return rc;
+ }
+ cifs_get_writable_path(tcon, from_name, inode,
+ FIND_WITH_DELETE, &cfile);
return smb2_set_path_attr(xid, tcon, from_name, to_name,
cifs_sb, co, FILE_READ_ATTRIBUTES,
- SMB2_OP_HARDLINK, NULL, NULL);
+ SMB2_OP_HARDLINK, cfile, NULL);
}
int
@@ -1367,15 +1319,16 @@ smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb, bool set_alloc,
struct dentry *dentry)
{
+ struct inode *inode = dentry ? d_inode(dentry) : NULL;
+ __le64 eof = cpu_to_le64(size);
struct cifs_open_parms oparms;
struct cifsFileInfo *cfile;
struct kvec in_iov;
- __le64 eof = cpu_to_le64(size);
int rc;
in_iov.iov_base = &eof;
in_iov.iov_len = sizeof(eof);
- cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
+ cifs_get_writable_path(tcon, full_path, inode, FIND_ANY, &cfile);
oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_WRITE_DATA,
FILE_OPEN, 0, ACL_NO_MODE);
@@ -1385,7 +1338,8 @@ smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
cfile, NULL, NULL, dentry);
if (rc == -EINVAL) {
cifs_dbg(FYI, "invalid lease key, resending request without lease");
- cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
+ cifs_get_writable_path(tcon, full_path,
+ inode, FIND_ANY, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb,
full_path, &oparms, &in_iov,
&(int){SMB2_OP_SET_EOF}, 1,
@@ -1415,7 +1369,8 @@ smb2_set_file_info(struct inode *inode, const char *full_path,
(buf->LastWriteTime == 0) && (buf->ChangeTime == 0)) {
if (buf->Attributes == 0)
goto out; /* would be a no op, no sense sending this */
- cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
+ cifs_get_writable_path(tcon, full_path,
+ inode, FIND_ANY, &cfile);
}
oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_WRITE_ATTRIBUTES,
@@ -1474,7 +1429,7 @@ struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data,
if (tcon->posix_extensions) {
cmds[1] = SMB2_OP_POSIX_QUERY_INFO;
- cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
+ cifs_get_writable_path(tcon, full_path, NULL, FIND_ANY, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms,
in_iov, cmds, 2, cfile, out_iov, out_buftype, NULL);
if (!rc) {
@@ -1483,7 +1438,7 @@ struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data,
}
} else {
cmds[1] = SMB2_OP_QUERY_INFO;
- cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
+ cifs_get_writable_path(tcon, full_path, NULL, FIND_ANY, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms,
in_iov, cmds, 2, cfile, out_iov, out_buftype, NULL);
if (!rc) {
@@ -1565,8 +1520,8 @@ int smb2_rename_pending_delete(const char *full_path,
struct dentry *dentry,
const unsigned int xid)
{
- struct cifs_sb_info *cifs_sb = CIFS_SB(d_inode(dentry)->i_sb);
struct cifsInodeInfo *cinode = CIFS_I(d_inode(dentry));
+ struct cifs_sb_info *cifs_sb = CIFS_SB(dentry);
__le16 *utf16_path __free(kfree) = NULL;
__u32 co = file_create_options(dentry);
int cmds[] = {
@@ -1578,14 +1533,10 @@ int smb2_rename_pending_delete(const char *full_path,
char *to_name __free(kfree) = NULL;
__u32 attrs = cinode->cifsAttrs;
struct cifs_open_parms oparms;
- static atomic_t sillycounter;
struct cifsFileInfo *cfile;
struct tcon_link *tlink;
struct cifs_tcon *tcon;
struct kvec iov[2];
- const char *ppath;
- void *page;
- size_t len;
int rc;
tlink = cifs_sb_tlink(cifs_sb);
@@ -1593,25 +1544,14 @@ int smb2_rename_pending_delete(const char *full_path,
return PTR_ERR(tlink);
tcon = tlink_tcon(tlink);
- page = alloc_dentry_path();
-
- ppath = build_path_from_dentry(dentry->d_parent, page);
- if (IS_ERR(ppath)) {
- rc = PTR_ERR(ppath);
+ to_name = cifs_silly_fullpath(dentry);
+ if (IS_ERR(to_name)) {
+ rc = PTR_ERR(to_name);
+ to_name = NULL;
goto out;
}
- len = strlen(ppath) + strlen("/.__smb1234") + 1;
- to_name = kmalloc(len, GFP_KERNEL);
- if (!to_name) {
- rc = -ENOMEM;
- goto out;
- }
-
- scnprintf(to_name, len, "%s%c.__smb%04X", ppath, CIFS_DIR_SEP(cifs_sb),
- atomic_inc_return(&sillycounter) & 0xffff);
-
- utf16_path = utf16_smb2_path(cifs_sb, to_name, len);
+ utf16_path = utf16_smb2_path(cifs_sb, to_name, strlen(to_name));
if (!utf16_path) {
rc = -ENOMEM;
goto out;
@@ -1634,13 +1574,14 @@ int smb2_rename_pending_delete(const char *full_path,
iov[1].iov_base = utf16_path;
iov[1].iov_len = sizeof(*utf16_path) * UniStrlen((wchar_t *)utf16_path);
- cifs_get_writable_path(tcon, full_path, FIND_WR_WITH_DELETE, &cfile);
+ cifs_get_writable_path(tcon, full_path, d_inode(dentry),
+ FIND_WITH_DELETE, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, iov,
cmds, num_cmds, cfile, NULL, NULL, dentry);
if (rc == -EINVAL) {
cifs_dbg(FYI, "invalid lease key, resending request without lease\n");
- cifs_get_writable_path(tcon, full_path,
- FIND_WR_WITH_DELETE, &cfile);
+ cifs_get_writable_path(tcon, full_path, d_inode(dentry),
+ FIND_WITH_DELETE, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, iov,
cmds, num_cmds, cfile, NULL, NULL, NULL);
}
@@ -1653,6 +1594,5 @@ int smb2_rename_pending_delete(const char *full_path,
}
out:
cifs_put_tlink(tlink);
- free_dentry_path(page);
return rc;
}
diff --git a/fs/smb/client/smb2maperror.c b/fs/smb/client/smb2maperror.c
index cd036365201f..d86f2460d0e5 100644
--- a/fs/smb/client/smb2maperror.c
+++ b/fs/smb/client/smb2maperror.c
@@ -8,7 +8,9 @@
*
*/
#include <linux/errno.h>
-#include "cifsglob.h"
+
+#include <kunit/visibility.h>
+
#include "cifsproto.h"
#include "cifs_debug.h"
#include "smb2proto.h"
@@ -16,12 +18,6 @@
#include "../common/smb2status.h"
#include "trace.h"
-struct status_to_posix_error {
- __u32 smb2_status;
- int posix_error;
- char *status_string;
-};
-
static const struct status_to_posix_error smb2_error_map_table[] = {
/*
* Automatically generated by the `gen_smb2_mapping` script,
@@ -115,10 +111,16 @@ int __init smb2_init_maperror(void)
return 0;
}
-#define SMB_CLIENT_KUNIT_AVAILABLE \
- ((IS_MODULE(CONFIG_CIFS) && IS_ENABLED(CONFIG_KUNIT)) || \
- (IS_BUILTIN(CONFIG_CIFS) && IS_BUILTIN(CONFIG_KUNIT)))
+#if IS_ENABLED(CONFIG_SMB_KUNIT_TESTS)
+const struct status_to_posix_error *smb2_get_err_map_test(__u32 smb2_status)
+{
+ return smb2_get_err_map(smb2_status);
+}
+EXPORT_SYMBOL_IF_KUNIT(smb2_get_err_map_test);
+
+const struct status_to_posix_error *smb2_error_map_table_test = smb2_error_map_table;
+EXPORT_SYMBOL_IF_KUNIT(smb2_error_map_table_test);
-#if SMB_CLIENT_KUNIT_AVAILABLE && IS_ENABLED(CONFIG_SMB_KUNIT_TESTS)
-#include "smb2maperror_test.c"
-#endif /* CONFIG_SMB_KUNIT_TESTS */
+unsigned int smb2_error_map_num = ARRAY_SIZE(smb2_error_map_table);
+EXPORT_SYMBOL_IF_KUNIT(smb2_error_map_num);
+#endif
diff --git a/fs/smb/client/smb2maperror_test.c b/fs/smb/client/smb2maperror_test.c
index 38ea6b846a99..44dc5e899cad 100644
--- a/fs/smb/client/smb2maperror_test.c
+++ b/fs/smb/client/smb2maperror_test.c
@@ -9,14 +9,17 @@
*/
#include <kunit/test.h>
+#include "cifsglob.h"
+#include "smb2glob.h"
+#include "smb2proto.h"
static void
test_cmp_map(struct kunit *test, const struct status_to_posix_error *expect)
{
const struct status_to_posix_error *result;
- result = smb2_get_err_map(expect->smb2_status);
- KUNIT_EXPECT_PTR_NE(test, NULL, result);
+ result = smb2_get_err_map_test(expect->smb2_status);
+ KUNIT_ASSERT_NOT_NULL(test, result);
KUNIT_EXPECT_EQ(test, expect->smb2_status, result->smb2_status);
KUNIT_EXPECT_EQ(test, expect->posix_error, result->posix_error);
KUNIT_EXPECT_STREQ(test, expect->status_string, result->status_string);
@@ -26,8 +29,8 @@ static void maperror_test_check_search(struct kunit *test)
{
unsigned int i;
- for (i = 0; i < ARRAY_SIZE(smb2_error_map_table); i++)
- test_cmp_map(test, &smb2_error_map_table[i]);
+ for (i = 0; i < smb2_error_map_num; i++)
+ test_cmp_map(test, &smb2_error_map_table_test[i]);
}
static struct kunit_case maperror_test_cases[] = {
@@ -43,3 +46,5 @@ static struct kunit_suite maperror_suite = {
kunit_test_suite(maperror_suite);
MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit tests of SMB2 maperror");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c
index 973fce3c959c..0cfe60ae42c3 100644
--- a/fs/smb/client/smb2misc.c
+++ b/fs/smb/client/smb2misc.c
@@ -19,6 +19,9 @@
#include "nterr.h"
#include "cached_dir.h"
+static unsigned int __smb2_calc_size(void *buf, bool *have_data,
+ bool *data_area_overlap);
+
static int
check_smb2_hdr(struct smb2_hdr *shdr, __u64 mid)
{
@@ -82,6 +85,36 @@ static const __le16 smb2_rsp_struct_sizes[NUMBER_OF_SMB2_COMMANDS] = {
/* SMB2_OPLOCK_BREAK */ cpu_to_le16(24)
};
+/*
+ * Minimum received PDU size for commands whose response carries a
+ * variable-length data area. A non-zero entry marks the command as
+ * having one, and gives the length smb2_check_message() requires
+ * before smb2_get_data_area_len() reads the offset and length fields
+ * out of the fixed response struct.
+ */
+static const size_t smb2_min_pdu_len[NUMBER_OF_SMB2_COMMANDS] = {
+ /* SMB2_NEGOTIATE */ sizeof(struct smb2_negotiate_rsp),
+ /* SMB2_SESSION_SETUP */ sizeof(struct smb2_sess_setup_rsp),
+ /* SMB2_LOGOFF */ 0,
+ /* SMB2_TREE_CONNECT */ 0,
+ /* SMB2_TREE_DISCONNECT */ 0,
+ /* SMB2_CREATE */ sizeof(struct smb2_create_rsp),
+ /* SMB2_CLOSE */ 0,
+ /* SMB2_FLUSH */ 0,
+ /* SMB2_READ */ sizeof(struct smb2_read_rsp),
+ /* SMB2_WRITE */ 0,
+ /* SMB2_LOCK */ 0,
+ /* SMB2_IOCTL */ sizeof(struct smb2_ioctl_rsp),
+ /* SMB2_CANCEL */ 0,
+ /* SMB2_ECHO */ 0,
+ /* SMB2_QUERY_DIRECTORY */ sizeof(struct smb2_query_directory_rsp),
+ /* SMB2_CHANGE_NOTIFY */ sizeof(struct smb2_change_notify_rsp),
+ /* SMB2_QUERY_INFO */ sizeof(struct smb2_query_info_rsp),
+ /* SMB2_SET_INFO */ 0,
+ /* SMB2_OPLOCK_BREAK */ 0,
+};
+
+#define smb2_has_data_area(cmd) (smb2_min_pdu_len[cmd] != 0)
#define SMB311_NEGPROT_BASE_SIZE (sizeof(struct smb2_hdr) + sizeof(struct smb2_negotiate_rsp))
static __u32 get_neg_ctxt_len(struct smb2_hdr *hdr, __u32 len,
@@ -145,6 +178,8 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
int command;
__u32 calc_len; /* calculated length */
__u64 mid;
+ bool have_data;
+ bool data_area_overlap;
/* If server is a channel, select the primary channel */
pserver = SERVER_IS_CHAN(server) ? server->primary_server : server;
@@ -228,7 +263,23 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
}
}
- calc_len = smb2_calc_size(buf);
+ if ((shdr->Status == STATUS_SUCCESS ||
+ shdr->Status == STATUS_MORE_PROCESSING_REQUIRED ||
+ pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE) &&
+ smb2_has_data_area(command) &&
+ len < smb2_min_pdu_len[command]) {
+ cifs_server_dbg(VFS, "SMB2 command %d response too short: %u < %zu\n",
+ command, len, smb2_min_pdu_len[command]);
+ return 1;
+ }
+
+ have_data = false;
+ data_area_overlap = false;
+ calc_len = __smb2_calc_size(buf, &have_data, &data_area_overlap);
+
+ /* Reject responses whose data area overlaps the fixed area. */
+ if (data_area_overlap)
+ return 1;
/* For SMB2_IOCTL, OutputOffset and OutputLength are optional, so might
* be 0, and not a real miscalculation */
@@ -241,13 +292,19 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
if (len != calc_len) {
/* create failed on symlink */
if (command == SMB2_CREATE_HE &&
- shdr->Status == STATUS_STOPPED_ON_SYMLINK)
+ shdr->Status == STATUS_STOPPED_ON_SYMLINK &&
+ len > calc_len)
return 0;
/* 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;
/*
@@ -282,33 +339,6 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
}
/*
- * The size of the variable area depends on the offset and length fields
- * located in different fields for various SMB2 responses. SMB2 responses
- * with no variable length info, show an offset of zero for the offset field.
- */
-static const bool has_smb2_data_area[NUMBER_OF_SMB2_COMMANDS] = {
- /* SMB2_NEGOTIATE */ true,
- /* SMB2_SESSION_SETUP */ true,
- /* SMB2_LOGOFF */ false,
- /* SMB2_TREE_CONNECT */ false,
- /* SMB2_TREE_DISCONNECT */ false,
- /* SMB2_CREATE */ true,
- /* SMB2_CLOSE */ false,
- /* SMB2_FLUSH */ false,
- /* SMB2_READ */ true,
- /* SMB2_WRITE */ false,
- /* SMB2_LOCK */ false,
- /* SMB2_IOCTL */ true,
- /* SMB2_CANCEL */ false, /* BB CHECK this not listed in documentation */
- /* SMB2_ECHO */ false,
- /* SMB2_QUERY_DIRECTORY */ true,
- /* SMB2_CHANGE_NOTIFY */ true,
- /* SMB2_QUERY_INFO */ true,
- /* SMB2_SET_INFO */ false,
- /* SMB2_OPLOCK_BREAK */ false
-};
-
-/*
* Returns the pointer to the beginning of the data area. Length of the data
* area and the offset to it (from the beginning of the smb are also returned.
*/
@@ -406,26 +436,35 @@ 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.
+ * Calculate the size of the SMB message based on the fixed header, fixed
+ * parameter area, and variable data area.
+ *
+ * If have_data is not NULL, it is set when a non-empty data area is found.
+ * If data_area_overlap is not NULL, it is set when the data area overlaps
+ * the fixed area.
*/
-unsigned int
-smb2_calc_size(void *buf)
+static unsigned int
+__smb2_calc_size(void *buf, bool *have_data, bool *data_area_overlap)
{
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);
+ if (have_data)
+ *have_data = false;
+ if (data_area_overlap)
+ *data_area_overlap = false;
+
/*
* StructureSize2, ie length of fixed parameter area has already
* been checked to make sure it is the correct length.
*/
len += le16_to_cpu(pdu->StructureSize2);
- if (has_smb2_data_area[le16_to_cpu(shdr->Command)] == false)
+ if (!smb2_has_data_area(le16_to_cpu(shdr->Command)))
goto calc_size_exit;
smb2_get_data_area_len(&offset, &data_length, shdr);
@@ -441,16 +480,27 @@ smb2_calc_size(void *buf)
if (offset + 1 < len) {
cifs_dbg(VFS, "data area offset %d overlaps SMB2 header %d\n",
offset + 1, len);
+ if (data_area_overlap)
+ *data_area_overlap = true;
data_length = 0;
+ goto calc_size_exit;
} else {
len = offset + data_length;
}
}
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, 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 7f2d3459cbf9..3464470d3297 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -111,10 +111,21 @@ smb2_add_credits(struct TCP_Server_Info *server,
cifs_trace_rw_credits_zero_in_flight);
}
server->in_flight--;
+
+ /*
+ * Rebalance credits when an op drains in_flight. For session setup,
+ * do this only when the total accumulated credits are high enough (>2)
+ * so that a newly established secondary channel can reserve credits for
+ * echoes and oplocks. We expect this to happen at the end of the final
+ * session setup response.
+ */
if (server->in_flight == 0 &&
((optype & CIFS_OP_MASK) != CIFS_NEG_OP) &&
((optype & CIFS_OP_MASK) != CIFS_SESS_OP))
rc = change_conf(server);
+ else if (server->in_flight == 0 &&
+ ((optype & CIFS_OP_MASK) == CIFS_SESS_OP) && *val > 2)
+ rc = change_conf(server);
/*
* Sometimes server returns 0 credits on oplock break ack - we need to
* rebalance credits in this case.
@@ -628,6 +639,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
struct smb_sockaddr_in6 *p6;
struct cifs_server_iface *info = NULL, *iface = NULL, *niface = NULL;
struct cifs_server_iface tmp_iface;
+ __be16 port;
ssize_t bytes_left;
size_t next = 0;
int nb_iface = 0;
@@ -662,6 +674,15 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
goto out;
}
+ spin_lock(&ses->server->srv_lock);
+ if (ses->server->dstaddr.ss_family == AF_INET)
+ port = ((struct sockaddr_in *)&ses->server->dstaddr)->sin_port;
+ else if (ses->server->dstaddr.ss_family == AF_INET6)
+ port = ((struct sockaddr_in6 *)&ses->server->dstaddr)->sin6_port;
+ else
+ port = cpu_to_be16(CIFS_PORT);
+ spin_unlock(&ses->server->srv_lock);
+
while (bytes_left >= (ssize_t)sizeof(*p)) {
memset(&tmp_iface, 0, sizeof(tmp_iface));
/* default to 1Gbps when link speed is unset */
@@ -682,7 +703,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
memcpy(&addr4->sin_addr, &p4->IPv4Address, 4);
/* [MS-SMB2] 2.2.32.5.1.1 Clients MUST ignore these */
- addr4->sin_port = cpu_to_be16(CIFS_PORT);
+ addr4->sin_port = port;
cifs_dbg(FYI, "%s: ipv4 %pI4\n", __func__,
&addr4->sin_addr);
@@ -696,7 +717,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
/* [MS-SMB2] 2.2.32.5.1.2 Clients MUST ignore these */
addr6->sin6_flowinfo = 0;
addr6->sin6_scope_id = 0;
- addr6->sin6_port = cpu_to_be16(CIFS_PORT);
+ addr6->sin6_port = port;
cifs_dbg(FYI, "%s: ipv6 %pI6\n", __func__,
&addr6->sin6_addr);
@@ -764,9 +785,9 @@ next_iface:
break;
}
/* Validate that Next doesn't point beyond the buffer */
- if (next > bytes_left) {
- cifs_dbg(VFS, "%s: invalid Next pointer %zu > %zd\n",
- __func__, next, bytes_left);
+ if (next < sizeof(*p) || next > bytes_left) {
+ cifs_dbg(VFS, "%s: invalid Next pointer %zu out of range [%zu, %zd]\n",
+ __func__, next, sizeof(*p), bytes_left);
rc = -EINVAL;
goto out;
}
@@ -1032,8 +1053,9 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
char *name, *value;
size_t buf_size = dst_size;
size_t name_len, value_len, user_name_len;
+ u32 next_off;
- while (src_size > 0) {
+ while (src_size >= sizeof(*src)) {
name_len = (size_t)src->ea_name_length;
value_len = (size_t)le16_to_cpu(src->ea_value_length);
@@ -1089,14 +1111,22 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
if (!src->next_entry_offset)
break;
- if (src_size < le32_to_cpu(src->next_entry_offset)) {
- /* stop before overrun buffer */
- rc = -ERANGE;
- break;
+ next_off = le32_to_cpu(src->next_entry_offset);
+ if (next_off < sizeof(*src) || src_size < next_off) {
+ cifs_dbg(FYI, "EA next_entry_offset %u out of range [%zu, %zu]\n",
+ next_off, sizeof(*src), src_size);
+ rc = smb_EIO2(smb_eio_trace_ea_next_offset,
+ next_off, src_size);
+ goto out;
+ }
+ src_size -= next_off;
+ src = (void *)((char *)src + next_off);
+ if (src_size > 0 && src_size < sizeof(*src)) {
+ cifs_dbg(FYI, "EA next_entry_offset %u left truncated entry (%zu bytes)\n",
+ next_off, src_size);
+ rc = smb_EIO2(smb_eio_trace_ea_next_offset, next_off, src_size);
+ goto out;
}
- src_size -= le32_to_cpu(src->next_entry_offset);
- src = (void *)((char *)src +
- le32_to_cpu(src->next_entry_offset));
}
/* didn't find the named attribute */
@@ -1487,6 +1517,7 @@ smb2_close_getattr(const unsigned int xid, struct cifs_tcon *tcon,
{
struct smb2_file_network_open_info file_inf;
struct inode *inode;
+ u64 asize;
int rc;
rc = __SMB2_close(xid, tcon, cfile->fid.persistent_fid,
@@ -1510,14 +1541,9 @@ smb2_close_getattr(const unsigned int xid, struct cifs_tcon *tcon,
inode_set_atime_to_ts(inode,
cifs_NTtimeToUnix(file_inf.LastAccessTime));
- /*
- * i_blocks is not related to (i_size / i_blksize),
- * but instead 512 byte (2**9) size is required for
- * calculating num blocks.
- */
- if (le64_to_cpu(file_inf.AllocationSize) > 4096)
- inode->i_blocks =
- (512 - 1 + le64_to_cpu(file_inf.AllocationSize)) >> 9;
+ asize = le64_to_cpu(file_inf.AllocationSize);
+ if (asize > 4096)
+ inode->i_blocks = CIFS_INO_BLOCKS(asize);
/* End of file and Attributes should not have to be updated on close */
spin_unlock(&inode->i_lock);
@@ -1552,7 +1578,7 @@ SMB2_request_res_key(const unsigned int xid, struct cifs_tcon *tcon,
memcpy(pcchunk->SourceKey, res_key->ResumeKey, COPY_CHUNK_RES_KEY_SIZE);
req_res_key_exit:
- kfree(res_key);
+ kfree_sensitive(res_key);
return rc;
}
@@ -1755,8 +1781,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;
}
@@ -1777,6 +1803,12 @@ replay_again:
qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
if (le32_to_cpu(qi_rsp->OutputBufferLength) < qi.input_buffer_length)
qi.input_buffer_length = le32_to_cpu(qi_rsp->OutputBufferLength);
+ if (qi.input_buffer_length > 0 &&
+ struct_size(qi_rsp, Buffer, qi.input_buffer_length) >
+ rsp_iov[1].iov_len) {
+ rc = -EFAULT;
+ goto out;
+ }
if (copy_to_user(&pqi->input_buffer_length,
&qi.input_buffer_length,
sizeof(qi.input_buffer_length))) {
@@ -1816,31 +1848,31 @@ free_vars:
*
* @tcon: destination file tcon
* @bytes_left: how many bytes are left to copy
+ * @chunk_size: maximum size of a single chunk
*
* Return: maximum number of chunks with which Chunks[] can be filled.
*/
static inline u32
-calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left)
+calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left, u32 chunk_size)
{
u32 max_chunks = READ_ONCE(tcon->max_chunks);
u32 max_bytes_copy = READ_ONCE(tcon->max_bytes_copy);
- u32 max_bytes_chunk = READ_ONCE(tcon->max_bytes_chunk);
u64 need;
u32 allowed;
- if (!max_bytes_chunk || !max_bytes_copy || !max_chunks)
+ if (!chunk_size || !max_bytes_copy || !max_chunks)
return 0;
/* chunks needed for the remaining bytes */
- need = DIV_ROUND_UP_ULL(bytes_left, max_bytes_chunk);
+ need = DIV_ROUND_UP_ULL(bytes_left, chunk_size);
/* chunks allowed per cc request */
- allowed = DIV_ROUND_UP(max_bytes_copy, max_bytes_chunk);
+ allowed = DIV_ROUND_UP(max_bytes_copy, chunk_size);
return (u32)umin(need, umin(max_chunks, allowed));
}
/**
- * smb2_copychunk_range - server-side copy of data range
+ * __smb2_copychunk_range - server-side copy of data range
*
* @xid: transaction id
* @src_file: source file
@@ -1852,15 +1884,15 @@ calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left)
* Obtains a resume key for @src_file and issues FSCTL_SRV_COPYCHUNK_WRITE
* IOCTLs, splitting the request into chunks limited by tcon->max_*.
*
- * Return: @len on success; negative errno on failure.
+ * Return: 0 on success; negative errno on failure.
*/
-static ssize_t
-smb2_copychunk_range(const unsigned int xid,
- struct cifsFileInfo *src_file,
- struct cifsFileInfo *dst_file,
- u64 src_off,
- u64 len,
- u64 dst_off)
+static int
+__smb2_copychunk_range(const unsigned int xid,
+ struct cifsFileInfo *src_file,
+ struct cifsFileInfo *dst_file,
+ u64 src_off,
+ u64 len,
+ u64 dst_off)
{
int rc = 0;
unsigned int ret_data_len = 0;
@@ -1868,12 +1900,14 @@ smb2_copychunk_range(const unsigned int xid,
struct copychunk_ioctl_rsp *cc_rsp = NULL;
struct cifs_tcon *tcon;
struct srv_copychunk *chunk;
- u32 chunks, chunk_count, chunk_bytes;
+ u32 chunks, chunk_count, chunk_bytes, chunk_size;
u32 copy_bytes, copy_bytes_left;
u32 chunks_written, bytes_written;
u64 total_bytes_left = len;
u64 src_off_prev, dst_off_prev;
+ u64 max_chunk = 0;
u32 retries = 0;
+ bool reverse = false;
tcon = tlink_tcon(dst_file->tlink);
@@ -1881,8 +1915,50 @@ smb2_copychunk_range(const unsigned int xid,
dst_file->fid.volatile_fid, tcon->tid,
tcon->ses->Suid, src_off, dst_off, len);
+ /*
+ * Same-file left shifts are safe in forward order. For a right shift,
+ * let L be the copy length, delta the distance between the source and
+ * destination, and C the normal chunk size:
+ *
+ * delta >= L: copy forwards using C
+ * delta < L:
+ * delta >= C: copy backwards using C
+ * delta < C: copy backwards with chunks limited to delta
+ *
+ * Copying backwards prevents one chunk from overwriting data needed by
+ * a later chunk. Limiting the chunk size to delta prevents an individual
+ * chunk from overlapping itself.
+ * This limit can be removed once all supported servers handle overlapping
+ * descriptors safely.
+ *
+ * A small right shift over a large range may therefore require many
+ * chunks.
+ */
+ if (src_file == dst_file && dst_off > src_off) {
+ u64 delta = dst_off - src_off;
+
+ if (delta < len) {
+ reverse = true;
+ max_chunk = delta;
+ }
+ }
+
+ /*
+ * A backward copy walks the offsets down from the end of the range.
+ * Do this once, outside the retry loop, so a retry does not move the
+ * offsets again.
+ */
+ if (reverse) {
+ src_off += len;
+ dst_off += len;
+ }
+
retry:
- chunk_count = calc_chunk_count(tcon, total_bytes_left);
+ chunk_size = READ_ONCE(tcon->max_bytes_chunk);
+ if (max_chunk && max_chunk < chunk_size)
+ chunk_size = (u32)max_chunk;
+
+ chunk_count = calc_chunk_count(tcon, total_bytes_left, chunk_size);
if (!chunk_count) {
rc = -EOPNOTSUPP;
goto out;
@@ -1923,16 +1999,21 @@ retry:
while (copy_bytes_left > 0 && chunks < chunk_count) {
chunk = &cc_req->Chunks[chunks++];
+ chunk_bytes = umin(copy_bytes_left, chunk_size);
+ if (reverse) {
+ src_off -= chunk_bytes;
+ dst_off -= chunk_bytes;
+ }
+
chunk->SourceOffset = cpu_to_le64(src_off);
chunk->TargetOffset = cpu_to_le64(dst_off);
-
- chunk_bytes = umin(copy_bytes_left, tcon->max_bytes_chunk);
-
chunk->Length = cpu_to_le32(chunk_bytes);
/* Buffer is zeroed, no need to set chunk->Reserved = 0 */
- src_off += chunk_bytes;
- dst_off += chunk_bytes;
+ if (!reverse) {
+ src_off += chunk_bytes;
+ dst_off += chunk_bytes;
+ }
copy_bytes_left -= chunk_bytes;
copy_bytes += chunk_bytes;
@@ -1980,6 +2061,18 @@ retry:
goto out;
}
+ /*
+ * A successful COPYCHUNK should copy every descriptor (MS-SMB2
+ * 3.3.5.15.6). Reject a short backward copy because the rewind
+ * below only supports forward copying.
+ */
+ if (unlikely(reverse && bytes_written < copy_bytes)) {
+ cifs_tcon_dbg(VFS, "Copychunk short write %u/%u (reverse)\n",
+ bytes_written, copy_bytes);
+ rc = -EIO;
+ goto out;
+ }
+
/* Partial write: rewind */
if (bytes_written < copy_bytes) {
u32 delta = copy_bytes - bytes_written;
@@ -2041,10 +2134,27 @@ out:
trace_smb3_copychunk_done(xid, src_file->fid.volatile_fid,
dst_file->fid.volatile_fid, tcon->tid,
tcon->ses->Suid, src_off, dst_off, len);
- return len;
+ return 0;
}
}
+static ssize_t
+smb2_copychunk_range(const unsigned int xid,
+ struct cifsFileInfo *src_file,
+ struct cifsFileInfo *dst_file,
+ u64 src_off,
+ u64 len,
+ u64 dst_off)
+{
+ int rc;
+
+ rc = __smb2_copychunk_range(xid, src_file, dst_file, src_off, len,
+ dst_off);
+ if (rc)
+ return rc;
+ return len;
+}
+
static int
smb2_flush_file(const unsigned int xid, struct cifs_tcon *tcon,
struct cifs_fid *fid)
@@ -2094,8 +2204,9 @@ smb2_sync_write(const unsigned int xid, struct cifs_fid *pfid,
}
/* Set or clear the SPARSE_FILE attribute based on value passed in setsparse */
-static bool smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
- struct cifsFileInfo *cfile, struct inode *inode, __u8 setsparse)
+static int smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
+ struct cifsFileInfo *cfile, struct inode *inode,
+ __u8 setsparse)
{
struct cifsInodeInfo *cifsi;
int rc;
@@ -2104,31 +2215,31 @@ static bool smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
/* if file already sparse don't bother setting sparse again */
if ((cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) && setsparse)
- return true; /* already sparse */
+ return 0; /* already sparse */
if (!(cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) && !setsparse)
- return true; /* already not sparse */
+ return 0; /* already not sparse */
/*
* Can't check for sparse support on share the usual way via the
* FS attribute info (FILE_SUPPORTS_SPARSE_FILES) on the share
* since Samba server doesn't set the flag on the share, yet
* supports the set sparse FSCTL and returns sparse correctly
- * in the file attributes. If we fail setting sparse though we
- * mark that server does not support sparse files for this share
- * to avoid repeatedly sending the unsupported fsctl to server
- * if the file is repeatedly extended.
+ * in the file attributes. If the server returns EOPNOTSUPP, mark
+ * that sparse files are not supported on this share to avoid
+ * repeatedly sending the unsupported FSCTL.
*/
if (tcon->broken_sparse_sup)
- return false;
+ return -EOPNOTSUPP;
rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, FSCTL_SET_SPARSE,
&setsparse, 1, CIFSMaxBufSize, NULL, NULL);
if (rc) {
- tcon->broken_sparse_sup = true;
+ if (rc == -EOPNOTSUPP)
+ tcon->broken_sparse_sup = true;
cifs_dbg(FYI, "set sparse rc = %d\n", rc);
- return false;
+ return rc;
}
if (setsparse)
@@ -2136,7 +2247,7 @@ static bool smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
else
cifsi->cifsAttrs &= (~FILE_ATTRIBUTE_SPARSE_FILE);
- return true;
+ return 0;
}
static int
@@ -2169,10 +2280,14 @@ smb2_duplicate_extents(const unsigned int xid,
u64 len, u64 dest_off)
{
int rc;
+ int qrc;
unsigned int ret_data_len;
struct inode *inode;
+ struct smb2_file_all_info file_inf;
struct duplicate_extents_to_file dup_ext_buf;
+ struct timespec64 ts;
struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink);
+ u64 asize;
/* server fileays advertise duplicate extent support with this flag */
if ((le32_to_cpu(tcon->fsAttrInfo.Attributes) &
@@ -2190,20 +2305,11 @@ smb2_duplicate_extents(const unsigned int xid,
trgtfile->fid.volatile_fid, tcon->tid,
tcon->ses->Suid, src_off, dest_off, len);
inode = d_inode(trgtfile->dentry);
- if (inode->i_size < dest_off + len) {
+ if (i_size_read(inode) < dest_off + len) {
rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false);
if (rc)
goto duplicate_extents_out;
-
- /*
- * Although also could set plausible allocation size (i_blocks)
- * here in addition to setting the file size, in reflink
- * it is likely that the target file is sparse. Its allocation
- * size will be queried on next revalidate, but it is important
- * to make sure that file's cached size is updated immediately
- */
- netfs_resize_file(netfs_inode(inode), dest_off + len, true);
- cifs_setsize(inode, dest_off + len);
+ cifs_resize_file_locked(inode, dest_off + len);
}
rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid,
trgtfile->fid.volatile_fid,
@@ -2216,6 +2322,35 @@ smb2_duplicate_extents(const unsigned int xid,
if (ret_data_len > 0)
cifs_dbg(FYI, "Non-zero response length in duplicate extents\n");
+ if (rc) {
+ CIFS_I(inode)->time = 0; /* force reval */
+ cifs_invalidate_cache(inode, 0);
+ } else {
+ qrc = SMB2_query_info(xid, tcon, trgtfile->fid.persistent_fid,
+ trgtfile->fid.volatile_fid, &file_inf);
+ spin_lock(&inode->i_lock);
+ if (qrc == 0) {
+ asize = le64_to_cpu(file_inf.AllocationSize);
+ CIFS_I(inode)->time = jiffies;
+ if (file_inf.LastWriteTime) {
+ ts = cifs_NTtimeToUnix(file_inf.LastWriteTime);
+ inode_set_mtime_to_ts(inode, ts);
+ }
+ if (file_inf.ChangeTime) {
+ ts = cifs_NTtimeToUnix(file_inf.ChangeTime);
+ inode_set_ctime_to_ts(inode, ts);
+ }
+ if (file_inf.LastAccessTime) {
+ ts = cifs_NTtimeToUnix(file_inf.LastAccessTime);
+ inode_set_atime_to_ts(inode, ts);
+ }
+ inode->i_blocks = CIFS_INO_BLOCKS(asize);
+ } else {
+ CIFS_I(inode)->time = 0; /* force reval */
+ }
+ spin_unlock(&inode->i_lock);
+ }
+
duplicate_extents_out:
if (rc)
trace_smb3_clone_err(xid, srcfile->fid.volatile_fid,
@@ -2231,10 +2366,10 @@ duplicate_extents_out:
static int
smb2_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
- struct cifsFileInfo *cfile)
+ struct cifsFileInfo *cfile, __u16 compression_state)
{
return SMB2_set_compression(xid, tcon, cfile->fid.persistent_fid,
- cfile->fid.volatile_fid);
+ cfile->fid.volatile_fid, compression_state);
}
static int
@@ -2328,8 +2463,14 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon,
* and retry the ioctl again with larger array size sufficient
* to hold all of the snapshot GMT tokens on the second try.
*/
- if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE)
+ if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) {
+ if (ret_data_len < sizeof(struct smb_snapshot_array)) {
+ rc = -EIO;
+ kfree(retbuf);
+ return rc;
+ }
ret_data_len = sizeof(struct smb_snapshot_array);
+ }
/*
* We return struct SRV_SNAPSHOT_ARRAY, followed by
@@ -3352,7 +3493,7 @@ get_smb2_acl(struct cifs_sb_info *cifs_sb,
struct cifsFileInfo *open_file = NULL;
if (inode && !(info & SACL_SECINFO))
- open_file = find_readable_file(CIFS_I(inode), true);
+ open_file = find_readable_file(CIFS_I(inode), FIND_FSUID_ONLY);
if (!open_file || (info & SACL_SECINFO))
return get_smb2_acl_by_path(cifs_sb, path, pacllen, info);
@@ -3387,8 +3528,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
struct inode *inode = file_inode(file);
struct cifsInodeInfo *cifsi = CIFS_I(inode);
struct cifsFileInfo *cfile = file->private_data;
- struct netfs_inode *ictx = netfs_inode(inode);
- unsigned long long i_size, new_size, remote_size;
+ unsigned long long i_size, new_size, remote_i_size, zero_point;
long rc;
unsigned int xid;
@@ -3397,11 +3537,17 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid,
ses->Suid, offset, len);
+ new_size = offset + len;
+ if (!keep_size && i_size_read(inode) < new_size) {
+ rc = inode_newsize_ok(inode, new_size);
+ if (rc)
+ goto out;
+ }
+
filemap_invalidate_lock(inode->i_mapping);
- i_size = i_size_read(inode);
- remote_size = ictx->remote_i_size;
- if (offset + len >= remote_size && offset < i_size) {
+ netfs_read_sizes(inode, &i_size, &remote_i_size, &zero_point);
+ if (offset + len >= remote_i_size && offset < i_size) {
unsigned long long top = umin(offset + len, i_size);
rc = filemap_write_and_wait_range(inode->i_mapping, offset, top - 1);
@@ -3421,6 +3567,9 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
if (keep_size == false && !CIFS_CACHE_READ(cifsi))
goto zero_range_exit;
+ fscache_invalidate(cifs_inode_cookie(inode), NULL,
+ i_size_read(inode), 0);
+
rc = smb3_zero_data(file, tcon, offset, len, xid);
if (rc < 0)
goto zero_range_exit;
@@ -3428,21 +3577,23 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
/*
* do we also need to change the size of the file?
*/
- new_size = offset + len;
if (keep_size == false && (unsigned long long)i_size_read(inode) < new_size) {
rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, cfile->pid, new_size);
if (rc >= 0) {
truncate_setsize(inode, new_size);
+ spin_lock(&inode->i_lock);
netfs_resize_file(&cifsi->netfs, new_size, true);
- if (offset < cifsi->netfs.zero_point)
- cifsi->netfs.zero_point = offset;
+ if (offset < cifsi->netfs._zero_point)
+ netfs_write_zero_point(inode, offset);
+ spin_unlock(&inode->i_lock);
fscache_resize_cookie(cifs_inode_cookie(inode), new_size);
}
}
zero_range_exit:
filemap_invalidate_unlock(inode->i_mapping);
+ out:
free_xid(xid);
if (rc)
trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid,
@@ -3459,7 +3610,7 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
struct inode *inode = file_inode(file);
struct cifsFileInfo *cfile = file->private_data;
struct file_zero_data_information fsctl_buf;
- unsigned long long end = offset + len, i_size, remote_i_size;
+ unsigned long long end = offset + len, i_size, remote_i_size, zero_point;
long rc;
unsigned int xid;
__u8 set_sparse = 1;
@@ -3468,18 +3619,28 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
/* Need to make file sparse, if not already, before freeing range. */
/* Consider adding equivalent for compressed since it could also work */
- if (!smb2_set_sparse(xid, tcon, cfile, inode, set_sparse)) {
- rc = -EOPNOTSUPP;
+ rc = smb2_set_sparse(xid, tcon, cfile, inode, set_sparse);
+ if (rc)
goto out;
- }
filemap_invalidate_lock(inode->i_mapping);
/*
+ * Flush dirty data first, otherwise a dirty folio spanning the punched
+ * range may be written back after the ioctl and refill the hole.
+ */
+ rc = filemap_write_and_wait_range(inode->i_mapping, offset,
+ offset + len - 1);
+ if (rc < 0)
+ goto unlock;
+
+ /*
* We implement the punch hole through ioctl, so we need remove the page
* caches first, otherwise the data may be inconsistent with the server.
*/
truncate_pagecache_range(inode, offset, offset + len - 1);
netfs_wait_for_outstanding_io(inode);
+ fscache_invalidate(cifs_inode_cookie(inode), NULL,
+ i_size_read(inode), 0);
cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len);
@@ -3501,14 +3662,17 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
* that we locally hole-punch the tail of the dirty data, the proposed
* EOF update will end up in the wrong place.
*/
- i_size = i_size_read(inode);
- remote_i_size = netfs_inode(inode)->remote_i_size;
+ netfs_read_sizes(inode, &i_size, &remote_i_size, &zero_point);
+
if (end > remote_i_size && i_size > remote_i_size) {
unsigned long long extend_to = umin(end, i_size);
rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, cfile->pid, extend_to);
- if (rc >= 0)
- netfs_inode(inode)->remote_i_size = extend_to;
+ if (rc >= 0) {
+ spin_lock(&inode->i_lock);
+ netfs_write_remote_i_size(inode, extend_to);
+ spin_unlock(&inode->i_lock);
+ }
}
unlock:
@@ -3525,7 +3689,7 @@ static int smb3_simple_fallocate_write_range(unsigned int xid,
char *buf)
{
struct cifs_io_parms io_parms = {0};
- int nbytes;
+ unsigned int nbytes;
int rc = 0;
struct kvec iov[2];
@@ -3546,9 +3710,10 @@ static int smb3_simple_fallocate_write_range(unsigned int xid,
rc = SMB2_write(xid, &io_parms, &nbytes, iov, 1);
if (rc)
break;
+ if (!nbytes)
+ return -EIO;
if (nbytes > len)
return -EINVAL;
- buf += nbytes;
off += nbytes;
len -= nbytes;
}
@@ -3561,11 +3726,25 @@ static int smb3_simple_fallocate_range(unsigned int xid,
loff_t off, loff_t len)
{
struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data;
+ struct inode *inode = d_inode(cfile->dentry);
u32 out_data_len;
char *buf = NULL;
+ u64 range_start, range_len, range_end;
loff_t l;
int rc;
+ buf = kvzalloc(min_t(loff_t, len, SMB2_MAX_BUFFER_SIZE), GFP_KERNEL);
+ if (!buf) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ if (off >= i_size_read(inode)) {
+ rc = smb3_simple_fallocate_write_range(xid, tcon, cfile,
+ off, len, buf);
+ goto out;
+ }
+
in_data.file_offset = cpu_to_le64(off);
in_data.length = cpu_to_le64(len);
rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
@@ -3577,12 +3756,6 @@ static int smb3_simple_fallocate_range(unsigned int xid,
if (rc)
goto out;
- buf = kzalloc(1024 * 1024, GFP_KERNEL);
- if (buf == NULL) {
- rc = -ENOMEM;
- goto out;
- }
-
tmp_data = out_data;
while (len) {
/*
@@ -3599,13 +3772,21 @@ static int smb3_simple_fallocate_range(unsigned int xid,
goto out;
}
- if (off < le64_to_cpu(tmp_data->file_offset)) {
+ range_start = le64_to_cpu(tmp_data->file_offset);
+ range_len = le64_to_cpu(tmp_data->length);
+ if (check_add_overflow(range_start, range_len, &range_end) ||
+ range_end > S64_MAX) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (off < range_start) {
/*
* We are at a hole. Write until the end of the region
* or until the next allocated data,
* whichever comes next.
*/
- l = le64_to_cpu(tmp_data->file_offset) - off;
+ l = range_start - off;
if (len < l)
l = len;
rc = smb3_simple_fallocate_write_range(xid, tcon,
@@ -3622,11 +3803,13 @@ static int smb3_simple_fallocate_range(unsigned int xid,
* until the end of the data or the end of the region
* we are supposed to fallocate, whichever comes first.
*/
- l = le64_to_cpu(tmp_data->length);
- if (len < l)
- l = len;
- off += l;
- len -= l;
+ if (off < range_end) {
+ l = range_end - off;
+ if (len < l)
+ l = len;
+ off += l;
+ len -= l;
+ }
tmp_data = &tmp_data[1];
out_data_len -= sizeof(struct file_allocated_range_buffer);
@@ -3634,7 +3817,7 @@ static int smb3_simple_fallocate_range(unsigned int xid,
out:
kfree(out_data);
- kfree(buf);
+ kvfree(buf);
return rc;
}
@@ -3647,18 +3830,22 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
struct cifsFileInfo *cfile = file->private_data;
long rc = -EOPNOTSUPP;
unsigned int xid;
- loff_t new_eof;
+ loff_t old_eof, new_eof;
+ struct smb2_file_all_info file_inf;
+ u64 asize;
+ int qrc;
xid = get_xid();
inode = d_inode(cfile->dentry);
cifsi = CIFS_I(inode);
+ old_eof = i_size_read(inode);
trace_smb3_falloc_enter(xid, cfile->fid.persistent_fid, tcon->tid,
tcon->ses->Suid, off, len);
/* if file not oplocked can't be sure whether asking to extend size */
if (!CIFS_CACHE_READ(cifsi))
- if (keep_size == false) {
+ if (!keep_size) {
trace_smb3_falloc_err(xid, cfile->fid.persistent_fid,
tcon->tid, tcon->ses->Suid, off, len, rc);
free_xid(xid);
@@ -3668,21 +3855,96 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
/*
* Extending the file
*/
- if ((keep_size == false) && i_size_read(inode) < off + len) {
+ if (!keep_size && old_eof < off + len) {
rc = inode_newsize_ok(inode, off + len);
if (rc)
goto out;
+ /*
+ * A small range at or beyond EOF can be allocated by writing
+ * zeroes. For off > old_eof, this preserves the intervening
+ * hole instead of allocating from offset 0.
+ */
+ if (off > old_eof ||
+ (off == old_eof && old_eof != 0 &&
+ (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE))) {
+ if (len > 1024 * 1024) {
+ rc = -EOPNOTSUPP;
+ goto out;
+ }
+
+ rc = smb3_simple_fallocate_range(xid, tcon, cfile,
+ off, len);
+ if (rc) {
+ spin_lock(&inode->i_lock);
+ cifsi->time = 0;
+ spin_unlock(&inode->i_lock);
+ goto out;
+ }
+
+ new_eof = off + len;
+ cifs_resize_file_locked(inode, new_eof);
+
+ qrc = SMB2_query_info(xid, tcon,
+ cfile->fid.persistent_fid,
+ cfile->fid.volatile_fid, &file_inf);
+ spin_lock(&inode->i_lock);
+ if (qrc == 0) {
+ asize = le64_to_cpu(file_inf.AllocationSize);
+ inode->i_blocks = CIFS_INO_BLOCKS(asize);
+ } else {
+ cifsi->time = 0;
+ }
+ spin_unlock(&inode->i_lock);
+ goto out;
+ }
+
if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)
smb2_set_sparse(xid, tcon, cfile, inode, false);
new_eof = off + len;
+
+ qrc = SMB2_query_info(xid, tcon,
+ cfile->fid.persistent_fid,
+ cfile->fid.volatile_fid, &file_inf);
+ if (qrc == 0)
+ asize = le64_to_cpu(file_inf.AllocationSize);
+
+ /*
+ * FILE_ALLOCATION_INFORMATION can only describe allocation up to
+ * new_eof. Some servers may accept it without allocating blocks,
+ * so refresh AllocationSize before updating i_blocks.
+ */
+ if (off == 0 || off == old_eof) {
+ if (qrc || asize < new_eof) {
+ rc = SMB2_set_allocation(xid, tcon,
+ cfile->fid.persistent_fid,
+ cfile->fid.volatile_fid,
+ cfile->pid, new_eof);
+ if (rc)
+ goto out;
+ }
+ }
+
rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, cfile->pid, new_eof);
- if (rc == 0) {
- netfs_resize_file(&cifsi->netfs, new_eof, true);
- cifs_setsize(inode, new_eof);
+ if (rc)
+ goto out;
+
+ cifs_resize_file_locked(inode, new_eof);
+
+ qrc = SMB2_query_info(xid, tcon,
+ cfile->fid.persistent_fid,
+ cfile->fid.volatile_fid, &file_inf);
+ spin_lock(&inode->i_lock);
+ if (qrc == 0) {
+ asize = le64_to_cpu(file_inf.AllocationSize);
+ if (asize >= new_eof)
+ inode->i_blocks = CIFS_INO_BLOCKS(asize);
+ } else {
+ cifsi->time = 0;
}
+ spin_unlock(&inode->i_lock);
goto out;
}
@@ -3772,7 +4034,6 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,
struct inode *inode = file_inode(file);
struct cifsInodeInfo *cifsi = CIFS_I(inode);
struct cifsFileInfo *cfile = file->private_data;
- struct netfs_inode *ictx = &cifsi->netfs;
loff_t old_eof, new_eof;
xid = get_xid();
@@ -3785,16 +4046,26 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,
}
filemap_invalidate_lock(inode->i_mapping);
- rc = filemap_write_and_wait_range(inode->i_mapping, off, old_eof - 1);
+ rc = filemap_write_and_wait_range(inode->i_mapping,
+ round_down(off, PAGE_SIZE),
+ old_eof - 1);
if (rc < 0)
goto out_2;
- truncate_pagecache_range(inode, off, old_eof);
- ictx->zero_point = old_eof;
netfs_wait_for_outstanding_io(inode);
+ /*
+ * Invalidate cached folios from the page containing off to EOF before
+ * moving data on the server, so subsequent reads do not see stale data.
+ */
+ truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1);
+ fscache_invalidate(cifs_inode_cookie(inode), NULL, old_eof, 0);
- rc = smb2_copychunk_range(xid, cfile, cfile, off + len,
- old_eof - off - len, off);
+ spin_lock(&inode->i_lock);
+ netfs_write_zero_point(inode, old_eof);
+ spin_unlock(&inode->i_lock);
+
+ rc = __smb2_copychunk_range(xid, cfile, cfile, off + len,
+ old_eof - off - len, off);
if (rc < 0)
goto out_2;
@@ -3807,8 +4078,10 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,
rc = 0;
truncate_setsize(inode, new_eof);
+ spin_lock(&inode->i_lock);
netfs_resize_file(&cifsi->netfs, new_eof, true);
- ictx->zero_point = new_eof;
+ netfs_write_zero_point(inode, new_eof);
+ spin_unlock(&inode->i_lock);
fscache_resize_cookie(cifs_inode_cookie(inode), new_eof);
out_2:
filemap_invalidate_unlock(inode->i_mapping);
@@ -3825,7 +4098,7 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,
struct cifsFileInfo *cfile = file->private_data;
struct inode *inode = file_inode(file);
struct cifsInodeInfo *cifsi = CIFS_I(inode);
- __u64 count, old_eof, new_eof;
+ loff_t old_eof, new_eof;
xid = get_xid();
@@ -3835,15 +4108,32 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,
goto out;
}
- count = old_eof - off;
- new_eof = old_eof + len;
+ if (check_add_overflow(old_eof, len, &new_eof)) {
+ rc = -EFBIG;
+ goto out;
+ }
+ rc = inode_newsize_ok(inode, new_eof);
+ if (rc)
+ goto out;
+
+ /* SET_ZERO_DATA creates a hole only in a sparse file. */
+ rc = smb2_set_sparse(xid, tcon, cfile, inode, true);
+ if (rc)
+ goto out;
filemap_invalidate_lock(inode->i_mapping);
- rc = filemap_write_and_wait_range(inode->i_mapping, off, new_eof - 1);
+ rc = filemap_write_and_wait_range(inode->i_mapping,
+ round_down(off, PAGE_SIZE),
+ old_eof - 1);
if (rc < 0)
goto out_2;
- truncate_pagecache_range(inode, off, old_eof);
netfs_wait_for_outstanding_io(inode);
+ /*
+ * Invalidate cached folios from the page containing off to EOF before
+ * moving data on the server, so subsequent reads do not see stale data.
+ */
+ truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1);
+ fscache_invalidate(cifs_inode_cookie(inode), NULL, old_eof, 0);
rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, cfile->pid, new_eof);
@@ -3851,13 +4141,22 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,
goto out_2;
truncate_setsize(inode, new_eof);
+ spin_lock(&inode->i_lock);
netfs_resize_file(&cifsi->netfs, i_size_read(inode), true);
+ spin_unlock(&inode->i_lock);
fscache_resize_cookie(cifs_inode_cookie(inode), i_size_read(inode));
- rc = smb2_copychunk_range(xid, cfile, cfile, off, count, off + len);
+ /*
+ * Move [off, old_eof) right by len. The helper copies backwards if the
+ * source and destination ranges overlap.
+ */
+ rc = __smb2_copychunk_range(xid, cfile, cfile, off, old_eof - off,
+ off + len);
if (rc < 0)
goto out_2;
- cifsi->netfs.zero_point = new_eof;
+ spin_lock(&inode->i_lock);
+ netfs_write_zero_point(inode, new_eof);
+ spin_unlock(&inode->i_lock);
rc = smb3_zero_data(file, tcon, off, len, xid);
if (rc < 0)
@@ -3898,7 +4197,7 @@ static loff_t smb3_llseek(struct file *file, struct cifs_tcon *tcon, loff_t offs
* some servers (Windows2016) will not reflect recent writes in
* QUERY_ALLOCATED_RANGES until SMB2_flush is called.
*/
- wrcfile = find_writable_file(cifsi, FIND_WR_ANY);
+ wrcfile = find_writable_file(cifsi, FIND_ANY);
if (wrcfile) {
filemap_write_and_wait(inode->i_mapping);
smb2_flush_file(xid, tcon, &wrcfile->fid);
@@ -4046,7 +4345,7 @@ static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode,
return smb3_collapse_range(file, tcon, off, len);
else if (mode == FALLOC_FL_INSERT_RANGE)
return smb3_insert_range(file, tcon, off, len);
- else if (mode == 0)
+ else if (mode == FALLOC_FL_ALLOCATE_RANGE)
return smb3_simple_falloc(file, tcon, off, len, false);
return -EOPNOTSUPP;
@@ -4334,11 +4633,13 @@ static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst
unsigned int req_size = sizeof(**req) + crypto_aead_reqsize(tfm);
unsigned int iv_size = crypto_aead_ivsize(tfm);
unsigned int len;
+ int ret;
u8 *p;
- *num_sgs = cifs_get_num_sgs(rqst, num_rqst, sig);
- if (IS_ERR_VALUE((long)(int)*num_sgs))
- return ERR_PTR(*num_sgs);
+ ret = cifs_get_num_sgs(rqst, num_rqst, sig);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ *num_sgs = ret;
len = iv_size;
len += crypto_aead_alignmask(tfm) & ~(crypto_tfm_ctx_alignment() - 1);
@@ -4470,7 +4771,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE);
else
rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE);
-
+ memzero_explicit(key, sizeof(key));
if (rc) {
cifs_server_dbg(VFS, "%s: Failed to set aead key %d\n", __func__, rc);
return rc;
@@ -4579,10 +4880,10 @@ smb3_init_transform_rq(struct TCP_Server_Info *server, int num_rqst,
size_t cur_size = 0;
rc = netfs_alloc_folioq_buffer(NULL, &buffer, &cur_size,
size, GFP_NOFS);
+ new->rq_buffer = buffer;
if (rc < 0)
goto err_free;
- new->rq_buffer = buffer;
iov_iter_folio_queue(&new->rq_iter, ITER_SOURCE,
buffer, 0, 0, size);
@@ -4681,9 +4982,15 @@ cifs_copy_folioq_to_iter(struct folio_queue *folioq, size_t data_size,
{
for (; folioq; folioq = folioq->next) {
for (int s = 0; s < folioq_count(folioq); s++) {
- struct folio *folio = folioq_folio(folioq, s);
- size_t fsize = folio_size(folio);
- size_t n, len = umin(fsize - skip, data_size);
+ struct folio *folio;
+ size_t fsize, n, len;
+
+ if (data_size == 0)
+ return 0;
+
+ folio = folioq_folio(folioq, s);
+ fsize = folio_size(folio);
+ len = umin(fsize - skip, data_size);
n = copy_folio_to_iter(folio, skip, len, iter);
if (n != len) {
@@ -4696,6 +5003,12 @@ cifs_copy_folioq_to_iter(struct folio_queue *folioq, size_t data_size,
}
}
+ if (data_size != 0) {
+ cifs_dbg(VFS, "%s: short copy, %zu bytes missing\n",
+ __func__, data_size);
+ return smb_EIO2(smb_eio_trace_rx_copy_to_iter, 0, data_size);
+ }
+
return 0;
}
@@ -4706,6 +5019,7 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
{
unsigned int data_offset;
unsigned int data_len;
+ unsigned int end_off;
unsigned int cur_off;
unsigned int cur_page_idx;
unsigned int pad_len;
@@ -4810,7 +5124,7 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
}
/* Copy the data to the output I/O iterator. */
- rdata->result = cifs_copy_folioq_to_iter(buffer, buffer_len,
+ rdata->result = cifs_copy_folioq_to_iter(buffer, data_len,
cur_off, &rdata->subreq.io_iter);
if (rdata->result != 0) {
if (is_offloaded)
@@ -4819,9 +5133,10 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
dequeue_mid(server, mid, rdata->result);
return 0;
}
- rdata->got_bytes = buffer_len;
+ rdata->got_bytes = data_len;
- } else if (buf_len >= data_offset + data_len) {
+ } else if (!check_add_overflow(data_offset, data_len, &end_off) &&
+ buf_len >= end_off) {
/* read response payload is in buf */
WARN_ONCE(buffer, "read data can be either in buf or in buffer");
copied = copy_to_iter(buf + data_offset, data_len, &rdata->subreq.io_iter);
@@ -4945,6 +5260,14 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,
goto free_dw;
server->total_read += rc;
+ if (le32_to_cpu(tr_hdr->OriginalMessageSize) <
+ server->vals->read_rsp_size) {
+ cifs_server_dbg(VFS, "OriginalMessageSize %u too small for read response (%zu)\n",
+ le32_to_cpu(tr_hdr->OriginalMessageSize),
+ server->vals->read_rsp_size);
+ rc = -EINVAL;
+ goto discard_data;
+ }
len = le32_to_cpu(tr_hdr->OriginalMessageSize) -
server->vals->read_rsp_size;
dw->len = len;
@@ -5057,14 +5380,29 @@ receive_encrypted_standard(struct TCP_Server_Info *server,
length = decrypt_raw_data(server, buf, buf_size, NULL, false);
if (length)
return length;
+ pdu_length = buf_size;
next_is_large = server->large_buf;
one_more:
shdr = (struct smb2_hdr *)buf;
next_cmd = le32_to_cpu(shdr->NextCommand);
+ server->total_read = next_cmd ? next_cmd : pdu_length;
+
+ if (*num_mids >= MAX_COMPOUND) {
+ cifs_server_dbg(VFS, "too many PDUs in compound\n");
+ return -1;
+ }
+
if (next_cmd) {
- if (WARN_ON_ONCE(next_cmd > pdu_length))
+ if (next_cmd < MID_HEADER_SIZE(server) ||
+ next_cmd > pdu_length ||
+ pdu_length - next_cmd < MID_HEADER_SIZE(server)) {
+ unsigned int max_next = pdu_length > (unsigned int)MID_HEADER_SIZE(server) ?
+ pdu_length - (unsigned int)MID_HEADER_SIZE(server) : 0;
+ cifs_server_dbg(VFS, "invalid NextCommand offset %u out of range [%zu, %u]\n",
+ next_cmd, MID_HEADER_SIZE(server), max_next);
return -1;
+ }
if (next_is_large)
next_buffer = (char *)cifs_buf_get();
else
@@ -5085,10 +5423,6 @@ one_more:
mid_entry->resp_buf_size = server->pdu_size;
}
- if (*num_mids >= MAX_COMPOUND) {
- cifs_server_dbg(VFS, "too many PDUs in compound\n");
- return -1;
- }
bufs[*num_mids] = buf;
mids[(*num_mids)++] = mid_entry;
@@ -5104,6 +5438,7 @@ one_more:
server->bigbuf = buf = next_buffer;
else
server->smallbuf = buf = next_buffer;
+ next_buffer = NULL;
goto one_more;
} else if (ret != 0) {
/*
@@ -5186,7 +5521,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;
@@ -5235,16 +5570,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 04e361ed2356..880ce12f50c4 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -36,7 +36,6 @@
#include "../common/smb2status.h"
#include "smb2glob.h"
#include "cifs_spnego.h"
-#include "../common/smbdirect/smbdirect.h"
#include "smbdirect.h"
#include "trace.h"
#ifdef CONFIG_CIFS_DFS_UPCALL
@@ -190,18 +189,19 @@ cifs_chan_skip_or_disable(struct cifs_ses *ses,
spin_unlock(&ses->chan_lock);
/*
- * the above reference of server by channel
- * needs to be dropped without holding chan_lock
- * as cifs_put_tcp_session takes a higher lock
- * i.e. cifs_tcp_ses_lock
+ * signal the channel and its primary server to
+ * reconnect before dropping the above reference of
+ * server by channel, which is done without holding
+ * chan_lock as cifs_put_tcp_session takes a higher
+ * lock i.e. cifs_tcp_ses_lock
*/
- cifs_put_tcp_session(server, from_reconnect);
-
cifs_signal_cifsd_for_reconnect(server, false);
/* mark primary server as needing reconnect */
pserver = server->primary_server;
cifs_signal_cifsd_for_reconnect(pserver, false);
+
+ cifs_put_tcp_session(server, from_reconnect);
skip_terminate:
return -EHOSTDOWN;
}
@@ -637,10 +637,16 @@ build_compression_ctxt(struct smb2_compression_capabilities_context *pneg_ctxt)
pneg_ctxt->DataLength =
cpu_to_le16(sizeof(struct smb2_compression_capabilities_context)
- sizeof(struct smb2_neg_context));
- pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(3);
+ /*
+ * Pattern_V1 is useful only as part of a chained transform. LZ77 remains
+ * the preferred general-purpose algorithm selected by this client.
+ */
+ pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(4);
+ pneg_ctxt->Flags = SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED;
pneg_ctxt->CompressionAlgorithms[0] = SMB3_COMPRESS_LZ77;
pneg_ctxt->CompressionAlgorithms[1] = SMB3_COMPRESS_LZ77_HUFF;
pneg_ctxt->CompressionAlgorithms[2] = SMB3_COMPRESS_LZNT1;
+ pneg_ctxt->CompressionAlgorithms[3] = SMB3_COMPRESS_PATTERN;
}
static unsigned int
@@ -828,9 +834,12 @@ static void decode_compress_ctx(struct TCP_Server_Info *server,
struct smb2_compression_capabilities_context *ctxt)
{
unsigned int len = le16_to_cpu(ctxt->DataLength);
- __le16 alg;
+ unsigned int count, i;
server->compression.enabled = false;
+ server->compression.chained = false;
+ server->compression.pattern = false;
+ server->compression.alg = SMB3_COMPRESS_NONE;
/*
* Caller checked that DataLength remains within SMB boundary. We still
@@ -842,20 +851,37 @@ static void decode_compress_ctx(struct TCP_Server_Info *server,
return;
}
- if (le16_to_cpu(ctxt->CompressionAlgorithmCount) != 1) {
+ count = le16_to_cpu(ctxt->CompressionAlgorithmCount);
+ if (!count || count > ARRAY_SIZE(ctxt->CompressionAlgorithms) ||
+ len < 8 + count * sizeof(__le16)) {
pr_warn_once("invalid SMB3 compress algorithm count\n");
return;
}
- alg = ctxt->CompressionAlgorithms[0];
-
- /* 'NONE' (0) compressor type is never negotiated */
- if (alg == 0 || le16_to_cpu(alg) > 3) {
- pr_warn_once("invalid compression algorithm '%u'\n", alg);
+ if (ctxt->Flags != SMB2_COMPRESSION_CAPABILITIES_FLAG_NONE &&
+ ctxt->Flags != SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED) {
+ pr_warn_once("invalid SMB3 compression flags\n");
return;
}
- server->compression.alg = alg;
+ for (i = 0; i < count; i++) {
+ /* Record the intersection supported by the shared SMB codec. */
+ if (ctxt->CompressionAlgorithms[i] == SMB3_COMPRESS_LZ77)
+ server->compression.alg = SMB3_COMPRESS_LZ77;
+ else if (ctxt->CompressionAlgorithms[i] == SMB3_COMPRESS_PATTERN)
+ server->compression.pattern = true;
+ }
+ if (server->compression.alg != SMB3_COMPRESS_LZ77)
+ return;
+
+ /*
+ * Pattern_V1 cannot appear in an unchained transform even if a broken
+ * peer lists it in the algorithm array.
+ */
+ server->compression.chained =
+ ctxt->Flags == SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED;
+ if (!server->compression.chained)
+ server->compression.pattern = false;
server->compression.enabled = true;
}
@@ -1714,17 +1740,30 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
is_binding = (ses->ses_status == SES_GOOD);
spin_unlock(&ses->ses_lock);
+ /*
+ * Per MS-SMB2 3.2.5.3, Session.SessionKey is the first 16 bytes of the
+ * GSS cryptographic key, right-padded with zero bytes if shorter.
+ * Allocate at least SMB2_NTLMV2_SESSKEY_SIZE bytes (zeroed) so the KDF
+ * input buffer is always valid for HMAC-SHA256 even with deprecated
+ * Kerberos enctypes that return a short session key.
+ */
+ if (unlikely(msg->sesskey_len < SMB2_NTLMV2_SESSKEY_SIZE))
+ cifs_dbg(VFS,
+ "short GSS session key (%u bytes); zero-padding per MS-SMB2 3.2.5.3\n",
+ msg->sesskey_len);
+
kfree_sensitive(ses->auth_key.response);
- ses->auth_key.response = kmemdup(msg->data,
- msg->sesskey_len,
- GFP_KERNEL);
+ ses->auth_key.len = max_t(unsigned int, msg->sesskey_len,
+ SMB2_NTLMV2_SESSKEY_SIZE);
+ ses->auth_key.response = kzalloc(ses->auth_key.len, GFP_KERNEL);
if (!ses->auth_key.response) {
cifs_dbg(VFS, "%s: can't allocate (%u bytes) memory\n",
- __func__, msg->sesskey_len);
+ __func__, ses->auth_key.len);
+ ses->auth_key.len = 0;
rc = -ENOMEM;
goto out_put_spnego_key;
}
- ses->auth_key.len = msg->sesskey_len;
+ memcpy(ses->auth_key.response, msg->data, msg->sesskey_len);
sess_data->iov[1].iov_base = msg->data + msg->sesskey_len;
sess_data->iov[1].iov_len = msg->secblob_len;
@@ -2117,8 +2156,8 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
unc_path_len = cifs_strtoUTF16(unc_path, tree, strlen(tree), cp);
if (unc_path_len <= 0) {
- kfree(unc_path);
- return -EINVAL;
+ rc = -EINVAL;
+ goto free_unc_path;
}
unc_path_len *= 2;
@@ -2127,10 +2166,8 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
atomic_set(&tcon->num_remote_opens, 0);
rc = smb2_plain_req_init(SMB2_TREE_CONNECT, tcon, server,
(void **) &req, &total_len);
- if (rc) {
- kfree(unc_path);
- return rc;
- }
+ if (rc)
+ goto free_unc_path;
if (smb3_encryption_required(tcon))
flags |= CIFS_TRANSFORM_REQ;
@@ -2221,6 +2258,7 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
tcon_exit:
free_rsp_buf(resp_buftype, rsp);
+free_unc_path:
kfree(unc_path);
return rc;
@@ -2258,7 +2296,7 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon)
}
spin_unlock(&ses->chan_lock);
- invalidate_all_cached_dirs(tcon);
+ invalidate_all_cached_dirs(tcon, true);
rc = smb2_plain_req_init(SMB2_TREE_DISCONNECT, tcon, server,
(void **) &req,
@@ -2359,9 +2397,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);
@@ -3044,7 +3082,8 @@ replay_again:
}
trace_smb3_posix_mkdir_done(xid, rsp->PersistentFileId, tcon->tid, ses->Suid,
- CREATE_NOT_FILE, FILE_WRITE_ATTRIBUTES);
+ CREATE_NOT_FILE, FILE_WRITE_ATTRIBUTES,
+ rsp->OplockLevel);
SMB2_close(xid, tcon, rsp->PersistentFileId, rsp->VolatileFileId);
@@ -3249,7 +3288,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)
{
@@ -3264,9 +3303,12 @@ 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 */
+ resp_buftype = CIFS_NO_BUFFER;
+ memset(&rsp_iov, 0, sizeof(rsp_iov));
flags = 0;
server = cifs_pick_channel(ses);
oparms->replay = !!(retries);
@@ -3321,9 +3363,6 @@ replay_again:
goto creat_exit;
} else if (rsp == NULL) /* unlikely to happen, but safer to check */
goto creat_exit;
- else
- trace_smb3_open_done(xid, rsp->PersistentFileId, tcon->tid, ses->Suid,
- oparms->create_options, oparms->desired_access);
atomic_inc(&tcon->num_remote_opens);
oparms->fid->persistent_fid = rsp->PersistentFileId;
@@ -3333,21 +3372,27 @@ 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) {
+ buf->contains_posix_file_info = false;
+ 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,
+ *oplock);
creat_exit:
SMB2_open_free(&rqst);
free_rsp_buf(resp_buftype, rsp);
@@ -3491,6 +3536,8 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
replay_again:
/* reinitialize for possible replay */
+ resp_buftype = CIFS_NO_BUFFER;
+ memset(&rsp_iov, 0, sizeof(rsp_iov));
flags = 0;
server = cifs_pick_channel(ses);
@@ -3612,14 +3659,14 @@ ioctl_exit:
int
SMB2_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
- u64 persistent_fid, u64 volatile_fid)
+ u64 persistent_fid, u64 volatile_fid,
+ __u16 compression_state)
{
int rc;
struct compress_ioctl fsctl_input;
char *ret_data = NULL;
- fsctl_input.CompressionState =
- cpu_to_le16(COMPRESSION_FORMAT_DEFAULT);
+ fsctl_input.CompressionState = cpu_to_le16(compression_state);
rc = SMB2_ioctl(xid, tcon, persistent_fid, volatile_fid,
FSCTL_SET_COMPRESSION,
@@ -3685,6 +3732,8 @@ __SMB2_close(const unsigned int xid, struct cifs_tcon *tcon,
replay_again:
/* reinitialize for possible replay */
+ resp_buftype = CIFS_NO_BUFFER;
+ memset(&rsp_iov, 0, sizeof(rsp_iov));
flags = 0;
query_attrs = false;
server = cifs_pick_channel(ses);
@@ -3897,6 +3946,8 @@ query_info(const unsigned int xid, struct cifs_tcon *tcon,
replay_again:
/* reinitialize for possible replay */
+ resp_buftype = CIFS_NO_BUFFER;
+ memset(&rsp_iov, 0, sizeof(rsp_iov));
flags = 0;
allocated = false;
server = cifs_pick_channel(ses);
@@ -3989,24 +4040,6 @@ int SMB2_query_info(const unsigned int xid, struct cifs_tcon *tcon,
NULL);
}
-#if 0
-/* currently unused, as now we are doing compounding instead (see smb311_posix_query_path_info) */
-int
-SMB311_posix_query_info(const unsigned int xid, struct cifs_tcon *tcon,
- u64 persistent_fid, u64 volatile_fid,
- struct smb311_posix_qinfo *data, u32 *plen)
-{
- size_t output_len = sizeof(struct smb311_posix_qinfo *) +
- (sizeof(struct smb_sid) * 2) + (PATH_MAX * 2);
- *plen = 0;
-
- return query_info(xid, tcon, persistent_fid, volatile_fid,
- SMB_FIND_FILE_POSIX_INFO, SMB2_O_INFO_FILE, 0,
- output_len, sizeof(struct smb311_posix_qinfo), (void **)&data, plen);
- /* Note caller must free "data" (passed in above). It may be allocated in query_info call */
-}
-#endif
-
int
SMB2_query_acl(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid,
@@ -4087,6 +4120,8 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon,
replay_again:
/* reinitialize for possible replay */
+ resp_buftype = CIFS_NO_BUFFER;
+ memset(&rsp_iov, 0, sizeof(rsp_iov));
flags = 0;
server = cifs_pick_channel(ses);
@@ -4429,6 +4464,8 @@ SMB2_flush(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
replay_again:
/* reinitialize for possible replay */
+ resp_buftype = CIFS_NO_BUFFER;
+ memset(&rsp_iov, 0, sizeof(rsp_iov));
flags = 0;
server = cifs_pick_channel(ses);
@@ -4529,8 +4566,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
if (rc)
return rc;
- if (server == NULL)
- return -ECONNABORTED;
+ if (!server) {
+ rc = -ECONNABORTED;
+ goto free_req;
+ }
shdr = &req->hdr;
shdr->Id.SyncId.ProcessId = cpu_to_le32(io_parms->pid);
@@ -4561,8 +4600,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
rdata->mr = smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter,
true, need_invalidate);
- if (!rdata->mr)
- return -EAGAIN;
+ if (!rdata->mr) {
+ rc = -EAGAIN;
+ goto free_req;
+ }
req->Channel = SMB2_CHANNEL_RDMA_V1_INVALIDATE;
if (need_invalidate)
@@ -4572,9 +4613,7 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
req->ReadChannelInfoLength =
cpu_to_le16(sizeof(struct smbdirect_buffer_descriptor_v1));
v1 = (struct smbdirect_buffer_descriptor_v1 *) &req->Buffer[0];
- v1->offset = cpu_to_le64(rdata->mr->mr->iova);
- v1->token = cpu_to_le32(rdata->mr->mr->rkey);
- v1->length = cpu_to_le32(rdata->mr->mr->length);
+ smbd_mr_fill_buffer_descriptor(rdata->mr, v1);
*total_len += sizeof(*v1) - 1;
}
@@ -4605,6 +4644,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
*buf = req;
return rc;
+
+free_req:
+ cifs_small_buf_release(req);
+ return rc;
}
static void
@@ -4614,6 +4657,7 @@ smb2_readv_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid)
struct netfs_inode *ictx = netfs_inode(rdata->rreq->inode);
struct cifs_tcon *tcon = tlink_tcon(rdata->req->cfile->tlink);
struct smb2_hdr *shdr = (struct smb2_hdr *)rdata->iov[0].iov_base;
+ struct inode *inode = &ictx->inode;
struct cifs_credits credits = {
.value = 0,
.instance = 0,
@@ -4727,7 +4771,7 @@ do_retry:
} else {
size_t trans = rdata->subreq.transferred + rdata->got_bytes;
if (trans < rdata->subreq.len &&
- rdata->subreq.start + trans >= ictx->remote_i_size) {
+ rdata->subreq.start + trans >= netfs_read_remote_i_size(inode)) {
__set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags);
rdata->result = 0;
}
@@ -4851,6 +4895,7 @@ out:
smb2_should_replay(tcon,
&rdata->retries,
&rdata->cur_sleep)) {
+ rdata->replay = true;
trace_netfs_sreq(&rdata->subreq, netfs_sreq_trace_io_retry_needed);
__set_bit(NETFS_SREQ_NEED_RETRY, &rdata->subreq.flags);
}
@@ -4960,7 +5005,7 @@ smb2_writev_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid)
unsigned int rreq_debug_id = wdata->rreq->debug_id;
unsigned int subreq_debug_index = wdata->subreq.debug_index;
ssize_t result = 0;
- size_t written;
+ size_t written = 0;
WARN_ONCE(wdata->server != server,
"wdata server %p != mid server %p",
@@ -5173,9 +5218,7 @@ smb2_async_writev(struct cifs_io_subrequest *wdata)
req->WriteChannelInfoLength =
cpu_to_le16(sizeof(struct smbdirect_buffer_descriptor_v1));
v1 = (struct smbdirect_buffer_descriptor_v1 *) &req->Buffer[0];
- v1->offset = cpu_to_le64(wdata->mr->mr->iova);
- v1->token = cpu_to_le32(wdata->mr->mr->rkey);
- v1->length = cpu_to_le32(wdata->mr->mr->length);
+ smbd_mr_fill_buffer_descriptor(wdata->mr, v1);
rqst.rq_iov[0].iov_len += sizeof(*v1);
@@ -5325,7 +5368,10 @@ replay_again:
memset(&rqst, 0, sizeof(struct smb_rqst));
rqst.rq_iov = iov;
- rqst.rq_nvec = n_vec + 1;
+ /* iov[0] is the SMB header; move payload to rq_iter for encryption safety */
+ rqst.rq_nvec = 1;
+ iov_iter_kvec(&rqst.rq_iter, ITER_SOURCE, &iov[1], n_vec,
+ io_parms->length);
if (retries) {
/* Back-off before retry */
@@ -5372,7 +5418,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);
@@ -5640,6 +5686,8 @@ smb2_parse_query_directory(struct cifs_tcon *tcon,
if (srch_inf->ntwrk_buf_start) {
if (srch_inf->smallBuf)
cifs_small_buf_release(srch_inf->ntwrk_buf_start);
+ else if (srch_inf->is_dynamic_buf)
+ kfree(srch_inf->ntwrk_buf_start);
else
cifs_buf_release(srch_inf->ntwrk_buf_start);
}
@@ -5659,12 +5707,18 @@ smb2_parse_query_directory(struct cifs_tcon *tcon,
cifs_dbg(FYI, "num entries %d last_index %lld srch start %p srch end %p\n",
srch_inf->entries_in_buffer, srch_inf->index_of_last_entry,
srch_inf->srch_entries_start, srch_inf->last_entry);
- if (resp_buftype == CIFS_LARGE_BUFFER)
+ if (resp_buftype == CIFS_LARGE_BUFFER) {
srch_inf->smallBuf = false;
- else if (resp_buftype == CIFS_SMALL_BUFFER)
+ srch_inf->is_dynamic_buf = false;
+ } else if (resp_buftype == CIFS_SMALL_BUFFER) {
srch_inf->smallBuf = true;
- else
+ srch_inf->is_dynamic_buf = false;
+ } else if (resp_buftype == CIFS_DYNAMIC_BUFFER) {
+ srch_inf->smallBuf = false;
+ srch_inf->is_dynamic_buf = true;
+ } else {
cifs_tcon_dbg(VFS, "Invalid search buffer type\n");
+ }
return 0;
}
@@ -5687,6 +5741,8 @@ SMB2_query_directory(const unsigned int xid, struct cifs_tcon *tcon,
replay_again:
/* reinitialize for possible replay */
+ resp_buftype = CIFS_NO_BUFFER;
+ memset(&rsp_iov, 0, sizeof(rsp_iov));
flags = 0;
server = cifs_pick_channel(ses);
@@ -5905,6 +5961,25 @@ SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
}
int
+SMB2_set_allocation(const unsigned int xid, struct cifs_tcon *tcon,
+ u64 persistent_fid, u64 volatile_fid, u32 pid,
+ loff_t allocation_size)
+{
+ struct smb2_file_alloc_info info;
+ void *data;
+ unsigned int size;
+
+ info.AllocationSize = cpu_to_le64(allocation_size);
+
+ data = &info;
+ size = sizeof(struct smb2_file_alloc_info);
+
+ return send_set_info(xid, tcon, persistent_fid, volatile_fid,
+ pid, FILE_ALLOCATION_INFORMATION, SMB2_O_INFO_FILE,
+ 0, 1, &data, &size);
+}
+
+int
SMB2_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid,
struct smb_ntsd *pnntsd, int pacllen, int aclflag)
@@ -6162,8 +6237,8 @@ replay_again:
max_len = sizeof(struct smb3_fs_ss_info);
min_len = sizeof(struct smb3_fs_ss_info);
} else if (level == FS_VOLUME_INFORMATION) {
- max_len = sizeof(struct smb3_fs_vol_info) + MAX_VOL_LABEL_LEN;
- min_len = sizeof(struct smb3_fs_vol_info);
+ max_len = sizeof(struct filesystem_vol_info) + MAX_VOL_LABEL_LEN;
+ min_len = sizeof(struct filesystem_vol_info);
} else {
cifs_dbg(FYI, "Invalid qfsinfo level %d\n", level);
return -EINVAL;
@@ -6218,9 +6293,9 @@ replay_again:
tcon->perf_sector_size =
le32_to_cpu(ss_info->PhysicalBytesPerSectorForPerf);
} else if (level == FS_VOLUME_INFORMATION) {
- struct smb3_fs_vol_info *vol_info = (struct smb3_fs_vol_info *)
+ struct filesystem_vol_info *vol_info = (struct filesystem_vol_info *)
(offset + (char *)rsp);
- tcon->vol_serial_number = vol_info->VolumeSerialNumber;
+ tcon->vol_serial_number = le32_to_cpu(vol_info->VolumeSerialNumber);
tcon->vol_create_time = vol_info->VolumeCreationTime;
}
@@ -6292,6 +6367,11 @@ replay_again:
smb2_set_replay(server, &rqst);
}
+ trace_smb3_lock_enter(xid, persist_fid, tcon->tid, tcon->ses->Suid,
+ le64_to_cpu(buf[0].Offset),
+ le64_to_cpu(buf[0].Length),
+ le32_to_cpu(buf[0].Flags), num_lock, 0);
+
rc = cifs_send_recv(xid, tcon->ses, server,
&rqst, &resp_buf_type, flags,
&rsp_iov);
@@ -6300,7 +6380,15 @@ replay_again:
cifs_dbg(FYI, "Send error in smb2_lockv = %d\n", rc);
cifs_stats_fail_inc(tcon, SMB2_LOCK_HE);
trace_smb3_lock_err(xid, persist_fid, tcon->tid,
- tcon->ses->Suid, rc);
+ tcon->ses->Suid,
+ le64_to_cpu(buf[0].Offset),
+ le64_to_cpu(buf[0].Length),
+ le32_to_cpu(buf[0].Flags), num_lock, rc);
+ } else {
+ trace_smb3_lock_done(xid, persist_fid, tcon->tid, tcon->ses->Suid,
+ le64_to_cpu(buf[0].Offset),
+ le64_to_cpu(buf[0].Length),
+ le32_to_cpu(buf[0].Flags), num_lock, 0);
}
if (is_replayable_error(rc) &&
diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h
index 78bb99f29d38..ab6c667bebc0 100644
--- a/fs/smb/client/smb2pdu.h
+++ b/fs/smb/client/smb2pdu.h
@@ -21,30 +21,6 @@
/* The total header size for SMB2 read and write */
#define SMB2_READWRITE_PDU_HEADER_SIZE (48 + sizeof(struct smb2_hdr))
-/* See MS-SMB2 2.2.43 */
-struct smb2_rdma_transform {
- __le16 RdmaDescriptorOffset;
- __le16 RdmaDescriptorLength;
- __le32 Channel; /* for values see channel description in smb2 read above */
- __le16 TransformCount;
- __le16 Reserved1;
- __le32 Reserved2;
-} __packed;
-
-/* TransformType */
-#define SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION 0x0001
-#define SMB2_RDMA_TRANSFORM_TYPE_SIGNING 0x0002
-
-struct smb2_rdma_crypto_transform {
- __le16 TransformType;
- __le16 SignatureLength;
- __le16 NonceLength;
- __u16 Reserved;
- __u8 Signature[]; /* variable length */
- /* u8 Nonce[] */
- /* followed by padding */
-} __packed;
-
/*
* Definitions for SMB2 Protocol Data Units (network frames)
*
@@ -195,10 +171,6 @@ struct network_resiliency_req {
#define NO_FILE_ID 0xFFFFFFFFFFFFFFFFULL /* general ioctls to srv not to file */
-struct compress_ioctl {
- __le16 CompressionState; /* See cifspdu.h for possible flag values */
-} __packed;
-
/*
* Maximum number of iovs we need for an ioctl request.
* [0] : struct smb2_ioctl_req
@@ -224,7 +196,7 @@ struct smb2_file_reparse_point_info {
__le32 Tag;
} __packed;
-/* See MS-FSCC 2.4.21 */
+/* See MS-FSCC 2.4.26 */
struct smb2_file_id_information {
__le64 VolumeSerialNumber;
__u64 PersistentFileId; /* opaque endianness */
@@ -251,7 +223,10 @@ struct smb2_file_id_extd_directory_info {
extern char smb2_padding[7];
-/* equivalent of the contents of SMB3.1.1 POSIX open context response */
+/*
+ * See POSIX-SMB2 2.2.14.2.16
+ * Link: https://gitlab.com/samba-team/smb3-posix-spec/-/blob/master/smb3_posix_extensions.md
+ */
struct create_posix_rsp {
u32 nlink;
u32 reparse_tag;
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index 881e42cf66ce..2e9f70096825 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -14,7 +14,6 @@
#include <linux/key-type.h>
#include "cached_dir.h"
-struct statfs;
struct smb_rqst;
/*
@@ -24,6 +23,12 @@ struct smb_rqst;
*/
int map_smb2_to_linux_error(char *buf, bool log_err);
int smb2_init_maperror(void);
+#if IS_ENABLED(CONFIG_SMB_KUNIT_TESTS)
+const struct status_to_posix_error *smb2_get_err_map_test(__u32 smb2_status);
+extern const struct status_to_posix_error *smb2_error_map_table_test;
+extern unsigned int smb2_error_map_num;
+#endif
+
int smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
struct TCP_Server_Info *server);
unsigned int smb2_calc_size(void *buf);
@@ -131,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,
@@ -167,9 +172,6 @@ int SMB2_flush_init(const unsigned int xid, struct smb_rqst *rqst,
struct cifs_tcon *tcon, struct TCP_Server_Info *server,
u64 persistent_fid, u64 volatile_fid);
void SMB2_flush_free(struct smb_rqst *rqst);
-int SMB311_posix_query_info(const unsigned int xid, struct cifs_tcon *tcon,
- u64 persistent_fid, u64 volatile_fid,
- struct smb311_posix_qinfo *data, u32 *plen);
int SMB2_query_info(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid,
struct smb2_file_all_info *data);
@@ -202,6 +204,9 @@ void SMB2_query_directory_free(struct smb_rqst *rqst);
int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, u32 pid,
loff_t new_eof);
+int SMB2_set_allocation(const unsigned int xid, struct cifs_tcon *tcon,
+ u64 persistent_fid, u64 volatile_fid, u32 pid,
+ loff_t allocation_size);
int SMB2_set_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
struct smb_rqst *rqst, u64 persistent_fid,
u64 volatile_fid, u32 pid, u8 info_class, u8 info_type,
@@ -214,7 +219,8 @@ int SMB2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid,
struct smb2_file_full_ea_info *buf, int len);
int SMB2_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
- u64 persistent_fid, u64 volatile_fid);
+ u64 persistent_fid, u64 volatile_fid,
+ __u16 compression_state);
int SMB2_oplock_break(const unsigned int xid, struct cifs_tcon *tcon,
const u64 persistent_fid, const u64 volatile_fid,
__u8 oplock_level);
@@ -255,7 +261,6 @@ int smb2_validate_and_copy_iov(unsigned int offset, unsigned int buffer_length,
char *data);
void smb2_copy_fs_info_to_kstatfs(struct smb2_fs_full_size_info *pfs_inf,
struct kstatfs *kst);
-int smb3_crypto_shash_allocate(struct TCP_Server_Info *server);
void smb311_update_preauth_hash(struct cifs_ses *ses,
struct TCP_Server_Info *server,
struct kvec *iov, int nvec);
diff --git a/fs/smb/client/smb2transport.c b/fs/smb/client/smb2transport.c
index 8b9000a83181..c407f30e0040 100644
--- a/fs/smb/client/smb2transport.c
+++ b/fs/smb/client/smb2transport.c
@@ -19,7 +19,9 @@
#include <linux/mempool.h>
#include <linux/highmem.h>
#include <crypto/aead.h>
+#include <crypto/aes-cbc-macs.h>
#include <crypto/sha2.h>
+#include <crypto/utils.h>
#include "cifsglob.h"
#include "cifsproto.h"
#include "smb2proto.h"
@@ -27,14 +29,6 @@
#include "../common/smb2status.h"
#include "smb2glob.h"
-int
-smb3_crypto_shash_allocate(struct TCP_Server_Info *server)
-{
- struct cifs_secmech *p = &server->secmech;
-
- return cifs_alloc_hash("cmac(aes)", &p->aes_cmac);
-}
-
static
int smb3_get_sign_key(__u64 ses_id, struct TCP_Server_Info *server, u8 *key)
{
@@ -175,7 +169,9 @@ smb2_find_smb_sess_tcon_unlocked(struct cifs_ses *ses, __u32 tid)
list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
if (tcon->tid != tid)
continue;
+ spin_lock(&tcon->tc_lock);
++tcon->tc_count;
+ spin_unlock(&tcon->tc_lock);
trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
netfs_trace_tcon_ref_get_find_sess_tcon);
return tcon;
@@ -210,8 +206,7 @@ smb2_find_smb_tcon(struct TCP_Server_Info *server, __u64 ses_id, __u32 tid)
}
static int
-smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
- bool allocate_crypto)
+smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
{
int rc;
unsigned char smb2_signature[SMB2_HMACSHA256_SIZE];
@@ -254,17 +249,19 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
if (!rc)
memcpy(shdr->Signature, smb2_signature, SMB2_SIGNATURE_SIZE);
+ memzero_explicit(key, sizeof(key));
+ memzero_explicit(&hmac_ctx, sizeof(hmac_ctx));
return rc;
}
-static int generate_key(struct cifs_ses *ses, struct kvec label,
- struct kvec context, __u8 *key, unsigned int key_size)
+static void generate_key(struct cifs_ses *ses, struct kvec label,
+ struct kvec context, __u8 *key, unsigned int key_size,
+ unsigned int full_key_size)
{
unsigned char zero = 0x0;
__u8 i[4] = {0, 0, 0, 1};
__u8 L128[4] = {0, 0, 0, 128};
__u8 L256[4] = {0, 0, 1, 0};
- int rc = 0;
unsigned char prfhash[SMB2_HMACSHA256_SIZE];
struct TCP_Server_Info *server = ses->server;
struct hmac_sha256_ctx hmac_ctx;
@@ -272,14 +269,8 @@ static int generate_key(struct cifs_ses *ses, struct kvec label,
memset(prfhash, 0x0, SMB2_HMACSHA256_SIZE);
memset(key, 0x0, key_size);
- rc = smb3_crypto_shash_allocate(server);
- if (rc) {
- cifs_server_dbg(VFS, "%s: crypto alloc failed\n", __func__);
- return rc;
- }
-
hmac_sha256_init_usingrawkey(&hmac_ctx, ses->auth_key.response,
- SMB2_NTLMV2_SESSKEY_SIZE);
+ full_key_size);
hmac_sha256_update(&hmac_ctx, i, 4);
hmac_sha256_update(&hmac_ctx, label.iov_base, label.iov_len);
hmac_sha256_update(&hmac_ctx, &zero, 1);
@@ -294,7 +285,7 @@ static int generate_key(struct cifs_ses *ses, struct kvec label,
hmac_sha256_final(&hmac_ctx, prfhash);
memcpy(key, prfhash, key_size);
- return 0;
+ memzero_explicit(prfhash, sizeof(prfhash));
}
struct derivation {
@@ -313,7 +304,7 @@ generate_smb3signingkey(struct cifs_ses *ses,
struct TCP_Server_Info *server,
const struct derivation_triplet *ptriplet)
{
- int rc;
+ unsigned int full_key_size = SMB2_NTLMV2_SESSKEY_SIZE;
bool is_binding = false;
int chan_index = 0;
@@ -344,19 +335,26 @@ generate_smb3signingkey(struct cifs_ses *ses,
*/
if (is_binding) {
- rc = generate_key(ses, ptriplet->signing.label,
- ptriplet->signing.context,
- ses->chans[chan_index].signkey,
- SMB3_SIGN_KEY_SIZE);
- if (rc)
- return rc;
+ generate_key(ses, ptriplet->signing.label,
+ ptriplet->signing.context,
+ ses->chans[chan_index].signkey, SMB3_SIGN_KEY_SIZE,
+ SMB2_NTLMV2_SESSKEY_SIZE);
} else {
- rc = generate_key(ses, ptriplet->signing.label,
- ptriplet->signing.context,
- ses->smb3signingkey,
- SMB3_SIGN_KEY_SIZE);
- if (rc)
- return rc;
+ generate_key(ses, ptriplet->signing.label,
+ ptriplet->signing.context, ses->smb3signingkey,
+ SMB3_SIGN_KEY_SIZE, SMB2_NTLMV2_SESSKEY_SIZE);
+
+ /*
+ * Per MS-SMB2 3.2.5.3.1, signing key always uses Session.SessionKey
+ * (first 16 bytes). Encryption/decryption keys use
+ * Session.FullSessionKey when dialect is 3.1.1 and cipher is
+ * AES-256-CCM or AES-256-GCM, otherwise Session.SessionKey.
+ */
+
+ if (server->dialect == SMB311_PROT_ID &&
+ (server->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
+ server->cipher_type == SMB2_ENCRYPTION_AES256_GCM))
+ full_key_size = ses->auth_key.len;
/* safe to access primary channel, since it will never go away */
spin_lock(&ses->chan_lock);
@@ -364,18 +362,15 @@ generate_smb3signingkey(struct cifs_ses *ses,
SMB3_SIGN_KEY_SIZE);
spin_unlock(&ses->chan_lock);
- rc = generate_key(ses, ptriplet->encryption.label,
- ptriplet->encryption.context,
- ses->smb3encryptionkey,
- SMB3_ENC_DEC_KEY_SIZE);
- if (rc)
- return rc;
- rc = generate_key(ses, ptriplet->decryption.label,
- ptriplet->decryption.context,
- ses->smb3decryptionkey,
- SMB3_ENC_DEC_KEY_SIZE);
- if (rc)
- return rc;
+ generate_key(ses, ptriplet->encryption.label,
+ ptriplet->encryption.context,
+ ses->smb3encryptionkey, SMB3_ENC_DEC_KEY_SIZE,
+ full_key_size);
+
+ generate_key(ses, ptriplet->decryption.label,
+ ptriplet->decryption.context,
+ ses->smb3decryptionkey, SMB3_ENC_DEC_KEY_SIZE,
+ full_key_size);
}
#ifdef CONFIG_CIFS_DEBUG_DUMP_KEYS
@@ -388,7 +383,7 @@ generate_smb3signingkey(struct cifs_ses *ses,
&ses->Suid);
cifs_dbg(VFS, "Cipher type %d\n", server->cipher_type);
cifs_dbg(VFS, "Session Key %*ph\n",
- SMB2_NTLMV2_SESSKEY_SIZE, ses->auth_key.response);
+ (int)ses->auth_key.len, ses->auth_key.response);
cifs_dbg(VFS, "Signing Key %*ph\n",
SMB3_SIGN_KEY_SIZE, ses->smb3signingkey);
if ((server->cipher_type == SMB2_ENCRYPTION_AES256_CCM) ||
@@ -404,7 +399,7 @@ generate_smb3signingkey(struct cifs_ses *ses,
SMB3_GCM128_CRYPTKEY_SIZE, ses->smb3decryptionkey);
}
#endif
- return rc;
+ return 0;
}
int
@@ -466,19 +461,19 @@ generate_smb311signingkey(struct cifs_ses *ses,
}
static int
-smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
- bool allocate_crypto)
+smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
{
int rc;
unsigned char smb3_signature[SMB2_CMACAES_SIZE];
struct kvec *iov = rqst->rq_iov;
struct smb2_hdr *shdr = (struct smb2_hdr *)iov[0].iov_base;
- struct shash_desc *shash = NULL;
+ struct aes_cmac_key cmac_key __cleanup(aes_cmac_zeroize_key);
+ struct aes_cmac_ctx cmac_ctx __cleanup(aes_cmac_zeroize_ctx);
struct smb_rqst drqst;
u8 key[SMB3_SIGN_KEY_SIZE];
if (server->vals->protocol_id <= SMB21_PROT_ID)
- return smb2_calc_signature(rqst, server, allocate_crypto);
+ return smb2_calc_signature(rqst, server);
rc = smb3_get_sign_key(le64_to_cpu(shdr->SessionId), server, key);
if (unlikely(rc)) {
@@ -486,33 +481,17 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
return rc;
}
- if (allocate_crypto) {
- rc = cifs_alloc_hash("cmac(aes)", &shash);
- if (rc)
- return rc;
- } else {
- shash = server->secmech.aes_cmac;
- }
-
memset(smb3_signature, 0x0, SMB2_CMACAES_SIZE);
memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE);
- rc = crypto_shash_setkey(shash->tfm, key, SMB2_CMACAES_SIZE);
+ rc = aes_cmac_preparekey(&cmac_key, key, SMB2_CMACAES_SIZE);
+ memzero_explicit(key, sizeof(key));
if (rc) {
cifs_server_dbg(VFS, "%s: Could not set key for cmac aes\n", __func__);
- goto out;
+ return rc;
}
- /*
- * we already allocate aes_cmac when we init smb3 signing key,
- * so unlike smb2 case we do not have to check here if secmech are
- * initialized
- */
- rc = crypto_shash_init(shash);
- if (rc) {
- cifs_server_dbg(VFS, "%s: Could not init cmac aes\n", __func__);
- goto out;
- }
+ aes_cmac_init(&cmac_ctx, &cmac_key);
/*
* For SMB2+, __cifs_calc_signature() expects to sign only the actual
@@ -523,26 +502,16 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
*/
drqst = *rqst;
if (drqst.rq_nvec >= 2 && iov[0].iov_len == 4) {
- rc = crypto_shash_update(shash, iov[0].iov_base,
- iov[0].iov_len);
- if (rc) {
- cifs_server_dbg(VFS, "%s: Could not update with payload\n",
- __func__);
- goto out;
- }
+ aes_cmac_update(&cmac_ctx, iov[0].iov_base, iov[0].iov_len);
drqst.rq_iov++;
drqst.rq_nvec--;
}
rc = __cifs_calc_signature(
&drqst, server, smb3_signature,
- &(struct cifs_calc_sig_ctx){ .shash = shash });
+ &(struct cifs_calc_sig_ctx){ .cmac = &cmac_ctx });
if (!rc)
memcpy(shdr->Signature, smb3_signature, SMB2_SIGNATURE_SIZE);
-
-out:
- if (allocate_crypto)
- cifs_free_hash(&shash);
return rc;
}
@@ -576,7 +545,7 @@ smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server)
return 0;
}
- return smb3_calc_signature(rqst, server, false);
+ return smb3_calc_signature(rqst, server);
}
int
@@ -612,12 +581,13 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
memset(shdr->Signature, 0, SMB2_SIGNATURE_SIZE);
- rc = smb3_calc_signature(rqst, server, true);
+ rc = smb3_calc_signature(rqst, server);
if (rc)
return rc;
- if (memcmp(server_response_sig, shdr->Signature, SMB2_SIGNATURE_SIZE)) {
+ if (crypto_memneq(server_response_sig, shdr->Signature,
+ SMB2_SIGNATURE_SIZE)) {
cifs_dbg(VFS, "sign fail cmd 0x%x message id 0x%llx\n",
shdr->Command, shdr->MessageId);
return -EACCES;
diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c
index c79304012b08..563ef488a225 100644
--- a/fs/smb/client/smbdirect.c
+++ b/fs/smb/client/smbdirect.c
@@ -4,61 +4,12 @@
*
* Author(s): Long Li <longli@microsoft.com>
*/
-#include <linux/module.h>
-#include <linux/highmem.h>
-#include <linux/folio_queue.h>
-#define __SMBDIRECT_SOCKET_DISCONNECT(__sc) smbd_disconnect_rdma_connection(__sc)
-#include "../common/smbdirect/smbdirect_pdu.h"
+
#include "smbdirect.h"
#include "cifs_debug.h"
#include "cifsproto.h"
#include "smb2proto.h"
-const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn)
-{
- struct smbdirect_socket *sc = &conn->socket;
-
- return &sc->parameters;
-}
-
-static struct smbdirect_recv_io *get_receive_buffer(
- struct smbdirect_socket *sc);
-static void put_receive_buffer(
- struct smbdirect_socket *sc,
- struct smbdirect_recv_io *response);
-static int allocate_receive_buffers(struct smbdirect_socket *sc, int num_buf);
-static void destroy_receive_buffers(struct smbdirect_socket *sc);
-
-static void enqueue_reassembly(
- struct smbdirect_socket *sc,
- struct smbdirect_recv_io *response, int data_length);
-static struct smbdirect_recv_io *_get_first_reassembly(
- struct smbdirect_socket *sc);
-
-static int smbd_post_send(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *batch,
- struct smbdirect_send_io *request);
-
-static int smbd_post_recv(
- struct smbdirect_socket *sc,
- struct smbdirect_recv_io *response);
-
-static int smbd_post_send_empty(struct smbdirect_socket *sc);
-
-static void destroy_mr_list(struct smbdirect_socket *sc);
-static int allocate_mr_list(struct smbdirect_socket *sc);
-
-struct smb_extract_to_rdma {
- struct ib_sge *sge;
- unsigned int nr_sge;
- unsigned int max_sge;
- struct ib_device *device;
- u32 local_dma_lkey;
- enum dma_data_direction direction;
-};
-static ssize_t smb_extract_iter_to_rdma(struct iov_iter *iter, size_t len,
- struct smb_extract_to_rdma *rdma);
-
/* Port numbers for SMBD transport */
#define SMB_PORT 445
#define SMBD_PORT 5445
@@ -72,21 +23,12 @@ static ssize_t smb_extract_iter_to_rdma(struct iov_iter *iter, size_t len,
/* The timeout to wait for a keepalive message from peer in seconds */
#define KEEPALIVE_RECV_TIMEOUT 5
-/* SMBD minimum receive size and fragmented sized defined in [MS-SMBD] */
-#define SMBD_MIN_RECEIVE_SIZE 128
-#define SMBD_MIN_FRAGMENTED_SIZE 131072
-
/*
* Default maximum number of RDMA read/write outstanding on this connection
* This value is possibly decreased during QP creation on hardware limit
*/
#define SMBD_CM_RESPONDER_RESOURCES 32
-/* Maximum number of retries on data transfer operations */
-#define SMBD_CM_RETRY 6
-/* No need to retry on Receiver Not Ready since SMBD manages credits */
-#define SMBD_CM_RNR_RETRY 0
-
/*
* User configurable initial values per SMBD transport connection
* as defined in [MS-SMBD] 3.1.1.1
@@ -162,6 +104,43 @@ module_param(smbd_logging_level, uint, 0644);
MODULE_PARM_DESC(smbd_logging_level,
"Logging level for SMBD transport, 0 (default): error, 1: info");
+static bool smbd_logging_needed(struct smbdirect_socket *sc,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls)
+{
+#define BUILD_BUG_SAME(x) BUILD_BUG_ON(x != SMBDIRECT_LOG_ ##x)
+ BUILD_BUG_SAME(ERR);
+ BUILD_BUG_SAME(INFO);
+#undef BUILD_BUG_SAME
+#define BUILD_BUG_SAME(x) BUILD_BUG_ON(x != SMBDIRECT_ ##x)
+ BUILD_BUG_SAME(LOG_OUTGOING);
+ BUILD_BUG_SAME(LOG_INCOMING);
+ BUILD_BUG_SAME(LOG_READ);
+ BUILD_BUG_SAME(LOG_WRITE);
+ BUILD_BUG_SAME(LOG_RDMA_SEND);
+ BUILD_BUG_SAME(LOG_RDMA_RECV);
+ BUILD_BUG_SAME(LOG_KEEP_ALIVE);
+ BUILD_BUG_SAME(LOG_RDMA_EVENT);
+ BUILD_BUG_SAME(LOG_RDMA_MR);
+#undef BUILD_BUG_SAME
+
+ if (lvl <= smbd_logging_level || cls & smbd_logging_class)
+ return true;
+ return false;
+}
+
+static void smbd_logging_vaprintf(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls,
+ struct va_format *vaf)
+{
+ cifs_dbg(VFS, "%s:%u %pV", func, line, vaf);
+}
+
#define log_rdma(level, class, fmt, args...) \
do { \
if (level <= smbd_logging_level || class & smbd_logging_class) \
@@ -185,1695 +164,34 @@ do { \
#define log_rdma_mr(level, fmt, args...) \
log_rdma(level, LOG_RDMA_MR, fmt, ##args)
-static void smbd_disconnect_wake_up_all(struct smbdirect_socket *sc)
-{
- /*
- * Wake up all waiters in all wait queues
- * in order to notice the broken connection.
- */
- wake_up_all(&sc->status_wait);
- wake_up_all(&sc->send_io.lcredits.wait_queue);
- wake_up_all(&sc->send_io.credits.wait_queue);
- wake_up_all(&sc->send_io.pending.dec_wait_queue);
- wake_up_all(&sc->send_io.pending.zero_wait_queue);
- wake_up_all(&sc->recv_io.reassembly.wait_queue);
- wake_up_all(&sc->mr_io.ready.wait_queue);
- wake_up_all(&sc->mr_io.cleanup.wait_queue);
-}
-
-static void smbd_disconnect_rdma_work(struct work_struct *work)
-{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, disconnect_work);
-
- if (sc->first_error == 0)
- sc->first_error = -ECONNABORTED;
-
- /*
- * make sure this and other work is not queued again
- * but here we don't block and avoid
- * disable[_delayed]_work_sync()
- */
- disable_work(&sc->disconnect_work);
- disable_work(&sc->recv_io.posted.refill_work);
- disable_work(&sc->mr_io.recovery_work);
- disable_work(&sc->idle.immediate_work);
- disable_delayed_work(&sc->idle.timer_work);
-
- switch (sc->status) {
- case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
- case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
- case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
- case SMBDIRECT_SOCKET_CONNECTED:
- case SMBDIRECT_SOCKET_ERROR:
- sc->status = SMBDIRECT_SOCKET_DISCONNECTING;
- rdma_disconnect(sc->rdma.cm_id);
- break;
-
- case SMBDIRECT_SOCKET_CREATED:
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
- /*
- * rdma_connect() never reached
- * RDMA_CM_EVENT_ESTABLISHED
- */
- sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
- break;
-
- case SMBDIRECT_SOCKET_DISCONNECTING:
- case SMBDIRECT_SOCKET_DISCONNECTED:
- case SMBDIRECT_SOCKET_DESTROYED:
- break;
- }
-
- /*
- * Wake up all waiters in all wait queues
- * in order to notice the broken connection.
- */
- smbd_disconnect_wake_up_all(sc);
-}
-
-static void smbd_disconnect_rdma_connection(struct smbdirect_socket *sc)
-{
- if (sc->first_error == 0)
- sc->first_error = -ECONNABORTED;
-
- /*
- * make sure other work (than disconnect_work) is
- * not queued again but here we don't block and avoid
- * disable[_delayed]_work_sync()
- */
- disable_work(&sc->recv_io.posted.refill_work);
- disable_work(&sc->mr_io.recovery_work);
- disable_work(&sc->idle.immediate_work);
- disable_delayed_work(&sc->idle.timer_work);
-
- switch (sc->status) {
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
- case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
- case SMBDIRECT_SOCKET_ERROR:
- case SMBDIRECT_SOCKET_DISCONNECTING:
- case SMBDIRECT_SOCKET_DISCONNECTED:
- case SMBDIRECT_SOCKET_DESTROYED:
- /*
- * Keep the current error status
- */
- break;
-
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED;
- break;
-
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED;
- break;
-
- case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
- sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED;
- break;
-
- case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
- case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED;
- break;
-
- case SMBDIRECT_SOCKET_CREATED:
- sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
- break;
-
- case SMBDIRECT_SOCKET_CONNECTED:
- sc->status = SMBDIRECT_SOCKET_ERROR;
- break;
- }
-
- /*
- * Wake up all waiters in all wait queues
- * in order to notice the broken connection.
- */
- smbd_disconnect_wake_up_all(sc);
-
- queue_work(sc->workqueue, &sc->disconnect_work);
-}
-
-/* Upcall from RDMA CM */
-static int smbd_conn_upcall(
- struct rdma_cm_id *id, struct rdma_cm_event *event)
-{
- struct smbdirect_socket *sc = id->context;
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- const char *event_name = rdma_event_msg(event->event);
- u8 peer_initiator_depth;
- u8 peer_responder_resources;
-
- log_rdma_event(INFO, "event=%s status=%d\n",
- event_name, event->status);
-
- switch (event->event) {
- case RDMA_CM_EVENT_ADDR_RESOLVED:
- if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING))
- break;
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED;
- wake_up(&sc->status_wait);
- break;
-
- case RDMA_CM_EVENT_ROUTE_RESOLVED:
- if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING))
- break;
- sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED;
- wake_up(&sc->status_wait);
- break;
-
- case RDMA_CM_EVENT_ADDR_ERROR:
- log_rdma_event(ERR, "connecting failed event=%s\n", event_name);
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED;
- smbd_disconnect_rdma_work(&sc->disconnect_work);
- break;
-
- case RDMA_CM_EVENT_ROUTE_ERROR:
- log_rdma_event(ERR, "connecting failed event=%s\n", event_name);
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED;
- smbd_disconnect_rdma_work(&sc->disconnect_work);
- break;
-
- case RDMA_CM_EVENT_ESTABLISHED:
- log_rdma_event(INFO, "connected event=%s\n", event_name);
-
- /*
- * Here we work around an inconsistency between
- * iWarp and other devices (at least rxe and irdma using RoCEv2)
- */
- if (rdma_protocol_iwarp(id->device, id->port_num)) {
- /*
- * iWarp devices report the peer's values
- * with the perspective of the peer here.
- * Tested with siw and irdma (in iwarp mode)
- * We need to change to our perspective here,
- * so we need to switch the values.
- */
- peer_initiator_depth = event->param.conn.responder_resources;
- peer_responder_resources = event->param.conn.initiator_depth;
- } else {
- /*
- * Non iWarp devices report the peer's values
- * already changed to our perspective here.
- * Tested with rxe and irdma (in roce mode).
- */
- peer_initiator_depth = event->param.conn.initiator_depth;
- peer_responder_resources = event->param.conn.responder_resources;
- }
- if (rdma_protocol_iwarp(id->device, id->port_num) &&
- event->param.conn.private_data_len == 8) {
- /*
- * Legacy clients with only iWarp MPA v1 support
- * need a private blob in order to negotiate
- * the IRD/ORD values.
- */
- const __be32 *ird_ord_hdr = event->param.conn.private_data;
- u32 ird32 = be32_to_cpu(ird_ord_hdr[0]);
- u32 ord32 = be32_to_cpu(ird_ord_hdr[1]);
-
- /*
- * cifs.ko sends the legacy IRD/ORD negotiation
- * event if iWarp MPA v2 was used.
- *
- * Here we check that the values match and only
- * mark the client as legacy if they don't match.
- */
- if ((u32)event->param.conn.initiator_depth != ird32 ||
- (u32)event->param.conn.responder_resources != ord32) {
- /*
- * There are broken clients (old cifs.ko)
- * using little endian and also
- * struct rdma_conn_param only uses u8
- * for initiator_depth and responder_resources,
- * so we truncate the value to U8_MAX.
- *
- * smb_direct_accept_client() will then
- * do the real negotiation in order to
- * select the minimum between client and
- * server.
- */
- ird32 = min_t(u32, ird32, U8_MAX);
- ord32 = min_t(u32, ord32, U8_MAX);
-
- sc->rdma.legacy_iwarp = true;
- peer_initiator_depth = (u8)ird32;
- peer_responder_resources = (u8)ord32;
- }
- }
-
- /*
- * negotiate the value by using the minimum
- * between client and server if the client provided
- * non 0 values.
- */
- if (peer_initiator_depth != 0)
- sp->initiator_depth =
- min_t(u8, sp->initiator_depth,
- peer_initiator_depth);
- if (peer_responder_resources != 0)
- sp->responder_resources =
- min_t(u8, sp->responder_resources,
- peer_responder_resources);
-
- if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING))
- break;
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_NEEDED;
- wake_up(&sc->status_wait);
- break;
-
- case RDMA_CM_EVENT_CONNECT_ERROR:
- case RDMA_CM_EVENT_UNREACHABLE:
- case RDMA_CM_EVENT_REJECTED:
- log_rdma_event(ERR, "connecting failed event=%s\n", event_name);
- sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED;
- smbd_disconnect_rdma_work(&sc->disconnect_work);
- break;
-
- case RDMA_CM_EVENT_DEVICE_REMOVAL:
- case RDMA_CM_EVENT_DISCONNECTED:
- /* This happens when we fail the negotiation */
- if (sc->status == SMBDIRECT_SOCKET_NEGOTIATE_FAILED) {
- log_rdma_event(ERR, "event=%s during negotiation\n", event_name);
- }
-
- sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
- smbd_disconnect_rdma_work(&sc->disconnect_work);
- break;
-
- default:
- log_rdma_event(ERR, "unexpected event=%s status=%d\n",
- event_name, event->status);
- break;
- }
-
- return 0;
-}
-
-/* Upcall from RDMA QP */
-static void
-smbd_qp_async_error_upcall(struct ib_event *event, void *context)
-{
- struct smbdirect_socket *sc = context;
-
- log_rdma_event(ERR, "%s on device %s socket %p\n",
- ib_event_msg(event->event), event->device->name, sc);
-
- switch (event->event) {
- case IB_EVENT_CQ_ERR:
- case IB_EVENT_QP_FATAL:
- smbd_disconnect_rdma_connection(sc);
- break;
-
- default:
- break;
- }
-}
-
-static inline void *smbdirect_send_io_payload(struct smbdirect_send_io *request)
-{
- return (void *)request->packet;
-}
-
-static inline void *smbdirect_recv_io_payload(struct smbdirect_recv_io *response)
-{
- return (void *)response->packet;
-}
-
-static struct smbdirect_send_io *smbd_alloc_send_io(struct smbdirect_socket *sc)
-{
- struct smbdirect_send_io *msg;
-
- msg = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL);
- if (!msg)
- return ERR_PTR(-ENOMEM);
- msg->socket = sc;
- INIT_LIST_HEAD(&msg->sibling_list);
- msg->num_sge = 0;
-
- return msg;
-}
-
-static void smbd_free_send_io(struct smbdirect_send_io *msg)
-{
- struct smbdirect_socket *sc = msg->socket;
- size_t i;
-
- /*
- * The list needs to be empty!
- * The caller should take care of it.
- */
- WARN_ON_ONCE(!list_empty(&msg->sibling_list));
-
- /*
- * Note we call ib_dma_unmap_page(), even if some sges are mapped using
- * ib_dma_map_single().
- *
- * The difference between _single() and _page() only matters for the
- * ib_dma_map_*() case.
- *
- * For the ib_dma_unmap_*() case it does not matter as both take the
- * dma_addr_t and dma_unmap_single_attrs() is just an alias to
- * dma_unmap_page_attrs().
- */
- for (i = 0; i < msg->num_sge; i++)
- ib_dma_unmap_page(sc->ib.dev,
- msg->sge[i].addr,
- msg->sge[i].length,
- DMA_TO_DEVICE);
-
- mempool_free(msg, sc->send_io.mem.pool);
-}
-
-/* Called when a RDMA send is done */
-static void send_done(struct ib_cq *cq, struct ib_wc *wc)
-{
- struct smbdirect_send_io *request =
- container_of(wc->wr_cqe, struct smbdirect_send_io, cqe);
- struct smbdirect_socket *sc = request->socket;
- struct smbdirect_send_io *sibling, *next;
- int lcredits = 0;
-
- log_rdma_send(INFO, "smbdirect_send_io 0x%p completed wc->status=%s\n",
- request, ib_wc_status_msg(wc->status));
-
- if (unlikely(!(request->wr.send_flags & IB_SEND_SIGNALED))) {
- /*
- * This happens when smbdirect_send_io is a sibling
- * before the final message, it is signaled on
- * error anyway, so we need to skip
- * smbdirect_connection_free_send_io here,
- * otherwise is will destroy the memory
- * of the siblings too, which will cause
- * use after free problems for the others
- * triggered from ib_drain_qp().
- */
- if (wc->status != IB_WC_SUCCESS)
- goto skip_free;
-
- /*
- * This should not happen!
- * But we better just close the
- * connection...
- */
- log_rdma_send(ERR,
- "unexpected send completion wc->status=%s (%d) wc->opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->status, wc->opcode);
- smbd_disconnect_rdma_connection(sc);
- return;
- }
-
- /*
- * Free possible siblings and then the main send_io
- */
- list_for_each_entry_safe(sibling, next, &request->sibling_list, sibling_list) {
- list_del_init(&sibling->sibling_list);
- smbd_free_send_io(sibling);
- lcredits += 1;
- }
- /* Note this frees wc->wr_cqe, but not wc */
- smbd_free_send_io(request);
- lcredits += 1;
-
- if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_SEND) {
-skip_free:
- if (wc->status != IB_WC_WR_FLUSH_ERR)
- log_rdma_send(ERR, "wc->status=%s wc->opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->opcode);
- smbd_disconnect_rdma_connection(sc);
- return;
- }
-
- atomic_add(lcredits, &sc->send_io.lcredits.count);
- wake_up(&sc->send_io.lcredits.wait_queue);
-
- if (atomic_dec_and_test(&sc->send_io.pending.count))
- wake_up(&sc->send_io.pending.zero_wait_queue);
-
- wake_up(&sc->send_io.pending.dec_wait_queue);
-}
-
-static void dump_smbdirect_negotiate_resp(struct smbdirect_negotiate_resp *resp)
-{
- log_rdma_event(INFO, "resp message min_version %u max_version %u negotiated_version %u credits_requested %u credits_granted %u status %u max_readwrite_size %u preferred_send_size %u max_receive_size %u max_fragmented_size %u\n",
- resp->min_version, resp->max_version,
- resp->negotiated_version, resp->credits_requested,
- resp->credits_granted, resp->status,
- resp->max_readwrite_size, resp->preferred_send_size,
- resp->max_receive_size, resp->max_fragmented_size);
-}
-
-/*
- * Process a negotiation response message, according to [MS-SMBD]3.1.5.7
- * response, packet_length: the negotiation response message
- * return value: true if negotiation is a success, false if failed
- */
-static bool process_negotiation_response(
- struct smbdirect_recv_io *response, int packet_length)
-{
- struct smbdirect_socket *sc = response->socket;
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_negotiate_resp *packet = smbdirect_recv_io_payload(response);
-
- if (packet_length < sizeof(struct smbdirect_negotiate_resp)) {
- log_rdma_event(ERR,
- "error: packet_length=%d\n", packet_length);
- return false;
- }
-
- if (le16_to_cpu(packet->negotiated_version) != SMBDIRECT_V1) {
- log_rdma_event(ERR, "error: negotiated_version=%x\n",
- le16_to_cpu(packet->negotiated_version));
- return false;
- }
-
- if (packet->credits_requested == 0) {
- log_rdma_event(ERR, "error: credits_requested==0\n");
- return false;
- }
- sc->recv_io.credits.target = le16_to_cpu(packet->credits_requested);
- sc->recv_io.credits.target = min_t(u16, sc->recv_io.credits.target, sp->recv_credit_max);
-
- if (packet->credits_granted == 0) {
- log_rdma_event(ERR, "error: credits_granted==0\n");
- return false;
- }
- atomic_set(&sc->send_io.lcredits.count, sp->send_credit_target);
- atomic_set(&sc->send_io.credits.count, le16_to_cpu(packet->credits_granted));
-
- if (le32_to_cpu(packet->preferred_send_size) > sp->max_recv_size) {
- log_rdma_event(ERR, "error: preferred_send_size=%d\n",
- le32_to_cpu(packet->preferred_send_size));
- return false;
- }
- sp->max_recv_size = le32_to_cpu(packet->preferred_send_size);
-
- if (le32_to_cpu(packet->max_receive_size) < SMBD_MIN_RECEIVE_SIZE) {
- log_rdma_event(ERR, "error: max_receive_size=%d\n",
- le32_to_cpu(packet->max_receive_size));
- return false;
- }
- sp->max_send_size = min_t(u32, sp->max_send_size,
- le32_to_cpu(packet->max_receive_size));
-
- if (le32_to_cpu(packet->max_fragmented_size) <
- SMBD_MIN_FRAGMENTED_SIZE) {
- log_rdma_event(ERR, "error: max_fragmented_size=%d\n",
- le32_to_cpu(packet->max_fragmented_size));
- return false;
- }
- sp->max_fragmented_send_size =
- le32_to_cpu(packet->max_fragmented_size);
-
-
- sp->max_read_write_size = min_t(u32,
- le32_to_cpu(packet->max_readwrite_size),
- sp->max_frmr_depth * PAGE_SIZE);
- sp->max_frmr_depth = sp->max_read_write_size / PAGE_SIZE;
-
- atomic_set(&sc->send_io.bcredits.count, 1);
- sc->recv_io.expected = SMBDIRECT_EXPECT_DATA_TRANSFER;
- return true;
-}
-
-static void smbd_post_send_credits(struct work_struct *work)
-{
- int rc;
- struct smbdirect_recv_io *response;
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, recv_io.posted.refill_work);
- int posted = 0;
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
- return;
- }
-
- if (sc->recv_io.credits.target >
- atomic_read(&sc->recv_io.credits.count)) {
- while (true) {
- response = get_receive_buffer(sc);
- if (!response)
- break;
-
- response->first_segment = false;
- rc = smbd_post_recv(sc, response);
- if (rc) {
- log_rdma_recv(ERR,
- "post_recv failed rc=%d\n", rc);
- put_receive_buffer(sc, response);
- break;
- }
-
- atomic_inc(&sc->recv_io.posted.count);
- posted += 1;
- }
- }
-
- atomic_add(posted, &sc->recv_io.credits.available);
-
- /*
- * If the last send credit is waiting for credits
- * it can grant we need to wake it up
- */
- if (posted &&
- atomic_read(&sc->send_io.bcredits.count) == 0 &&
- atomic_read(&sc->send_io.credits.count) == 0)
- wake_up(&sc->send_io.credits.wait_queue);
-
- /* Promptly send an immediate packet as defined in [MS-SMBD] 3.1.1.1 */
- if (atomic_read(&sc->recv_io.credits.count) <
- sc->recv_io.credits.target - 1) {
- log_keep_alive(INFO, "schedule send of an empty message\n");
- queue_work(sc->workqueue, &sc->idle.immediate_work);
- }
-}
-
-/* Called from softirq, when recv is done */
-static void recv_done(struct ib_cq *cq, struct ib_wc *wc)
-{
- struct smbdirect_data_transfer *data_transfer;
- struct smbdirect_recv_io *response =
- container_of(wc->wr_cqe, struct smbdirect_recv_io, cqe);
- struct smbdirect_socket *sc = response->socket;
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- int current_recv_credits;
- u16 old_recv_credit_target;
- u32 data_offset = 0;
- u32 data_length = 0;
- u32 remaining_data_length = 0;
- bool negotiate_done = false;
-
- log_rdma_recv(INFO,
- "response=0x%p type=%d wc status=%s wc opcode %d byte_len=%d pkey_index=%u\n",
- response, sc->recv_io.expected,
- ib_wc_status_msg(wc->status), wc->opcode,
- wc->byte_len, wc->pkey_index);
-
- if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV) {
- if (wc->status != IB_WC_WR_FLUSH_ERR)
- log_rdma_recv(ERR, "wc->status=%s opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->opcode);
- goto error;
- }
-
- ib_dma_sync_single_for_cpu(
- wc->qp->device,
- response->sge.addr,
- response->sge.length,
- DMA_FROM_DEVICE);
-
- /*
- * Reset timer to the keepalive interval in
- * order to trigger our next keepalive message.
- */
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->keepalive_interval_msec));
-
- switch (sc->recv_io.expected) {
- /* SMBD negotiation response */
- case SMBDIRECT_EXPECT_NEGOTIATE_REP:
- dump_smbdirect_negotiate_resp(smbdirect_recv_io_payload(response));
- sc->recv_io.reassembly.full_packet_received = true;
- negotiate_done =
- process_negotiation_response(response, wc->byte_len);
- put_receive_buffer(sc, response);
- if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_NEGOTIATE_RUNNING))
- negotiate_done = false;
- if (!negotiate_done) {
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED;
- smbd_disconnect_rdma_connection(sc);
- } else {
- sc->status = SMBDIRECT_SOCKET_CONNECTED;
- wake_up(&sc->status_wait);
- }
-
- return;
-
- /* SMBD data transfer packet */
- case SMBDIRECT_EXPECT_DATA_TRANSFER:
- data_transfer = smbdirect_recv_io_payload(response);
-
- if (wc->byte_len <
- offsetof(struct smbdirect_data_transfer, padding))
- goto error;
-
- remaining_data_length = le32_to_cpu(data_transfer->remaining_data_length);
- data_offset = le32_to_cpu(data_transfer->data_offset);
- data_length = le32_to_cpu(data_transfer->data_length);
- if (wc->byte_len < data_offset ||
- (u64)wc->byte_len < (u64)data_offset + data_length)
- goto error;
-
- if (remaining_data_length > sp->max_fragmented_recv_size ||
- data_length > sp->max_fragmented_recv_size ||
- (u64)remaining_data_length + (u64)data_length > (u64)sp->max_fragmented_recv_size)
- goto error;
-
- if (data_length) {
- if (sc->recv_io.reassembly.full_packet_received)
- response->first_segment = true;
-
- if (le32_to_cpu(data_transfer->remaining_data_length))
- sc->recv_io.reassembly.full_packet_received = false;
- else
- sc->recv_io.reassembly.full_packet_received = true;
- }
-
- atomic_dec(&sc->recv_io.posted.count);
- current_recv_credits = atomic_dec_return(&sc->recv_io.credits.count);
-
- old_recv_credit_target = sc->recv_io.credits.target;
- sc->recv_io.credits.target =
- le16_to_cpu(data_transfer->credits_requested);
- sc->recv_io.credits.target =
- min_t(u16, sc->recv_io.credits.target, sp->recv_credit_max);
- sc->recv_io.credits.target =
- max_t(u16, sc->recv_io.credits.target, 1);
- if (le16_to_cpu(data_transfer->credits_granted)) {
- atomic_add(le16_to_cpu(data_transfer->credits_granted),
- &sc->send_io.credits.count);
- /*
- * We have new send credits granted from remote peer
- * If any sender is waiting for credits, unblock it
- */
- wake_up(&sc->send_io.credits.wait_queue);
- }
-
- log_incoming(INFO, "data flags %d data_offset %d data_length %d remaining_data_length %d\n",
- le16_to_cpu(data_transfer->flags),
- le32_to_cpu(data_transfer->data_offset),
- le32_to_cpu(data_transfer->data_length),
- le32_to_cpu(data_transfer->remaining_data_length));
-
- /* Send an immediate response right away if requested */
- if (le16_to_cpu(data_transfer->flags) &
- SMBDIRECT_FLAG_RESPONSE_REQUESTED) {
- log_keep_alive(INFO, "schedule send of immediate response\n");
- queue_work(sc->workqueue, &sc->idle.immediate_work);
- }
-
- /*
- * If this is a packet with data playload place the data in
- * reassembly queue and wake up the reading thread
- */
- if (data_length) {
- if (current_recv_credits <= (sc->recv_io.credits.target / 4) ||
- sc->recv_io.credits.target > old_recv_credit_target)
- queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
-
- enqueue_reassembly(sc, response, data_length);
- wake_up(&sc->recv_io.reassembly.wait_queue);
- } else
- put_receive_buffer(sc, response);
-
- return;
-
- case SMBDIRECT_EXPECT_NEGOTIATE_REQ:
- /* Only server... */
- break;
- }
-
- /*
- * This is an internal error!
- */
- log_rdma_recv(ERR, "unexpected response type=%d\n", sc->recv_io.expected);
- WARN_ON_ONCE(sc->recv_io.expected != SMBDIRECT_EXPECT_DATA_TRANSFER);
-error:
- put_receive_buffer(sc, response);
- smbd_disconnect_rdma_connection(sc);
-}
-
-static struct rdma_cm_id *smbd_create_id(
- struct smbdirect_socket *sc,
- struct sockaddr *dstaddr, int port)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct rdma_cm_id *id;
- u8 node_type = RDMA_NODE_UNSPECIFIED;
- int rc;
- __be16 *sport;
-
- id = rdma_create_id(&init_net, smbd_conn_upcall, sc,
- RDMA_PS_TCP, IB_QPT_RC);
- if (IS_ERR(id)) {
- rc = PTR_ERR(id);
- log_rdma_event(ERR, "rdma_create_id() failed %i\n", rc);
- return id;
- }
-
- switch (port) {
- case SMBD_PORT:
- /*
- * only allow iWarp devices
- * for port 5445.
- */
- node_type = RDMA_NODE_RNIC;
- break;
- case SMB_PORT:
- /*
- * only allow InfiniBand, RoCEv1 or RoCEv2
- * devices for port 445.
- *
- * (Basically don't allow iWarp devices)
- */
- node_type = RDMA_NODE_IB_CA;
- break;
- }
- rc = rdma_restrict_node_type(id, node_type);
- if (rc) {
- log_rdma_event(ERR, "rdma_restrict_node_type(%u) failed %i\n",
- node_type, rc);
- goto out;
- }
-
- if (dstaddr->sa_family == AF_INET6)
- sport = &((struct sockaddr_in6 *)dstaddr)->sin6_port;
- else
- sport = &((struct sockaddr_in *)dstaddr)->sin_port;
-
- *sport = htons(port);
-
- WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED);
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING;
- rc = rdma_resolve_addr(id, NULL, (struct sockaddr *)dstaddr,
- sp->resolve_addr_timeout_msec);
- if (rc) {
- log_rdma_event(ERR, "rdma_resolve_addr() failed %i\n", rc);
- goto out;
- }
- rc = wait_event_interruptible_timeout(
- sc->status_wait,
- sc->status != SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING,
- msecs_to_jiffies(sp->resolve_addr_timeout_msec));
- /* e.g. if interrupted returns -ERESTARTSYS */
- if (rc < 0) {
- log_rdma_event(ERR, "rdma_resolve_addr timeout rc: %i\n", rc);
- goto out;
- }
- if (sc->status == SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING) {
- rc = -ETIMEDOUT;
- log_rdma_event(ERR, "rdma_resolve_addr() completed %i\n", rc);
- goto out;
- }
- if (sc->status != SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED) {
- rc = -EHOSTUNREACH;
- log_rdma_event(ERR, "rdma_resolve_addr() completed %i\n", rc);
- goto out;
- }
-
- WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED);
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING;
- rc = rdma_resolve_route(id, sp->resolve_route_timeout_msec);
- if (rc) {
- log_rdma_event(ERR, "rdma_resolve_route() failed %i\n", rc);
- goto out;
- }
- rc = wait_event_interruptible_timeout(
- sc->status_wait,
- sc->status != SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING,
- msecs_to_jiffies(sp->resolve_route_timeout_msec));
- /* e.g. if interrupted returns -ERESTARTSYS */
- if (rc < 0) {
- log_rdma_event(ERR, "rdma_resolve_addr timeout rc: %i\n", rc);
- goto out;
- }
- if (sc->status == SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING) {
- rc = -ETIMEDOUT;
- log_rdma_event(ERR, "rdma_resolve_route() completed %i\n", rc);
- goto out;
- }
- if (sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED) {
- rc = -ENETUNREACH;
- log_rdma_event(ERR, "rdma_resolve_route() completed %i\n", rc);
- goto out;
- }
-
- return id;
-
-out:
- rdma_destroy_id(id);
- return ERR_PTR(rc);
-}
-
-/*
- * Test if FRWR (Fast Registration Work Requests) is supported on the device
- * This implementation requires FRWR on RDMA read/write
- * return value: true if it is supported
- */
-static bool frwr_is_supported(struct ib_device_attr *attrs)
-{
- if (!(attrs->device_cap_flags & IB_DEVICE_MEM_MGT_EXTENSIONS))
- return false;
- if (attrs->max_fast_reg_page_list_len == 0)
- return false;
- return true;
-}
-
-static int smbd_ia_open(
- struct smbdirect_socket *sc,
- struct sockaddr *dstaddr, int port)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- int rc;
-
- WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED);
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED;
-
- sc->rdma.cm_id = smbd_create_id(sc, dstaddr, port);
- if (IS_ERR(sc->rdma.cm_id)) {
- rc = PTR_ERR(sc->rdma.cm_id);
- goto out1;
- }
- sc->ib.dev = sc->rdma.cm_id->device;
-
- if (!frwr_is_supported(&sc->ib.dev->attrs)) {
- log_rdma_event(ERR, "Fast Registration Work Requests (FRWR) is not supported\n");
- log_rdma_event(ERR, "Device capability flags = %llx max_fast_reg_page_list_len = %u\n",
- sc->ib.dev->attrs.device_cap_flags,
- sc->ib.dev->attrs.max_fast_reg_page_list_len);
- rc = -EPROTONOSUPPORT;
- goto out2;
- }
- sp->max_frmr_depth = min_t(u32,
- sp->max_frmr_depth,
- sc->ib.dev->attrs.max_fast_reg_page_list_len);
- sc->mr_io.type = IB_MR_TYPE_MEM_REG;
- if (sc->ib.dev->attrs.kernel_cap_flags & IBK_SG_GAPS_REG)
- sc->mr_io.type = IB_MR_TYPE_SG_GAPS;
-
- return 0;
-
-out2:
- rdma_destroy_id(sc->rdma.cm_id);
- sc->rdma.cm_id = NULL;
-
-out1:
- return rc;
-}
-
-/*
- * Send a negotiation request message to the peer
- * The negotiation procedure is in [MS-SMBD] 3.1.5.2 and 3.1.5.3
- * After negotiation, the transport is connected and ready for
- * carrying upper layer SMB payload
- */
-static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- int rc;
- struct smbdirect_send_io *request;
- struct smbdirect_negotiate_req *packet;
-
- request = smbd_alloc_send_io(sc);
- if (IS_ERR(request))
- return PTR_ERR(request);
-
- packet = smbdirect_send_io_payload(request);
- packet->min_version = cpu_to_le16(SMBDIRECT_V1);
- packet->max_version = cpu_to_le16(SMBDIRECT_V1);
- packet->reserved = 0;
- packet->credits_requested = cpu_to_le16(sp->send_credit_target);
- packet->preferred_send_size = cpu_to_le32(sp->max_send_size);
- packet->max_receive_size = cpu_to_le32(sp->max_recv_size);
- packet->max_fragmented_size =
- cpu_to_le32(sp->max_fragmented_recv_size);
-
- request->sge[0].addr = ib_dma_map_single(
- sc->ib.dev, (void *)packet,
- sizeof(*packet), DMA_TO_DEVICE);
- if (ib_dma_mapping_error(sc->ib.dev, request->sge[0].addr)) {
- rc = -EIO;
- goto dma_mapping_failed;
- }
- request->num_sge = 1;
-
- request->sge[0].length = sizeof(*packet);
- request->sge[0].lkey = sc->ib.pd->local_dma_lkey;
-
- rc = smbd_post_send(sc, NULL, request);
- if (!rc)
- return 0;
-
- if (rc == -EAGAIN)
- rc = -EIO;
-
-dma_mapping_failed:
- smbd_free_send_io(request);
- return rc;
-}
-
-/*
- * Extend the credits to remote peer
- * This implements [MS-SMBD] 3.1.5.9
- * The idea is that we should extend credits to remote peer as quickly as
- * it's allowed, to maintain data flow. We allocate as much receive
- * buffer as possible, and extend the receive credits to remote peer
- * return value: the new credtis being granted.
- */
-static int manage_credits_prior_sending(struct smbdirect_socket *sc)
-{
- int missing;
- int available;
- int new_credits;
-
- if (atomic_read(&sc->recv_io.credits.count) >= sc->recv_io.credits.target)
- return 0;
-
- missing = (int)sc->recv_io.credits.target - atomic_read(&sc->recv_io.credits.count);
- available = atomic_xchg(&sc->recv_io.credits.available, 0);
- new_credits = (u16)min3(U16_MAX, missing, available);
- if (new_credits <= 0) {
- /*
- * If credits are available, but not granted
- * we need to re-add them again.
- */
- if (available)
- atomic_add(available, &sc->recv_io.credits.available);
- return 0;
- }
-
- if (new_credits < available) {
- /*
- * Readd the remaining available again.
- */
- available -= new_credits;
- atomic_add(available, &sc->recv_io.credits.available);
- }
-
- /*
- * Remember we granted the credits
- */
- atomic_add(new_credits, &sc->recv_io.credits.count);
- return new_credits;
-}
-
-/*
- * Check if we need to send a KEEP_ALIVE message
- * The idle connection timer triggers a KEEP_ALIVE message when expires
- * SMBDIRECT_FLAG_RESPONSE_REQUESTED is set in the message flag to have peer send
- * back a response.
- * return value:
- * 1 if SMBDIRECT_FLAG_RESPONSE_REQUESTED needs to be set
- * 0: otherwise
- */
-static int manage_keep_alive_before_sending(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
-
- if (sc->idle.keepalive == SMBDIRECT_KEEPALIVE_PENDING) {
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_SENT;
- /*
- * Now use the keepalive timeout (instead of keepalive interval)
- * in order to wait for a response
- */
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->keepalive_timeout_msec));
- return 1;
- }
- return 0;
-}
-
-static int smbd_ib_post_send(struct smbdirect_socket *sc,
- struct ib_send_wr *wr)
-{
- int ret;
-
- atomic_inc(&sc->send_io.pending.count);
- ret = ib_post_send(sc->ib.qp, wr, NULL);
- if (ret) {
- pr_err("failed to post send: %d\n", ret);
- smbd_disconnect_rdma_connection(sc);
- ret = -EAGAIN;
- }
- return ret;
-}
-
-/* Post the send request */
-static int smbd_post_send(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *batch,
- struct smbdirect_send_io *request)
-{
- int i;
-
- for (i = 0; i < request->num_sge; i++) {
- log_rdma_send(INFO,
- "rdma_request sge[%d] addr=0x%llx length=%u\n",
- i, request->sge[i].addr, request->sge[i].length);
- ib_dma_sync_single_for_device(
- sc->ib.dev,
- request->sge[i].addr,
- request->sge[i].length,
- DMA_TO_DEVICE);
- }
-
- request->cqe.done = send_done;
- request->wr.next = NULL;
- request->wr.sg_list = request->sge;
- request->wr.num_sge = request->num_sge;
- request->wr.opcode = IB_WR_SEND;
-
- if (batch) {
- request->wr.wr_cqe = NULL;
- request->wr.send_flags = 0;
- if (!list_empty(&batch->msg_list)) {
- struct smbdirect_send_io *last;
-
- last = list_last_entry(&batch->msg_list,
- struct smbdirect_send_io,
- sibling_list);
- last->wr.next = &request->wr;
- }
- list_add_tail(&request->sibling_list, &batch->msg_list);
- batch->wr_cnt++;
- return 0;
- }
-
- request->wr.wr_cqe = &request->cqe;
- request->wr.send_flags = IB_SEND_SIGNALED;
- return smbd_ib_post_send(sc, &request->wr);
-}
-
-static void smbd_send_batch_init(struct smbdirect_send_batch *batch,
- bool need_invalidate_rkey,
- unsigned int remote_key)
-{
- INIT_LIST_HEAD(&batch->msg_list);
- batch->wr_cnt = 0;
- batch->need_invalidate_rkey = need_invalidate_rkey;
- batch->remote_key = remote_key;
- batch->credit = 0;
-}
-
-static int smbd_send_batch_flush(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *batch,
- bool is_last)
-{
- struct smbdirect_send_io *first, *last;
- int ret = 0;
-
- if (list_empty(&batch->msg_list))
- goto release_credit;
-
- first = list_first_entry(&batch->msg_list,
- struct smbdirect_send_io,
- sibling_list);
- last = list_last_entry(&batch->msg_list,
- struct smbdirect_send_io,
- sibling_list);
-
- if (batch->need_invalidate_rkey) {
- first->wr.opcode = IB_WR_SEND_WITH_INV;
- first->wr.ex.invalidate_rkey = batch->remote_key;
- batch->need_invalidate_rkey = false;
- batch->remote_key = 0;
- }
-
- last->wr.send_flags = IB_SEND_SIGNALED;
- last->wr.wr_cqe = &last->cqe;
-
- /*
- * Remove last from batch->msg_list
- * and splice the rest of batch->msg_list
- * to last->sibling_list.
- *
- * batch->msg_list is a valid empty list
- * at the end.
- */
- list_del_init(&last->sibling_list);
- list_splice_tail_init(&batch->msg_list, &last->sibling_list);
- batch->wr_cnt = 0;
-
- ret = smbd_ib_post_send(sc, &first->wr);
- if (ret) {
- struct smbdirect_send_io *sibling, *next;
-
- list_for_each_entry_safe(sibling, next, &last->sibling_list, sibling_list) {
- list_del_init(&sibling->sibling_list);
- smbd_free_send_io(sibling);
- }
- smbd_free_send_io(last);
- }
-
-release_credit:
- if (is_last && !ret && batch->credit) {
- atomic_add(batch->credit, &sc->send_io.bcredits.count);
- batch->credit = 0;
- wake_up(&sc->send_io.bcredits.wait_queue);
- }
-
- return ret;
-}
-
-static int wait_for_credits(struct smbdirect_socket *sc,
- wait_queue_head_t *waitq, atomic_t *total_credits,
- int needed)
-{
- int ret;
-
- do {
- if (atomic_sub_return(needed, total_credits) >= 0)
- return 0;
-
- atomic_add(needed, total_credits);
- ret = wait_event_interruptible(*waitq,
- atomic_read(total_credits) >= needed ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return -ENOTCONN;
- else if (ret < 0)
- return ret;
- } while (true);
-}
-
-static int wait_for_send_bcredit(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *batch)
-{
- int ret;
-
- if (batch->credit)
- return 0;
-
- ret = wait_for_credits(sc,
- &sc->send_io.bcredits.wait_queue,
- &sc->send_io.bcredits.count,
- 1);
- if (ret)
- return ret;
-
- batch->credit = 1;
- return 0;
-}
-
-static int wait_for_send_lcredit(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *batch)
-{
- if (batch && (atomic_read(&sc->send_io.lcredits.count) <= 1)) {
- int ret;
-
- ret = smbd_send_batch_flush(sc, batch, false);
- if (ret)
- return ret;
- }
-
- return wait_for_credits(sc,
- &sc->send_io.lcredits.wait_queue,
- &sc->send_io.lcredits.count,
- 1);
-}
-
-static int wait_for_send_credits(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *batch)
-{
- if (batch &&
- (batch->wr_cnt >= 16 || atomic_read(&sc->send_io.credits.count) <= 1)) {
- int ret;
-
- ret = smbd_send_batch_flush(sc, batch, false);
- if (ret)
- return ret;
- }
-
- return wait_for_credits(sc,
- &sc->send_io.credits.wait_queue,
- &sc->send_io.credits.count,
- 1);
-}
-
-static int smbd_post_send_iter(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *batch,
- struct iov_iter *iter,
- int *_remaining_data_length)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- int rc;
- int header_length;
- int data_length;
- struct smbdirect_send_io *request;
- struct smbdirect_data_transfer *packet;
- int new_credits = 0;
- struct smbdirect_send_batch _batch;
-
- if (!batch) {
- smbd_send_batch_init(&_batch, false, 0);
- batch = &_batch;
- }
-
- rc = wait_for_send_bcredit(sc, batch);
- if (rc) {
- log_outgoing(ERR, "disconnected not sending on wait_bcredit\n");
- rc = -EAGAIN;
- goto err_wait_bcredit;
- }
-
- rc = wait_for_send_lcredit(sc, batch);
- if (rc) {
- log_outgoing(ERR, "disconnected not sending on wait_lcredit\n");
- rc = -EAGAIN;
- goto err_wait_lcredit;
- }
-
- rc = wait_for_send_credits(sc, batch);
- if (rc) {
- log_outgoing(ERR, "disconnected not sending on wait_credit\n");
- rc = -EAGAIN;
- goto err_wait_credit;
- }
-
- new_credits = manage_credits_prior_sending(sc);
- if (new_credits == 0 &&
- atomic_read(&sc->send_io.credits.count) == 0 &&
- atomic_read(&sc->recv_io.credits.count) == 0) {
- queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
- rc = wait_event_interruptible(sc->send_io.credits.wait_queue,
- atomic_read(&sc->send_io.credits.count) >= 1 ||
- atomic_read(&sc->recv_io.credits.available) >= 1 ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- rc = -ENOTCONN;
- if (rc < 0) {
- log_outgoing(ERR, "disconnected not sending on last credit\n");
- rc = -EAGAIN;
- goto err_wait_credit;
- }
-
- new_credits = manage_credits_prior_sending(sc);
- }
-
- request = smbd_alloc_send_io(sc);
- if (IS_ERR(request)) {
- rc = PTR_ERR(request);
- goto err_alloc;
- }
-
- memset(request->sge, 0, sizeof(request->sge));
-
- /* Map the packet to DMA */
- header_length = sizeof(struct smbdirect_data_transfer);
- /* If this is a packet without payload, don't send padding */
- if (!iter)
- header_length = offsetof(struct smbdirect_data_transfer, padding);
-
- packet = smbdirect_send_io_payload(request);
- request->sge[0].addr = ib_dma_map_single(sc->ib.dev,
- (void *)packet,
- header_length,
- DMA_TO_DEVICE);
- if (ib_dma_mapping_error(sc->ib.dev, request->sge[0].addr)) {
- rc = -EIO;
- goto err_dma;
- }
-
- request->sge[0].length = header_length;
- request->sge[0].lkey = sc->ib.pd->local_dma_lkey;
- request->num_sge = 1;
-
- /* Fill in the data payload to find out how much data we can add */
- if (iter) {
- struct smb_extract_to_rdma extract = {
- .nr_sge = request->num_sge,
- .max_sge = SMBDIRECT_SEND_IO_MAX_SGE,
- .sge = request->sge,
- .device = sc->ib.dev,
- .local_dma_lkey = sc->ib.pd->local_dma_lkey,
- .direction = DMA_TO_DEVICE,
- };
- size_t payload_len = umin(*_remaining_data_length,
- sp->max_send_size - sizeof(*packet));
-
- rc = smb_extract_iter_to_rdma(iter, payload_len,
- &extract);
- if (rc < 0)
- goto err_dma;
- data_length = rc;
- request->num_sge = extract.nr_sge;
- *_remaining_data_length -= data_length;
- } else {
- data_length = 0;
- }
-
- /* Fill in the packet header */
- packet->credits_requested = cpu_to_le16(sp->send_credit_target);
- packet->credits_granted = cpu_to_le16(new_credits);
-
- packet->flags = 0;
- if (manage_keep_alive_before_sending(sc))
- packet->flags |= cpu_to_le16(SMBDIRECT_FLAG_RESPONSE_REQUESTED);
-
- packet->reserved = 0;
- if (!data_length)
- packet->data_offset = 0;
- else
- packet->data_offset = cpu_to_le32(24);
- packet->data_length = cpu_to_le32(data_length);
- packet->remaining_data_length = cpu_to_le32(*_remaining_data_length);
- packet->padding = 0;
-
- log_outgoing(INFO, "credits_requested=%d credits_granted=%d data_offset=%d data_length=%d remaining_data_length=%d\n",
- le16_to_cpu(packet->credits_requested),
- le16_to_cpu(packet->credits_granted),
- le32_to_cpu(packet->data_offset),
- le32_to_cpu(packet->data_length),
- le32_to_cpu(packet->remaining_data_length));
-
- rc = smbd_post_send(sc, batch, request);
- if (!rc) {
- if (batch != &_batch)
- return 0;
-
- rc = smbd_send_batch_flush(sc, batch, true);
- if (!rc)
- return 0;
- }
-
-err_dma:
- smbd_free_send_io(request);
-
-err_alloc:
- atomic_inc(&sc->send_io.credits.count);
- wake_up(&sc->send_io.credits.wait_queue);
-
-err_wait_credit:
- atomic_inc(&sc->send_io.lcredits.count);
- wake_up(&sc->send_io.lcredits.wait_queue);
-
-err_wait_lcredit:
- atomic_add(batch->credit, &sc->send_io.bcredits.count);
- batch->credit = 0;
- wake_up(&sc->send_io.bcredits.wait_queue);
-
-err_wait_bcredit:
- return rc;
-}
-
-/*
- * Send an empty message
- * Empty message is used to extend credits to peer to for keep live
- * while there is no upper layer payload to send at the time
- */
-static int smbd_post_send_empty(struct smbdirect_socket *sc)
-{
- int remaining_data_length = 0;
-
- sc->statistics.send_empty++;
- return smbd_post_send_iter(sc, NULL, NULL, &remaining_data_length);
-}
-
static int smbd_post_send_full_iter(struct smbdirect_socket *sc,
struct smbdirect_send_batch *batch,
struct iov_iter *iter,
- int *_remaining_data_length)
+ u32 remaining_data_length)
{
- int rc = 0;
+ int bytes = 0;
/*
- * smbd_post_send_iter() respects the
+ * smbdirect_connection_send_single_iter() respects the
* negotiated max_send_size, so we need to
* loop until the full iter is posted
*/
while (iov_iter_count(iter) > 0) {
- rc = smbd_post_send_iter(sc, batch, iter, _remaining_data_length);
- if (rc < 0)
- break;
- }
-
- return rc;
-}
-
-/*
- * Post a receive request to the transport
- * The remote peer can only send data when a receive request is posted
- * The interaction is controlled by send/receive credit system
- */
-static int smbd_post_recv(
- struct smbdirect_socket *sc, struct smbdirect_recv_io *response)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct ib_recv_wr recv_wr;
- int rc = -EIO;
-
- response->sge.addr = ib_dma_map_single(
- sc->ib.dev, response->packet,
- sp->max_recv_size, DMA_FROM_DEVICE);
- if (ib_dma_mapping_error(sc->ib.dev, response->sge.addr))
- return rc;
-
- response->sge.length = sp->max_recv_size;
- response->sge.lkey = sc->ib.pd->local_dma_lkey;
-
- response->cqe.done = recv_done;
-
- recv_wr.wr_cqe = &response->cqe;
- recv_wr.next = NULL;
- recv_wr.sg_list = &response->sge;
- recv_wr.num_sge = 1;
-
- rc = ib_post_recv(sc->ib.qp, &recv_wr, NULL);
- if (rc) {
- ib_dma_unmap_single(sc->ib.dev, response->sge.addr,
- response->sge.length, DMA_FROM_DEVICE);
- response->sge.length = 0;
- smbd_disconnect_rdma_connection(sc);
- log_rdma_recv(ERR, "ib_post_recv failed rc=%d\n", rc);
- }
-
- return rc;
-}
-
-/* Perform SMBD negotiate according to [MS-SMBD] 3.1.5.2 */
-static int smbd_negotiate(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- int rc;
- struct smbdirect_recv_io *response = get_receive_buffer(sc);
-
- WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_NEGOTIATE_NEEDED);
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_RUNNING;
-
- sc->recv_io.expected = SMBDIRECT_EXPECT_NEGOTIATE_REP;
- rc = smbd_post_recv(sc, response);
- log_rdma_event(INFO, "smbd_post_recv rc=%d iov.addr=0x%llx iov.length=%u iov.lkey=0x%x\n",
- rc, response->sge.addr,
- response->sge.length, response->sge.lkey);
- if (rc) {
- put_receive_buffer(sc, response);
- return rc;
- }
-
- rc = smbd_post_send_negotiate_req(sc);
- if (rc)
- return rc;
-
- rc = wait_event_interruptible_timeout(
- sc->status_wait,
- sc->status != SMBDIRECT_SOCKET_NEGOTIATE_RUNNING,
- msecs_to_jiffies(sp->negotiate_timeout_msec));
- log_rdma_event(INFO, "wait_event_interruptible_timeout rc=%d\n", rc);
-
- if (sc->status == SMBDIRECT_SOCKET_CONNECTED)
- return 0;
-
- if (rc == 0)
- rc = -ETIMEDOUT;
- else if (rc == -ERESTARTSYS)
- rc = -EINTR;
- else
- rc = -ENOTCONN;
-
- return rc;
-}
-
-/*
- * Implement Connection.FragmentReassemblyBuffer defined in [MS-SMBD] 3.1.1.1
- * This is a queue for reassembling upper layer payload and present to upper
- * layer. All the inncoming payload go to the reassembly queue, regardless of
- * if reassembly is required. The uuper layer code reads from the queue for all
- * incoming payloads.
- * Put a received packet to the reassembly queue
- * response: the packet received
- * data_length: the size of payload in this packet
- */
-static void enqueue_reassembly(
- struct smbdirect_socket *sc,
- struct smbdirect_recv_io *response,
- int data_length)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
- list_add_tail(&response->list, &sc->recv_io.reassembly.list);
- sc->recv_io.reassembly.queue_length++;
- /*
- * Make sure reassembly_data_length is updated after list and
- * reassembly_queue_length are updated. On the dequeue side
- * reassembly_data_length is checked without a lock to determine
- * if reassembly_queue_length and list is up to date
- */
- virt_wmb();
- sc->recv_io.reassembly.data_length += data_length;
- spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
- sc->statistics.enqueue_reassembly_queue++;
-}
-
-/*
- * Get the first entry at the front of reassembly queue
- * Caller is responsible for locking
- * return value: the first entry if any, NULL if queue is empty
- */
-static struct smbdirect_recv_io *_get_first_reassembly(struct smbdirect_socket *sc)
-{
- struct smbdirect_recv_io *ret = NULL;
-
- if (!list_empty(&sc->recv_io.reassembly.list)) {
- ret = list_first_entry(
- &sc->recv_io.reassembly.list,
- struct smbdirect_recv_io, list);
- }
- return ret;
-}
-
-/*
- * Get a receive buffer
- * For each remote send, we need to post a receive. The receive buffers are
- * pre-allocated in advance.
- * return value: the receive buffer, NULL if none is available
- */
-static struct smbdirect_recv_io *get_receive_buffer(struct smbdirect_socket *sc)
-{
- struct smbdirect_recv_io *ret = NULL;
- unsigned long flags;
-
- spin_lock_irqsave(&sc->recv_io.free.lock, flags);
- if (!list_empty(&sc->recv_io.free.list)) {
- ret = list_first_entry(
- &sc->recv_io.free.list,
- struct smbdirect_recv_io, list);
- list_del(&ret->list);
- sc->statistics.get_receive_buffer++;
- }
- spin_unlock_irqrestore(&sc->recv_io.free.lock, flags);
-
- return ret;
-}
-
-/*
- * Return a receive buffer
- * Upon returning of a receive buffer, we can post new receive and extend
- * more receive credits to remote peer. This is done immediately after a
- * receive buffer is returned.
- */
-static void put_receive_buffer(
- struct smbdirect_socket *sc, struct smbdirect_recv_io *response)
-{
- unsigned long flags;
-
- if (likely(response->sge.length != 0)) {
- ib_dma_unmap_single(sc->ib.dev,
- response->sge.addr,
- response->sge.length,
- DMA_FROM_DEVICE);
- response->sge.length = 0;
- }
-
- spin_lock_irqsave(&sc->recv_io.free.lock, flags);
- list_add_tail(&response->list, &sc->recv_io.free.list);
- sc->statistics.put_receive_buffer++;
- spin_unlock_irqrestore(&sc->recv_io.free.lock, flags);
-
- queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
-}
-
-/* Preallocate all receive buffer on transport establishment */
-static int allocate_receive_buffers(struct smbdirect_socket *sc, int num_buf)
-{
- struct smbdirect_recv_io *response;
- int i;
-
- for (i = 0; i < num_buf; i++) {
- response = mempool_alloc(sc->recv_io.mem.pool, GFP_KERNEL);
- if (!response)
- goto allocate_failed;
-
- response->socket = sc;
- response->sge.length = 0;
- list_add_tail(&response->list, &sc->recv_io.free.list);
- }
-
- return 0;
-
-allocate_failed:
- while (!list_empty(&sc->recv_io.free.list)) {
- response = list_first_entry(
- &sc->recv_io.free.list,
- struct smbdirect_recv_io, list);
- list_del(&response->list);
-
- mempool_free(response, sc->recv_io.mem.pool);
- }
- return -ENOMEM;
-}
-
-static void destroy_receive_buffers(struct smbdirect_socket *sc)
-{
- struct smbdirect_recv_io *response;
-
- while ((response = get_receive_buffer(sc)))
- mempool_free(response, sc->recv_io.mem.pool);
-}
-
-static void send_immediate_empty_message(struct work_struct *work)
-{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, idle.immediate_work);
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return;
-
- log_keep_alive(INFO, "send an empty message\n");
- smbd_post_send_empty(sc);
-}
+ int rc;
-/* Implement idle connection timer [MS-SMBD] 3.1.6.2 */
-static void idle_connection_timer(struct work_struct *work)
-{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, idle.timer_work.work);
- struct smbdirect_socket_parameters *sp = &sc->parameters;
-
- if (sc->idle.keepalive != SMBDIRECT_KEEPALIVE_NONE) {
- log_keep_alive(ERR,
- "error status sc->idle.keepalive=%d\n",
- sc->idle.keepalive);
- smbd_disconnect_rdma_connection(sc);
- return;
+ rc = smbdirect_connection_send_single_iter(sc,
+ batch,
+ iter,
+ 0, /* flags */
+ remaining_data_length);
+ if (rc < 0)
+ return rc;
+ remaining_data_length -= rc;
+ bytes += rc;
}
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return;
-
- /*
- * Now use the keepalive timeout (instead of keepalive interval)
- * in order to wait for a response
- */
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_PENDING;
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->keepalive_timeout_msec));
- log_keep_alive(INFO, "schedule send of empty idle message\n");
- queue_work(sc->workqueue, &sc->idle.immediate_work);
+ return bytes;
}
/*
@@ -1884,88 +202,14 @@ static void idle_connection_timer(struct work_struct *work)
void smbd_destroy(struct TCP_Server_Info *server)
{
struct smbd_connection *info = server->smbd_conn;
- struct smbdirect_socket *sc;
- struct smbdirect_recv_io *response;
- unsigned long flags;
if (!info) {
log_rdma_event(INFO, "rdma session already destroyed\n");
return;
}
- sc = &info->socket;
-
- log_rdma_event(INFO, "cancelling and disable disconnect_work\n");
- disable_work_sync(&sc->disconnect_work);
-
- log_rdma_event(INFO, "destroying rdma session\n");
- if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING)
- smbd_disconnect_rdma_work(&sc->disconnect_work);
- if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
- log_rdma_event(INFO, "wait for transport being disconnected\n");
- wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
- log_rdma_event(INFO, "waited for transport being disconnected\n");
- }
-
- /*
- * Wake up all waiters in all wait queues
- * in order to notice the broken connection.
- *
- * Most likely this was already called via
- * smbd_disconnect_rdma_work(), but call it again...
- */
- smbd_disconnect_wake_up_all(sc);
- log_rdma_event(INFO, "cancelling recv_io.posted.refill_work\n");
- disable_work_sync(&sc->recv_io.posted.refill_work);
+ smbdirect_socket_release(info->socket);
- log_rdma_event(INFO, "destroying qp\n");
- ib_drain_qp(sc->ib.qp);
- rdma_destroy_qp(sc->rdma.cm_id);
- sc->ib.qp = NULL;
-
- log_rdma_event(INFO, "cancelling idle timer\n");
- disable_delayed_work_sync(&sc->idle.timer_work);
- log_rdma_event(INFO, "cancelling send immediate work\n");
- disable_work_sync(&sc->idle.immediate_work);
-
- /* It's not possible for upper layer to get to reassembly */
- log_rdma_event(INFO, "drain the reassembly queue\n");
- do {
- spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
- response = _get_first_reassembly(sc);
- if (response) {
- list_del(&response->list);
- spin_unlock_irqrestore(
- &sc->recv_io.reassembly.lock, flags);
- put_receive_buffer(sc, response);
- } else
- spin_unlock_irqrestore(
- &sc->recv_io.reassembly.lock, flags);
- } while (response);
- sc->recv_io.reassembly.data_length = 0;
-
- log_rdma_event(INFO, "free receive buffers\n");
- destroy_receive_buffers(sc);
-
- log_rdma_event(INFO, "freeing mr list\n");
- destroy_mr_list(sc);
-
- ib_free_cq(sc->ib.send_cq);
- ib_free_cq(sc->ib.recv_cq);
- ib_dealloc_pd(sc->ib.pd);
- rdma_destroy_id(sc->rdma.cm_id);
-
- /* free mempools */
- mempool_destroy(sc->send_io.mem.pool);
- kmem_cache_destroy(sc->send_io.mem.cache);
-
- mempool_destroy(sc->recv_io.mem.pool);
- kmem_cache_destroy(sc->recv_io.mem.cache);
-
- sc->status = SMBDIRECT_SOCKET_DESTROYED;
-
- destroy_workqueue(sc->workqueue);
- log_rdma_event(INFO, "rdma session destroyed\n");
kfree(info);
server->smbd_conn = NULL;
}
@@ -1987,10 +231,8 @@ int smbd_reconnect(struct TCP_Server_Info *server)
* This is possible if transport is disconnected and we haven't received
* notification from RDMA, but upper layer has detected timeout
*/
- if (server->smbd_conn->socket.status == SMBDIRECT_SOCKET_CONNECTED) {
- log_rdma_event(INFO, "disconnecting transport\n");
- smbd_destroy(server);
- }
+ log_rdma_event(INFO, "disconnecting transport\n");
+ smbd_destroy(server);
create_conn:
log_rdma_event(INFO, "creating rdma session\n");
@@ -2006,112 +248,43 @@ create_conn:
return -ENOENT;
}
-static void destroy_caches(struct smbdirect_socket *sc)
-{
- destroy_receive_buffers(sc);
- mempool_destroy(sc->recv_io.mem.pool);
- kmem_cache_destroy(sc->recv_io.mem.cache);
- mempool_destroy(sc->send_io.mem.pool);
- kmem_cache_destroy(sc->send_io.mem.cache);
-}
-
-#define MAX_NAME_LEN 80
-static int allocate_caches(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- char name[MAX_NAME_LEN];
- int rc;
-
- if (WARN_ON_ONCE(sp->max_recv_size < sizeof(struct smbdirect_data_transfer)))
- return -ENOMEM;
-
- scnprintf(name, MAX_NAME_LEN, "smbdirect_send_io_%p", sc);
- sc->send_io.mem.cache =
- kmem_cache_create(
- name,
- sizeof(struct smbdirect_send_io) +
- sizeof(struct smbdirect_data_transfer),
- 0, SLAB_HWCACHE_ALIGN, NULL);
- if (!sc->send_io.mem.cache)
- return -ENOMEM;
-
- sc->send_io.mem.pool =
- mempool_create(sp->send_credit_target, mempool_alloc_slab,
- mempool_free_slab, sc->send_io.mem.cache);
- if (!sc->send_io.mem.pool)
- goto out1;
-
- scnprintf(name, MAX_NAME_LEN, "smbdirect_recv_io_%p", sc);
-
- struct kmem_cache_args response_args = {
- .align = __alignof__(struct smbdirect_recv_io),
- .useroffset = (offsetof(struct smbdirect_recv_io, packet) +
- sizeof(struct smbdirect_data_transfer)),
- .usersize = sp->max_recv_size - sizeof(struct smbdirect_data_transfer),
- };
- sc->recv_io.mem.cache =
- kmem_cache_create(name,
- sizeof(struct smbdirect_recv_io) + sp->max_recv_size,
- &response_args, SLAB_HWCACHE_ALIGN);
- if (!sc->recv_io.mem.cache)
- goto out2;
-
- sc->recv_io.mem.pool =
- mempool_create(sp->recv_credit_max, mempool_alloc_slab,
- mempool_free_slab, sc->recv_io.mem.cache);
- if (!sc->recv_io.mem.pool)
- goto out3;
-
- rc = allocate_receive_buffers(sc, sp->recv_credit_max);
- if (rc) {
- log_rdma_event(ERR, "failed to allocate receive buffers\n");
- goto out4;
- }
-
- return 0;
-
-out4:
- mempool_destroy(sc->recv_io.mem.pool);
-out3:
- kmem_cache_destroy(sc->recv_io.mem.cache);
-out2:
- mempool_destroy(sc->send_io.mem.pool);
-out1:
- kmem_cache_destroy(sc->send_io.mem.cache);
- return -ENOMEM;
-}
-
/* Create a SMBD connection, called by upper layer */
static struct smbd_connection *_smbd_get_connection(
struct TCP_Server_Info *server, struct sockaddr *dstaddr, int port)
{
- int rc;
+ struct net *net = cifs_net_ns(server);
struct smbd_connection *info;
struct smbdirect_socket *sc;
+ struct smbdirect_socket_parameters init_params = {};
struct smbdirect_socket_parameters *sp;
- struct rdma_conn_param conn_param;
- struct ib_qp_cap qp_cap;
- struct ib_qp_init_attr qp_attr;
- struct sockaddr_in *addr_in = (struct sockaddr_in *) dstaddr;
- struct ib_port_immutable port_immutable;
- __be32 ird_ord_hdr[2];
- char wq_name[80];
- struct workqueue_struct *workqueue;
-
- info = kzalloc_obj(struct smbd_connection);
- if (!info)
- return NULL;
- sc = &info->socket;
- scnprintf(wq_name, ARRAY_SIZE(wq_name), "smbd_%p", sc);
- workqueue = create_workqueue(wq_name);
- if (!workqueue)
- goto create_wq_failed;
- smbdirect_socket_init(sc);
- sc->workqueue = workqueue;
- sp = &sc->parameters;
+ __be16 *sport;
+ u64 port_flags = 0;
+ int ret;
- INIT_WORK(&sc->disconnect_work, smbd_disconnect_rdma_work);
+ switch (port) {
+ case SMBD_PORT:
+ /*
+ * only allow iWarp devices
+ * for port 5445.
+ */
+ port_flags |= SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW;
+ break;
+ case SMB_PORT:
+ /*
+ * only allow InfiniBand, RoCEv1 or RoCEv2
+ * devices for port 445.
+ *
+ * (Basically don't allow iWarp devices)
+ */
+ port_flags |= SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB;
+ break;
+ }
+ /*
+ * Create the initial parameters
+ */
+ sp = &init_params;
+ sp->flags = port_flags;
sp->resolve_addr_timeout_msec = RDMA_RESOLVE_TIMEOUT;
sp->resolve_route_timeout_msec = RDMA_RESOLVE_TIMEOUT;
sp->rdma_connect_timeout_msec = RDMA_RESOLVE_TIMEOUT;
@@ -2127,213 +300,55 @@ static struct smbd_connection *_smbd_get_connection(
sp->keepalive_interval_msec = smbd_keep_alive_interval * 1000;
sp->keepalive_timeout_msec = KEEPALIVE_RECV_TIMEOUT * 1000;
- rc = smbd_ia_open(sc, dstaddr, port);
- if (rc) {
- log_rdma_event(INFO, "smbd_ia_open rc=%d\n", rc);
- goto create_id_failed;
- }
-
- if (sp->send_credit_target > sc->ib.dev->attrs.max_cqe ||
- sp->send_credit_target > sc->ib.dev->attrs.max_qp_wr) {
- log_rdma_event(ERR, "consider lowering send_credit_target = %d. Possible CQE overrun, device reporting max_cqe %d max_qp_wr %d\n",
- sp->send_credit_target,
- sc->ib.dev->attrs.max_cqe,
- sc->ib.dev->attrs.max_qp_wr);
- goto config_failed;
- }
-
- if (sp->recv_credit_max > sc->ib.dev->attrs.max_cqe ||
- sp->recv_credit_max > sc->ib.dev->attrs.max_qp_wr) {
- log_rdma_event(ERR, "consider lowering receive_credit_max = %d. Possible CQE overrun, device reporting max_cqe %d max_qp_wr %d\n",
- sp->recv_credit_max,
- sc->ib.dev->attrs.max_cqe,
- sc->ib.dev->attrs.max_qp_wr);
- goto config_failed;
- }
-
- if (sc->ib.dev->attrs.max_send_sge < SMBDIRECT_SEND_IO_MAX_SGE ||
- sc->ib.dev->attrs.max_recv_sge < SMBDIRECT_RECV_IO_MAX_SGE) {
- log_rdma_event(ERR,
- "device %.*s max_send_sge/max_recv_sge = %d/%d too small\n",
- IB_DEVICE_NAME_MAX,
- sc->ib.dev->name,
- sc->ib.dev->attrs.max_send_sge,
- sc->ib.dev->attrs.max_recv_sge);
- goto config_failed;
- }
-
- sp->responder_resources =
- min_t(u8, sp->responder_resources,
- sc->ib.dev->attrs.max_qp_rd_atom);
- log_rdma_mr(INFO, "responder_resources=%d\n",
- sp->responder_resources);
-
- /*
- * We use allocate sp->responder_resources * 2 MRs
- * and each MR needs WRs for REG and INV, so
- * we use '* 4'.
- *
- * +1 for ib_drain_qp()
- */
- memset(&qp_cap, 0, sizeof(qp_cap));
- qp_cap.max_send_wr = sp->send_credit_target + sp->responder_resources * 4 + 1;
- qp_cap.max_recv_wr = sp->recv_credit_max + 1;
- qp_cap.max_send_sge = SMBDIRECT_SEND_IO_MAX_SGE;
- qp_cap.max_recv_sge = SMBDIRECT_RECV_IO_MAX_SGE;
-
- sc->ib.pd = ib_alloc_pd(sc->ib.dev, 0);
- if (IS_ERR(sc->ib.pd)) {
- rc = PTR_ERR(sc->ib.pd);
- sc->ib.pd = NULL;
- log_rdma_event(ERR, "ib_alloc_pd() returned %d\n", rc);
- goto alloc_pd_failed;
- }
-
- sc->ib.send_cq =
- ib_alloc_cq_any(sc->ib.dev, sc,
- qp_cap.max_send_wr, IB_POLL_SOFTIRQ);
- if (IS_ERR(sc->ib.send_cq)) {
- sc->ib.send_cq = NULL;
- goto alloc_cq_failed;
- }
-
- sc->ib.recv_cq =
- ib_alloc_cq_any(sc->ib.dev, sc,
- qp_cap.max_recv_wr, IB_POLL_SOFTIRQ);
- if (IS_ERR(sc->ib.recv_cq)) {
- sc->ib.recv_cq = NULL;
- goto alloc_cq_failed;
- }
-
- memset(&qp_attr, 0, sizeof(qp_attr));
- qp_attr.event_handler = smbd_qp_async_error_upcall;
- qp_attr.qp_context = sc;
- qp_attr.cap = qp_cap;
- qp_attr.sq_sig_type = IB_SIGNAL_REQ_WR;
- qp_attr.qp_type = IB_QPT_RC;
- qp_attr.send_cq = sc->ib.send_cq;
- qp_attr.recv_cq = sc->ib.recv_cq;
- qp_attr.port_num = ~0;
-
- rc = rdma_create_qp(sc->rdma.cm_id, sc->ib.pd, &qp_attr);
- if (rc) {
- log_rdma_event(ERR, "rdma_create_qp failed %i\n", rc);
- goto create_qp_failed;
- }
- sc->ib.qp = sc->rdma.cm_id->qp;
-
- memset(&conn_param, 0, sizeof(conn_param));
- conn_param.initiator_depth = sp->initiator_depth;
- conn_param.responder_resources = sp->responder_resources;
-
- /* Need to send IRD/ORD in private data for iWARP */
- sc->ib.dev->ops.get_port_immutable(
- sc->ib.dev, sc->rdma.cm_id->port_num, &port_immutable);
- if (port_immutable.core_cap_flags & RDMA_CORE_PORT_IWARP) {
- ird_ord_hdr[0] = cpu_to_be32(conn_param.responder_resources);
- ird_ord_hdr[1] = cpu_to_be32(conn_param.initiator_depth);
- conn_param.private_data = ird_ord_hdr;
- conn_param.private_data_len = sizeof(ird_ord_hdr);
- } else {
- conn_param.private_data = NULL;
- conn_param.private_data_len = 0;
- }
-
- conn_param.retry_count = SMBD_CM_RETRY;
- conn_param.rnr_retry_count = SMBD_CM_RNR_RETRY;
- conn_param.flow_control = 0;
-
- log_rdma_event(INFO, "connecting to IP %pI4 port %d\n",
- &addr_in->sin_addr, port);
-
- WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED);
- sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING;
- rc = rdma_connect(sc->rdma.cm_id, &conn_param);
- if (rc) {
- log_rdma_event(ERR, "rdma_connect() failed with %i\n", rc);
- goto rdma_connect_failed;
- }
-
- wait_event_interruptible_timeout(
- sc->status_wait,
- sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING,
- msecs_to_jiffies(sp->rdma_connect_timeout_msec));
+ info = kzalloc_obj(*info);
+ if (!info)
+ return NULL;
+ ret = smbdirect_socket_create_kern(net, &sc);
+ if (ret)
+ goto socket_init_failed;
+ smbdirect_socket_set_logging(sc, NULL, smbd_logging_needed, smbd_logging_vaprintf);
+ ret = smbdirect_socket_set_initial_parameters(sc, sp);
+ if (ret)
+ goto set_params_failed;
+ ret = smbdirect_socket_set_kernel_settings(sc, IB_POLL_SOFTIRQ, GFP_KERNEL);
+ if (ret)
+ goto set_settings_failed;
- if (sc->status != SMBDIRECT_SOCKET_NEGOTIATE_NEEDED) {
- log_rdma_event(ERR, "rdma_connect failed port=%d\n", port);
- goto rdma_connect_failed;
- }
+ if (dstaddr->sa_family == AF_INET6)
+ sport = &((struct sockaddr_in6 *)dstaddr)->sin6_port;
+ else
+ sport = &((struct sockaddr_in *)dstaddr)->sin_port;
- log_rdma_event(INFO, "rdma_connect connected\n");
+ *sport = htons(port);
- rc = allocate_caches(sc);
- if (rc) {
- log_rdma_event(ERR, "cache allocation failed\n");
- goto allocate_cache_failed;
+ ret = smbdirect_connect_sync(sc, dstaddr);
+ if (ret) {
+ log_rdma_event(ERR, "connect to %pISpsfc failed: %1pe\n",
+ dstaddr, ERR_PTR(ret));
+ goto connect_failed;
}
- INIT_WORK(&sc->idle.immediate_work, send_immediate_empty_message);
- INIT_DELAYED_WORK(&sc->idle.timer_work, idle_connection_timer);
- /*
- * start with the negotiate timeout and SMBDIRECT_KEEPALIVE_PENDING
- * so that the timer will cause a disconnect.
- */
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_PENDING;
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->negotiate_timeout_msec));
+ info->socket = sc;
+ return info;
- INIT_WORK(&sc->recv_io.posted.refill_work, smbd_post_send_credits);
+connect_failed:
+set_settings_failed:
+set_params_failed:
+ smbdirect_socket_release(sc);
+socket_init_failed:
+ kfree(info);
+ return NULL;
+}
- rc = smbd_negotiate(sc);
- if (rc) {
- log_rdma_event(ERR, "smbd_negotiate rc=%d\n", rc);
- goto negotiation_failed;
- }
+const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn)
+{
+ if (unlikely(!conn->socket)) {
+ static const struct smbdirect_socket_parameters zero_params;
- rc = allocate_mr_list(sc);
- if (rc) {
- log_rdma_mr(ERR, "memory registration allocation failed\n");
- goto allocate_mr_failed;
+ return &zero_params;
}
- return info;
-
-allocate_mr_failed:
- /* At this point, need to a full transport shutdown */
- server->smbd_conn = info;
- smbd_destroy(server);
- return NULL;
-
-negotiation_failed:
- disable_delayed_work_sync(&sc->idle.timer_work);
- destroy_caches(sc);
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED;
- rdma_disconnect(sc->rdma.cm_id);
- wait_event(sc->status_wait,
- sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
-
-allocate_cache_failed:
-rdma_connect_failed:
- rdma_destroy_qp(sc->rdma.cm_id);
-
-create_qp_failed:
-alloc_cq_failed:
- if (sc->ib.send_cq)
- ib_free_cq(sc->ib.send_cq);
- if (sc->ib.recv_cq)
- ib_free_cq(sc->ib.recv_cq);
-
- ib_dealloc_pd(sc->ib.pd);
-
-alloc_pd_failed:
-config_failed:
- rdma_destroy_id(sc->rdma.cm_id);
-
-create_id_failed:
- destroy_workqueue(sc->workqueue);
-create_wq_failed:
- kfree(info);
- return NULL;
+ return smbdirect_socket_get_current_parameters(conn->socket);
}
struct smbd_connection *smbd_get_connection(
@@ -2354,7 +369,7 @@ try_again:
if (!ret)
return NULL;
- sp = &ret->socket.parameters;
+ sp = smbd_get_parameters(ret);
server->rdma_readwrite_threshold =
rdma_readwrite_threshold > sp->max_fragmented_send_size ?
@@ -2380,138 +395,12 @@ try_again:
*/
int smbd_recv(struct smbd_connection *info, struct msghdr *msg)
{
- struct smbdirect_socket *sc = &info->socket;
- struct smbdirect_recv_io *response;
- struct smbdirect_data_transfer *data_transfer;
- size_t size = iov_iter_count(&msg->msg_iter);
- int to_copy, to_read, data_read, offset;
- u32 data_length, remaining_data_length, data_offset;
- int rc;
-
- if (WARN_ON_ONCE(iov_iter_rw(&msg->msg_iter) == WRITE))
- return -EINVAL; /* It's a bug in upper layer to get there */
-
-again:
- /*
- * No need to hold the reassembly queue lock all the time as we are
- * the only one reading from the front of the queue. The transport
- * may add more entries to the back of the queue at the same time
- */
- log_read(INFO, "size=%zd sc->recv_io.reassembly.data_length=%d\n", size,
- sc->recv_io.reassembly.data_length);
- if (sc->recv_io.reassembly.data_length >= size) {
- int queue_length;
- int queue_removed = 0;
- unsigned long flags;
+ struct smbdirect_socket *sc = info->socket;
- /*
- * Need to make sure reassembly_data_length is read before
- * reading reassembly_queue_length and calling
- * _get_first_reassembly. This call is lock free
- * as we never read at the end of the queue which are being
- * updated in SOFTIRQ as more data is received
- */
- virt_rmb();
- queue_length = sc->recv_io.reassembly.queue_length;
- data_read = 0;
- to_read = size;
- offset = sc->recv_io.reassembly.first_entry_offset;
- while (data_read < size) {
- response = _get_first_reassembly(sc);
- data_transfer = smbdirect_recv_io_payload(response);
- data_length = le32_to_cpu(data_transfer->data_length);
- remaining_data_length =
- le32_to_cpu(
- data_transfer->remaining_data_length);
- data_offset = le32_to_cpu(data_transfer->data_offset);
-
- /*
- * The upper layer expects RFC1002 length at the
- * beginning of the payload. Return it to indicate
- * the total length of the packet. This minimize the
- * change to upper layer packet processing logic. This
- * will be eventually remove when an intermediate
- * transport layer is added
- */
- if (response->first_segment && size == 4) {
- unsigned int rfc1002_len =
- data_length + remaining_data_length;
- __be32 rfc1002_hdr = cpu_to_be32(rfc1002_len);
- if (copy_to_iter(&rfc1002_hdr, sizeof(rfc1002_hdr),
- &msg->msg_iter) != sizeof(rfc1002_hdr))
- return -EFAULT;
- data_read = 4;
- response->first_segment = false;
- log_read(INFO, "returning rfc1002 length %d\n",
- rfc1002_len);
- goto read_rfc1002_done;
- }
+ if (!smbdirect_connection_is_connected(sc))
+ return -ENOTCONN;
- to_copy = min_t(int, data_length - offset, to_read);
- if (copy_to_iter((char *)data_transfer + data_offset + offset,
- to_copy, &msg->msg_iter) != to_copy)
- return -EFAULT;
-
- /* move on to the next buffer? */
- if (to_copy == data_length - offset) {
- queue_length--;
- /*
- * No need to lock if we are not at the
- * end of the queue
- */
- if (queue_length)
- list_del(&response->list);
- else {
- spin_lock_irqsave(
- &sc->recv_io.reassembly.lock, flags);
- list_del(&response->list);
- spin_unlock_irqrestore(
- &sc->recv_io.reassembly.lock, flags);
- }
- queue_removed++;
- sc->statistics.dequeue_reassembly_queue++;
- put_receive_buffer(sc, response);
- offset = 0;
- log_read(INFO, "put_receive_buffer offset=0\n");
- } else
- offset += to_copy;
-
- to_read -= to_copy;
- data_read += to_copy;
-
- log_read(INFO, "_get_first_reassembly memcpy %d bytes data_transfer_length-offset=%d after that to_read=%d data_read=%d offset=%d\n",
- to_copy, data_length - offset,
- to_read, data_read, offset);
- }
-
- spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
- sc->recv_io.reassembly.data_length -= data_read;
- sc->recv_io.reassembly.queue_length -= queue_removed;
- spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
-
- sc->recv_io.reassembly.first_entry_offset = offset;
- log_read(INFO, "returning to thread data_read=%d reassembly_data_length=%d first_entry_offset=%d\n",
- data_read, sc->recv_io.reassembly.data_length,
- sc->recv_io.reassembly.first_entry_offset);
-read_rfc1002_done:
- return data_read;
- }
-
- log_read(INFO, "wait_event on more data\n");
- rc = wait_event_interruptible(
- sc->recv_io.reassembly.wait_queue,
- sc->recv_io.reassembly.data_length >= size ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
- /* Don't return any data if interrupted */
- if (rc)
- return rc;
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
- log_read(ERR, "disconnected\n");
- return -ECONNABORTED;
- }
-
- goto again;
+ return smbdirect_connection_recvmsg(sc, msg, 0);
}
/*
@@ -2524,16 +413,17 @@ int smbd_send(struct TCP_Server_Info *server,
int num_rqst, struct smb_rqst *rqst_array)
{
struct smbd_connection *info = server->smbd_conn;
- struct smbdirect_socket *sc = &info->socket;
- struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_socket *sc = info->socket;
+ const struct smbdirect_socket_parameters *sp = smbd_get_parameters(info);
struct smb_rqst *rqst;
struct iov_iter iter;
- struct smbdirect_send_batch batch;
+ struct smbdirect_send_batch_storage bstorage;
+ struct smbdirect_send_batch *batch;
unsigned int remaining_data_length, klen;
int rc, i, rqst_idx;
int error = 0;
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
+ if (!smbdirect_connection_is_connected(sc))
return -EAGAIN;
/*
@@ -2556,7 +446,7 @@ int smbd_send(struct TCP_Server_Info *server,
num_rqst, remaining_data_length);
rqst_idx = 0;
- smbd_send_batch_init(&batch, false, 0);
+ batch = smbdirect_init_send_batch_storage(&bstorage, false, 0);
do {
rqst = &rqst_array[rqst_idx];
@@ -2575,25 +465,27 @@ int smbd_send(struct TCP_Server_Info *server,
klen += rqst->rq_iov[i].iov_len;
iov_iter_kvec(&iter, ITER_SOURCE, rqst->rq_iov, rqst->rq_nvec, klen);
- rc = smbd_post_send_full_iter(sc, &batch, &iter, &remaining_data_length);
+ rc = smbd_post_send_full_iter(sc, batch, &iter, remaining_data_length);
if (rc < 0) {
error = rc;
break;
}
+ remaining_data_length -= rc;
if (iov_iter_count(&rqst->rq_iter) > 0) {
/* And then the data pages if there are any */
- rc = smbd_post_send_full_iter(sc, &batch, &rqst->rq_iter,
- &remaining_data_length);
+ rc = smbd_post_send_full_iter(sc, batch, &rqst->rq_iter,
+ remaining_data_length);
if (rc < 0) {
error = rc;
break;
}
+ remaining_data_length -= rc;
}
} while (++rqst_idx < num_rqst);
- rc = smbd_send_batch_flush(sc, &batch, true);
+ rc = smbdirect_connection_send_batch_flush(sc, batch, true);
if (unlikely(!rc && error))
rc = error;
@@ -2604,298 +496,15 @@ int smbd_send(struct TCP_Server_Info *server,
* that means all the I/Os have been out and we are good to return
*/
- wait_event(sc->send_io.pending.zero_wait_queue,
- atomic_read(&sc->send_io.pending.count) == 0 ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED && rc == 0)
- rc = -EAGAIN;
-
- return rc;
-}
-
-static void register_mr_done(struct ib_cq *cq, struct ib_wc *wc)
-{
- struct smbdirect_mr_io *mr =
- container_of(wc->wr_cqe, struct smbdirect_mr_io, cqe);
- struct smbdirect_socket *sc = mr->socket;
-
- if (wc->status) {
- log_rdma_mr(ERR, "status=%d\n", wc->status);
- smbd_disconnect_rdma_connection(sc);
- }
-}
-
-/*
- * The work queue function that recovers MRs
- * We need to call ib_dereg_mr() and ib_alloc_mr() before this MR can be used
- * again. Both calls are slow, so finish them in a workqueue. This will not
- * block I/O path.
- * There is one workqueue that recovers MRs, there is no need to lock as the
- * I/O requests calling smbd_register_mr will never update the links in the
- * mr_list.
- */
-static void smbd_mr_recovery_work(struct work_struct *work)
-{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, mr_io.recovery_work);
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_mr_io *smbdirect_mr;
- int rc;
-
- list_for_each_entry(smbdirect_mr, &sc->mr_io.all.list, list) {
- if (smbdirect_mr->state == SMBDIRECT_MR_ERROR) {
-
- /* recover this MR entry */
- rc = ib_dereg_mr(smbdirect_mr->mr);
- if (rc) {
- log_rdma_mr(ERR,
- "ib_dereg_mr failed rc=%x\n",
- rc);
- smbd_disconnect_rdma_connection(sc);
- continue;
- }
-
- smbdirect_mr->mr = ib_alloc_mr(
- sc->ib.pd, sc->mr_io.type,
- sp->max_frmr_depth);
- if (IS_ERR(smbdirect_mr->mr)) {
- log_rdma_mr(ERR, "ib_alloc_mr failed mr_type=%x max_frmr_depth=%x\n",
- sc->mr_io.type,
- sp->max_frmr_depth);
- smbd_disconnect_rdma_connection(sc);
- continue;
- }
- } else
- /* This MR is being used, don't recover it */
- continue;
-
- smbdirect_mr->state = SMBDIRECT_MR_READY;
-
- /* smbdirect_mr->state is updated by this function
- * and is read and updated by I/O issuing CPUs trying
- * to get a MR, the call to atomic_inc_return
- * implicates a memory barrier and guarantees this
- * value is updated before waking up any calls to
- * get_mr() from the I/O issuing CPUs
- */
- if (atomic_inc_return(&sc->mr_io.ready.count) == 1)
- wake_up(&sc->mr_io.ready.wait_queue);
- }
-}
-
-static void smbd_mr_disable_locked(struct smbdirect_mr_io *mr)
-{
- struct smbdirect_socket *sc = mr->socket;
-
- lockdep_assert_held(&mr->mutex);
-
- if (mr->state == SMBDIRECT_MR_DISABLED)
- return;
+ error = rc;
+ rc = smbdirect_connection_send_wait_zero_pending(sc);
+ if (unlikely(rc && !error))
+ error = -EAGAIN;
- if (mr->mr)
- ib_dereg_mr(mr->mr);
- if (mr->sgt.nents)
- ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir);
- kfree(mr->sgt.sgl);
-
- mr->mr = NULL;
- mr->sgt.sgl = NULL;
- mr->sgt.nents = 0;
-
- mr->state = SMBDIRECT_MR_DISABLED;
-}
-
-static void smbd_mr_free_locked(struct kref *kref)
-{
- struct smbdirect_mr_io *mr =
- container_of(kref, struct smbdirect_mr_io, kref);
-
- lockdep_assert_held(&mr->mutex);
-
- /*
- * smbd_mr_disable_locked() should already be called!
- */
- if (WARN_ON_ONCE(mr->state != SMBDIRECT_MR_DISABLED))
- smbd_mr_disable_locked(mr);
-
- mutex_unlock(&mr->mutex);
- mutex_destroy(&mr->mutex);
- kfree(mr);
-}
-
-static void destroy_mr_list(struct smbdirect_socket *sc)
-{
- struct smbdirect_mr_io *mr, *tmp;
- LIST_HEAD(all_list);
- unsigned long flags;
-
- disable_work_sync(&sc->mr_io.recovery_work);
-
- spin_lock_irqsave(&sc->mr_io.all.lock, flags);
- list_splice_tail_init(&sc->mr_io.all.list, &all_list);
- spin_unlock_irqrestore(&sc->mr_io.all.lock, flags);
-
- list_for_each_entry_safe(mr, tmp, &all_list, list) {
- mutex_lock(&mr->mutex);
-
- smbd_mr_disable_locked(mr);
- list_del(&mr->list);
- mr->socket = NULL;
-
- /*
- * No kref_put_mutex() as it's already locked.
- *
- * If smbd_mr_free_locked() is called
- * and the mutex is unlocked and mr is gone,
- * in that case kref_put() returned 1.
- *
- * If kref_put() returned 0 we know that
- * smbd_mr_free_locked() didn't
- * run. Not by us nor by anyone else, as we
- * still hold the mutex, so we need to unlock.
- *
- * If the mr is still registered it will
- * be dangling (detached from the connection
- * waiting for smbd_deregister_mr() to be
- * called in order to free the memory.
- */
- if (!kref_put(&mr->kref, smbd_mr_free_locked))
- mutex_unlock(&mr->mutex);
- }
-}
-
-/*
- * Allocate MRs used for RDMA read/write
- * The number of MRs will not exceed hardware capability in responder_resources
- * All MRs are kept in mr_list. The MR can be recovered after it's used
- * Recovery is done in smbd_mr_recovery_work. The content of list entry changes
- * as MRs are used and recovered for I/O, but the list links will not change
- */
-static int allocate_mr_list(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_mr_io *mr;
- int ret;
- u32 i;
-
- if (sp->responder_resources == 0) {
- log_rdma_mr(ERR, "responder_resources negotiated as 0\n");
- return -EINVAL;
- }
-
- /* Allocate more MRs (2x) than hardware responder_resources */
- for (i = 0; i < sp->responder_resources * 2; i++) {
- mr = kzalloc_obj(*mr);
- if (!mr) {
- ret = -ENOMEM;
- goto kzalloc_mr_failed;
- }
-
- kref_init(&mr->kref);
- mutex_init(&mr->mutex);
-
- mr->mr = ib_alloc_mr(sc->ib.pd,
- sc->mr_io.type,
- sp->max_frmr_depth);
- if (IS_ERR(mr->mr)) {
- ret = PTR_ERR(mr->mr);
- log_rdma_mr(ERR, "ib_alloc_mr failed mr_type=%x max_frmr_depth=%x\n",
- sc->mr_io.type, sp->max_frmr_depth);
- goto ib_alloc_mr_failed;
- }
-
- mr->sgt.sgl = kzalloc_objs(struct scatterlist,
- sp->max_frmr_depth);
- if (!mr->sgt.sgl) {
- ret = -ENOMEM;
- log_rdma_mr(ERR, "failed to allocate sgl\n");
- goto kcalloc_sgl_failed;
- }
- mr->state = SMBDIRECT_MR_READY;
- mr->socket = sc;
-
- list_add_tail(&mr->list, &sc->mr_io.all.list);
- atomic_inc(&sc->mr_io.ready.count);
- }
-
- INIT_WORK(&sc->mr_io.recovery_work, smbd_mr_recovery_work);
+ if (unlikely(error))
+ return error;
return 0;
-
-kcalloc_sgl_failed:
- ib_dereg_mr(mr->mr);
-ib_alloc_mr_failed:
- mutex_destroy(&mr->mutex);
- kfree(mr);
-kzalloc_mr_failed:
- destroy_mr_list(sc);
- return ret;
-}
-
-/*
- * Get a MR from mr_list. This function waits until there is at least one
- * MR available in the list. It may access the list while the
- * smbd_mr_recovery_work is recovering the MR list. This doesn't need a lock
- * as they never modify the same places. However, there may be several CPUs
- * issuing I/O trying to get MR at the same time, mr_list_lock is used to
- * protect this situation.
- */
-static struct smbdirect_mr_io *get_mr(struct smbdirect_socket *sc)
-{
- struct smbdirect_mr_io *ret;
- unsigned long flags;
- int rc;
-again:
- rc = wait_event_interruptible(sc->mr_io.ready.wait_queue,
- atomic_read(&sc->mr_io.ready.count) ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
- if (rc) {
- log_rdma_mr(ERR, "wait_event_interruptible rc=%x\n", rc);
- return NULL;
- }
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
- log_rdma_mr(ERR, "sc->status=%x\n", sc->status);
- return NULL;
- }
-
- spin_lock_irqsave(&sc->mr_io.all.lock, flags);
- list_for_each_entry(ret, &sc->mr_io.all.list, list) {
- if (ret->state == SMBDIRECT_MR_READY) {
- ret->state = SMBDIRECT_MR_REGISTERED;
- kref_get(&ret->kref);
- spin_unlock_irqrestore(&sc->mr_io.all.lock, flags);
- atomic_dec(&sc->mr_io.ready.count);
- atomic_inc(&sc->mr_io.used.count);
- return ret;
- }
- }
-
- spin_unlock_irqrestore(&sc->mr_io.all.lock, flags);
- /*
- * It is possible that we could fail to get MR because other processes may
- * try to acquire a MR at the same time. If this is the case, retry it.
- */
- goto again;
-}
-
-/*
- * Transcribe the pages from an iterator into an MR scatterlist.
- */
-static int smbd_iter_to_mr(struct iov_iter *iter,
- struct sg_table *sgt,
- unsigned int max_sg)
-{
- int ret;
-
- memset(sgt->sgl, 0, max_sg * sizeof(struct scatterlist));
-
- ret = extract_iter_to_sg(iter, iov_iter_count(iter), sgt, max_sg, 0);
- WARN_ON(ret < 0);
- if (sgt->nents > 0)
- sg_mark_end(&sgt->sgl[sgt->nents - 1]);
- return ret;
}
/*
@@ -2909,132 +518,18 @@ struct smbdirect_mr_io *smbd_register_mr(struct smbd_connection *info,
struct iov_iter *iter,
bool writing, bool need_invalidate)
{
- struct smbdirect_socket *sc = &info->socket;
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_mr_io *mr;
- int rc, num_pages;
- struct ib_reg_wr *reg_wr;
-
- num_pages = iov_iter_npages(iter, sp->max_frmr_depth + 1);
- if (num_pages > sp->max_frmr_depth) {
- log_rdma_mr(ERR, "num_pages=%d max_frmr_depth=%d\n",
- num_pages, sp->max_frmr_depth);
- WARN_ON_ONCE(1);
- return NULL;
- }
+ struct smbdirect_socket *sc = info->socket;
- mr = get_mr(sc);
- if (!mr) {
- log_rdma_mr(ERR, "get_mr returning NULL\n");
+ if (!smbdirect_connection_is_connected(sc))
return NULL;
- }
-
- mutex_lock(&mr->mutex);
-
- mr->dir = writing ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
- mr->need_invalidate = need_invalidate;
- mr->sgt.nents = 0;
- mr->sgt.orig_nents = 0;
-
- log_rdma_mr(INFO, "num_pages=0x%x count=0x%zx depth=%u\n",
- num_pages, iov_iter_count(iter), sp->max_frmr_depth);
- smbd_iter_to_mr(iter, &mr->sgt, sp->max_frmr_depth);
- rc = ib_dma_map_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir);
- if (!rc) {
- log_rdma_mr(ERR, "ib_dma_map_sg num_pages=%x dir=%x rc=%x\n",
- num_pages, mr->dir, rc);
- goto dma_map_error;
- }
-
- rc = ib_map_mr_sg(mr->mr, mr->sgt.sgl, mr->sgt.nents, NULL, PAGE_SIZE);
- if (rc != mr->sgt.nents) {
- log_rdma_mr(ERR,
- "ib_map_mr_sg failed rc = %d nents = %x\n",
- rc, mr->sgt.nents);
- goto map_mr_error;
- }
-
- ib_update_fast_reg_key(mr->mr, ib_inc_rkey(mr->mr->rkey));
- reg_wr = &mr->wr;
- reg_wr->wr.opcode = IB_WR_REG_MR;
- mr->cqe.done = register_mr_done;
- reg_wr->wr.wr_cqe = &mr->cqe;
- reg_wr->wr.num_sge = 0;
- reg_wr->wr.send_flags = IB_SEND_SIGNALED;
- reg_wr->mr = mr->mr;
- reg_wr->key = mr->mr->rkey;
- reg_wr->access = writing ?
- IB_ACCESS_REMOTE_WRITE | IB_ACCESS_LOCAL_WRITE :
- IB_ACCESS_REMOTE_READ;
-
- /*
- * There is no need for waiting for complemtion on ib_post_send
- * on IB_WR_REG_MR. Hardware enforces a barrier and order of execution
- * on the next ib_post_send when we actually send I/O to remote peer
- */
- rc = ib_post_send(sc->ib.qp, &reg_wr->wr, NULL);
- if (!rc) {
- /*
- * get_mr() gave us a reference
- * via kref_get(&mr->kref), we keep that and let
- * the caller use smbd_deregister_mr()
- * to remove it again.
- */
- mutex_unlock(&mr->mutex);
- return mr;
- }
-
- log_rdma_mr(ERR, "ib_post_send failed rc=%x reg_wr->key=%x\n",
- rc, reg_wr->key);
-
- /* If all failed, attempt to recover this MR by setting it SMBDIRECT_MR_ERROR*/
-map_mr_error:
- ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir);
-
-dma_map_error:
- mr->sgt.nents = 0;
- mr->state = SMBDIRECT_MR_ERROR;
- if (atomic_dec_and_test(&sc->mr_io.used.count))
- wake_up(&sc->mr_io.cleanup.wait_queue);
-
- smbd_disconnect_rdma_connection(sc);
-
- /*
- * get_mr() gave us a reference
- * via kref_get(&mr->kref), we need to remove it again
- * on error.
- *
- * No kref_put_mutex() as it's already locked.
- *
- * If smbd_mr_free_locked() is called
- * and the mutex is unlocked and mr is gone,
- * in that case kref_put() returned 1.
- *
- * If kref_put() returned 0 we know that
- * smbd_mr_free_locked() didn't
- * run. Not by us nor by anyone else, as we
- * still hold the mutex, so we need to unlock.
- */
- if (!kref_put(&mr->kref, smbd_mr_free_locked))
- mutex_unlock(&mr->mutex);
-
- return NULL;
+ return smbdirect_connection_register_mr_io(sc, iter, writing, need_invalidate);
}
-static void local_inv_done(struct ib_cq *cq, struct ib_wc *wc)
+void smbd_mr_fill_buffer_descriptor(struct smbdirect_mr_io *mr,
+ struct smbdirect_buffer_descriptor_v1 *v1)
{
- struct smbdirect_mr_io *smbdirect_mr;
- struct ib_cqe *cqe;
-
- cqe = wc->wr_cqe;
- smbdirect_mr = container_of(cqe, struct smbdirect_mr_io, cqe);
- smbdirect_mr->state = SMBDIRECT_MR_INVALIDATED;
- if (wc->status != IB_WC_SUCCESS) {
- log_rdma_mr(ERR, "invalidate failed status=%x\n", wc->status);
- smbdirect_mr->state = SMBDIRECT_MR_ERROR;
- }
- complete(&smbdirect_mr->invalidate_done);
+ smbdirect_mr_io_fill_buffer_descriptor(mr, v1);
}
/*
@@ -3045,300 +540,22 @@ static void local_inv_done(struct ib_cq *cq, struct ib_wc *wc)
*/
void smbd_deregister_mr(struct smbdirect_mr_io *mr)
{
- struct smbdirect_socket *sc = mr->socket;
-
- mutex_lock(&mr->mutex);
- if (mr->state == SMBDIRECT_MR_DISABLED)
- goto put_kref;
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
- smbd_mr_disable_locked(mr);
- goto put_kref;
- }
-
- if (mr->need_invalidate) {
- struct ib_send_wr *wr = &mr->inv_wr;
- int rc;
-
- /* Need to finish local invalidation before returning */
- wr->opcode = IB_WR_LOCAL_INV;
- mr->cqe.done = local_inv_done;
- wr->wr_cqe = &mr->cqe;
- wr->num_sge = 0;
- wr->ex.invalidate_rkey = mr->mr->rkey;
- wr->send_flags = IB_SEND_SIGNALED;
-
- init_completion(&mr->invalidate_done);
- rc = ib_post_send(sc->ib.qp, wr, NULL);
- if (rc) {
- log_rdma_mr(ERR, "ib_post_send failed rc=%x\n", rc);
- smbd_mr_disable_locked(mr);
- smbd_disconnect_rdma_connection(sc);
- goto done;
- }
- wait_for_completion(&mr->invalidate_done);
- mr->need_invalidate = false;
- } else
- /*
- * For remote invalidation, just set it to SMBDIRECT_MR_INVALIDATED
- * and defer to mr_recovery_work to recover the MR for next use
- */
- mr->state = SMBDIRECT_MR_INVALIDATED;
-
- if (mr->sgt.nents) {
- ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir);
- mr->sgt.nents = 0;
- }
-
- if (mr->state == SMBDIRECT_MR_INVALIDATED) {
- mr->state = SMBDIRECT_MR_READY;
- if (atomic_inc_return(&sc->mr_io.ready.count) == 1)
- wake_up(&sc->mr_io.ready.wait_queue);
- } else
- /*
- * Schedule the work to do MR recovery for future I/Os MR
- * recovery is slow and don't want it to block current I/O
- */
- queue_work(sc->workqueue, &sc->mr_io.recovery_work);
-
-done:
- if (atomic_dec_and_test(&sc->mr_io.used.count))
- wake_up(&sc->mr_io.cleanup.wait_queue);
-
-put_kref:
- /*
- * No kref_put_mutex() as it's already locked.
- *
- * If smbd_mr_free_locked() is called
- * and the mutex is unlocked and mr is gone,
- * in that case kref_put() returned 1.
- *
- * If kref_put() returned 0 we know that
- * smbd_mr_free_locked() didn't
- * run. Not by us nor by anyone else, as we
- * still hold the mutex, so we need to unlock
- * and keep the mr in SMBDIRECT_MR_READY or
- * SMBDIRECT_MR_ERROR state.
- */
- if (!kref_put(&mr->kref, smbd_mr_free_locked))
- mutex_unlock(&mr->mutex);
-}
-
-static bool smb_set_sge(struct smb_extract_to_rdma *rdma,
- struct page *lowest_page, size_t off, size_t len)
-{
- struct ib_sge *sge = &rdma->sge[rdma->nr_sge];
- u64 addr;
-
- addr = ib_dma_map_page(rdma->device, lowest_page,
- off, len, rdma->direction);
- if (ib_dma_mapping_error(rdma->device, addr))
- return false;
-
- sge->addr = addr;
- sge->length = len;
- sge->lkey = rdma->local_dma_lkey;
- rdma->nr_sge++;
- return true;
-}
-
-/*
- * Extract page fragments from a BVEC-class iterator and add them to an RDMA
- * element list. The pages are not pinned.
- */
-static ssize_t smb_extract_bvec_to_rdma(struct iov_iter *iter,
- struct smb_extract_to_rdma *rdma,
- ssize_t maxsize)
-{
- const struct bio_vec *bv = iter->bvec;
- unsigned long start = iter->iov_offset;
- unsigned int i;
- ssize_t ret = 0;
-
- for (i = 0; i < iter->nr_segs; i++) {
- size_t off, len;
-
- len = bv[i].bv_len;
- if (start >= len) {
- start -= len;
- continue;
- }
-
- len = min_t(size_t, maxsize, len - start);
- off = bv[i].bv_offset + start;
-
- if (!smb_set_sge(rdma, bv[i].bv_page, off, len))
- return -EIO;
-
- ret += len;
- maxsize -= len;
- if (rdma->nr_sge >= rdma->max_sge || maxsize <= 0)
- break;
- start = 0;
- }
-
- if (ret > 0)
- iov_iter_advance(iter, ret);
- return ret;
+ smbdirect_connection_deregister_mr_io(mr);
}
-/*
- * Extract fragments from a KVEC-class iterator and add them to an RDMA list.
- * This can deal with vmalloc'd buffers as well as kmalloc'd or static buffers.
- * The pages are not pinned.
- */
-static ssize_t smb_extract_kvec_to_rdma(struct iov_iter *iter,
- struct smb_extract_to_rdma *rdma,
- ssize_t maxsize)
+void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m)
{
- const struct kvec *kv = iter->kvec;
- unsigned long start = iter->iov_offset;
- unsigned int i;
- ssize_t ret = 0;
-
- for (i = 0; i < iter->nr_segs; i++) {
- struct page *page;
- unsigned long kaddr;
- size_t off, len, seg;
-
- len = kv[i].iov_len;
- if (start >= len) {
- start -= len;
- continue;
- }
-
- kaddr = (unsigned long)kv[i].iov_base + start;
- off = kaddr & ~PAGE_MASK;
- len = min_t(size_t, maxsize, len - start);
- kaddr &= PAGE_MASK;
-
- maxsize -= len;
- do {
- seg = min_t(size_t, len, PAGE_SIZE - off);
-
- if (is_vmalloc_or_module_addr((void *)kaddr))
- page = vmalloc_to_page((void *)kaddr);
- else
- page = virt_to_page((void *)kaddr);
-
- if (!smb_set_sge(rdma, page, off, seg))
- return -EIO;
-
- ret += seg;
- len -= seg;
- kaddr += PAGE_SIZE;
- off = 0;
- } while (len > 0 && rdma->nr_sge < rdma->max_sge);
-
- if (rdma->nr_sge >= rdma->max_sge || maxsize <= 0)
- break;
- start = 0;
- }
-
- if (ret > 0)
- iov_iter_advance(iter, ret);
- return ret;
-}
+ if (!server->rdma)
+ return;
-/*
- * Extract folio fragments from a FOLIOQ-class iterator and add them to an RDMA
- * list. The folios are not pinned.
- */
-static ssize_t smb_extract_folioq_to_rdma(struct iov_iter *iter,
- struct smb_extract_to_rdma *rdma,
- ssize_t maxsize)
-{
- const struct folio_queue *folioq = iter->folioq;
- unsigned int slot = iter->folioq_slot;
- ssize_t ret = 0;
- size_t offset = iter->iov_offset;
-
- BUG_ON(!folioq);
-
- if (slot >= folioq_nr_slots(folioq)) {
- folioq = folioq->next;
- if (WARN_ON_ONCE(!folioq))
- return -EIO;
- slot = 0;
+ if (!server->smbd_conn) {
+ seq_puts(m, "\nSMBDirect transport not available");
+ return;
}
- do {
- struct folio *folio = folioq_folio(folioq, slot);
- size_t fsize = folioq_folio_size(folioq, slot);
-
- if (offset < fsize) {
- size_t part = umin(maxsize, fsize - offset);
-
- if (!smb_set_sge(rdma, folio_page(folio, 0), offset, part))
- return -EIO;
-
- offset += part;
- ret += part;
- maxsize -= part;
- }
-
- if (offset >= fsize) {
- offset = 0;
- slot++;
- if (slot >= folioq_nr_slots(folioq)) {
- if (!folioq->next) {
- WARN_ON_ONCE(ret < iter->count);
- break;
- }
- folioq = folioq->next;
- slot = 0;
- }
- }
- } while (rdma->nr_sge < rdma->max_sge && maxsize > 0);
-
- iter->folioq = folioq;
- iter->folioq_slot = slot;
- iter->iov_offset = offset;
- iter->count -= ret;
- return ret;
+ smbdirect_connection_legacy_debug_proc_show(server->smbd_conn->socket,
+ server->rdma_readwrite_threshold,
+ m);
}
-/*
- * Extract page fragments from up to the given amount of the source iterator
- * and build up an RDMA list that refers to all of those bits. The RDMA list
- * is appended to, up to the maximum number of elements set in the parameter
- * block.
- *
- * The extracted page fragments are not pinned or ref'd in any way; if an
- * IOVEC/UBUF-type iterator is to be used, it should be converted to a
- * BVEC-type iterator and the pages pinned, ref'd or otherwise held in some
- * way.
- */
-static ssize_t smb_extract_iter_to_rdma(struct iov_iter *iter, size_t len,
- struct smb_extract_to_rdma *rdma)
-{
- ssize_t ret;
- int before = rdma->nr_sge;
-
- switch (iov_iter_type(iter)) {
- case ITER_BVEC:
- ret = smb_extract_bvec_to_rdma(iter, rdma, len);
- break;
- case ITER_KVEC:
- ret = smb_extract_kvec_to_rdma(iter, rdma, len);
- break;
- case ITER_FOLIOQ:
- ret = smb_extract_folioq_to_rdma(iter, rdma, len);
- break;
- default:
- WARN_ON_ONCE(1);
- return -EIO;
- }
-
- if (ret < 0) {
- while (rdma->nr_sge > before) {
- struct ib_sge *sge = &rdma->sge[rdma->nr_sge--];
-
- ib_dma_unmap_single(rdma->device, sge->addr, sge->length,
- rdma->direction);
- sge->addr = 0;
- }
- }
-
- return ret;
-}
+MODULE_IMPORT_NS("SMBDIRECT");
diff --git a/fs/smb/client/smbdirect.h b/fs/smb/client/smbdirect.h
index 577d37dbeb8a..be205ec02077 100644
--- a/fs/smb/client/smbdirect.h
+++ b/fs/smb/client/smbdirect.h
@@ -11,12 +11,8 @@
#define cifs_rdma_enabled(server) ((server)->rdma)
#include "cifsglob.h"
-#include <rdma/ib_verbs.h>
-#include <rdma/rdma_cm.h>
-#include <linux/mempool.h>
-#include "../common/smbdirect/smbdirect.h"
-#include "../common/smbdirect/smbdirect_socket.h"
+#include <linux/smbdirect.h>
extern int rdma_readwrite_threshold;
extern int smbd_max_frmr_depth;
@@ -27,17 +23,8 @@ extern int smbd_max_send_size;
extern int smbd_send_credit_target;
extern int smbd_receive_credit_max;
-/*
- * The context for the SMBDirect transport
- * Everything related to the transport is here. It has several logical parts
- * 1. RDMA related structures
- * 2. SMBDirect connection parameters
- * 3. Memory registrations
- * 4. Receive and reassembly queues for data receive path
- * 5. mempools for allocating packets
- */
struct smbd_connection {
- struct smbdirect_socket socket;
+ struct smbdirect_socket *socket;
};
/* Create a SMBDirect session */
@@ -60,8 +47,12 @@ int smbd_send(struct TCP_Server_Info *server,
struct smbdirect_mr_io *smbd_register_mr(
struct smbd_connection *info, struct iov_iter *iter,
bool writing, bool need_invalidate);
+void smbd_mr_fill_buffer_descriptor(struct smbdirect_mr_io *mr,
+ struct smbdirect_buffer_descriptor_v1 *v1);
void smbd_deregister_mr(struct smbdirect_mr_io *mr);
+void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m);
+
#else
#define cifs_rdma_enabled(server) 0
struct smbd_connection {};
diff --git a/fs/smb/client/smberr.h b/fs/smb/client/smberr.h
index 6fb63f9e9a95..5c3415bf18d7 100644
--- a/fs/smb/client/smberr.h
+++ b/fs/smb/client/smberr.h
@@ -9,163 +9,290 @@
*
*/
-#define SUCCESS 0x00 /* The request was successful. */
-#define ERRDOS 0x01 /* Error is from the core DOS operating system set */
-#define ERRSRV 0x02 /* Error is generated by the file server daemon */
-#define ERRHRD 0x03 /* Error is a hardware error. */
-#define ERRCMD 0xFF /* Command was not in the "SMB" format. */
+struct smb_to_posix_error {
+ __u16 smb_err;
+ int posix_code;
+};
+
+/* The request was successful. */
+#define SUCCESS 0x00
+/* Error is from the core DOS operating system set */
+#define ERRDOS 0x01
+/* Error is generated by the file server daemon */
+#define ERRSRV 0x02
+/* Error is a hardware error. */
+#define ERRHRD 0x03
+/* Command was not in the "SMB" format. */
+#define ERRCMD 0xFF
/* The following error codes may be generated with the SUCCESS error class.*/
/*#define SUCCESS 0 The request was successful. */
-/* The following error codes may be generated with the ERRDOS error class.*/
+/*
+ * The following error codes may be generated with the ERRDOS error class.
+ * The comment at the end of each definition indicates the POSIX error
+ * code; it is used to generate the `mapping_table_ERRDOS` array.
+ */
-#define ERRbadfunc 1 /* Invalid function. The server did not
- recognize or could not perform a
- system call generated by the server,
- e.g., set the DIRECTORY attribute on
- a data file, invalid seek mode. */
-#define ERRbadfile 2 /* File not found. The last component
- of a file's pathname could not be
- found. */
-#define ERRbadpath 3 /* Directory invalid. A directory
- component in a pathname could not be
- found. */
-#define ERRnofids 4 /* Too many open files. The server has
- no file handles available. */
-#define ERRnoaccess 5 /* Access denied, the client's context
- does not permit the requested
- function. This includes the
- following conditions: invalid rename
- command, write to Fid open for read
- only, read on Fid open for write
- only, attempt to delete a non-empty
- directory */
-#define ERRbadfid 6 /* Invalid file handle. The file handle
- specified was not recognized by the
- server. */
-#define ERRbadmcb 7 /* Memory control blocks destroyed. */
-#define ERRnomem 8 /* Insufficient server memory to
- perform the requested function. */
-#define ERRbadmem 9 /* Invalid memory block address. */
-#define ERRbadenv 10 /* Invalid environment. */
-#define ERRbadformat 11 /* Invalid format. */
-#define ERRbadaccess 12 /* Invalid open mode. */
-#define ERRbaddata 13 /* Invalid data (generated only by
- IOCTL calls within the server). */
-#define ERRbaddrive 15 /* Invalid drive specified. */
-#define ERRremcd 16 /* A Delete Directory request attempted
- to remove the server's current
- directory. */
-#define ERRdiffdevice 17 /* Not same device (e.g., a cross
- volume rename was attempted */
-#define ERRnofiles 18 /* A File Search command can find no
- more files matching the specified
- criteria. */
-#define ERRwriteprot 19 /* media is write protected */
+/*
+ * Invalid function. The server did not
+ * recognize or could not perform a
+ * system call generated by the server,
+ * e.g., set the DIRECTORY attribute on
+ * a data file, invalid seek mode.
+ */
+#define ERRbadfunc 1 // -EINVAL
+/*
+ * File not found. The last component
+ * of a file's pathname could not be
+ * found.
+ */
+#define ERRbadfile 2 // -ENOENT
+/*
+ * Directory invalid. A directory
+ * component in a pathname could not be
+ * found.
+ */
+#define ERRbadpath 3 // -ENOTDIR
+/*
+ * Too many open files. The server has
+ * no file handles available.
+ */
+#define ERRnofids 4 // -EMFILE
+/*
+ * Access denied, the client's context
+ * does not permit the requested
+ * function. This includes the
+ * following conditions: invalid rename
+ * command, write to Fid open for read
+ * only, read on Fid open for write
+ * only, attempt to delete a non-empty
+ * directory
+ */
+#define ERRnoaccess 5 // -EACCES
+/*
+ * Invalid file handle. The file handle
+ * specified was not recognized by the
+ * server.
+ */
+#define ERRbadfid 6 // -EBADF
+/* Memory control blocks destroyed. */
+#define ERRbadmcb 7 // -EIO
+/*
+ * Insufficient server memory to
+ * perform the requested function.
+ */
+#define ERRnomem 8 // -EREMOTEIO
+/* Invalid memory block address. */
+#define ERRbadmem 9 // -EFAULT
+/* Invalid environment. */
+#define ERRbadenv 10 // -EFAULT
+/* Invalid format. */
+#define ERRbadformat 11 // -EINVAL
+/* Invalid open mode. */
+#define ERRbadaccess 12 // -EACCES
+/*
+ * Invalid data (generated only by
+ * IOCTL calls within the server).
+ */
+#define ERRbaddata 13 // -EIO
+/* Invalid drive specified. */
+#define ERRbaddrive 15 // -ENXIO
+/*
+ * A Delete Directory request attempted
+ * to remove the server's current
+ * directory.
+ */
+#define ERRremcd 16 // -EACCES
+/*
+ * Not same device (e.g., a cross
+ * volume rename was attempted
+ */
+#define ERRdiffdevice 17 // -EXDEV
+/*
+ * A File Search command can find no
+ * more files matching the specified
+ * criteria.
+ */
+#define ERRnofiles 18 // -ENOENT
+/* media is write protected */
+#define ERRwriteprot 19 // -EROFS
#define ERRgeneral 31
-#define ERRbadshare 32 /* The sharing mode specified for an
- Open conflicts with existing FIDs on
- the file. */
-#define ERRlock 33 /* A Lock request conflicted with an
- existing lock or specified an
- invalid mode, or an Unlock requested
- attempted to remove a lock held by
- another process. */
-#define ERRunsup 50
-#define ERRnosuchshare 67
-#define ERRfilexists 80 /* The file named in the request
- already exists. */
-#define ERRinvparm 87
-#define ERRdiskfull 112
-#define ERRinvname 123
-#define ERRunknownlevel 124
-#define ERRdirnotempty 145
-#define ERRnotlocked 158
-#define ERRcancelviolation 173
-#define ERRalreadyexists 183
+/*
+ * The sharing mode specified for an
+ * Open conflicts with existing FIDs on
+ * the file.
+ */
+#define ERRbadshare 32 // -EBUSY
+/*
+ * A Lock request conflicted with an
+ * existing lock or specified an
+ * invalid mode, or an Unlock requested
+ * attempted to remove a lock held by
+ * another process.
+ */
+#define ERRlock 33 // -EACCES
+#define ERRunsup 50 // -EINVAL
+#define ERRnosuchshare 67 // -ENXIO
+/*
+ * The file named in the request
+ * already exists.
+ */
+#define ERRfilexists 80 // -EEXIST
+#define ERRinvparm 87 // -EINVAL
+#define ERRdiskfull 112 // -ENOSPC
+#define ERRinvname 123 // -ENOENT
+#define ERRunknownlevel 124 // -EOPNOTSUPP
+#define ERRdirnotempty 145 // -ENOTEMPTY
+#define ERRnotlocked 158 // -ENOLCK
+#define ERRcancelviolation 173 // -ENOLCK
+#define ERRalreadyexists 183 // -EEXIST
#define ERRbadpipe 230
#define ERRpipebusy 231
#define ERRpipeclosing 232
#define ERRnotconnected 233
-#define ERRmoredata 234
-#define ERReasnotsupported 282
-#define ErrQuota 0x200 /* The operation would cause a quota
- limit to be exceeded. */
-#define ErrNotALink 0x201 /* A link operation was performed on a
- pathname that was not a link. */
+#define ERRmoredata 234 // -EOVERFLOW
+#define ERReasnotsupported 282 // -EOPNOTSUPP
+/*
+ * The operation would cause a quota
+ * limit to be exceeded.
+ */
+#define ErrQuota 0x200 // -EDQUOT
+/*
+ * A link operation was performed on a
+ * pathname that was not a link.
+ */
+#define ErrNotALink 0x201 // -ENOLINK
+#define ERRnetlogonNotStarted 2455 // -ENOPROTOOPT
/* Below errors are used internally (do not come over the wire) for passthrough
from STATUS codes to POSIX only */
-#define ERRsymlink 0xFFFD
-#define ErrTooManyLinks 0xFFFE
+#define ERRsymlink 0xFFFD // -EOPNOTSUPP
+#define ErrTooManyLinks 0xFFFE // -EMLINK
-/* Following error codes may be generated with the ERRSRV error class.*/
+/*
+ * The following error codes may be generated with the ERRSRV error class.
+ * The comment at the end of each definition indicates the POSIX error
+ * code; it is used to generate the `mapping_table_ERRSRV` array.
+ */
-#define ERRerror 1 /* Non-specific error code. It is
- returned under the following
- conditions: resource other than disk
- space exhausted (e.g. TIDs), first
- SMB command was not negotiate,
- multiple negotiates attempted, and
- internal server error. */
-#define ERRbadpw 2 /* Bad password - name/password pair in
- a TreeConnect or Session Setup are
- invalid. */
-#define ERRbadtype 3 /* used for indicating DFS referral
- needed */
-#define ERRaccess 4 /* The client does not have the
- necessary access rights within the
- specified context for requested
- function. */
-#define ERRinvtid 5 /* The Tid specified in a command was
- invalid. */
-#define ERRinvnetname 6 /* Invalid network name in tree
- connect. */
-#define ERRinvdevice 7 /* Invalid device - printer request
- made to non-printer connection or
- non-printer request made to printer
- connection. */
-#define ERRqfull 49 /* Print queue full (files) -- returned
- by open print file. */
-#define ERRqtoobig 50 /* Print queue full -- no space. */
-#define ERRqeof 51 /* EOF on print queue dump */
-#define ERRinvpfid 52 /* Invalid print file FID. */
-#define ERRsmbcmd 64 /* The server did not recognize the
- command received. */
-#define ERRsrverror 65 /* The server encountered an internal
- error, e.g., system file
- unavailable. */
-#define ERRbadBID 66 /* (obsolete) */
-#define ERRfilespecs 67 /* The Fid and pathname parameters
- contained an invalid combination of
- values. */
-#define ERRbadLink 68 /* (obsolete) */
-#define ERRbadpermits 69 /* The access permissions specified for
- a file or directory are not a valid
- combination. */
-#define ERRbadPID 70
-#define ERRsetattrmode 71 /* attribute (mode) is invalid */
-#define ERRpaused 81 /* Server is paused */
-#define ERRmsgoff 82 /* reserved - messaging off */
-#define ERRnoroom 83 /* reserved - no room for message */
-#define ERRrmuns 87 /* reserved - too many remote names */
-#define ERRtimeout 88 /* operation timed out */
-#define ERRnoresource 89 /* No resources available for request
- */
-#define ERRtoomanyuids 90 /* Too many UIDs active on this session
- */
-#define ERRbaduid 91 /* The UID is not known as a valid user
- */
-#define ERRusempx 250 /* temporarily unable to use raw */
-#define ERRusestd 251 /* temporarily unable to use either raw
- or mpx */
-#define ERR_NOTIFY_ENUM_DIR 1024
-#define ERRnoSuchUser 2238 /* user account does not exist */
-#define ERRaccountexpired 2239
-#define ERRbadclient 2240 /* can not logon from this client */
-#define ERRbadLogonTime 2241 /* logon hours do not allow this */
-#define ERRpasswordExpired 2242
-#define ERRnetlogonNotStarted 2455
-#define ERRnosupport 0xFFFF
+/*
+ * Non-specific error code. It is
+ * returned under the following
+ * conditions: resource other than disk
+ * space exhausted (e.g. TIDs), first
+ * SMB command was not negotiate,
+ * multiple negotiates attempted, and
+ * internal server error.
+ */
+#define ERRerror 1 // -EIO
+/*
+ * Bad password - name/password pair in
+ * a TreeConnect or Session Setup are
+ * invalid.
+ */
+#define ERRbadpw 2 // -EACCES
+/*
+ * used for indicating DFS referral
+ * needed
+ */
+#define ERRbadtype 3 // -EREMOTE
+/*
+ * The client does not have the
+ * necessary access rights within the
+ * specified context for requested
+ * function.
+ */
+#define ERRaccess 4 // -EACCES
+/*
+ * The Tid specified in a command was
+ * invalid.
+ */
+#define ERRinvtid 5 // -ENXIO
+/*
+ * Invalid network name in tree
+ * connect.
+ */
+#define ERRinvnetname 6 // -ENXIO
+/*
+ * Invalid device - printer request
+ * made to non-printer connection or
+ * non-printer request made to printer
+ * connection.
+ */
+#define ERRinvdevice 7 // -ENXIO
+/*
+ * Print queue full (files) -- returned
+ * by open print file.
+ */
+#define ERRqfull 49 // -ENOSPC
+/* Print queue full -- no space. */
+#define ERRqtoobig 50 // -ENOSPC
+/* EOF on print queue dump */
+#define ERRqeof 51 // -EIO
+/* Invalid print file FID. */
+#define ERRinvpfid 52 // -EBADF
+/*
+ * The server did not recognize the
+ * command received.
+ */
+#define ERRsmbcmd 64 // -EBADRQC
+/*
+ * The server encountered an internal
+ * error, e.g., system file
+ * unavailable.
+ */
+#define ERRsrverror 65 // -EIO
+/* (obsolete) */
+#define ERRbadBID 66 // -EIO
+/*
+ * The Fid and pathname parameters
+ * contained an invalid combination of
+ * values.
+ */
+#define ERRfilespecs 67 // -EINVAL
+/* (obsolete) */
+#define ERRbadLink 68 // -EIO
+/*
+ * The access permissions specified for
+ * a file or directory are not a valid
+ * combination.
+ */
+#define ERRbadpermits 69 // -EINVAL
+#define ERRbadPID 70 // -ESRCH
+/* attribute (mode) is invalid */
+#define ERRsetattrmode 71 // -EINVAL
+/* Server is paused */
+#define ERRpaused 81 // -EHOSTDOWN
+/* reserved - messaging off */
+#define ERRmsgoff 82 // -EHOSTDOWN
+/* reserved - no room for message */
+#define ERRnoroom 83 // -ENOSPC
+/* reserved - too many remote names */
+#define ERRrmuns 87 // -EUSERS
+/* operation timed out */
+#define ERRtimeout 88 // -ETIME
+/* No resources available for request */
+#define ERRnoresource 89 // -EREMOTEIO
+/* Too many UIDs active on this session */
+#define ERRtoomanyuids 90 // -EUSERS
+/* The UID is not known as a valid user */
+#define ERRbaduid 91 // -EACCES
+/* temporarily unable to use raw */
+#define ERRusempx 250 // -EIO
+/*
+ * temporarily unable to use either raw
+ * or mpx
+ */
+#define ERRusestd 251 // -EIO
+#define ERR_NOTIFY_ENUM_DIR 1024 // -ENOBUFS
+/* user account does not exist */
+#define ERRnoSuchUser 2238 // -EACCES
+#define ERRaccountexpired 2239 // -EKEYEXPIRED
+/* can not logon from this client */
+#define ERRbadclient 2240 // -EACCES
+/* logon hours do not allow this */
+#define ERRbadLogonTime 2241 // -EACCES
+#define ERRpasswordExpired 2242 // -EKEYEXPIRED
+#define ERRnosupport 0xFFFF // -EINVAL
diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h
index 9228f95cae2b..bb8d0197cb54 100644
--- a/fs/smb/client/trace.h
+++ b/fs/smb/client/trace.h
@@ -27,6 +27,7 @@
EM(smb_eio_trace_copychunk_overcopy_c, "copychunk_overcopy_c") \
EM(smb_eio_trace_create_rsp_too_small, "create_rsp_too_small") \
EM(smb_eio_trace_dfsref_no_rsp, "dfsref_no_rsp") \
+ EM(smb_eio_trace_ea_next_offset, "ea_next_offset") \
EM(smb_eio_trace_ea_overrun, "ea_overrun") \
EM(smb_eio_trace_extract_will_pin, "extract_will_pin") \
EM(smb_eio_trace_forced_shutdown, "forced_shutdown") \
@@ -79,6 +80,7 @@
EM(smb_eio_trace_qreparse_setup_count, "qreparse_setup_count") \
EM(smb_eio_trace_qreparse_sizes_wrong, "qreparse_sizes_wrong") \
EM(smb_eio_trace_qsym_bcc_too_small, "qsym_bcc_too_small") \
+ EM(smb_eio_trace_read_bad_offset, "read_bad_offset") \
EM(smb_eio_trace_read_mid_state_unknown, "read_mid_state_unknown") \
EM(smb_eio_trace_read_overlarge, "read_overlarge") \
EM(smb_eio_trace_read_rsp_malformed, "read_rsp_malformed") \
@@ -133,6 +135,7 @@
EM(smb_eio_trace_sym_slash, "sym_slash") \
EM(smb_eio_trace_sym_target_len, "sym_target_len") \
EM(smb_eio_trace_symlink_file_size, "symlink_file_size") \
+ EM(smb_eio_trace_tcon_bcc_too_small, "tcon_bcc_too_small") \
EM(smb_eio_trace_tdis_in_reconnect, "tdis_in_reconnect") \
EM(smb_eio_trace_tx_chained_async, "tx_chained_async") \
EM(smb_eio_trace_tx_compress_failed, "tx_compress_failed") \
@@ -176,10 +179,12 @@
EM(netfs_trace_tcon_ref_get_cached_laundromat, "GET Ch-Lau") \
EM(netfs_trace_tcon_ref_get_cached_lease_break, "GET Ch-Lea") \
EM(netfs_trace_tcon_ref_get_cancelled_close, "GET Cn-Cls") \
+ EM(netfs_trace_tcon_ref_get_close_defer_files, "GET Cl-Def") \
EM(netfs_trace_tcon_ref_get_dfs_refer, "GET DfsRef") \
EM(netfs_trace_tcon_ref_get_find, "GET Find ") \
EM(netfs_trace_tcon_ref_get_find_sess_tcon, "GET FndSes") \
EM(netfs_trace_tcon_ref_get_reconnect_server, "GET Reconn") \
+ EM(netfs_trace_tcon_ref_get_swn_notify, "GET SwnNot") \
EM(netfs_trace_tcon_ref_new, "NEW ") \
EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \
EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \
@@ -187,9 +192,11 @@
EM(netfs_trace_tcon_ref_put_cancelled_close, "PUT Cn-Cls") \
EM(netfs_trace_tcon_ref_put_cancelled_close_fid, "PUT Cn-Fid") \
EM(netfs_trace_tcon_ref_put_cancelled_mid, "PUT Cn-Mid") \
+ EM(netfs_trace_tcon_ref_put_close_defer_files, "PUT Cl-Def") \
EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \
EM(netfs_trace_tcon_ref_put_dfs_refer, "PUT DfsRfr") \
EM(netfs_trace_tcon_ref_put_reconnect_server, "PUT Reconn") \
+ EM(netfs_trace_tcon_ref_put_swn_notify, "PUT SwnNot") \
EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \
EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \
EM(netfs_trace_tcon_ref_see_fscache_collision, "SEE FV-CO!") \
@@ -209,7 +216,7 @@
#define EM(a, b) a,
#define E_(a, b) a
-enum smb_eio_trace { smb_eio_traces } __mode(byte);
+enum smb_eio_trace { smb_eio_traces };
enum smb3_rw_credits_trace { smb3_rw_credits_traces } __mode(byte);
enum smb3_tcon_ref_trace { smb3_tcon_ref_traces } __mode(byte);
@@ -668,9 +675,100 @@ DEFINE_EVENT(smb3_fd_err_class, smb3_##name, \
TP_ARGS(xid, fid, tid, sesid, rc))
DEFINE_SMB3_FD_ERR_EVENT(flush_err);
-DEFINE_SMB3_FD_ERR_EVENT(lock_err);
DEFINE_SMB3_FD_ERR_EVENT(close_err);
+DECLARE_EVENT_CLASS(smb3_lock_class,
+ TP_PROTO(unsigned int xid,
+ __u64 fid,
+ __u32 tid,
+ __u64 sesid,
+ __u64 offset,
+ __u64 len,
+ __u32 flags,
+ __u32 num_lock,
+ int rc),
+ TP_ARGS(xid, fid, tid, sesid, offset, len, flags, num_lock, rc),
+ TP_STRUCT__entry(
+ __field(unsigned int, xid)
+ __field(__u64, fid)
+ __field(__u32, tid)
+ __field(__u64, sesid)
+ __field(__u64, offset)
+ __field(__u64, len)
+ __field(__u32, flags)
+ __field(__u32, num_lock)
+ __field(int, rc)
+ ),
+ TP_fast_assign(
+ __entry->xid = xid;
+ __entry->fid = fid;
+ __entry->tid = tid;
+ __entry->sesid = sesid;
+ __entry->offset = offset;
+ __entry->len = len;
+ __entry->flags = flags;
+ __entry->num_lock = num_lock;
+ __entry->rc = rc;
+ ),
+ TP_printk("xid=%u sid=0x%llx tid=0x%x fid=0x%llx offset=0x%llx len=0x%llx flags=0x%x num_lock=%u rc=%d",
+ __entry->xid, __entry->sesid, __entry->tid, __entry->fid,
+ __entry->offset, __entry->len, __entry->flags, __entry->num_lock,
+ __entry->rc)
+)
+
+#define DEFINE_SMB3_LOCK_EVENT(name) \
+DEFINE_EVENT(smb3_lock_class, smb3_##name, \
+ TP_PROTO(unsigned int xid, \
+ __u64 fid, \
+ __u32 tid, \
+ __u64 sesid, \
+ __u64 offset, \
+ __u64 len, \
+ __u32 flags, \
+ __u32 num_lock, \
+ int rc), \
+ TP_ARGS(xid, fid, tid, sesid, offset, len, flags, num_lock, rc))
+
+DEFINE_SMB3_LOCK_EVENT(lock_enter);
+DEFINE_SMB3_LOCK_EVENT(lock_done);
+DEFINE_SMB3_LOCK_EVENT(lock_err);
+DEFINE_SMB3_LOCK_EVENT(lock_cached);
+
+TRACE_EVENT(smb3_lock_conflict,
+ TP_PROTO(__u64 fid,
+ __u64 req_offset,
+ __u64 req_len,
+ __u8 req_type,
+ __u64 conf_offset,
+ __u64 conf_len,
+ __u16 conf_type,
+ __u32 conf_pid),
+ TP_ARGS(fid, req_offset, req_len, req_type, conf_offset, conf_len, conf_type, conf_pid),
+ TP_STRUCT__entry(
+ __field(__u64, fid)
+ __field(__u64, req_offset)
+ __field(__u64, req_len)
+ __field(__u8, req_type)
+ __field(__u64, conf_offset)
+ __field(__u64, conf_len)
+ __field(__u16, conf_type)
+ __field(__u32, conf_pid)
+ ),
+ TP_fast_assign(
+ __entry->fid = fid;
+ __entry->req_offset = req_offset;
+ __entry->req_len = req_len;
+ __entry->req_type = req_type;
+ __entry->conf_offset = conf_offset;
+ __entry->conf_len = conf_len;
+ __entry->conf_type = conf_type;
+ __entry->conf_pid = conf_pid;
+ ),
+ TP_printk("fid=0x%llx req=[0x%llx:0x%llx] type=0x%x conflicts with [0x%llx:0x%llx] type=0x%x pid=%u",
+ __entry->fid, __entry->req_offset, __entry->req_len, __entry->req_type,
+ __entry->conf_offset, __entry->conf_len, __entry->conf_type, __entry->conf_pid)
+);
+
/*
* For handle based query/set info calls
*/
@@ -1228,8 +1326,9 @@ DECLARE_EVENT_CLASS(smb3_open_done_class,
__u32 tid,
__u64 sesid,
int create_options,
- int desired_access),
- TP_ARGS(xid, fid, tid, sesid, create_options, desired_access),
+ int desired_access,
+ __u8 oplock),
+ TP_ARGS(xid, fid, tid, sesid, create_options, desired_access, oplock),
TP_STRUCT__entry(
__field(unsigned int, xid)
__field(__u64, fid)
@@ -1237,6 +1336,7 @@ DECLARE_EVENT_CLASS(smb3_open_done_class,
__field(__u64, sesid)
__field(int, create_options)
__field(int, desired_access)
+ __field(__u8, oplock)
),
TP_fast_assign(
__entry->xid = xid;
@@ -1245,10 +1345,11 @@ DECLARE_EVENT_CLASS(smb3_open_done_class,
__entry->sesid = sesid;
__entry->create_options = create_options;
__entry->desired_access = desired_access;
+ __entry->oplock = oplock;
),
- TP_printk("xid=%u sid=0x%llx tid=0x%x fid=0x%llx cr_opts=0x%x des_access=0x%x",
+ TP_printk("xid=%u sid=0x%llx tid=0x%x fid=0x%llx cr_opts=0x%x des_access=0x%x oplock=0x%x",
__entry->xid, __entry->sesid, __entry->tid, __entry->fid,
- __entry->create_options, __entry->desired_access)
+ __entry->create_options, __entry->desired_access, __entry->oplock)
)
#define DEFINE_SMB3_OPEN_DONE_EVENT(name) \
@@ -1258,12 +1359,64 @@ DEFINE_EVENT(smb3_open_done_class, smb3_##name, \
__u32 tid, \
__u64 sesid, \
int create_options, \
- int desired_access), \
- TP_ARGS(xid, fid, tid, sesid, create_options, desired_access))
+ int desired_access, \
+ __u8 oplock), \
+ TP_ARGS(xid, fid, tid, sesid, create_options, desired_access, oplock))
DEFINE_SMB3_OPEN_DONE_EVENT(open_done);
DEFINE_SMB3_OPEN_DONE_EVENT(posix_mkdir_done);
+TRACE_EVENT(smb3_open_cached,
+ TP_PROTO(unsigned int xid,
+ __u32 tid,
+ __u64 sesid,
+ __u64 fid,
+ unsigned int oflags,
+ unsigned int cflags),
+ TP_ARGS(xid, tid, sesid, fid, oflags, cflags),
+ TP_STRUCT__entry(
+ __field(unsigned int, xid)
+ __field(__u32, tid)
+ __field(__u64, sesid)
+ __field(__u64, fid)
+ __field(unsigned int, oflags)
+ __field(unsigned int, cflags)
+ ),
+ TP_fast_assign(
+ __entry->xid = xid;
+ __entry->tid = tid;
+ __entry->sesid = sesid;
+ __entry->fid = fid;
+ __entry->oflags = oflags;
+ __entry->cflags = cflags;
+ ),
+ TP_printk("xid=%u sid=0x%llx tid=0x%x fid=0x%llx oflags=0x%x cflags=0x%x",
+ __entry->xid, __entry->sesid, __entry->tid, __entry->fid,
+ __entry->oflags, __entry->cflags)
+);
+
+TRACE_EVENT(smb3_close_cached,
+ TP_PROTO(__u32 tid,
+ __u64 sesid,
+ __u64 fid,
+ unsigned long delay_jiffies),
+ TP_ARGS(tid, sesid, fid, delay_jiffies),
+ TP_STRUCT__entry(
+ __field(__u32, tid)
+ __field(__u64, sesid)
+ __field(__u64, fid)
+ __field(unsigned long, delay_jiffies)
+ ),
+ TP_fast_assign(
+ __entry->tid = tid;
+ __entry->sesid = sesid;
+ __entry->fid = fid;
+ __entry->delay_jiffies = delay_jiffies;
+ ),
+ TP_printk("sid=0x%llx tid=0x%x fid=0x%llx delay_jiffies=%lu",
+ __entry->sesid, __entry->tid, __entry->fid, __entry->delay_jiffies)
+);
+
DECLARE_EVENT_CLASS(smb3_lease_done_class,
TP_PROTO(__u32 lease_state,
diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c
index 05f8099047e1..e266859818a4 100644
--- a/fs/smb/client/transport.c
+++ b/fs/smb/client/transport.c
@@ -101,12 +101,11 @@ void __release_mid(struct TCP_Server_Info *server, struct mid_q_entry *midEntry)
trace_smb3_slow_rsp(smb_cmd, midEntry->mid, midEntry->pid,
midEntry->when_sent, midEntry->when_received);
if (cifsFYI & CIFS_TIMER) {
- pr_debug("slow rsp: cmd %d mid %llu",
- midEntry->command, midEntry->mid);
- cifs_info("A: 0x%lx S: 0x%lx R: 0x%lx\n",
- now - midEntry->when_alloc,
- now - midEntry->when_sent,
- now - midEntry->when_received);
+ pr_debug("slow rsp: cmd %d mid %llu A: 0x%lx S: 0x%lx R: 0x%lx\n",
+ midEntry->command, midEntry->mid,
+ now - midEntry->when_alloc,
+ now - midEntry->when_sent,
+ now - midEntry->when_received);
}
}
#endif
@@ -1158,7 +1157,7 @@ int
cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
{
int length, len;
- unsigned int data_offset, data_len;
+ unsigned int data_offset, data_len, end_off;
struct cifs_io_subrequest *rdata = mid->callback_data;
char *buf = server->smallbuf;
unsigned int buflen = server->pdu_size;
@@ -1256,11 +1255,14 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
use_rdma_mr = rdata->mr;
#endif
data_len = server->ops->read_data_length(buf, use_rdma_mr);
- if (!use_rdma_mr && (data_offset + data_len > buflen)) {
- /* data_len is corrupt -- discard frame */
- rdata->result = smb_EIO2(smb_eio_trace_read_rsp_malformed,
- data_offset + data_len, buflen);
- return cifs_readv_discard(server, mid);
+ if (!use_rdma_mr) {
+ if (check_add_overflow(data_offset, data_len, &end_off) ||
+ end_off > buflen) {
+ /* data_len is corrupt -- discard frame */
+ rdata->result = smb_EIO2(smb_eio_trace_read_rsp_malformed,
+ end_off, buflen);
+ return cifs_readv_discard(server, mid);
+ }
}
#ifdef CONFIG_CIFS_SMB_DIRECT
diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c
index 23227f2f9428..5091f6c0d7fe 100644
--- a/fs/smb/client/xattr.c
+++ b/fs/smb/client/xattr.c
@@ -272,7 +272,7 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
struct dentry *dentry, struct inode *inode,
const char *name, void *value, size_t size)
{
- ssize_t rc = -EOPNOTSUPP;
+ int rc = -EOPNOTSUPP;
unsigned int xid;
struct super_block *sb = dentry->d_sb;
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
@@ -354,7 +354,7 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
inode, full_path, &acllen, extra_info);
if (IS_ERR(pacl)) {
rc = PTR_ERR(pacl);
- cifs_dbg(VFS, "%s: error %zd getting sec desc\n",
+ cifs_dbg(VFS, "%s: error %d getting sec desc\n",
__func__, rc);
} else {
if (value) {
diff --git a/fs/smb/common/Makefile b/fs/smb/common/Makefile
index 9e0730a385fb..f2c6e09d4e77 100644
--- a/fs/smb/common/Makefile
+++ b/fs/smb/common/Makefile
@@ -4,3 +4,6 @@
#
obj-$(CONFIG_SMBFS) += cifs_md4.o
+obj-$(CONFIG_SMBFS) += smb_compress.o
+
+smb_compress-y := compress/compress.o compress/lz77.o
diff --git a/fs/smb/common/compress/compress.c b/fs/smb/common/compress/compress.c
new file mode 100644
index 000000000000..a4123c8f1c0a
--- /dev/null
+++ b/fs/smb/common/compress/compress.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SMB2 compression transform helpers.
+ *
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ */
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#include "compress.h"
+#include "lz77.h"
+
+#define SMB2_COMPRESSION_CHAINED_HDR_LEN \
+ offsetof(struct smb2_compression_hdr, CompressionAlgorithm)
+#define SMB2_COMPRESSION_PAYLOAD_BASE_LEN \
+ (sizeof(struct smb2_compression_payload_hdr) - sizeof(__le32))
+
+/*
+ * A NONE payload carries bytes verbatim. Keep both cursors and remaining
+ * lengths together so every chained payload handler applies identical bounds
+ * accounting.
+ */
+static int smb_decompress_none(const u8 **src, u32 *slen, u8 **dst, u32 *dlen,
+ u32 len)
+{
+ if (len > *slen || len > *dlen)
+ return -EINVAL;
+
+ memcpy(*dst, *src, len);
+ *src += len;
+ *slen -= len;
+ *dst += len;
+ *dlen -= len;
+ return 0;
+}
+
+/*
+ * Pattern_V1 represents a run of one byte. Its wire payload is always the
+ * fixed-size smb2_compression_pattern_v1 structure.
+ */
+static int smb_decompress_pattern(const u8 **src, u32 *slen, u8 **dst,
+ u32 *dlen, u32 len)
+{
+ const struct smb2_compression_pattern_v1 *pattern;
+ u32 repetitions;
+
+ if (len != sizeof(*pattern) || len > *slen)
+ return -EINVAL;
+
+ pattern = (const struct smb2_compression_pattern_v1 *)*src;
+ repetitions = le32_to_cpu(pattern->Repetitions);
+ if (repetitions > *dlen)
+ return -EINVAL;
+
+ memset(*dst, pattern->Pattern, repetitions);
+ *src += len;
+ *slen -= len;
+ *dst += repetitions;
+ *dlen -= repetitions;
+ return 0;
+}
+
+/*
+ * LZ77 payload Length includes the four-byte OriginalPayloadSize field.
+ * Consume that field before passing the compressed stream to the raw codec.
+ */
+static int smb_decompress_lz77_payload(const u8 **src, u32 *slen, u8 **dst,
+ u32 *dlen, u32 len)
+{
+ u32 orig_size;
+ int rc;
+
+ if (len < sizeof(__le32) || len > *slen)
+ return -EINVAL;
+
+ orig_size = get_unaligned_le32(*src);
+ if (orig_size > *dlen)
+ return -EINVAL;
+
+ *src += sizeof(__le32);
+ *slen -= sizeof(__le32);
+ len -= sizeof(__le32);
+
+ rc = smb_lz77_decompress(*src, len, *dst, orig_size);
+ if (rc)
+ return rc;
+
+ *src += len;
+ *slen -= len;
+ *dst += orig_size;
+ *dlen -= orig_size;
+ return 0;
+}
+
+static int smb_decompress_chained(__le16 alg, bool allow_chained,
+ bool allow_pattern,
+ const struct smb2_compression_hdr *hdr,
+ u32 slen, void *dst, u32 dlen)
+{
+ const struct smb2_compression_payload_hdr *payload;
+ const u8 *src = (const u8 *)hdr + SMB2_COMPRESSION_CHAINED_HDR_LEN;
+ u32 orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+ u32 remaining = slen - SMB2_COMPRESSION_CHAINED_HDR_LEN;
+ u8 *out = dst;
+ u32 out_remaining = dlen;
+ bool first = true;
+ int rc;
+
+ if (!allow_chained || orig_size != dlen)
+ return -EINVAL;
+
+ /*
+ * The chained transform has an eight-byte top-level header. The next
+ * bytes are a sequence of payload headers whose Length fields account
+ * for payload data, including OriginalPayloadSize where applicable.
+ */
+ while (remaining) {
+ __le16 payload_alg;
+ __le16 flags;
+ u32 len;
+
+ if (remaining < SMB2_COMPRESSION_PAYLOAD_BASE_LEN)
+ return -EINVAL;
+
+ payload = (const struct smb2_compression_payload_hdr *)src;
+ payload_alg = payload->CompressionAlgorithm;
+ flags = payload->Flags;
+ len = le32_to_cpu(payload->Length);
+
+ /*
+ * CHAINED marks only the first payload. Requiring NONE on every
+ * later payload rejects ambiguous or independently chained data.
+ */
+ if ((first && flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) ||
+ (!first && flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)))
+ return -EINVAL;
+
+ src += SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
+ remaining -= SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
+
+ if (payload_alg == SMB3_COMPRESS_NONE) {
+ rc = smb_decompress_none(&src, &remaining, &out,
+ &out_remaining, len);
+ } else if (payload_alg == SMB3_COMPRESS_PATTERN) {
+ if (!allow_pattern)
+ return -EINVAL;
+ rc = smb_decompress_pattern(&src, &remaining, &out,
+ &out_remaining, len);
+ } else if (payload_alg == alg && alg == SMB3_COMPRESS_LZ77) {
+ rc = smb_decompress_lz77_payload(&src, &remaining, &out,
+ &out_remaining, len);
+ } else {
+ return -EINVAL;
+ }
+ if (rc)
+ return rc;
+ first = false;
+ }
+
+ return out_remaining ? -EINVAL : 0;
+}
+
+static int smb_decompress_unchained(__le16 alg,
+ const struct smb2_compression_hdr *hdr,
+ u32 slen, void *dst, u32 dlen)
+{
+ u32 orig_size, offset, comp_size;
+
+ if (hdr->CompressionAlgorithm != alg ||
+ !smb_compress_alg_valid(hdr->CompressionAlgorithm, false))
+ return -EINVAL;
+
+ orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+ offset = le32_to_cpu(hdr->Offset);
+ if (offset > slen - sizeof(*hdr) || offset > dlen ||
+ orig_size > dlen - offset || orig_size + offset != dlen)
+ return -EINVAL;
+
+ memcpy(dst, (const u8 *)hdr + sizeof(*hdr), offset);
+ comp_size = slen - sizeof(*hdr) - offset;
+ return smb_lz77_decompress((const u8 *)hdr + sizeof(*hdr) + offset,
+ comp_size, (u8 *)dst + offset, orig_size);
+}
+
+/**
+ * smb_compression_decompress() - decode an SMB2 compression transform
+ * @alg: negotiated general-purpose compression algorithm
+ * @allow_chained: whether chained transforms were negotiated
+ * @allow_pattern: whether Pattern_V1 payloads were negotiated
+ * @src: transform header followed by compressed payload data
+ * @slen: total number of bytes available at @src
+ * @dst: output buffer for the reconstructed SMB2 message
+ * @dlen: exact expected size of the reconstructed SMB2 message
+ *
+ * Validate the transform type and negotiated capabilities before dispatching
+ * to the chained or unchained decoder. The caller supplies the expected output
+ * size after applying its transport-specific message size limits.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int smb_compression_decompress(__le16 alg, bool allow_chained,
+ bool allow_pattern, const void *src, u32 slen,
+ void *dst, u32 dlen)
+{
+ const struct smb2_compression_hdr *hdr = src;
+
+ if (!src || !dst || slen < sizeof(*hdr) ||
+ hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID ||
+ alg == SMB3_COMPRESS_NONE)
+ return -EINVAL;
+
+ if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED))
+ return smb_decompress_chained(alg, allow_chained, allow_pattern,
+ hdr, slen, dst, dlen);
+
+ if (hdr->Flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE))
+ return -EINVAL;
+
+ return smb_decompress_unchained(alg, hdr, slen, dst, dlen);
+}
+EXPORT_SYMBOL_GPL(smb_compression_decompress);
+
+struct smb_compression_builder {
+ u8 *pos;
+ u32 remaining;
+ bool first;
+};
+
+/*
+ * Reserve one chained payload header and initialize its common fields.
+ * OriginalPayloadSize is present only for LZNT1/LZ77/LZ77+Huffman payloads.
+ */
+static struct smb2_compression_payload_hdr *
+smb_compression_add_payload(struct smb_compression_builder *builder,
+ __le16 alg, u32 payload_len, bool orig_size)
+{
+ struct smb2_compression_payload_hdr *payload;
+ u32 hdr_len = SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
+ u32 total_len;
+
+ if (orig_size)
+ hdr_len += sizeof(payload->OriginalPayloadSize);
+ if (check_add_overflow(hdr_len, payload_len, &total_len) ||
+ total_len > builder->remaining)
+ return NULL;
+
+ payload = (struct smb2_compression_payload_hdr *)builder->pos;
+ payload->CompressionAlgorithm = alg;
+ payload->Flags = cpu_to_le16(builder->first ?
+ SMB2_COMPRESSION_FLAG_CHAINED : SMB2_COMPRESSION_FLAG_NONE);
+ payload->Length = cpu_to_le32(payload_len +
+ (orig_size ? sizeof(payload->OriginalPayloadSize) : 0));
+
+ builder->pos += hdr_len;
+ builder->remaining -= hdr_len;
+ builder->first = false;
+ return payload;
+}
+
+static int smb_compression_add_pattern(struct smb_compression_builder *builder,
+ u8 pattern, u32 repetitions)
+{
+ struct smb2_compression_pattern_v1 *payload;
+
+ if (!smb_compression_add_payload(builder, SMB3_COMPRESS_PATTERN,
+ sizeof(*payload), false))
+ return -ENOSPC;
+
+ payload = (struct smb2_compression_pattern_v1 *)builder->pos;
+ payload->Pattern = pattern;
+ payload->Reserved1 = 0;
+ payload->Reserved2 = 0;
+ payload->Repetitions = cpu_to_le32(repetitions);
+ builder->pos += sizeof(*payload);
+ builder->remaining -= sizeof(*payload);
+ return 0;
+}
+
+static int smb_compression_add_none(struct smb_compression_builder *builder,
+ const u8 *src, u32 len)
+{
+ if (!smb_compression_add_payload(builder, SMB3_COMPRESS_NONE, len, false))
+ return -ENOSPC;
+
+ memcpy(builder->pos, src, len);
+ builder->pos += len;
+ builder->remaining -= len;
+ return 0;
+}
+
+static int smb_compression_add_lz77(struct smb_compression_builder *builder,
+ const u8 *src, u32 len)
+{
+ struct smb2_compression_payload_hdr *payload;
+ u32 comp_len;
+ int rc;
+
+ if (builder->remaining <= sizeof(*payload))
+ return -ENOSPC;
+
+ comp_len = builder->remaining - sizeof(*payload);
+ payload = smb_compression_add_payload(builder, SMB3_COMPRESS_LZ77,
+ comp_len, true);
+ if (!payload)
+ return -ENOSPC;
+
+ rc = smb_lz77_compress(src, len, builder->pos, &comp_len);
+ if (rc)
+ return rc;
+
+ payload->Length = cpu_to_le32(comp_len +
+ sizeof(payload->OriginalPayloadSize));
+ payload->OriginalPayloadSize = cpu_to_le32(len);
+ builder->pos += comp_len;
+ builder->remaining -= comp_len;
+ return 0;
+}
+
+/**
+ * smb_compression_compress_chained() - build a chained SMB2 transform
+ * @alg: negotiated general-purpose compression algorithm
+ * @allow_pattern: whether Pattern_V1 was negotiated
+ * @src: complete uncompressed SMB2 message
+ * @slen: size of @src
+ * @dst: output buffer for the transform
+ * @dlen: input capacity of @dst and output transform size
+ *
+ * Following the algorithm in [MS-SMB2] 3.1.4.4, encode sufficiently long
+ * repeated runs at the front and back as Pattern_V1 payloads. Compress a
+ * middle region larger than 1 KiB with LZ77; smaller middle regions are
+ * represented by a chained NONE payload.
+ *
+ * This helper does not decide whether the final transform is smaller than the
+ * original message. The transport caller owns that policy decision.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int smb_compression_compress_chained(__le16 alg, bool allow_pattern,
+ const void *src, u32 slen,
+ void *dst, u32 *dlen)
+{
+ struct smb2_compression_hdr *hdr = dst;
+ struct smb_compression_builder builder;
+ const u8 *input = src;
+ u32 forward = 0, backward = 0, middle_len;
+ int rc;
+
+ if (!src || !dst || !dlen || alg != SMB3_COMPRESS_LZ77 ||
+ *dlen <= SMB2_COMPRESSION_CHAINED_HDR_LEN || !slen)
+ return -EINVAL;
+
+ hdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
+ hdr->OriginalCompressedSegmentSize = cpu_to_le32(slen);
+ builder.pos = (u8 *)dst + SMB2_COMPRESSION_CHAINED_HDR_LEN;
+ builder.remaining = *dlen - SMB2_COMPRESSION_CHAINED_HDR_LEN;
+ builder.first = true;
+
+ if (allow_pattern && slen > 32) {
+ for (forward = 1; forward < slen; forward++) {
+ if (input[forward] != input[0])
+ break;
+ }
+ if (forward <= 32)
+ forward = 0;
+
+ for (backward = 1; backward < slen - forward; backward++) {
+ if (input[slen - backward - 1] != input[slen - 1])
+ break;
+ }
+ if (backward <= 32)
+ backward = 0;
+ }
+
+ if (forward) {
+ rc = smb_compression_add_pattern(&builder, input[0], forward);
+ if (rc)
+ return rc;
+ }
+
+ middle_len = slen - forward - backward;
+ if (middle_len > 1024)
+ rc = smb_compression_add_lz77(&builder, input + forward,
+ middle_len);
+ else if (middle_len)
+ rc = smb_compression_add_none(&builder,
+ input + forward, middle_len);
+ else
+ rc = 0;
+ if (rc)
+ return rc;
+
+ if (backward) {
+ rc = smb_compression_add_pattern(&builder, input[slen - 1],
+ backward);
+ if (rc)
+ return rc;
+ }
+
+ *dlen = builder.pos - (u8 *)dst;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smb_compression_compress_chained);
diff --git a/fs/smb/common/compress/compress.h b/fs/smb/common/compress/compress.h
new file mode 100644
index 000000000000..d6916669f887
--- /dev/null
+++ b/fs/smb/common/compress/compress.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ */
+#ifndef _COMMON_SMB_COMPRESS_H
+#define _COMMON_SMB_COMPRESS_H
+
+#include "../smb2pdu.h"
+
+/*
+ * SMB3_COMPRESS_NONE is valid only in chained payload headers. It is never
+ * negotiated as a compression algorithm.
+ */
+static __always_inline bool smb_compress_alg_valid(__le16 alg, bool valid_none)
+{
+ if (alg == SMB3_COMPRESS_NONE)
+ return valid_none;
+
+ return alg == SMB3_COMPRESS_LZ77 || alg == SMB3_COMPRESS_PATTERN;
+}
+
+int smb_compression_decompress(__le16 alg, bool allow_chained,
+ bool allow_pattern, const void *src, u32 slen,
+ void *dst, u32 dlen);
+int smb_compression_compress_chained(__le16 alg, bool allow_pattern,
+ const void *src, u32 slen,
+ void *dst, u32 *dlen);
+
+#endif /* _COMMON_SMB_COMPRESS_H */
diff --git a/fs/smb/common/compress/lz77.c b/fs/smb/common/compress/lz77.c
new file mode 100644
index 000000000000..9216d973d876
--- /dev/null
+++ b/fs/smb/common/compress/lz77.c
@@ -0,0 +1,461 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2026, SUSE LLC
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ *
+ * Authors: Enzo Matsumiya <ematsumiya@suse.de>
+ * Namjae Jeon <linkinjeon@kernel.org>
+ *
+ * Implementation of the LZ77 "plain" compression algorithm, as per MS-XCA spec.
+ */
+#include <linux/slab.h>
+#include <linux/sizes.h>
+#include <linux/count_zeros.h>
+#include <linux/unaligned.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+
+#include "lz77.h"
+
+/*
+ * Compression parameters.
+ *
+ * LZ77_MATCH_MAX_DIST: Farthest back a match can be from current position (can be 1 - 8K).
+ * LZ77_HASH_LOG:
+ * LZ77_HASH_SIZE: ilog2 hash size (recommended to be 13 - 18, default 15 (hash size
+ * 32k)).
+ * LZ77_RSTEP_SIZE: Number of bytes to read from input buffer for hashing and initial
+ * match check (default 4 bytes, this effectivelly makes this the min
+ * match len).
+ * LZ77_MSTEP_SIZE: Number of bytes to extend-compare a found match (default 8 bytes).
+ * LZ77_SKIP_TRIGGER: ilog2 value for adaptive skipping, i.e. to progressively skip input
+ * bytes when we can't find matches. Default is 4.
+ * Higher values (>0) will decrease compression time, but will result
+ * in worse compression ratio. Lower values will give better
+ * compression ratio (more matches found), but will increase time.
+ */
+#define LZ77_MATCH_MAX_DIST SZ_8K
+#define LZ77_HASH_LOG 15
+#define LZ77_HASH_SIZE BIT(LZ77_HASH_LOG)
+#define LZ77_RSTEP_SIZE sizeof(u32)
+#define LZ77_MSTEP_SIZE sizeof(u64)
+#define LZ77_SKIP_TRIGGER 4
+
+#define LZ77_PREFETCH(ptr) __builtin_prefetch((ptr), 0, 3)
+#define LZ77_FLAG_MAX 32
+
+static __always_inline u8 lz77_read8(const u8 *ptr)
+{
+ return get_unaligned(ptr);
+}
+
+static __always_inline u32 lz77_read32(const u32 *ptr)
+{
+ return get_unaligned(ptr);
+}
+
+static __always_inline u64 lz77_read64(const u64 *ptr)
+{
+ return get_unaligned(ptr);
+}
+
+static __always_inline void lz77_write8(u8 *ptr, u8 v)
+{
+ put_unaligned(v, ptr);
+}
+
+static __always_inline void lz77_write16(u16 *ptr, u16 v)
+{
+ put_unaligned_le16(v, ptr);
+}
+
+static __always_inline void lz77_write32(u32 *ptr, u32 v)
+{
+ put_unaligned_le32(v, ptr);
+}
+
+static __always_inline u32 lz77_match_len(const void *match, const void *cur, const void *end)
+{
+ const void *start = cur;
+
+ /* Safe for a do/while because otherwise we wouldn't reach here from the main loop. */
+ do {
+ const u64 diff = lz77_read64(cur) ^ lz77_read64(match);
+
+ if (!diff) {
+ cur += LZ77_MSTEP_SIZE;
+ match += LZ77_MSTEP_SIZE;
+
+ continue;
+ }
+
+ /* This computes the number of common bytes in @diff. */
+ cur += count_trailing_zeros(diff) >> 3;
+
+ return (cur - start);
+ } while (likely(cur + LZ77_MSTEP_SIZE <= end));
+
+ /* Fallback to byte-by-byte comparison for last <8 bytes. */
+ while (cur < end && lz77_read8(cur) == lz77_read8(match)) {
+ cur++;
+ match++;
+ }
+
+ return (cur - start);
+}
+
+/**
+ * lz77_encode_match() - Match encoding.
+ * @dst: compressed buffer
+ * @nib: pointer to an address in @dst
+ * @dist: match distance
+ * @len: match length
+ *
+ * Assumes all args were previously checked.
+ *
+ * Return: @dst advanced to new position
+ *
+ * Ref: MS-XCA 2.3.4 "Plain LZ77 Compression Algorithm Details" - "Processing"
+ */
+static __always_inline void *lz77_encode_match(void *dst, void **nib, u16 dist, u32 len)
+{
+ len -= 3;
+ dist--;
+ dist <<= 3;
+
+ if (len < 7) {
+ lz77_write16(dst, dist + len);
+
+ return dst + sizeof(u16);
+ }
+
+ dist |= 7;
+ lz77_write16(dst, dist);
+ dst += sizeof(u16);
+ len -= 7;
+
+ if (!*nib) {
+ lz77_write8(dst, umin(len, 15));
+ *nib = dst;
+ dst++;
+ } else {
+ u8 *b = *nib;
+
+ lz77_write8(b, *b | umin(len, 15) << 4);
+ *nib = NULL;
+ }
+
+ if (len < 15)
+ return dst;
+
+ len -= 15;
+ if (len < 255) {
+ lz77_write8(dst, len);
+
+ return dst + 1;
+ }
+
+ lz77_write8(dst, 0xff);
+ dst++;
+ len += 7 + 15;
+ if (len <= 0xffff) {
+ lz77_write16(dst, len);
+
+ return dst + sizeof(u16);
+ }
+
+ lz77_write16(dst, 0);
+ dst += sizeof(u16);
+ lz77_write32(dst, len);
+
+ return dst + sizeof(u32);
+}
+
+/**
+ * lz77_encode_literals() - Literals encoding.
+ * @start: where to start copying literals (uncompressed buffer)
+ * @end: when to stop copying (uncompressed buffer)
+ * @dst: compressed buffer
+ * @f: pointer to current flag value
+ * @fc: pointer to current flag count
+ * @fp: pointer to current flag address
+ *
+ * Batch copy literals from @start to @dst, updating flag values accordingly.
+ * Assumes all args were previously checked.
+ *
+ * Return: @dst advanced to new position
+ *
+ * MS-XCA 2.3.4 "Plain LZ77 Compression Algorithm Details" - "Processing"
+ */
+static __always_inline void *lz77_encode_literals(const void *start, const void *end, void *dst,
+ long *f, u32 *fc, void **fp)
+{
+ if (start >= end)
+ return dst;
+
+ do {
+ const u32 len = umin(end - start, LZ77_FLAG_MAX - *fc);
+
+ memcpy(dst, start, len);
+
+ dst += len;
+ start += len;
+
+ *f <<= len;
+ *fc += len;
+ if (*fc == LZ77_FLAG_MAX) {
+ lz77_write32(*fp, *f);
+ *fc = 0;
+ *fp = dst;
+ dst += sizeof(u32);
+ }
+ } while (start < end);
+
+ return dst;
+}
+
+static __always_inline u32 lz77_hash(const u32 v)
+{
+ return ((v ^ 0x9E3779B9) * 0x85EBCA6B) >> (32 - LZ77_HASH_LOG);
+}
+
+noinline int smb_lz77_compress(const void *src, const u32 slen,
+ void *dst, u32 *dlen)
+{
+ const void *srcp, *rlim, *end, *anchor;
+ u32 *htable, hash, flag_count = 0;
+ void *dstp, *nib, *flag_pos;
+ long flag = 0;
+
+ /* This is probably a bug, so throw a warning. */
+ if (WARN_ON_ONCE(*dlen < smb_lz77_compressed_alloc_size(slen)))
+ return -EINVAL;
+
+ srcp = src;
+ anchor = src;
+ end = srcp + slen; /* absolute end */
+ rlim = end - LZ77_MSTEP_SIZE; /* read limit (for lz77_match_len()) */
+ dstp = dst;
+ flag_pos = dstp;
+ dstp += sizeof(u32);
+ nib = NULL;
+
+ htable = kvcalloc(LZ77_HASH_SIZE, sizeof(*htable), GFP_KERNEL);
+ if (!htable)
+ return -ENOMEM;
+
+ LZ77_PREFETCH(srcp + LZ77_RSTEP_SIZE);
+
+ /*
+ * Adjust @srcp so we don't get a false positive match on first iteration.
+ * Then prepare hash for first loop iteration (don't advance @srcp again).
+ */
+ hash = lz77_hash(lz77_read32(srcp++));
+ htable[hash] = 0;
+ hash = lz77_hash(lz77_read32(srcp));
+
+ /*
+ * Main loop.
+ *
+ * @dlen is >= smb_lz77_compressed_alloc_size(), so run without
+ * bound-checking @dstp.
+ *
+ * This code was crafted in a way to best utilise fetch-decode-execute CPU flow.
+ * Any attempt to optimize it, or even organize it, can lead to huge performance loss.
+ */
+ do {
+ const void *match, *next = srcp;
+ u32 len, step = 1, skip = 1U << LZ77_SKIP_TRIGGER;
+
+ /* Match finding (hot path -- don't change the read/check/write order). */
+ do {
+ const u32 cur_hash = hash;
+
+ srcp = next;
+ next += step;
+
+ /*
+ * Adaptive skipping.
+ *
+ * Increment @step every (1 << LZ77_SKIP_TRIGGER, 16 in our case) bytes
+ * without a match.
+ * Reset to 1 when a match is found.
+ */
+ step = (skip++ >> LZ77_SKIP_TRIGGER);
+ if (unlikely(next > rlim))
+ goto out;
+
+ hash = lz77_hash(lz77_read32(next));
+ match = src + htable[cur_hash];
+ htable[cur_hash] = srcp - src;
+ } while (likely(match + LZ77_MATCH_MAX_DIST < srcp) ||
+ lz77_read32(match) != lz77_read32(srcp));
+
+ /*
+ * Match found. Warm/cold path; begin parsing @srcp and writing to @dstp:
+ * - flush literals
+ * - compute match length (*)
+ * - encode match
+ *
+ * (*) Current minimum match length is defined by the memory read size above, so
+ * here we already know that we have 4 matching bytes, but it's just faster to
+ * redundantly compute it again in lz77_match_len() than to adjust pointers/len.
+ */
+ dstp = lz77_encode_literals(anchor, srcp, dstp, &flag, &flag_count, &flag_pos);
+ len = lz77_match_len(match, srcp, end);
+ dstp = lz77_encode_match(dstp, &nib, srcp - match, len);
+ srcp += len;
+ anchor = srcp;
+
+ LZ77_PREFETCH(srcp);
+
+ flag = (flag << 1) | 1;
+ flag_count++;
+ if (flag_count == LZ77_FLAG_MAX) {
+ lz77_write32(flag_pos, flag);
+ flag_count = 0;
+ flag_pos = dstp;
+ dstp += sizeof(u32);
+ }
+
+ if (unlikely(srcp > rlim))
+ break;
+
+ /* Prepare for next loop. */
+ hash = lz77_hash(lz77_read32(srcp));
+ } while (srcp < end);
+out:
+ dstp = lz77_encode_literals(anchor, end, dstp, &flag, &flag_count, &flag_pos);
+
+ flag_count = LZ77_FLAG_MAX - flag_count;
+ flag <<= flag_count;
+ flag |= (1UL << flag_count) - 1;
+ lz77_write32(flag_pos, flag);
+
+ *dlen = dstp - dst;
+ kvfree(htable);
+
+ if (*dlen < slen)
+ return 0;
+
+ return -EMSGSIZE;
+}
+EXPORT_SYMBOL_GPL(smb_lz77_compress);
+
+static int lz77_decode_match_len(const u8 **src, const u8 *end, u16 token,
+ u8 *nibble, bool *have_nibble, u32 *len)
+{
+ u8 extra;
+
+ *len = (token & 0x7) + 3;
+ if ((token & 0x7) != 0x7)
+ return 0;
+
+ if (!*have_nibble) {
+ if (*src >= end)
+ return -EINVAL;
+ *nibble = *(*src)++;
+ extra = *nibble & 0xf;
+ *have_nibble = true;
+ } else {
+ extra = *nibble >> 4;
+ *have_nibble = false;
+ }
+
+ *len += extra;
+ if (extra == 0xf) {
+ u8 b;
+
+ if (*src >= end)
+ return -EINVAL;
+ b = *(*src)++;
+ if (b != 0xff) {
+ *len += b;
+ } else {
+ u16 w;
+
+ if (end - *src < 2)
+ return -EINVAL;
+ w = get_unaligned_le16(*src);
+ *src += 2;
+ if (w) {
+ *len = w + 3;
+ } else {
+ u32 long_len;
+
+ if (end - *src < 4)
+ return -EINVAL;
+ long_len = get_unaligned_le32(*src);
+ *src += 4;
+ if (check_add_overflow(long_len, 3, len))
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int smb_lz77_decompress(const void *src, const u32 slen, void *dst,
+ const u32 dlen)
+{
+ const u8 *sp = src, *send = sp + slen;
+ u8 *dp = dst, *dend = dp + dlen;
+ u32 flags = 0;
+ int flag_count = 0;
+ u8 nibble = 0;
+ bool have_nibble = false;
+
+ while (dp < dend) {
+ u32 len, dist;
+ u16 token;
+
+ if (!flag_count) {
+ if (send - sp < 4)
+ return -EINVAL;
+ flags = get_unaligned_le32(sp);
+ sp += 4;
+ flag_count = 32;
+ }
+
+ if (!(flags & 0x80000000)) {
+ if (sp >= send)
+ return -EINVAL;
+ *dp++ = *sp++;
+ flags <<= 1;
+ flag_count--;
+ continue;
+ }
+
+ flags <<= 1;
+ flag_count--;
+
+ if (send - sp < 2)
+ return -EINVAL;
+
+ token = get_unaligned_le16(sp);
+ sp += 2;
+
+ dist = (token >> 3) + 1;
+ if (dist > dp - (u8 *)dst)
+ return -EINVAL;
+
+ if (lz77_decode_match_len(&sp, send, token, &nibble,
+ &have_nibble, &len))
+ return -EINVAL;
+
+ if (len > dend - dp)
+ return -EINVAL;
+
+ while (len--) {
+ *dp = *(dp - dist);
+ dp++;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smb_lz77_decompress);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SMB plain LZ77 compression");
diff --git a/fs/smb/common/compress/lz77.h b/fs/smb/common/compress/lz77.h
new file mode 100644
index 000000000000..e032c0f1b48d
--- /dev/null
+++ b/fs/smb/common/compress/lz77.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024-2026, SUSE LLC
+ *
+ * Authors: Enzo Matsumiya <ematsumiya@suse.de>
+ *
+ * Implementation of the LZ77 "plain" compression algorithm, as per MS-XCA spec.
+ */
+#ifndef _SMB_COMPRESS_LZ77_H
+#define _SMB_COMPRESS_LZ77_H
+
+#include <linux/kernel.h>
+
+/**
+ * smb_lz77_compressed_alloc_size() - Compute compressed buffer size.
+ * @size: uncompressed (src) size
+ *
+ * Compute allocation size for the compressed buffer based on uncompressed size.
+ * Accounts for metadata and overprovision for the worst case scenario.
+ *
+ * LZ77 metadata is a 4-byte flag that is written:
+ * - on dst begin (pos 0)
+ * - every 32 literals or matches
+ * - on end-of-stream (possibly, if last write was another flag)
+ *
+ * Worst case scenario is an all-literal compression, which means:
+ * metadata bytes = 4 + ((@size / 32) * 4) + 4, or, simplified, (@size >> 3) + 8
+ *
+ * The worst case scenario rarely happens, but such overprovisioning also
+ * allows smb_lz77_compress() main loop to run without ever bound checking dst,
+ * which is a huge perf improvement, while also being safe when compression goes
+ * bad.
+ *
+ * Return: required (*) allocation size for compressed buffer.
+ *
+ * (*) checked once in the beginning of smb_lz77_compress()
+ */
+static __always_inline u32 smb_lz77_compressed_alloc_size(const u32 size)
+{
+ return size + (size >> 3) + 8;
+}
+
+int smb_lz77_compress(const void *src, const u32 slen, void *dst, u32 *dlen);
+int smb_lz77_decompress(const void *src, const u32 slen, void *dst,
+ const u32 dlen);
+#endif /* _SMB_COMPRESS_LZ77_H */
diff --git a/fs/smb/common/fscc.h b/fs/smb/common/fscc.h
index 0123f34db1e8..e46d3379b779 100644
--- a/fs/smb/common/fscc.h
+++ b/fs/smb/common/fscc.h
@@ -12,6 +12,253 @@
#ifndef _COMMON_SMB_FSCC_H
#define _COMMON_SMB_FSCC_H
+/* Reparse structures - see MS-FSCC 2.1.2 */
+
+/* struct fsctl_reparse_info_req is empty, only response structs (see below) */
+struct reparse_data_buffer {
+ __le32 ReparseTag;
+ __le16 ReparseDataLength;
+ __u16 Reserved;
+ __u8 DataBuffer[]; /* Variable Length */
+} __packed;
+
+struct reparse_guid_data_buffer {
+ __le32 ReparseTag;
+ __le16 ReparseDataLength;
+ __u16 Reserved;
+ __u8 ReparseGuid[16];
+ __u8 DataBuffer[]; /* Variable Length */
+} __packed;
+
+struct reparse_mount_point_data_buffer {
+ __le32 ReparseTag;
+ __le16 ReparseDataLength;
+ __u16 Reserved;
+ __le16 SubstituteNameOffset;
+ __le16 SubstituteNameLength;
+ __le16 PrintNameOffset;
+ __le16 PrintNameLength;
+ __u8 PathBuffer[]; /* Variable Length */
+} __packed;
+
+#define SYMLINK_FLAG_RELATIVE 0x00000001
+
+struct reparse_symlink_data_buffer {
+ __le32 ReparseTag;
+ __le16 ReparseDataLength;
+ __u16 Reserved;
+ __le16 SubstituteNameOffset;
+ __le16 SubstituteNameLength;
+ __le16 PrintNameOffset;
+ __le16 PrintNameLength;
+ __le32 Flags;
+ __u8 PathBuffer[]; /* Variable Length */
+} __packed;
+
+/* For IO_REPARSE_TAG_NFS - see MS-FSCC 2.1.2.6 */
+#define NFS_SPECFILE_LNK 0x00000000014B4E4C
+#define NFS_SPECFILE_CHR 0x0000000000524843
+#define NFS_SPECFILE_BLK 0x00000000004B4C42
+#define NFS_SPECFILE_FIFO 0x000000004F464946
+#define NFS_SPECFILE_SOCK 0x000000004B434F53
+struct reparse_nfs_data_buffer {
+ __le32 ReparseTag;
+ __le16 ReparseDataLength;
+ __u16 Reserved;
+ __le64 InodeType; /* NFS_SPECFILE_* */
+ __u8 DataBuffer[];
+} __packed;
+
+/* For IO_REPARSE_TAG_LX_SYMLINK - see MS-FSCC 2.1.2.7 */
+struct reparse_wsl_symlink_data_buffer {
+ __le32 ReparseTag;
+ __le16 ReparseDataLength;
+ __u16 Reserved;
+ __le32 Version; /* Always 2 */
+ __u8 Target[]; /* Variable Length UTF-8 string without nul-term */
+} __packed;
+
+/* See MS-FSCC 2.3.7 */
+struct duplicate_extents_to_file {
+ __u64 PersistentFileHandle; /* source file handle, opaque endianness */
+ __u64 VolatileFileHandle;
+ __le64 SourceFileOffset;
+ __le64 TargetFileOffset;
+ __le64 ByteCount; /* Bytes to be copied */
+} __packed;
+
+/* See MS-FSCC 2.3.9 */
+#define DUPLICATE_EXTENTS_DATA_EX_SOURCE_ATOMIC 0x00000001
+struct duplicate_extents_to_file_ex {
+ __le64 StructureSize; /* MUST be set to 0x30 */
+ __u64 PersistentFileHandle; /* source file handle, opaque endianness */
+ __u64 VolatileFileHandle;
+ __le64 SourceFileOffset;
+ __le64 TargetFileOffset;
+ __le64 ByteCount; /* Bytes to be copied */
+ __le32 Flags;
+ __le32 Reserved;
+} __packed;
+
+/*
+ * compression state flags
+ * See MS-FSCC 2.3.18
+ * MS-FSCC 2.3.67
+ * MS-FSCC 2.4.9
+ */
+#define COMPRESSION_FORMAT_NONE 0x0000
+#define COMPRESSION_FORMAT_DEFAULT 0x0001
+#define COMPRESSION_FORMAT_LZNT1 0x0002
+
+/*
+ * See MS-FSCC 2.3.18
+ * MS-FSCC 2.3.67
+ */
+struct compress_ioctl {
+ __le16 CompressionState;
+} __packed;
+
+/* See MS-FSCC 2.3.20 */
+struct fsctl_get_integrity_information_rsp {
+ __le16 ChecksumAlgorithm;
+ __le16 Reserved;
+ __le32 Flags;
+ __le32 ChecksumChunkSizeInBytes;
+ __le32 ClusterSizeInBytes;
+} __packed;
+
+/* See MS-FSCC 2.3.52 */
+struct file_allocated_range_buffer {
+ __le64 file_offset;
+ __le64 length;
+} __packed;
+
+/* See MS-FSCC 2.3.55 */
+struct fsctl_query_file_regions_req {
+ __le64 FileOffset;
+ __le64 Length;
+ __le32 DesiredUsage;
+ __le32 Reserved;
+} __packed;
+
+/* DesiredUsage flags see MS-FSCC 2.3.56.1 */
+#define FILE_USAGE_INVALID_RANGE 0x00000000
+#define FILE_USAGE_VALID_CACHED_DATA 0x00000001
+#define FILE_USAGE_NONCACHED_DATA 0x00000002
+struct file_region_info {
+ __le64 FileOffset;
+ __le64 Length;
+ __le32 DesiredUsage;
+ __le32 Reserved;
+} __packed;
+
+/* See MS-FSCC 2.3.56 */
+struct fsctl_query_file_region_rsp {
+ __le32 Flags;
+ __le32 TotalRegionEntryCount;
+ __le32 RegionEntryCount;
+ __u32 Reserved;
+ struct file_region_info Regions[];
+} __packed;
+
+/* See MS-FSCC 2.3.58 */
+struct fsctl_query_on_disk_vol_info_rsp {
+ __le64 DirectoryCount;
+ __le64 FileCount;
+ __le16 FsFormatMajVersion;
+ __le16 FsFormatMinVersion;
+ __u8 FsFormatName[24];
+ __le64 FormatTime;
+ __le64 LastUpdateTime;
+ __u8 CopyrightInfo[68];
+ __u8 AbstractInfo[68];
+ __u8 FormatImplInfo[68];
+ __u8 LastModifyImplInfo[68];
+} __packed;
+
+/* See MS-FSCC 2.3.73 */
+struct fsctl_set_integrity_information_req {
+ __le16 ChecksumAlgorithm;
+ __le16 Reserved;
+ __le32 Flags;
+} __packed;
+
+/* See MS-FSCC 2.3.75 */
+struct fsctl_set_integrity_info_ex_req {
+ __u8 EnableIntegrity;
+ __u8 KeepState;
+ __u16 Reserved;
+ __le32 Flags;
+ __u8 Version;
+ __u8 Reserved2[7];
+} __packed;
+
+/*
+ * this goes in the ioctl buffer when doing FSCTL_SET_ZERO_DATA
+ * See MS-FSCC 2.3.85
+ */
+struct file_zero_data_information {
+ __le64 FileOffset;
+ __le64 BeyondFinalZero;
+} __packed;
+
+struct file_level_trim_range {
+ __le64 Offset;
+ __le64 Length;
+} __packed;
+
+struct file_level_trim {
+ __le32 Key;
+ __le32 NumRanges;
+ struct file_level_trim_range Ranges[];
+} __packed;
+
+struct file_level_trim_output {
+ __le32 NumRangesProcessed;
+} __packed;
+
+/*
+ * This level 18, although with struct with same name is different from cifs
+ * level 0x107. Level 0x107 has an extra u64 between AccessFlags and
+ * CurrentByteOffset.
+ * See MS-FSCC 2.4.2
+ */
+struct smb2_file_all_info { /* data block encoding of response to level 18 */
+ __le64 CreationTime; /* Beginning of FILE_BASIC_INFO equivalent */
+ __le64 LastAccessTime;
+ __le64 LastWriteTime;
+ __le64 ChangeTime;
+ __le32 Attributes;
+ __u32 Pad1; /* End of FILE_BASIC_INFO_INFO equivalent */
+ __le64 AllocationSize; /* Beginning of FILE_STANDARD_INFO equivalent */
+ __le64 EndOfFile; /* size ie offset to first free byte in file */
+ __le32 NumberOfLinks; /* hard links */
+ __u8 DeletePending;
+ __u8 Directory;
+ __u16 Pad2; /* End of FILE_STANDARD_INFO equivalent */
+ __le64 IndexNumber;
+ __le32 EASize;
+ __le32 AccessFlags;
+ __le64 CurrentByteOffset;
+ __le32 Mode;
+ __le32 AlignmentRequirement;
+ __le32 FileNameLength;
+ union {
+ char __pad; /* Legacy structure padding */
+ DECLARE_FLEX_ARRAY(char, FileName);
+ };
+} __packed; /* level 18 Query */
+
+/* See MS-FSCC 2.4.7 */
+typedef struct file_basic_info { /* data block encoding of response to level 18 */
+ __le64 CreationTime;
+ __le64 LastAccessTime;
+ __le64 LastWriteTime;
+ __le64 ChangeTime;
+ __le32 Attributes;
+ __u32 Pad;
+} __packed FILE_BASIC_INFO; /* size info, level 0x101 */
+
/* See MS-FSCC 2.4.8 */
typedef struct {
__le32 NextEntryOffset;
@@ -47,6 +294,16 @@ typedef struct {
} __packed FILE_DIRECTORY_INFO; /* level 0x101 FF resp data */
/* See MS-FSCC 2.4.14 */
+struct smb2_file_eof_info { /* encoding of request for level 10 */
+ __le64 EndOfFile; /* new end of file value */
+} __packed; /* level 20 Set */
+
+/* See MS-FSCC 2.4.4 */
+struct smb2_file_alloc_info { /* encoding of request for level 19 */
+ __le64 AllocationSize;
+} __packed;
+
+/* See MS-FSCC 2.4.15 */
typedef struct {
__le32 NextEntryOffset;
__u32 FileIndex;
@@ -80,6 +337,26 @@ typedef struct {
char FileName[];
} __packed FILE_ID_FULL_DIR_INFO; /* level 0x105 FF rsp data */
+/* See MS-FSCC 2.4.27 */
+struct smb2_file_internal_info {
+ __le64 IndexNumber;
+} __packed; /* level 6 Query */
+
+/* See MS-FSCC 2.4.28.2 */
+struct smb2_file_link_info { /* encoding of request for level 11 */
+ /* New members MUST be added within the struct_group() macro below. */
+ __struct_group(smb2_file_link_info_hdr, __hdr, __packed,
+ __u8 ReplaceIfExists; /* 1 = replace existing link with new */
+ /* 0 = fail if link already exists */
+ __u8 Reserved[7];
+ __u64 RootDirectory; /* MBZ for network operations (why says spec?) */
+ __le32 FileNameLength;
+ );
+ char FileName[]; /* Name to be assigned to new link */
+} __packed; /* level 11 Set */
+static_assert(offsetof(struct smb2_file_link_info, FileName) == sizeof(struct smb2_file_link_info_hdr),
+ "struct member likely outside of __struct_group()");
+
/* See MS-FSCC 2.4.34 */
struct smb2_file_network_open_info {
struct_group_attr(network_open_info, __packed,
@@ -94,6 +371,37 @@ struct smb2_file_network_open_info {
__le32 Reserved;
} __packed; /* level 34 Query also similar returned in close rsp and open rsp */
+/* See MS-FSCC 2.4.42.2 */
+struct smb2_file_rename_info { /* encoding of request for level 10 */
+ /* New members MUST be added within the struct_group() macro below. */
+ __struct_group(smb2_file_rename_info_hdr, __hdr, __packed,
+ __u8 ReplaceIfExists; /* 1 = replace existing target with new */
+ /* 0 = fail if target already exists */
+ __u8 Reserved[7];
+ __u64 RootDirectory; /* MBZ for network operations (why says spec?) */
+ __le32 FileNameLength;
+ );
+ char FileName[]; /* New name to be assigned */
+ /* padding - overall struct size must be >= 24 so filename + pad >= 6 */
+} __packed; /* level 10 Set */
+static_assert(offsetof(struct smb2_file_rename_info, FileName) == sizeof(struct smb2_file_rename_info_hdr),
+ "struct member likely outside of __struct_group()");
+
+/* File System Information Classes */
+/* See MS-FSCC 2.5 */
+#define FS_VOLUME_INFORMATION 1 /* Query */
+#define FS_LABEL_INFORMATION 2 /* Set */
+#define FS_SIZE_INFORMATION 3 /* Query */
+#define FS_DEVICE_INFORMATION 4 /* Query */
+#define FS_ATTRIBUTE_INFORMATION 5 /* Query */
+#define FS_CONTROL_INFORMATION 6 /* Query, Set */
+#define FS_FULL_SIZE_INFORMATION 7 /* Query */
+#define FS_OBJECT_ID_INFORMATION 8 /* Query, Set */
+#define FS_DRIVER_PATH_INFORMATION 9 /* Query */
+#define FS_SECTOR_SIZE_INFORMATION 11 /* SMB3 or later. Query */
+/* See POSIX Extensions to MS-FSCC 2.3.1.1 */
+#define FS_POSIX_INFORMATION 100 /* SMB3.1.1 POSIX. Query */
+
/* See MS-FSCC 2.5.1 */
#define MAX_FS_NAME_LEN 52
typedef struct {
@@ -130,6 +438,45 @@ typedef struct {
#define FILE_CASE_PRESERVED_NAMES 0x00000002
#define FILE_CASE_SENSITIVE_SEARCH 0x00000001
+/*
+ * File System Control Information
+ * See MS-FSCC 2.5.2
+ */
+struct smb2_fs_control_info {
+ __le64 FreeSpaceStartFiltering;
+ __le64 FreeSpaceThreshold;
+ __le64 FreeSpaceStopFiltering;
+ __le64 DefaultQuotaThreshold;
+ __le64 DefaultQuotaLimit;
+ __le32 FileSystemControlFlags;
+ __le32 Padding;
+} __packed;
+
+/* See MS-FSCC 2.5.4 */
+struct smb2_fs_full_size_info {
+ __le64 TotalAllocationUnits;
+ __le64 CallerAvailableAllocationUnits;
+ __le64 ActualAvailableAllocationUnits;
+ __le32 SectorsPerAllocationUnit;
+ __le32 BytesPerSector;
+} __packed;
+
+/* See MS-FSCC 2.5.7 */
+#define SSINFO_FLAGS_ALIGNED_DEVICE 0x00000001
+#define SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE 0x00000002
+#define SSINFO_FLAGS_NO_SEEK_PENALTY 0x00000004
+#define SSINFO_FLAGS_TRIM_ENABLED 0x00000008
+/* sector size info struct */
+struct smb3_fs_ss_info {
+ __le32 LogicalBytesPerSector;
+ __le32 PhysicalBytesPerSectorForAtomicity;
+ __le32 PhysicalBytesPerSectorForPerf;
+ __le32 FSEffPhysicalBytesPerSectorForAtomicity;
+ __le32 Flags;
+ __le32 ByteOffsetForSectorAlignment;
+ __le32 ByteOffsetForPartitionAlignment;
+} __packed;
+
/* See MS-FSCC 2.5.8 */
typedef struct {
__le64 TotalAllocationUnits;
@@ -138,6 +485,17 @@ typedef struct {
__le32 BytesPerSector;
} __packed FILE_SYSTEM_SIZE_INFO; /* size info, level 0x103 */
+/* volume info struct - see MS-FSCC 2.5.9 */
+#define MAX_VOL_LABEL_LEN 32
+struct filesystem_vol_info {
+ __le64 VolumeCreationTime;
+ __le32 VolumeSerialNumber;
+ __le32 VolumeLabelLength; /* includes trailing null */
+ __u8 SupportsObjects; /* True if eg like NTFS, supports objects */
+ __u8 Reserved;
+ __u8 VolumeLabel[]; /* variable len */
+} __packed;
+
/* See MS-FSCC 2.5.10 */
typedef struct {
__le32 DeviceType;
@@ -190,6 +548,22 @@ typedef struct {
#define FILE_ATTRIBUTE_MASK_LE cpu_to_le32(FILE_ATTRIBUTE_MASK)
/*
+ * SMB2 Notify Action Flags
+ * See MS-FSCC 2.7.1
+ */
+#define FILE_ACTION_ADDED 0x00000001
+#define FILE_ACTION_REMOVED 0x00000002
+#define FILE_ACTION_MODIFIED 0x00000003
+#define FILE_ACTION_RENAMED_OLD_NAME 0x00000004
+#define FILE_ACTION_RENAMED_NEW_NAME 0x00000005
+#define FILE_ACTION_ADDED_STREAM 0x00000006
+#define FILE_ACTION_REMOVED_STREAM 0x00000007
+#define FILE_ACTION_MODIFIED_STREAM 0x00000008
+#define FILE_ACTION_REMOVED_BY_DELETE 0x00000009
+#define FILE_ACTION_ID_NOT_TUNNELLED 0x0000000A
+#define FILE_ACTION_TUNNELLED_ID_COLLISION 0x0000000B
+
+/*
* Response contains array of the following structures
* See MS-FSCC 2.7.1
*/
diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h
index e482c86ceb00..f9a8862cb3d4 100644
--- a/fs/smb/common/smb2pdu.h
+++ b/fs/smb/common/smb2pdu.h
@@ -218,10 +218,9 @@ struct smb2_transform_hdr {
* These are simplified versions from the spec, as we don't need a fully fledged
* form of both unchained and chained structs.
*
- * Moreover, even in chained compressed payloads, the initial compression header
- * has the form of the unchained one -- i.e. it never has the
- * OriginalPayloadSize field and ::Offset field always represent an offset
- * (instead of a length, as it is in the chained header).
+ * For chained payloads, only the first 8 bytes belong to the transform header.
+ * CompressionAlgorithm, Flags and Offset below overlay the first chained
+ * payload header, where Offset represents Length.
*
* See MS-SMB2 2.2.42 for more details.
*/
@@ -371,7 +370,8 @@ struct smb2_tree_connect_req {
#define SMB2_SHAREFLAG_FORCE_LEVELII_OPLOCK 0x00001000
#define SMB2_SHAREFLAG_ENABLE_HASH_V1 0x00002000
#define SMB2_SHAREFLAG_ENABLE_HASH_V2 0x00004000
-#define SHI1005_FLAGS_ENCRYPT_DATA 0x00008000
+#define SMB2_SHAREFLAG_ENCRYPT_DATA 0x00008000
+#define SHI1005_FLAGS_ENCRYPT_DATA SMB2_SHAREFLAG_ENCRYPT_DATA
#define SMB2_SHAREFLAG_IDENTITY_REMOTING 0x00040000 /* 3.1.1 */
#define SMB2_SHAREFLAG_COMPRESS_DATA 0x00100000 /* 3.1.1 */
#define SMB2_SHAREFLAG_ISOLATED_TRANSPORT 0x00200000
@@ -510,6 +510,8 @@ struct smb2_encryption_neg_context {
/* Pattern scanning algorithm See MS-SMB2 3.1.4.4.1 */
#define SMB3_COMPRESS_PATTERN cpu_to_le16(0x0004) /* Pattern_V1 */
#define SMB3_COMPRESS_LZ4 cpu_to_le16(0x0005)
+/* Account for NONE for easier array indexing */
+#define SMB3_COMPRESS_MAX_ALGS 6
/* Compression Flags */
#define SMB2_COMPRESSION_CAPABILITIES_FLAG_NONE cpu_to_le32(0x00000000)
@@ -522,9 +524,7 @@ struct smb2_compression_capabilities_context {
__le16 CompressionAlgorithmCount;
__le16 Padding;
__le32 Flags;
- __le16 CompressionAlgorithms[3];
- __u16 Pad; /* Some servers require pad to DataLen multiple of 8 */
- /* Check if pad needed */
+ __le16 CompressionAlgorithms[4];
} __packed;
/*
@@ -743,6 +743,28 @@ struct smb2_close_rsp {
#define SMB2_CHANNEL_RDMA_V1_INVALIDATE cpu_to_le32(0x00000002)
#define SMB2_CHANNEL_RDMA_TRANSFORM cpu_to_le32(0x00000003)
+/* See MS-SMB2 2.2.43. */
+struct smb2_rdma_transform {
+ __le16 RdmaDescriptorOffset;
+ __le16 RdmaDescriptorLength;
+ __le32 Channel;
+ __le16 TransformCount;
+ __le16 Reserved1;
+ __le32 Reserved2;
+} __packed;
+
+#define SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION 0x0001
+#define SMB2_RDMA_TRANSFORM_TYPE_SIGNING 0x0002
+
+struct smb2_rdma_crypto_transform {
+ __le16 TransformType;
+ __le16 SignatureLength;
+ __le16 NonceLength;
+ __le16 Reserved;
+ __u8 Signature[];
+ /* Followed by Nonce[] and optional alignment padding. */
+} __packed;
+
/* SMB2 read request without RFC1001 length at the beginning */
struct smb2_read_req {
struct smb2_hdr hdr;
@@ -848,8 +870,8 @@ struct smb2_lock_req {
__le16 StructureSize; /* Must be 48 */
__le16 LockCount;
/*
- * The least significant four bits are the index, the other 28 bits are
- * the lock sequence number (0 to 64). See MS-SMB2 2.2.26
+ * The least significant four bits are the lock sequence number. The
+ * other 28 bits are the index (0 to 64). See MS-SMB2 2.2.26.
*/
__le32 LockSequenceNumber;
__u64 PersistentFileId;
@@ -1006,22 +1028,6 @@ struct smb2_set_info_rsp {
#define FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400
#define FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800
-/*
- * SMB2 Notify Action Flags
- * See MS-FSCC 2.7.1
- */
-#define FILE_ACTION_ADDED 0x00000001
-#define FILE_ACTION_REMOVED 0x00000002
-#define FILE_ACTION_MODIFIED 0x00000003
-#define FILE_ACTION_RENAMED_OLD_NAME 0x00000004
-#define FILE_ACTION_RENAMED_NEW_NAME 0x00000005
-#define FILE_ACTION_ADDED_STREAM 0x00000006
-#define FILE_ACTION_REMOVED_STREAM 0x00000007
-#define FILE_ACTION_MODIFIED_STREAM 0x00000008
-#define FILE_ACTION_REMOVED_BY_DELETE 0x00000009
-#define FILE_ACTION_ID_NOT_TUNNELLED 0x0000000A
-#define FILE_ACTION_TUNNELLED_ID_COLLISION 0x0000000B
-
/* See MS-SMB2 2.2.35 */
struct smb2_change_notify_req {
struct smb2_hdr hdr;
@@ -1261,6 +1267,38 @@ struct create_mxac_req {
} __packed;
/*
+ * AAPL flags. See Samba libcli/smb/smb2_create_ctx.h
+ */
+
+/* "AAPL" Context Command Codes */
+#define SMB2_CRTCTX_AAPL_SERVER_QUERY 1
+#define SMB2_CRTCTX_AAPL_RESOLVE_ID 2
+
+/* "AAPL" Server Query request/response bitmap */
+#define SMB2_CRTCTX_AAPL_SERVER_CAPS 1
+#define SMB2_CRTCTX_AAPL_VOLUME_CAPS 2
+#define SMB2_CRTCTX_AAPL_MODEL_INFO 4
+
+/* "AAPL" Client/Server Capabilities bitmap */
+#define SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR 1
+#define SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE 2
+#define SMB2_CRTCTX_AAPL_UNIX_BASED 4
+#define SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE 8
+/*
+ * V2 extends the same inline-FinderInfo mechanism as
+ * SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR with an added flags field,
+ * confirmed byte-identical to V1 otherwise against AAPL's actual
+ * public client behavior. Mutually exclusive with the V1 bit on
+ * the wire, not both set together.
+ */
+#define SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2 16
+
+/* "AAPL" Volume Capabilities bitmap */
+#define SMB2_CRTCTX_AAPL_SUPPORT_RESOLVE_ID 1
+#define SMB2_CRTCTX_AAPL_CASE_SENSITIVE 2
+#define SMB2_CRTCTX_AAPL_FULL_SYNC 4
+
+/*
* Flags
* See MS-SMB2 2.2.13.2.11
* MS-SMB2 2.2.13.2.12
@@ -1445,7 +1483,7 @@ struct resume_key_ioctl_rsp {
__u64 ResumeKeyU64[3];
};
__le32 ContextLength; /* MBZ */
- char Context[]; /* ignored, Windows sets to 4 bytes of zero */
+ char Context[4]; /* ignored, Windows sets to 4 bytes of zero */
} __packed;
struct smb2_ioctl_rsp {
@@ -1499,105 +1537,6 @@ struct network_interface_info_ioctl_rsp {
};
} __packed;
-/* this goes in the ioctl buffer when doing FSCTL_SET_ZERO_DATA */
-struct file_zero_data_information {
- __le64 FileOffset;
- __le64 BeyondFinalZero;
-} __packed;
-
-/* See MS-FSCC 2.3.7 */
-struct duplicate_extents_to_file {
- __u64 PersistentFileHandle; /* source file handle, opaque endianness */
- __u64 VolatileFileHandle;
- __le64 SourceFileOffset;
- __le64 TargetFileOffset;
- __le64 ByteCount; /* Bytes to be copied */
-} __packed;
-
-/* See MS-FSCC 2.3.9 */
-#define DUPLICATE_EXTENTS_DATA_EX_SOURCE_ATOMIC 0x00000001
-struct duplicate_extents_to_file_ex {
- __le64 StructureSize; /* MUST be set to 0x30 */
- __u64 PersistentFileHandle; /* source file handle, opaque endianness */
- __u64 VolatileFileHandle;
- __le64 SourceFileOffset;
- __le64 TargetFileOffset;
- __le64 ByteCount; /* Bytes to be copied */
- __le32 Flags;
- __le32 Reserved;
-} __packed;
-
-
-/* See MS-FSCC 2.3.20 */
-struct fsctl_get_integrity_information_rsp {
- __le16 ChecksumAlgorithm;
- __le16 Reserved;
- __le32 Flags;
- __le32 ChecksumChunkSizeInBytes;
- __le32 ClusterSizeInBytes;
-} __packed;
-
-/* See MS-FSCC 2.3.55 */
-struct fsctl_query_file_regions_req {
- __le64 FileOffset;
- __le64 Length;
- __le32 DesiredUsage;
- __le32 Reserved;
-} __packed;
-
-/* DesiredUsage flags see MS-FSCC 2.3.56.1 */
-#define FILE_USAGE_INVALID_RANGE 0x00000000
-#define FILE_USAGE_VALID_CACHED_DATA 0x00000001
-#define FILE_USAGE_NONCACHED_DATA 0x00000002
-
-struct file_region_info {
- __le64 FileOffset;
- __le64 Length;
- __le32 DesiredUsage;
- __le32 Reserved;
-} __packed;
-
-/* See MS-FSCC 2.3.56 */
-struct fsctl_query_file_region_rsp {
- __le32 Flags;
- __le32 TotalRegionEntryCount;
- __le32 RegionEntryCount;
- __u32 Reserved;
- struct file_region_info Regions[];
-} __packed;
-
-/* See MS-FSCC 2.3.58 */
-struct fsctl_query_on_disk_vol_info_rsp {
- __le64 DirectoryCount;
- __le64 FileCount;
- __le16 FsFormatMajVersion;
- __le16 FsFormatMinVersion;
- __u8 FsFormatName[24];
- __le64 FormatTime;
- __le64 LastUpdateTime;
- __u8 CopyrightInfo[68];
- __u8 AbstractInfo[68];
- __u8 FormatImplInfo[68];
- __u8 LastModifyImplInfo[68];
-} __packed;
-
-/* See MS-FSCC 2.3.73 */
-struct fsctl_set_integrity_information_req {
- __le16 ChecksumAlgorithm;
- __le16 Reserved;
- __le32 Flags;
-} __packed;
-
-/* See MS-FSCC 2.3.75 */
-struct fsctl_set_integrity_info_ex_req {
- __u8 EnableIntegrity;
- __u8 KeepState;
- __u16 Reserved;
- __le32 Flags;
- __u8 Version;
- __u8 Reserved2[7];
-} __packed;
-
/* Integrity ChecksumAlgorithm choices for above */
#define CHECKSUM_TYPE_NONE 0x0000
#define CHECKSUM_TYPE_CRC64 0x0002
@@ -1606,72 +1545,6 @@ struct fsctl_set_integrity_info_ex_req {
/* Integrity flags for above */
#define FSCTL_INTEGRITY_FLAG_CHECKSUM_ENFORCEMENT_OFF 0x00000001
-/* Reparse structures - see MS-FSCC 2.1.2 */
-
-/* struct fsctl_reparse_info_req is empty, only response structs (see below) */
-struct reparse_data_buffer {
- __le32 ReparseTag;
- __le16 ReparseDataLength;
- __u16 Reserved;
- __u8 DataBuffer[]; /* Variable Length */
-} __packed;
-
-struct reparse_guid_data_buffer {
- __le32 ReparseTag;
- __le16 ReparseDataLength;
- __u16 Reserved;
- __u8 ReparseGuid[16];
- __u8 DataBuffer[]; /* Variable Length */
-} __packed;
-
-struct reparse_mount_point_data_buffer {
- __le32 ReparseTag;
- __le16 ReparseDataLength;
- __u16 Reserved;
- __le16 SubstituteNameOffset;
- __le16 SubstituteNameLength;
- __le16 PrintNameOffset;
- __le16 PrintNameLength;
- __u8 PathBuffer[]; /* Variable Length */
-} __packed;
-
-#define SYMLINK_FLAG_RELATIVE 0x00000001
-
-struct reparse_symlink_data_buffer {
- __le32 ReparseTag;
- __le16 ReparseDataLength;
- __u16 Reserved;
- __le16 SubstituteNameOffset;
- __le16 SubstituteNameLength;
- __le16 PrintNameOffset;
- __le16 PrintNameLength;
- __le32 Flags;
- __u8 PathBuffer[]; /* Variable Length */
-} __packed;
-
-/* For IO_REPARSE_TAG_NFS - see MS-FSCC 2.1.2.6 */
-#define NFS_SPECFILE_LNK 0x00000000014B4E4C
-#define NFS_SPECFILE_CHR 0x0000000000524843
-#define NFS_SPECFILE_BLK 0x00000000004B4C42
-#define NFS_SPECFILE_FIFO 0x000000004F464946
-#define NFS_SPECFILE_SOCK 0x000000004B434F53
-struct reparse_nfs_data_buffer {
- __le32 ReparseTag;
- __le16 ReparseDataLength;
- __u16 Reserved;
- __le64 InodeType; /* NFS_SPECFILE_* */
- __u8 DataBuffer[];
-} __packed;
-
-/* For IO_REPARSE_TAG_LX_SYMLINK - see MS-FSCC 2.1.2.7 */
-struct reparse_wsl_symlink_data_buffer {
- __le32 ReparseTag;
- __le16 ReparseDataLength;
- __u16 Reserved;
- __le32 Version; /* Always 2 */
- __u8 Target[]; /* Variable Length UTF-8 string without nul-term */
-} __packed;
-
struct validate_negotiate_info_req {
__le32 Capabilities;
__u8 Guid[SMB2_CLIENT_GUID_SIZE];
@@ -1745,6 +1618,10 @@ struct validate_negotiate_info_rsp {
#define FILE_STANDARD_LINK_INFORMATION 54
#define FILE_ID_INFORMATION 59
#define FILE_ID_EXTD_DIRECTORY_INFORMATION 60 /* also for QUERY_DIR */
+#define FileId64ExtdDirectoryInformation 78 /* also for QUERY_DIR */
+#define FileId64ExtdBothDirectoryInformation 79 /* also for QUERY_DIR */
+#define FileIdAllExtdDirectoryInformation 80 /* also for QUERY_DIR */
+#define FileIdAllExtdBothDirectoryInformation 81 /* also for QUERY_DIR */
/* Used for Query Info and Find File POSIX Info for SMB3.1.1 and SMB1 */
#define SMB_FIND_FILE_POSIX_INFO 0x064
@@ -1791,84 +1668,6 @@ struct smb2_query_info_rsp {
__u8 Buffer[];
} __packed;
-/*
- * PDU query infolevel structure definitions
- */
-
-/* See MS-FSCC 2.3.52 */
-struct file_allocated_range_buffer {
- __le64 file_offset;
- __le64 length;
-} __packed;
-
-struct smb2_file_internal_info {
- __le64 IndexNumber;
-} __packed; /* level 6 Query */
-
-struct smb2_file_rename_info { /* encoding of request for level 10 */
- /* New members MUST be added within the struct_group() macro below. */
- __struct_group(smb2_file_rename_info_hdr, __hdr, __packed,
- __u8 ReplaceIfExists; /* 1 = replace existing target with new */
- /* 0 = fail if target already exists */
- __u8 Reserved[7];
- __u64 RootDirectory; /* MBZ for network operations (why says spec?) */
- __le32 FileNameLength;
- );
- char FileName[]; /* New name to be assigned */
- /* padding - overall struct size must be >= 24 so filename + pad >= 6 */
-} __packed; /* level 10 Set */
-static_assert(offsetof(struct smb2_file_rename_info, FileName) == sizeof(struct smb2_file_rename_info_hdr),
- "struct member likely outside of __struct_group()");
-
-struct smb2_file_link_info { /* encoding of request for level 11 */
- /* New members MUST be added within the struct_group() macro below. */
- __struct_group(smb2_file_link_info_hdr, __hdr, __packed,
- __u8 ReplaceIfExists; /* 1 = replace existing link with new */
- /* 0 = fail if link already exists */
- __u8 Reserved[7];
- __u64 RootDirectory; /* MBZ for network operations (why says spec?) */
- __le32 FileNameLength;
- );
- char FileName[]; /* Name to be assigned to new link */
-} __packed; /* level 11 Set */
-static_assert(offsetof(struct smb2_file_link_info, FileName) == sizeof(struct smb2_file_link_info_hdr),
- "struct member likely outside of __struct_group()");
-
-/*
- * This level 18, although with struct with same name is different from cifs
- * level 0x107. Level 0x107 has an extra u64 between AccessFlags and
- * CurrentByteOffset.
- */
-struct smb2_file_all_info { /* data block encoding of response to level 18 */
- __le64 CreationTime; /* Beginning of FILE_BASIC_INFO equivalent */
- __le64 LastAccessTime;
- __le64 LastWriteTime;
- __le64 ChangeTime;
- __le32 Attributes;
- __u32 Pad1; /* End of FILE_BASIC_INFO_INFO equivalent */
- __le64 AllocationSize; /* Beginning of FILE_STANDARD_INFO equivalent */
- __le64 EndOfFile; /* size ie offset to first free byte in file */
- __le32 NumberOfLinks; /* hard links */
- __u8 DeletePending;
- __u8 Directory;
- __u16 Pad2; /* End of FILE_STANDARD_INFO equivalent */
- __le64 IndexNumber;
- __le32 EASize;
- __le32 AccessFlags;
- __le64 CurrentByteOffset;
- __le32 Mode;
- __le32 AlignmentRequirement;
- __le32 FileNameLength;
- union {
- char __pad; /* Legacy structure padding */
- DECLARE_FLEX_ARRAY(char, FileName);
- };
-} __packed; /* level 18 Query */
-
-struct smb2_file_eof_info { /* encoding of request for level 10 */
- __le64 EndOfFile; /* new end of file value */
-} __packed; /* level 20 Set */
-
/* Level 100 query info */
struct smb311_posix_qinfo {
__le64 CreationTime;
@@ -1894,65 +1693,6 @@ struct smb311_posix_qinfo {
*/
} __packed;
-/* File System Information Classes */
-#define FS_VOLUME_INFORMATION 1 /* Query */
-#define FS_LABEL_INFORMATION 2 /* Set */
-#define FS_SIZE_INFORMATION 3 /* Query */
-#define FS_DEVICE_INFORMATION 4 /* Query */
-#define FS_ATTRIBUTE_INFORMATION 5 /* Query */
-#define FS_CONTROL_INFORMATION 6 /* Query, Set */
-#define FS_FULL_SIZE_INFORMATION 7 /* Query */
-#define FS_OBJECT_ID_INFORMATION 8 /* Query, Set */
-#define FS_DRIVER_PATH_INFORMATION 9 /* Query */
-#define FS_SECTOR_SIZE_INFORMATION 11 /* SMB3 or later. Query */
-#define FS_POSIX_INFORMATION 100 /* SMB3.1.1 POSIX. Query */
-
-struct smb2_fs_full_size_info {
- __le64 TotalAllocationUnits;
- __le64 CallerAvailableAllocationUnits;
- __le64 ActualAvailableAllocationUnits;
- __le32 SectorsPerAllocationUnit;
- __le32 BytesPerSector;
-} __packed;
-
-#define SSINFO_FLAGS_ALIGNED_DEVICE 0x00000001
-#define SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE 0x00000002
-#define SSINFO_FLAGS_NO_SEEK_PENALTY 0x00000004
-#define SSINFO_FLAGS_TRIM_ENABLED 0x00000008
-
-/* sector size info struct */
-struct smb3_fs_ss_info {
- __le32 LogicalBytesPerSector;
- __le32 PhysicalBytesPerSectorForAtomicity;
- __le32 PhysicalBytesPerSectorForPerf;
- __le32 FSEffPhysicalBytesPerSectorForAtomicity;
- __le32 Flags;
- __le32 ByteOffsetForSectorAlignment;
- __le32 ByteOffsetForPartitionAlignment;
-} __packed;
-
-/* File System Control Information */
-struct smb2_fs_control_info {
- __le64 FreeSpaceStartFiltering;
- __le64 FreeSpaceThreshold;
- __le64 FreeSpaceStopFiltering;
- __le64 DefaultQuotaThreshold;
- __le64 DefaultQuotaLimit;
- __le32 FileSystemControlFlags;
- __le32 Padding;
-} __packed;
-
-/* volume info struct - see MS-FSCC 2.5.9 */
-#define MAX_VOL_LABEL_LEN 32
-struct smb3_fs_vol_info {
- __le64 VolumeCreationTime;
- __u32 VolumeSerialNumber;
- __le32 VolumeLabelLength; /* includes trailing null */
- __u8 SupportsObjects; /* True if eg like NTFS, supports objects */
- __u8 Reserved;
- __u8 VolumeLabel[]; /* variable len */
-} __packed;
-
/* See MS-SMB2 2.2.23 through 2.2.25 */
struct smb2_oplock_break {
struct smb2_hdr hdr;
diff --git a/fs/smb/common/smb2status.h b/fs/smb/common/smb2status.h
index b6421bc5113c..2989c3a5cb67 100644
--- a/fs/smb/common/smb2status.h
+++ b/fs/smb/common/smb2status.h
@@ -1049,6 +1049,7 @@ struct ntstatus {
#define STATUS_WOW_ASSERTION cpu_to_le32(0xC0009898) // -EIO
#define STATUS_INVALID_SIGNATURE cpu_to_le32(0xC000A000) // -EIO
#define STATUS_HMAC_NOT_SUPPORTED cpu_to_le32(0xC000A001) // -EIO
+#define STATUS_AUTH_TAG_MISMATCH cpu_to_le32(0xC000A002) // -EBADMSG
#define STATUS_IPSEC_QUEUE_OVERFLOW cpu_to_le32(0xC000A010) // -EIO
#define STATUS_ND_QUEUE_OVERFLOW cpu_to_le32(0xC000A011) // -EIO
#define STATUS_HOPLIMIT_EXCEEDED cpu_to_le32(0xC000A012) // -EIO
diff --git a/fs/smb/common/smbdirect/smbdirect.h b/fs/smb/common/smbdirect/smbdirect.h
deleted file mode 100644
index 05cc6a9d0ccd..000000000000
--- a/fs/smb/common/smbdirect/smbdirect.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2017, Microsoft Corporation.
- * Copyright (C) 2018, LG Electronics.
- */
-
-#ifndef __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__
-#define __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__
-
-/* SMB-DIRECT buffer descriptor V1 structure [MS-SMBD] 2.2.3.1 */
-struct smbdirect_buffer_descriptor_v1 {
- __le64 offset;
- __le32 token;
- __le32 length;
-} __packed;
-
-/*
- * Connection parameters mostly from [MS-SMBD] 3.1.1.1
- *
- * These are setup and negotiated at the beginning of a
- * connection and remain constant unless explicitly changed.
- *
- * Some values are important for the upper layer.
- */
-struct smbdirect_socket_parameters {
- __u32 resolve_addr_timeout_msec;
- __u32 resolve_route_timeout_msec;
- __u32 rdma_connect_timeout_msec;
- __u32 negotiate_timeout_msec;
- __u8 initiator_depth;
- __u8 responder_resources;
- __u16 recv_credit_max;
- __u16 send_credit_target;
- __u32 max_send_size;
- __u32 max_fragmented_send_size;
- __u32 max_recv_size;
- __u32 max_fragmented_recv_size;
- __u32 max_read_write_size;
- __u32 max_frmr_depth;
- __u32 keepalive_interval_msec;
- __u32 keepalive_timeout_msec;
-} __packed;
-
-#endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__ */
diff --git a/fs/smb/common/smbfsctl.h b/fs/smb/common/smbfsctl.h
index 3253a18ecb5c..b1123b0f768d 100644
--- a/fs/smb/common/smbfsctl.h
+++ b/fs/smb/common/smbfsctl.h
@@ -61,8 +61,8 @@
#define FSCTL_LOCK_VOLUME 0x00090018
#define FSCTL_UNLOCK_VOLUME 0x0009001C
#define FSCTL_IS_PATHNAME_VALID 0x0009002C /* BB add struct */
-#define FSCTL_GET_COMPRESSION 0x0009003C /* BB add struct */
-#define FSCTL_SET_COMPRESSION 0x0009C040 /* BB add struct */
+#define FSCTL_GET_COMPRESSION 0x0009003C
+#define FSCTL_SET_COMPRESSION 0x0009C040
#define FSCTL_QUERY_FAT_BPB 0x00090058 /* BB add struct */
/* Verify the next FSCTL number, we had it as 0x00090090 before */
#define FSCTL_FILESYSTEM_GET_STATS 0x00090060 /* BB add struct */
@@ -119,6 +119,7 @@
#define FSCTL_SRV_ENUMERATE_SNAPSHOTS 0x00144064
/* Retrieve an opaque file reference for server-side data movement ie copy */
#define FSCTL_SRV_REQUEST_RESUME_KEY 0x00140078
+#define FSCTL_SRV_ENUM_SNAPS 0x00144064
#define FSCTL_LMR_REQUEST_RESILIENCY 0x001401D4
#define FSCTL_LMR_GET_LINK_TRACK_INF 0x001400E8 /* BB add struct */
#define FSCTL_LMR_SET_LINK_TRACK_INF 0x001400EC /* BB add struct */
@@ -159,6 +160,12 @@
#define IO_REPARSE_TAG_LX_CHR 0x80000025
#define IO_REPARSE_TAG_LX_BLK 0x80000026
+#define IO_REPARSE_TAG_LX_SYMLINK_LE cpu_to_le32(IO_REPARSE_TAG_LX_SYMLINK)
+#define IO_REPARSE_TAG_AF_UNIX_LE cpu_to_le32(IO_REPARSE_TAG_AF_UNIX)
+#define IO_REPARSE_TAG_LX_FIFO_LE cpu_to_le32(IO_REPARSE_TAG_LX_FIFO)
+#define IO_REPARSE_TAG_LX_CHR_LE cpu_to_le32(IO_REPARSE_TAG_LX_CHR)
+#define IO_REPARSE_TAG_LX_BLK_LE cpu_to_le32(IO_REPARSE_TAG_LX_BLK)
+
/* If Name Surrogate Bit is set, the file or directory represents another named entity in the system. */
#define IS_REPARSE_TAG_NAME_SURROGATE(tag) (!!((tag) & 0x20000000))
diff --git a/fs/smb/common/smbglob.h b/fs/smb/common/smbglob.h
index 4e33d91cdc9d..d9c7e6e7af29 100644
--- a/fs/smb/common/smbglob.h
+++ b/fs/smb/common/smbglob.h
@@ -39,6 +39,7 @@ struct smb_version_values {
size_t create_mxac_size;
size_t create_disk_id_size;
size_t create_posix_size;
+ size_t create_aapl_size;
};
static inline unsigned int get_rfc1002_len(void *buf)
diff --git a/fs/smb/server/Kconfig b/fs/smb/server/Kconfig
index 12594879cb64..221ec9717a83 100644
--- a/fs/smb/server/Kconfig
+++ b/fs/smb/server/Kconfig
@@ -7,14 +7,12 @@ config SMB_SERVER
select NLS_UTF8
select NLS_UCS2_UTILS
select CRYPTO
- select CRYPTO_ECB
+ select CRYPTO_LIB_AES_CBC_MACS
select CRYPTO_LIB_ARC4
- select CRYPTO_LIB_DES
select CRYPTO_LIB_MD5
select CRYPTO_LIB_SHA256
select CRYPTO_LIB_SHA512
select CRYPTO_LIB_UTILS
- select CRYPTO_CMAC
select CRYPTO_AEAD2
select CRYPTO_CCM
select CRYPTO_GCM
@@ -48,8 +46,9 @@ if SMB_SERVER
config SMB_SERVER_SMBDIRECT
bool "Support for SMB Direct protocol"
- depends on SMB_SERVER=m && INFINIBAND && INFINIBAND_ADDR_TRANS || SMB_SERVER=y && INFINIBAND=y && INFINIBAND_ADDR_TRANS=y
- select SG_POOL
+ depends on SMB_SERVER && INFINIBAND && INFINIBAND_ADDR_TRANS
+ depends on SMB_SERVER=m || INFINIBAND=y
+ select SMBDIRECT
default n
help
diff --git a/fs/smb/server/Makefile b/fs/smb/server/Makefile
index 6407ba6b9340..a3e9306055e8 100644
--- a/fs/smb/server/Makefile
+++ b/fs/smb/server/Makefile
@@ -10,7 +10,7 @@ ksmbd-y := unicode.o auth.o vfs.o vfs_cache.o server.o ndr.o \
mgmt/tree_connect.o mgmt/user_session.o smb_common.o \
transport_tcp.o transport_ipc.o smbacl.o smb2pdu.o \
smb2ops.o smb2misc.o ksmbd_spnego_negtokeninit.asn1.o \
- ksmbd_spnego_negtokentarg.asn1.o asn1.o
+ ksmbd_spnego_negtokentarg.asn1.o asn1.o compress.o
$(obj)/asn1.o: $(obj)/ksmbd_spnego_negtokeninit.asn1.h $(obj)/ksmbd_spnego_negtokentarg.asn1.h
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 5fe8c667c6b1..1b24eb3f3998 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -11,8 +11,8 @@
#include <linux/writeback.h>
#include <linux/uio.h>
#include <linux/xattr.h>
-#include <crypto/hash.h>
#include <crypto/aead.h>
+#include <crypto/aes-cbc-macs.h>
#include <crypto/md5.h>
#include <crypto/sha2.h>
#include <crypto/utils.h>
@@ -24,7 +24,6 @@
#include <linux/fips.h>
#include <crypto/arc4.h>
-#include <crypto/des.h>
#include "server.h"
#include "smb_common.h"
@@ -122,6 +121,8 @@ static int calc_ntlmv2_hash(struct ksmbd_conn *conn, struct ksmbd_session *sess,
out:
kfree(uniname);
kfree(domain);
+ if (ret) /* Done by hmac_md5_final() already if ret == 0 */
+ memzero_explicit(&ctx, sizeof(ctx));
return ret;
}
@@ -133,15 +134,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 base_key[SMB2_NTLMV2_SESSKEY_SIZE];
struct hmac_md5_ctx ctx;
int rc;
@@ -164,12 +167,21 @@ 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->sess_key);
+ base_key);
if (crypto_memneq(ntlmv2->ntlmv2_hash, ntlmv2_rsp,
- CIFS_HMAC_MD5_HASH_SIZE))
- return -EINVAL;
- return 0;
+ CIFS_HMAC_MD5_HASH_SIZE)) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ 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(base_key, sizeof(base_key));
+ return rc;
}
/**
@@ -179,12 +191,13 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
* @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;
@@ -224,8 +237,10 @@ 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;
/* The recovered secondary session key */
if (conn->ntlmssp.client_flags & NTLMSSP_NEGOTIATE_KEY_XCH) {
@@ -245,8 +260,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);
}
@@ -388,7 +403,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;
@@ -424,6 +440,7 @@ int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
resp_ext = ksmbd_ipc_login_request_ext(resp->login_response.account);
user = ksmbd_alloc_user(&resp->login_response, resp_ext);
+ kvfree(resp_ext);
if (!user) {
ksmbd_debug(AUTH, "login failure\n");
retval = -ENOMEM;
@@ -436,25 +453,28 @@ 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;
+ sess->kerberos_expiry = resp->session_expiry;
retval = 0;
out:
- kvfree(resp);
+ kvfree_sensitive(resp, sizeof(*resp) + resp->session_key_len +
+ resp->spnego_blob_len);
return retval;
}
#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;
}
@@ -490,46 +510,21 @@ void ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
* @sig: signature value generated for client request packet
*
*/
-int ksmbd_sign_smb3_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,
+ int n_vec, char *sig)
{
- struct ksmbd_crypto_ctx *ctx;
- int rc, i;
-
- ctx = ksmbd_crypto_ctx_find_cmacaes();
- if (!ctx) {
- ksmbd_debug(AUTH, "could not crypto alloc cmac\n");
- return -ENOMEM;
- }
-
- rc = crypto_shash_setkey(CRYPTO_CMACAES_TFM(ctx),
- key,
- SMB2_CMACAES_SIZE);
- if (rc)
- goto out;
+ struct aes_cmac_key cmac_key __cleanup(aes_cmac_zeroize_key);
+ struct aes_cmac_ctx cmac_ctx;
+ int i;
- rc = crypto_shash_init(CRYPTO_CMACAES(ctx));
- if (rc) {
- ksmbd_debug(AUTH, "cmaces init error %d\n", rc);
- goto out;
- }
+ /* This cannot fail, since we always pass a valid key length. */
+ static_assert(SMB2_CMACAES_SIZE == AES_KEYSIZE_128);
+ aes_cmac_preparekey(&cmac_key, key, SMB2_CMACAES_SIZE);
- for (i = 0; i < n_vec; i++) {
- rc = crypto_shash_update(CRYPTO_CMACAES(ctx),
- iov[i].iov_base,
- iov[i].iov_len);
- if (rc) {
- ksmbd_debug(AUTH, "cmaces update error %d\n", rc);
- goto out;
- }
- }
-
- rc = crypto_shash_final(CRYPTO_CMACAES(ctx), sig);
- if (rc)
- ksmbd_debug(AUTH, "cmaces generation error %d\n", rc);
-out:
- ksmbd_release_crypto_ctx(ctx);
- return rc;
+ aes_cmac_init(&cmac_ctx, &cmac_key);
+ for (i = 0; i < n_vec; i++)
+ aes_cmac_update(&cmac_ctx, iov[i].iov_base, iov[i].iov_len);
+ aes_cmac_final(&cmac_ctx, sig);
}
struct derivation {
@@ -538,7 +533,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)
{
@@ -549,7 +544,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);
@@ -565,6 +560,7 @@ static void generate_key(struct ksmbd_conn *conn, struct ksmbd_session *sess,
hmac_sha256_final(&ctx, prfhash);
memcpy(key, prfhash, key_size);
+ memzero_explicit(prfhash, sizeof(prfhash));
}
static int generate_smb3signingkey(struct ksmbd_session *sess,
@@ -572,29 +568,28 @@ 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))
memcpy(chann->smb3signingkey, key, SMB3_SIGN_KEY_SIZE);
- ksmbd_debug(AUTH, "dumping generated AES signing keys\n");
+ ksmbd_debug(AUTH, "generated SMB3 signing key\n");
ksmbd_debug(AUTH, "Session Id %llu\n", sess->id);
- ksmbd_debug(AUTH, "Session Key %*ph\n",
- SMB2_NTLMV2_SESSKEY_SIZE, sess->sess_key);
- ksmbd_debug(AUTH, "Signing Key %*ph\n",
- SMB3_SIGN_KEY_SIZE, key);
return 0;
}
@@ -644,31 +639,17 @@ 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);
- ksmbd_debug(AUTH, "dumping generated AES encryption keys\n");
+ ksmbd_debug(AUTH, "generated SMB3 encryption/decryption keys\n");
ksmbd_debug(AUTH, "Cipher type %d\n", conn->cipher_type);
ksmbd_debug(AUTH, "Session Id %llu\n", sess->id);
- ksmbd_debug(AUTH, "Session Key %*ph\n",
- SMB2_NTLMV2_SESSKEY_SIZE, sess->sess_key);
- if (conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
- conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
- ksmbd_debug(AUTH, "ServerIn Key %*ph\n",
- SMB3_GCM256_CRYPTKEY_SIZE, sess->smb3encryptionkey);
- ksmbd_debug(AUTH, "ServerOut Key %*ph\n",
- SMB3_GCM256_CRYPTKEY_SIZE, sess->smb3decryptionkey);
- } else {
- ksmbd_debug(AUTH, "ServerIn Key %*ph\n",
- SMB3_GCM128_CRYPTKEY_SIZE, sess->smb3encryptionkey);
- ksmbd_debug(AUTH, "ServerOut Key %*ph\n",
- SMB3_GCM128_CRYPTKEY_SIZE, sess->smb3decryptionkey);
- }
}
void ksmbd_gen_smb30_encryptionkey(struct ksmbd_conn *conn,
@@ -740,8 +721,21 @@ static int ksmbd_get_encryption_key(struct ksmbd_work *work, __u64 ses_id,
if (enc)
sess = work->sess;
- else
- sess = ksmbd_session_lookup_all(work->conn, ses_id);
+ else {
+ /*
+ * A previous-session replacement leaves the old encryption key in
+ * place. Use it to authenticate an encrypted request, then let
+ * session validation reject the expired session. This preserves the
+ * encrypted STATUS_USER_SESSION_DELETED response without reviving
+ * the session.
+ */
+ sess = ksmbd_session_lookup_all_states(work->conn, ses_id);
+ if (sess && sess->state != SMB2_SESSION_VALID &&
+ (sess->state != SMB2_SESSION_EXPIRED || !sess->enc)) {
+ ksmbd_user_session_put(sess);
+ sess = NULL;
+ }
+ }
if (!sess)
return -EINVAL;
@@ -838,6 +832,189 @@ static struct scatterlist *ksmbd_init_sg(struct kvec *iov, unsigned int nvec,
return sg;
}
+/**
+ * ksmbd_init_rdma_sg() - build an AEAD scatterlist for an RDMA payload
+ * @buf: payload buffer
+ * @buflen: payload length
+ * @tag: authentication tag buffer
+ * @taglen: authentication tag length
+ *
+ * Split vmalloc-backed payloads at page boundaries and append the detached
+ * authentication tag as the final scatterlist entry.
+ *
+ * Return: allocated scatterlist, or NULL on allocation failure
+ */
+static struct scatterlist *ksmbd_init_rdma_sg(void *buf,
+ unsigned int buflen,
+ u8 *tag,
+ unsigned int taglen)
+{
+ struct scatterlist *sg;
+ unsigned int nr_data = 1, nr_entries, i = 0;
+ void *data = buf;
+ int len = buflen;
+
+ if (is_vmalloc_addr(buf))
+ nr_data = DIV_ROUND_UP(offset_in_page(buf) + buflen, PAGE_SIZE);
+ nr_entries = nr_data + 1;
+
+ sg = kmalloc_objs(struct scatterlist, nr_entries, KSMBD_DEFAULT_GFP);
+ if (!sg)
+ return NULL;
+
+ sg_init_table(sg, nr_entries);
+ if (!is_vmalloc_addr(buf)) {
+ smb2_sg_set_buf(&sg[i++], buf, buflen);
+ } else {
+ while (len) {
+ unsigned int bytes = min_t(unsigned int,
+ PAGE_SIZE - offset_in_page(data), len);
+
+ sg_set_page(&sg[i++], vmalloc_to_page(data), bytes,
+ offset_in_page(data));
+ data += bytes;
+ len -= bytes;
+ }
+ }
+ smb2_sg_set_buf(&sg[i], tag, taglen);
+ return sg;
+}
+
+/**
+ * ksmbd_crypt_rdma() - encrypt or decrypt an SMB Direct data buffer
+ * @conn: connection containing the negotiated cipher
+ * @key: session encryption or decryption key
+ * @buf: RDMA payload, transformed in place
+ * @buflen: payload length (the authentication tag is carried out of band)
+ * @nonce: transform nonce
+ * @nonce_len: nonce length
+ * @tag: authentication tag output for encryption, input for decryption
+ * @tag_len: authentication tag length
+ * @enc: true to encrypt, false to decrypt
+ *
+ * SMB2_RDMA_CRYPTO_TRANSFORM carries the nonce and authentication tag in the
+ * SMB2 message while only the payload is transferred through RDMA. Therefore
+ * this uses AEAD without the normal SMB3 transform header as associated data.
+ *
+ * Return: 0 on success, otherwise a negative errno
+ */
+int ksmbd_crypt_rdma(struct ksmbd_conn *conn, const u8 *key,
+ void *buf, unsigned int buflen, const u8 *nonce,
+ unsigned int nonce_len, u8 *tag, unsigned int tag_len,
+ bool enc)
+{
+ struct ksmbd_crypto_ctx *ctx;
+ struct crypto_aead *tfm;
+ struct aead_request *req = NULL;
+ struct scatterlist *sg = NULL;
+ unsigned int iv_len, crypt_len;
+ u8 auth_tag[SMB2_SIGNATURE_SIZE] = {};
+ u8 *iv = NULL;
+ u16 cipher = le16_to_cpu(conn->cipher_type);
+ int rc;
+ DECLARE_CRYPTO_WAIT(wait);
+
+ if (!buflen || !tag_len || tag_len > SMB2_SIGNATURE_SIZE) {
+ pr_err("RDMA %s rejected: cipher=0x%04x payload=%u nonce=%u tag=%u\n",
+ enc ? "encryption" : "decryption", cipher, buflen,
+ nonce_len, tag_len);
+ return -EINVAL;
+ }
+ if (!enc)
+ memcpy(auth_tag, tag, tag_len);
+
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
+ if (nonce_len != SMB3_AES_GCM_NONCE) {
+ pr_err("RDMA %s rejected: cipher=0x%04x invalid nonce=%u expected=%u\n",
+ enc ? "encryption" : "decryption", cipher,
+ nonce_len, SMB3_AES_GCM_NONCE);
+ return -EINVAL;
+ }
+ ctx = ksmbd_crypto_ctx_find_gcm();
+ } else {
+ if (nonce_len != SMB3_AES_CCM_NONCE) {
+ pr_err("RDMA %s rejected: cipher=0x%04x invalid nonce=%u expected=%u\n",
+ enc ? "encryption" : "decryption", cipher,
+ nonce_len, SMB3_AES_CCM_NONCE);
+ return -EINVAL;
+ }
+ ctx = ksmbd_crypto_ctx_find_ccm();
+ }
+ if (!ctx) {
+ pr_err("RDMA %s failed: cipher=0x%04x crypto context unavailable\n",
+ enc ? "encryption" : "decryption", cipher);
+ return -ENOMEM;
+ }
+
+ tfm = (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
+ CRYPTO_GCM(ctx) : CRYPTO_CCM(ctx);
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
+ rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE);
+ else
+ rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE);
+ if (rc)
+ goto out;
+
+ rc = crypto_aead_setauthsize(tfm, tag_len);
+ if (rc)
+ goto out;
+
+ req = aead_request_alloc(tfm, KSMBD_DEFAULT_GFP);
+ if (!req) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ sg = ksmbd_init_rdma_sg(buf, buflen, auth_tag, tag_len);
+ if (!sg) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ iv_len = crypto_aead_ivsize(tfm);
+ iv = kzalloc(iv_len, KSMBD_DEFAULT_GFP);
+ if (!iv) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
+ memcpy(iv, nonce, nonce_len);
+ } else {
+ iv[0] = 3;
+ memcpy(iv + 1, nonce, nonce_len);
+ }
+
+ crypt_len = buflen + (enc ? 0 : tag_len);
+ aead_request_set_crypt(req, sg, sg, crypt_len, iv);
+ aead_request_set_ad(req, 0);
+ aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+ CRYPTO_TFM_REQ_MAY_SLEEP,
+ crypto_req_done, &wait);
+ rc = crypto_wait_req(enc ? crypto_aead_encrypt(req) :
+ crypto_aead_decrypt(req), &wait);
+ if (!rc && enc)
+ memcpy(tag, auth_tag, tag_len);
+out:
+ kfree(iv);
+ kfree(sg);
+ aead_request_free(req);
+ ksmbd_release_crypto_ctx(ctx);
+ if (rc)
+ pr_err("RDMA %s failed: cipher=0x%04x payload=%u nonce=%u tag=%u rc=%d\n",
+ enc ? "encryption" : "decryption", cipher, buflen,
+ nonce_len, tag_len, rc);
+ else
+ ksmbd_debug(RDMA,
+ "RDMA %s completed: cipher=0x%04x payload=%u nonce=%u tag=%u\n",
+ enc ? "encryption" : "decryption", cipher, buflen,
+ nonce_len, tag_len);
+ return rc;
+}
+
int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
unsigned int nvec, int enc)
{
@@ -845,6 +1022,7 @@ int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
struct smb2_transform_hdr *tr_hdr = smb_get_msg(iov[0].iov_base);
unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 20;
int rc;
+ DECLARE_CRYPTO_WAIT(wait);
struct scatterlist *sg;
u8 sign[SMB2_SIGNATURE_SIZE] = {};
u8 key[SMB3_ENC_DEC_KEY_SIZE];
@@ -871,7 +1049,8 @@ int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
ctx = ksmbd_crypto_ctx_find_ccm();
if (!ctx) {
pr_err("crypto alloc failed\n");
- return -ENOMEM;
+ rc = -ENOMEM;
+ goto zeroize_key;
}
if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
@@ -931,12 +1110,12 @@ int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
aead_request_set_crypt(req, sg, sg, crypt_len, iv);
aead_request_set_ad(req, assoc_data_len);
- aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP, NULL, NULL);
+ aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+ CRYPTO_TFM_REQ_MAY_SLEEP,
+ crypto_req_done, &wait);
- if (enc)
- rc = crypto_aead_encrypt(req);
- else
- rc = crypto_aead_decrypt(req);
+ rc = crypto_wait_req(enc ? crypto_aead_encrypt(req) :
+ crypto_aead_decrypt(req), &wait);
if (rc)
goto free_iv;
@@ -951,5 +1130,8 @@ free_req:
aead_request_free(req);
free_ctx:
ksmbd_release_crypto_ctx(ctx);
+zeroize_key:
+ memzero_explicit(key, sizeof(key));
+ memzero_explicit(sign, sizeof(sign));
return rc;
}
diff --git a/fs/smb/server/auth.h b/fs/smb/server/auth.h
index 6d351d61b0e5..7ce9c42d58f1 100644
--- a/fs/smb/server/auth.h
+++ b/fs/smb/server/auth.h
@@ -38,24 +38,29 @@ struct kvec;
int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
unsigned int nvec, int enc);
+int ksmbd_crypt_rdma(struct ksmbd_conn *conn, const u8 *key,
+ void *buf, unsigned int buflen, const u8 *nonce,
+ unsigned int nonce_len, u8 *tag, unsigned int tag_len,
+ bool enc);
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);
-int ksmbd_sign_smb3_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,
+ int n_vec, char *sig);
int ksmbd_gen_smb30_signingkey(struct ksmbd_session *sess,
struct ksmbd_conn *conn);
int ksmbd_gen_smb311_signingkey(struct ksmbd_session *sess,
diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c
new file mode 100644
index 000000000000..5162fb84c755
--- /dev/null
+++ b/fs/smb/server/compress.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SMB2 compression support for ksmbd.
+ *
+ * Receive and send SMB 3.1.1 compression transforms using the common helpers.
+ *
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ */
+#include <linux/slab.h>
+
+#include "compress.h"
+#include "smb_common.h"
+#include "../common/compress/lz77.h"
+
+#define SMB_COMPRESS_MIN_LEN PAGE_SIZE
+
+static int __ksmbd_decompress_request(struct ksmbd_conn *conn,
+ void *request_buf, void **out_buf)
+{
+ struct smb2_compression_hdr *hdr;
+ unsigned int pdu_size = get_rfc1002_len(request_buf);
+ u32 orig_size, offset, out_size;
+ u32 max_allowed_pdu_size;
+ char *out;
+ int rc;
+
+ if (pdu_size < sizeof(struct smb2_compression_hdr))
+ return -EINVAL;
+
+ if (conn->dialect != SMB311_PROT_ID ||
+ conn->compress_algorithm == SMB3_COMPRESS_NONE)
+ return -EINVAL;
+
+ hdr = smb_get_msg(request_buf);
+ if (hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID)
+ return -EINVAL;
+
+ orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+ /*
+ * For chained transforms the top-level header is only eight bytes; the
+ * Flags field overlays the first payload header. Reject unknown Flags
+ * and unnegotiated chained mode before allocating the output buffer.
+ */
+ if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) {
+ if (!conn->compress_chained)
+ return -EINVAL;
+ out_size = orig_size;
+ } else if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)) {
+ offset = le32_to_cpu(hdr->Offset);
+ if (offset > pdu_size - sizeof(*hdr) ||
+ check_add_overflow(orig_size, offset, &out_size))
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+
+ max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn);
+ if (out_size < sizeof(struct smb2_pdu) ||
+ out_size > max_allowed_pdu_size ||
+ out_size > MAX_STREAM_PROT_LEN)
+ return -EINVAL;
+
+ out = kvmalloc(out_size + 4 + 1, KSMBD_DEFAULT_GFP);
+ if (!out)
+ return -ENOMEM;
+
+ *(__be32 *)out = cpu_to_be32(out_size);
+ rc = smb_compression_decompress(conn->compress_algorithm,
+ conn->compress_chained,
+ conn->compress_pattern,
+ (char *)hdr, pdu_size, out + 4, out_size);
+ if (rc) {
+ kvfree(out);
+ return rc;
+ }
+
+ *out_buf = out;
+ return 0;
+}
+
+/**
+ * ksmbd_decompress_request() - replace a compressed request with its SMB2 PDU
+ * @conn: connection which owns the current RFC1002 request buffer
+ *
+ * Derive the uncompressed size from the transform variant, enforce ksmbd's
+ * normal message limits, and ask the common decoder to validate every payload.
+ * On success, replace conn->request_buf with a regular RFC1002-framed SMB2
+ * message so the rest of the request path needs no compression awareness.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int ksmbd_decompress_request(struct ksmbd_conn *conn)
+{
+ void *out_buf;
+ int rc;
+
+ rc = __ksmbd_decompress_request(conn, conn->request_buf, &out_buf);
+ if (rc)
+ return rc;
+
+ kvfree(conn->request_buf);
+ conn->request_buf = out_buf;
+ return 0;
+}
+
+/**
+ * ksmbd_decompress_work_request() - decompress an encrypted work request
+ * @work: work item whose request buffer contains a compression transform
+ *
+ * SMB3 encrypts a compressed message by applying compression first and
+ * encryption second. The receive loop can therefore only decode the
+ * compression transform before work allocation for an unencrypted request;
+ * an encrypted request must be decompressed after its encryption layer has
+ * been removed.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int ksmbd_decompress_work_request(struct ksmbd_work *work)
+{
+ void *out_buf;
+ int rc;
+
+ rc = __ksmbd_decompress_request(work->conn, work->request_buf,
+ &out_buf);
+ if (rc)
+ return rc;
+
+ kvfree(work->request_buf);
+ work->request_buf = out_buf;
+ return 0;
+}
+
+/**
+ * ksmbd_compress_response() - compress an eligible ksmbd response
+ * @work: request work item containing the response iov
+ *
+ * Compression transforms describe one contiguous SMB2 message, while ksmbd
+ * builds responses from multiple iov entries. Flatten the response first,
+ * produce the negotiated transform, and replace the response iov only when the
+ * result is smaller than the original message.
+ *
+ * Encrypted and compound responses are intentionally left unchanged. The
+ * caller may still continue sending the original response when this function
+ * returns zero.
+ *
+ * Return: 1 if the response was replaced, 0 if compression was skipped, or a
+ * negative errno on failure.
+ */
+int ksmbd_compress_response(struct ksmbd_work *work)
+{
+ struct smb2_compression_hdr *chdr;
+ struct smb2_hdr *req_hdr;
+ u32 src_len, dst_len, compressed_pdu_len, max_dst_len;
+ u8 *src = NULL, *out = NULL, *p;
+ int i, rc;
+
+ if (!work->compress_response || work->encrypted ||
+ work->conn->compress_algorithm != SMB3_COMPRESS_LZ77)
+ return 0;
+
+ req_hdr = smb_get_msg(work->request_buf);
+ if (req_hdr->NextCommand || work->next_smb2_rcv_hdr_off ||
+ work->next_smb2_rsp_hdr_off)
+ return 0;
+
+ src_len = get_rfc1002_len(work->iov[0].iov_base);
+ if (src_len < SMB_COMPRESS_MIN_LEN)
+ return 0;
+
+ src = kvmalloc(src_len, KSMBD_DEFAULT_GFP);
+ if (!src)
+ return -ENOMEM;
+
+ p = src;
+ /* iov[0] contains only the RFC1002 length; the SMB2 PDU starts at iov[1]. */
+ for (i = 1; i < work->iov_cnt; i++) {
+ if (work->iov[i].iov_len > src + src_len - p) {
+ rc = -EINVAL;
+ goto out;
+ }
+ memcpy(p, work->iov[i].iov_base, work->iov[i].iov_len);
+ p += work->iov[i].iov_len;
+ }
+ if (p != src + src_len) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ max_dst_len = smb_lz77_compressed_alloc_size(src_len) +
+ sizeof(struct smb2_compression_hdr) +
+ 3 * sizeof(struct smb2_compression_payload_hdr) +
+ 2 * sizeof(struct smb2_compression_pattern_v1);
+ out = kvzalloc(sizeof(__be32) + max_dst_len,
+ KSMBD_DEFAULT_GFP);
+ if (!out) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ if (work->conn->compress_chained) {
+ dst_len = max_dst_len;
+ rc = smb_compression_compress_chained(SMB3_COMPRESS_LZ77,
+ work->conn->compress_pattern,
+ src, src_len,
+ out + sizeof(__be32),
+ &dst_len);
+ if (rc == -EMSGSIZE || dst_len >= src_len) {
+ rc = 0;
+ goto out;
+ }
+ if (rc)
+ goto out;
+ compressed_pdu_len = dst_len;
+ } else {
+ /*
+ * Peers which did not negotiate chained compression still use
+ * the original 16-byte unchained transform format.
+ */
+ dst_len = smb_lz77_compressed_alloc_size(src_len);
+ rc = smb_lz77_compress(src, src_len,
+ out + sizeof(__be32) + sizeof(*chdr),
+ &dst_len);
+ if (rc == -EMSGSIZE ||
+ dst_len + sizeof(*chdr) >= src_len) {
+ rc = 0;
+ goto out;
+ }
+ if (rc)
+ goto out;
+
+ compressed_pdu_len = sizeof(*chdr) + dst_len;
+ chdr = (struct smb2_compression_hdr *)(out + sizeof(__be32));
+ chdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
+ chdr->OriginalCompressedSegmentSize = cpu_to_le32(src_len);
+ chdr->CompressionAlgorithm = SMB3_COMPRESS_LZ77;
+ chdr->Flags = cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE);
+ chdr->Offset = 0;
+ }
+
+ *(__be32 *)out = cpu_to_be32(compressed_pdu_len);
+
+ /*
+ * Keep the transform in work->compress_buf until send completion.
+ * Existing response iovs can then be replaced without changing their
+ * individual ownership rules.
+ */
+ work->compress_buf = out;
+ work->iov[0].iov_base = out;
+ work->iov[0].iov_len = sizeof(__be32);
+ work->iov[1].iov_base = out + sizeof(__be32);
+ work->iov[1].iov_len = compressed_pdu_len;
+ work->iov_cnt = 2;
+ work->iov_idx = 1;
+ out = NULL;
+ rc = 1;
+out:
+ kvfree(out);
+ kvfree(src);
+ return rc;
+}
diff --git a/fs/smb/server/compress.h b/fs/smb/server/compress.h
new file mode 100644
index 000000000000..13df2eb221e8
--- /dev/null
+++ b/fs/smb/server/compress.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * SMB2 compression support for ksmbd.
+ *
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ */
+#ifndef __KSMBD_COMPRESS_H__
+#define __KSMBD_COMPRESS_H__
+
+#include "connection.h"
+#include "../common/compress/compress.h"
+
+int ksmbd_decompress_request(struct ksmbd_conn *conn);
+int ksmbd_decompress_work_request(struct ksmbd_work *work);
+int ksmbd_compress_response(struct ksmbd_work *work);
+
+#endif /* __KSMBD_COMPRESS_H__ */
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 1bb2081c492c..d211861ff86f 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -11,7 +11,10 @@
#include "server.h"
#include "smb_common.h"
#include "mgmt/ksmbd_ida.h"
+#include "mgmt/user_session.h"
#include "connection.h"
+#include "vfs_cache.h"
+#include "compress.h"
#include "transport_tcp.h"
#include "transport_rdma.h"
#include "misc.h"
@@ -19,6 +22,8 @@
static DEFINE_MUTEX(init_lock);
static struct ksmbd_conn_ops default_conn_ops;
+static struct delayed_work session_expiration_work;
+static bool stopping_session_expiration_work;
DEFINE_HASHTABLE(conn_list, CONN_HASH_BITS);
DECLARE_RWSEM(conn_list_lock);
@@ -26,33 +31,107 @@ DECLARE_RWSEM(conn_list_lock);
#ifdef CONFIG_PROC_FS
static struct proc_dir_entry *proc_clients;
+static const char *ksmbd_conn_state_string(struct ksmbd_conn *conn)
+{
+ switch (READ_ONCE(conn->status)) {
+ case KSMBD_SESS_NEW:
+ return "new";
+ case KSMBD_SESS_GOOD:
+ return "good";
+ case KSMBD_SESS_EXITING:
+ return "exiting";
+ case KSMBD_SESS_NEED_RECONNECT:
+ return "reconnect";
+ case KSMBD_SESS_NEED_NEGOTIATE:
+ return "negotiate";
+ case KSMBD_SESS_NEED_SETUP:
+ return "setup";
+ case KSMBD_SESS_RELEASING:
+ return "releasing";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *ksmbd_conn_transport_string(struct ksmbd_conn *conn)
+{
+ if (conn->transport->ops->rdma_read || conn->transport->ops->rdma_write)
+ return "smbdirect";
+ return "tcp";
+}
+
+static void proc_show_conn_feature(struct seq_file *m, bool *separator,
+ bool enabled, const char *name)
+{
+ if (!enabled)
+ return;
+ seq_printf(m, "%s%s", *separator ? "," : "", name);
+ *separator = true;
+}
+
+static void proc_show_conn_features(struct seq_file *m,
+ struct ksmbd_conn *conn)
+{
+ bool separator = false;
+
+ proc_show_conn_feature(m, &separator,
+ conn->sign || conn->signing_negotiated, "sign");
+ proc_show_conn_feature(m, &separator, conn->cipher_type, "encrypt");
+ proc_show_conn_feature(m, &separator,
+ conn->compress_algorithm != SMB3_COMPRESS_NONE,
+ "compress");
+ proc_show_conn_feature(m, &separator, conn->rdma_transform_ids,
+ "rdma-transform");
+ proc_show_conn_feature(m, &separator, conn->posix_ext_supported, "posix");
+ if (!separator)
+ seq_puts(m, "none");
+}
+
static int proc_show_clients(struct seq_file *m, void *v)
{
struct ksmbd_conn *conn;
struct timespec64 now, t;
int i;
- seq_printf(m, "#%-20s %-10s %-10s %-10s %-10s %-10s\n",
- "<name>", "<dialect>", "<credits>", "<open files>",
- "<requests>", "<last active>");
-
down_read(&conn_list_lock);
hash_for_each(conn_list, i, conn, hlist) {
+ unsigned int outstanding_credits, total_credits;
+ unsigned long id;
+ void *entry;
+ unsigned int sessions = 0;
+
jiffies_to_timespec64(jiffies - conn->last_active, &t);
ktime_get_real_ts64(&now);
t = timespec64_sub(now, t);
+
+ spin_lock(&conn->credits_lock);
+ outstanding_credits = conn->outstanding_credits;
+ total_credits = conn->total_credits;
+ spin_unlock(&conn->credits_lock);
+
+ rcu_read_lock();
+ xa_for_each(&conn->sessions, id, entry)
+ sessions++;
+ rcu_read_unlock();
#if IS_ENABLED(CONFIG_IPV6)
if (!conn->inet_addr)
- seq_printf(m, "%-20pI6c", &conn->inet6_addr);
+ seq_printf(m, "client:\t%pI6c\n", &conn->inet6_addr);
else
#endif
- seq_printf(m, "%-20pI4", &conn->inet_addr);
- seq_printf(m, " 0x%-10x %-10u %-12d %-10d %ptT\n",
- conn->dialect,
- conn->total_credits,
- atomic_read(&conn->stats.open_files_count),
- atomic_read(&conn->req_running),
- &t);
+ seq_printf(m, "client:\t%pI4\n", &conn->inet_addr);
+ seq_printf(m, "transport:\t%s\n", ksmbd_conn_transport_string(conn));
+ seq_printf(m, "state:\t%s\n", ksmbd_conn_state_string(conn));
+ seq_printf(m, "dialect:\t0x%04x\n", conn->dialect);
+ seq_printf(m, "credits:\t%u/%u\n", outstanding_credits,
+ total_credits);
+ seq_printf(m, "sessions:\t%u\n", sessions);
+ seq_printf(m, "open_files:\t%d\n",
+ atomic_read(&conn->stats.open_files_count));
+ seq_printf(m, "requests:\t%lld\n",
+ atomic64_read(&conn->stats.request_served));
+ seq_puts(m, "features:\t");
+ proc_show_conn_features(m, conn);
+ seq_printf(m, "\nlast_active:\t%ptT\n\n", &t);
}
up_read(&conn_list_lock);
return 0;
@@ -79,6 +158,95 @@ static int create_proc_clients(void) { return 0; }
static void delete_proc_clients(void) {}
#endif
+static struct workqueue_struct *ksmbd_conn_wq;
+
+static void ksmbd_session_expiration_worker(struct work_struct *work);
+
+int ksmbd_conn_wq_init(void)
+{
+ ksmbd_conn_wq = alloc_workqueue("ksmbd-conn-release",
+ WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
+ if (!ksmbd_conn_wq)
+ return -ENOMEM;
+
+ WRITE_ONCE(stopping_session_expiration_work, false);
+ INIT_DELAYED_WORK(&session_expiration_work,
+ ksmbd_session_expiration_worker);
+ queue_delayed_work(ksmbd_conn_wq, &session_expiration_work,
+ KSMBD_SESSION_EXPIRATION_INTERVAL);
+ return 0;
+}
+
+void ksmbd_conn_wq_destroy(void)
+{
+ if (ksmbd_conn_wq) {
+ WRITE_ONCE(stopping_session_expiration_work, true);
+ cancel_delayed_work_sync(&session_expiration_work);
+ destroy_workqueue(ksmbd_conn_wq);
+ ksmbd_conn_wq = NULL;
+ }
+}
+
+/*
+ * __ksmbd_conn_release_work() - perform the final, once-per-struct cleanup
+ * of a ksmbd_conn whose refcount has just dropped to zero.
+ *
+ * This is the common release path used by ksmbd_conn_put() for the embedded
+ * state that outlives the connection thread: async_ida and the attached
+ * transport (which owns the socket and iov for TCP). Called from a workqueue
+ * so that sleep-allowed teardown (sock_release -> tcp_close ->
+ * lock_sock_nested) never runs from an RCU softirq callback (free_opinfo_rcu)
+ * or any other non-sleeping putter context.
+ */
+static void __ksmbd_conn_release_work(struct work_struct *work)
+{
+ struct ksmbd_conn *conn =
+ container_of(work, struct ksmbd_conn, release_work);
+
+ ida_destroy(&conn->async_ida);
+ conn->transport->ops->free_transport(conn->transport);
+ kfree_sensitive(conn);
+}
+
+/**
+ * ksmbd_conn_get() - take a reference on @conn and return it.
+ *
+ * @conn: connection instance to get a reference to
+ *
+ * Returns @conn unchanged so callers can write
+ * "fp->conn = ksmbd_conn_get(work->conn);" in one expression. Returns NULL
+ * if @conn is NULL.
+ */
+struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn)
+{
+ if (!conn)
+ return NULL;
+
+ atomic_inc(&conn->refcnt);
+ return conn;
+}
+
+/**
+ * ksmbd_conn_put() - drop a reference and, if it was the last, queue the
+ * release onto ksmbd_conn_wq so it runs from process context.
+ *
+ * @conn: connection instance to put a reference to
+ *
+ * Callable from any context including RCU softirq callbacks and non-sleeping
+ * locks; the actual release is deferred to the workqueue. ksmbd_conn_wq is
+ * created in ksmbd_server_init() before any conn can be allocated and is
+ * destroyed in ksmbd_server_exit() after rcu_barrier(), so it is always
+ * non-NULL while a conn reference is held.
+ */
+void ksmbd_conn_put(struct ksmbd_conn *conn)
+{
+ if (!conn)
+ return;
+
+ if (atomic_dec_and_test(&conn->refcnt))
+ queue_work(ksmbd_conn_wq, &conn->release_work);
+}
+
/**
* ksmbd_conn_free() - free resources of the connection instance
*
@@ -93,13 +261,20 @@ void ksmbd_conn_free(struct ksmbd_conn *conn)
hash_del(&conn->hlist);
up_write(&conn_list_lock);
+ /*
+ * request_buf / preauth_info / mechToken are only ever accessed by the
+ * connection handler thread that owns @conn. ksmbd_conn_free() is
+ * called from the transport free_transport() path when that thread is
+ * exiting, so it is safe to release them unconditionally even when
+ * ksmbd_conn_put() below is not the final putter (oplock / ksmbd_file
+ * holders only retain the conn pointer, not these per-thread buffers).
+ */
xa_destroy(&conn->sessions);
kvfree(conn->request_buf);
- kfree(conn->preauth_info);
- if (atomic_dec_and_test(&conn->refcnt)) {
- conn->transport->ops->free_transport(conn->transport);
- kfree(conn);
- }
+ kfree_sensitive(conn->preauth_info);
+ kfree(conn->mechToken);
+ ksmbd_preauth_session_destroy(conn);
+ ksmbd_conn_put(conn);
}
/**
@@ -116,6 +291,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
return NULL;
conn->need_neg = true;
+ conn->creation_time = jiffies;
ksmbd_conn_set_new(conn);
conn->local_nls = load_nls("utf8");
if (!conn->local_nls)
@@ -126,16 +302,26 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
conn->um = ERR_PTR(-EOPNOTSUPP);
if (IS_ERR(conn->um))
conn->um = NULL;
+ INIT_WORK(&conn->release_work, __ksmbd_conn_release_work);
atomic_set(&conn->req_running, 0);
atomic_set(&conn->r_count, 0);
atomic_set(&conn->refcnt, 1);
conn->total_credits = 1;
conn->outstanding_credits = 0;
+ /*
+ * The command sequence window starts as the set { 0 } when the
+ * connection is established.
+ */
+ conn->seq_low = 0;
+ conn->seq_high = 1;
+ __set_bit(0, conn->seq_bitmap);
+
init_waitqueue_head(&conn->req_running_q);
init_waitqueue_head(&conn->r_count_q);
INIT_LIST_HEAD(&conn->requests);
INIT_LIST_HEAD(&conn->async_requests);
+ INIT_LIST_HEAD(&conn->preauth_sess_table);
spin_lock_init(&conn->request_lock);
spin_lock_init(&conn->credits_lock);
ida_init(&conn->async_ida);
@@ -204,6 +390,26 @@ void ksmbd_conn_try_dequeue_request(struct ksmbd_work *work)
wake_up_all(&conn->req_running_q);
}
+static void ksmbd_conn_cancel_async_requests(struct ksmbd_conn *conn)
+{
+ struct ksmbd_work *work, *tmp;
+
+ ksmbd_debug(CONN, "Cancel pending async requests on releasing connection\n");
+ spin_lock(&conn->request_lock);
+ list_for_each_entry_safe(work, tmp, &conn->async_requests,
+ async_request_entry) {
+ if (cmpxchg(&work->state, KSMBD_WORK_ACTIVE,
+ KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE)
+ continue;
+
+ ksmbd_debug(CONN, "Cancel async request id %d\n",
+ work->async_id);
+ if (work->cancel_fn)
+ work->cancel_fn(work->cancel_argv);
+ }
+ spin_unlock(&conn->request_lock);
+}
+
void ksmbd_conn_lock(struct ksmbd_conn *conn)
{
mutex_lock(&conn->srv_mutex);
@@ -214,39 +420,80 @@ void ksmbd_conn_unlock(struct ksmbd_conn *conn)
mutex_unlock(&conn->srv_mutex);
}
-void ksmbd_all_conn_set_status(u64 sess_id, u32 status)
+static bool ksmbd_session_is_bound_to_conn(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn)
+{
+ bool found;
+
+ rcu_read_lock();
+ found = xa_load(&conn->sessions, sess->id) == sess;
+ rcu_read_unlock();
+ if (found)
+ return true;
+
+ down_read(&sess->chann_lock);
+ found = xa_load(&sess->ksmbd_chann_list, (long)conn);
+ up_read(&sess->chann_lock);
+ return found;
+}
+
+void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status)
{
struct ksmbd_conn *conn;
int bkt;
down_read(&conn_list_lock);
hash_for_each(conn_list, bkt, conn, hlist) {
- if (conn->binding || xa_load(&conn->sessions, sess_id))
- WRITE_ONCE(conn->status, status);
+ if (ksmbd_session_is_bound_to_conn(sess, conn)) {
+ spin_lock(&conn->request_lock);
+ if (!ksmbd_conn_exiting(conn) &&
+ !ksmbd_conn_releasing(conn))
+ WRITE_ONCE(conn->status, status);
+ spin_unlock(&conn->request_lock);
+ }
}
up_read(&conn_list_lock);
}
+void ksmbd_conn_abort(struct ksmbd_conn *conn)
+{
+ bool shutdown = false;
+
+ spin_lock(&conn->request_lock);
+ if (!ksmbd_conn_exiting(conn) && !ksmbd_conn_releasing(conn)) {
+ ksmbd_conn_set_exiting(conn);
+ shutdown = true;
+ }
+ spin_unlock(&conn->request_lock);
+ wake_up_all(&conn->req_running_q);
+
+ if (shutdown && conn->transport->ops->shutdown)
+ conn->transport->ops->shutdown(conn->transport);
+}
+
void ksmbd_conn_wait_idle(struct ksmbd_conn *conn)
{
wait_event(conn->req_running_q, atomic_read(&conn->req_running) < 2);
}
-int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id)
+int ksmbd_conn_wait_idle_sess(struct ksmbd_conn *curr_conn,
+ struct ksmbd_session *sess)
{
struct ksmbd_conn *conn;
int rc, retry_count = 0, max_timeout = 120;
- int rcount = 1, bkt;
+ int rcount, bkt;
retry_idle:
if (retry_count >= max_timeout)
return -EIO;
+ /* A blocked byte-range lock cannot drain until teardown wakes it. */
+ ksmbd_wake_session_blocked_works(sess);
+
down_read(&conn_list_lock);
hash_for_each(conn_list, bkt, conn, hlist) {
- if (conn->binding || xa_load(&conn->sessions, sess_id)) {
- if (conn == curr_conn)
- rcount = 2;
+ if (ksmbd_session_is_bound_to_conn(sess, conn)) {
+ rcount = (conn == curr_conn) ? 2 : 1;
if (atomic_read(&conn->req_running) >= rcount) {
rc = wait_event_timeout(conn->req_running_q,
atomic_read(&conn->req_running) < rcount,
@@ -264,7 +511,8 @@ retry_idle:
return 0;
}
-int ksmbd_conn_write(struct ksmbd_work *work)
+static int __ksmbd_conn_write(struct ksmbd_work *work,
+ struct ksmbd_transport_write *tx)
{
struct ksmbd_conn *conn = work->conn;
int sent;
@@ -280,12 +528,14 @@ int ksmbd_conn_write(struct ksmbd_work *work)
if (!work->iov_idx)
return -EINVAL;
+ tx->iov = work->iov;
+ tx->iov_cnt = work->iov_cnt;
+ tx->size = get_rfc1002_len(work->iov[0].iov_base) + 4;
+ tx->need_invalidate_rkey = work->need_invalidate_rkey;
+ tx->remote_key = work->remote_key;
+
ksmbd_conn_lock(conn);
- sent = conn->transport->ops->writev(conn->transport, work->iov,
- work->iov_cnt,
- get_rfc1002_len(work->iov[0].iov_base) + 4,
- work->need_invalidate_rkey,
- work->remote_key);
+ sent = conn->transport->ops->writev(conn->transport, tx);
ksmbd_conn_unlock(conn);
if (sent < 0) {
@@ -296,6 +546,22 @@ int ksmbd_conn_write(struct ksmbd_work *work)
return 0;
}
+int ksmbd_conn_write(struct ksmbd_work *work)
+{
+ struct ksmbd_transport_write tx = {};
+
+ return __ksmbd_conn_write(work, &tx);
+}
+
+int ksmbd_conn_write_eor(struct ksmbd_work *work)
+{
+ struct ksmbd_transport_write tx = {
+ .msg_flags = MSG_EOR,
+ };
+
+ return __ksmbd_conn_write(work, &tx);
+}
+
int ksmbd_conn_rdma_read(struct ksmbd_conn *conn,
void *buf, unsigned int buflen,
struct smbdirect_buffer_descriptor_v1 *desc,
@@ -335,6 +601,20 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn)
if (kthread_should_stop())
return false;
+ /*
+ * Stale connections that have not completed NEGOTIATE and SESSION_SETUP
+ * must be disconnected. Do not race a request that is currently
+ * completing authentication.
+ */
+ if (!atomic_read(&conn->req_running) &&
+ time_after(jiffies, conn->creation_time +
+ KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) &&
+ (READ_ONCE(conn->need_neg) ||
+ !ksmbd_conn_has_valid_or_expired_session(conn))) {
+ ksmbd_debug(CONN, "Connection setup timed out\n");
+ return false;
+ }
+
if (atomic_read(&conn->stats.open_files_count) > 0)
return true;
@@ -352,9 +632,56 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn)
return true;
}
+static void ksmbd_session_expiration_worker(struct work_struct *work)
+{
+ struct ksmbd_conn *conn, *target;
+ int bkt;
+
+ if (!ksmbd_server_running())
+ goto reschedule;
+
+ ksmbd_expire_sessions();
+
+ /*
+ * An old connection without a Valid or Expired session must be
+ * disconnected. Process one connection at a time without holding
+ * conn_list_lock across transport shutdown.
+ */
+again:
+ target = NULL;
+ down_read(&conn_list_lock);
+ hash_for_each(conn_list, bkt, conn, hlist) {
+ if (ksmbd_conn_exiting(conn) || ksmbd_conn_releasing(conn) ||
+ atomic_read(&conn->req_running) ||
+ time_before_eq(jiffies, conn->creation_time +
+ KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) ||
+ (!READ_ONCE(conn->need_neg) &&
+ ksmbd_conn_has_valid_or_expired_session(conn)))
+ continue;
+
+ target = ksmbd_conn_get(conn);
+ break;
+ }
+ up_read(&conn_list_lock);
+
+ if (target) {
+ ksmbd_debug(CONN, "Connection setup timed out\n");
+ ksmbd_conn_abort(target);
+ ksmbd_conn_put(target);
+ goto again;
+ }
+
+reschedule:
+ if (!READ_ONCE(stopping_session_expiration_work))
+ queue_delayed_work(ksmbd_conn_wq, &session_expiration_work,
+ KSMBD_SESSION_EXPIRATION_INTERVAL);
+}
+
/* "+2" for BCC field (ByteCount, 2 bytes) */
#define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2)
#define SMB2_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb2_pdu))
+#define SMB2_TRANSFORM_MIN_SUPPORTED_PDU_SIZE \
+ (sizeof(struct smb2_transform_hdr) + sizeof(struct smb2_hdr))
/**
* ksmbd_conn_handler_loop() - session thread to listen on new smb requests
@@ -369,15 +696,13 @@ int ksmbd_conn_handler_loop(void *p)
struct ksmbd_conn *conn = (struct ksmbd_conn *)p;
struct ksmbd_transport *t = conn->transport;
unsigned int pdu_size, max_allowed_pdu_size, max_req;
+ __le32 proto;
char hdr_buf[4] = {0,};
int size;
mutex_init(&conn->srv_mutex);
__module_get(THIS_MODULE);
- if (t->ops->prepare && t->ops->prepare(t))
- goto out;
-
max_req = server_conf.max_inflight_req;
conn->last_active = jiffies;
set_freezable();
@@ -402,11 +727,7 @@ recheck:
pdu_size = get_rfc1002_len(hdr_buf);
ksmbd_debug(CONN, "RFC1002 header %u bytes\n", pdu_size);
- if (ksmbd_conn_good(conn))
- max_allowed_pdu_size =
- SMB3_MAX_MSGSIZE + conn->vals->max_write_size;
- else
- max_allowed_pdu_size = SMB3_MAX_MSGSIZE;
+ max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn);
if (pdu_size > max_allowed_pdu_size) {
pr_err_ratelimited("PDU length(%u) exceeded maximum allowed pdu size(%u) on connection(%d)\n",
@@ -449,15 +770,29 @@ recheck:
continue;
}
- if (!ksmbd_smb_request(conn))
- break;
-
if (((struct smb2_hdr *)smb_get_msg(conn->request_buf))->ProtocolId ==
- SMB2_PROTO_NUMBER) {
- if (pdu_size < SMB2_MIN_SUPPORTED_PDU_SIZE)
+ SMB2_COMPRESSION_TRANSFORM_ID) {
+ /*
+ * Convert the transform into a normal RFC1002-framed SMB2
+ * request before protocol validation and work allocation.
+ */
+ if (ksmbd_decompress_request(conn))
break;
+ pdu_size = get_rfc1002_len(conn->request_buf);
}
+ if (!ksmbd_smb_request(conn))
+ break;
+
+ proto = *(__le32 *)smb_get_msg(conn->request_buf);
+ if (proto == SMB2_PROTO_NUMBER &&
+ pdu_size < SMB2_MIN_SUPPORTED_PDU_SIZE)
+ break;
+
+ if (proto == SMB2_TRANSFORM_PROTO_NUM &&
+ pdu_size < SMB2_TRANSFORM_MIN_SUPPORTED_PDU_SIZE)
+ break;
+
if (!default_conn_ops.process_fn) {
pr_err("No connection request callback\n");
break;
@@ -469,8 +804,8 @@ recheck:
}
}
-out:
ksmbd_conn_set_releasing(conn);
+ ksmbd_conn_cancel_async_requests(conn);
/* Wait till all reference dropped to the Server object*/
ksmbd_debug(CONN, "Wait for all pending requests(%d)\n", atomic_read(&conn->r_count));
wait_event(conn->r_count_q, atomic_read(&conn->r_count) == 0);
@@ -507,8 +842,7 @@ void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn)
if (!atomic_dec_return(&conn->r_count) && waitqueue_active(&conn->r_count_q))
wake_up(&conn->r_count_q);
- if (atomic_dec_and_test(&conn->refcnt))
- kfree(conn);
+ ksmbd_conn_put(conn);
}
int ksmbd_conn_transport_init(void)
@@ -529,30 +863,63 @@ int ksmbd_conn_transport_init(void)
}
out:
mutex_unlock(&init_lock);
- create_proc_clients();
+ if (create_proc_clients())
+ pr_warn("Unable to create clients procfs entry\n");
return ret;
}
static void stop_sessions(void)
{
- struct ksmbd_conn *conn;
+ struct ksmbd_conn *conn, *target;
struct ksmbd_transport *t;
+ bool any;
int bkt;
+ /*
+ * Serialised via init_lock; no concurrent stop_sessions() can
+ * touch conn->stop_called, so writing it under the read lock is
+ * safe.
+ */
again:
+ target = NULL;
+ any = false;
down_read(&conn_list_lock);
hash_for_each(conn_list, bkt, conn, hlist) {
- t = conn->transport;
- ksmbd_conn_set_exiting(conn);
- if (t->ops->shutdown) {
- up_read(&conn_list_lock);
+ any = true;
+ if (conn->stop_called)
+ continue;
+ atomic_inc(&conn->refcnt);
+ conn->stop_called = true;
+ /*
+ * Mark the connection EXITING while still holding the
+ * read lock so the selection and the status transition
+ * happen together. Do not regress a connection that has
+ * already advanced to RELEASING on its own (e.g. the
+ * handler exited its receive loop for an unrelated
+ * reason).
+ */
+ spin_lock(&conn->request_lock);
+ if (!ksmbd_conn_releasing(conn))
+ ksmbd_conn_set_exiting(conn);
+ spin_unlock(&conn->request_lock);
+ target = conn;
+ break;
+ }
+ up_read(&conn_list_lock);
+
+ if (target) {
+ t = target->transport;
+ if (t->ops->shutdown)
t->ops->shutdown(t);
- down_read(&conn_list_lock);
+ if (atomic_dec_and_test(&target->refcnt)) {
+ ida_destroy(&target->async_ida);
+ t->ops->free_transport(t);
+ kfree_sensitive(target);
}
+ goto again;
}
- up_read(&conn_list_lock);
- if (!hash_empty(conn_list)) {
+ if (any) {
msleep(100);
goto again;
}
@@ -565,6 +932,5 @@ void ksmbd_conn_transport_destroy(void)
ksmbd_tcp_destroy();
ksmbd_rdma_stop_listening();
stop_sessions();
- ksmbd_rdma_destroy();
mutex_unlock(&init_lock);
}
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index 1e2587036bca..371f17b4f02a 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -16,14 +16,26 @@
#include <linux/kthread.h>
#include <linux/nls.h>
#include <linux/unicode.h>
+#include <linux/workqueue.h>
+#include <linux/bitmap.h>
#include "smb_common.h"
#include "ksmbd_work.h"
struct smbdirect_buffer_descriptor_v1;
+struct ksmbd_session;
#define KSMBD_SOCKET_BACKLOG 16
+/*
+ * Size of the per-connection SMB2 command sequence window. This mirrors
+ * SMB2_MAX_CREDITS, the maximum number of credits (and therefore the
+ * maximum number of outstanding sequence numbers) that can be granted on
+ * a connection. It must be a power of two so the window can be indexed as
+ * a ring.
+ */
+#define KSMBD_CMD_SEQ_WINDOW 8192
+
enum {
KSMBD_SESS_NEW = 0,
KSMBD_SESS_GOOD,
@@ -49,6 +61,7 @@ struct ksmbd_conn {
struct mutex srv_mutex;
int status;
unsigned int cli_cap;
+ bool stop_called;
union {
__be32 inet_addr;
#if IS_ENABLED(CONFIG_IPV6)
@@ -64,6 +77,7 @@ struct ksmbd_conn {
struct rw_semaphore session_lock;
/* smb session 1 per user */
struct xarray sessions;
+ unsigned long creation_time;
unsigned long last_active;
/* How many request are running currently */
atomic_t req_running;
@@ -72,6 +86,16 @@ struct ksmbd_conn {
unsigned int total_credits;
unsigned int outstanding_credits;
spinlock_t credits_lock;
+ /*
+ * Connection command sequence window. [seq_low, seq_high) is the
+ * range of granted sequence numbers (message IDs). seq_bitmap marks
+ * the ones in that range that have been granted but
+ * not yet consumed by a received request. All three are protected by
+ * credits_lock.
+ */
+ u64 seq_low;
+ u64 seq_high;
+ DECLARE_BITMAP(seq_bitmap, KSMBD_CMD_SEQ_WINDOW);
wait_queue_head_t req_running_q;
wait_queue_head_t r_count_q;
/* Lock to protect requests list*/
@@ -113,12 +137,21 @@ struct ksmbd_conn {
__le16 cipher_type;
__le16 compress_algorithm;
+ /* Negotiated SMB 3.1.1 compression capabilities. */
+ bool compress_chained;
+ bool compress_pattern;
+ /* Bitmap indexed by SMB2_RDMA_TRANSFORM_* IDs. */
+ unsigned long rdma_transform_ids;
+ bool rdma_transform_negotiated;
bool posix_ext_supported;
bool signing_negotiated;
__le16 signing_algorithm;
bool binding;
atomic_t refcnt;
bool is_aapl;
+ bool aapl_readdir_attr; /* READDIR_ATTR negotiated */
+ bool aapl_readdir_attr_v2; /* V2 specifically */
+ struct work_struct release_work;
};
struct ksmbd_conn_ops {
@@ -126,15 +159,22 @@ struct ksmbd_conn_ops {
int (*terminate_fn)(struct ksmbd_conn *conn);
};
+struct ksmbd_transport_write {
+ struct kvec *iov;
+ int iov_cnt;
+ int size;
+ bool need_invalidate_rkey;
+ unsigned int remote_key;
+ int msg_flags;
+};
+
struct ksmbd_transport_ops {
- int (*prepare)(struct ksmbd_transport *t);
void (*disconnect)(struct ksmbd_transport *t);
void (*shutdown)(struct ksmbd_transport *t);
int (*read)(struct ksmbd_transport *t, char *buf,
unsigned int size, int max_retries);
- int (*writev)(struct ksmbd_transport *t, struct kvec *iovs, int niov,
- int size, bool need_invalidate_rkey,
- unsigned int remote_key);
+ int (*writev)(struct ksmbd_transport *t,
+ const struct ksmbd_transport_write *tx);
int (*rdma_read)(struct ksmbd_transport *t,
void *buf, unsigned int len,
struct smbdirect_buffer_descriptor_v1 *desc,
@@ -153,6 +193,8 @@ struct ksmbd_transport {
#define KSMBD_TCP_RECV_TIMEOUT (7 * HZ)
#define KSMBD_TCP_SEND_TIMEOUT (5 * HZ)
+#define KSMBD_SESSION_EXPIRATION_INTERVAL (5 * HZ)
+#define KSMBD_UNAUTHENTICATED_CONN_TIMEOUT (45 * HZ)
#define KSMBD_TCP_PEER_SOCKADDR(c) ((struct sockaddr *)&((c)->peer_addr))
#define CONN_HASH_BITS 12
@@ -161,11 +203,18 @@ extern struct rw_semaphore conn_list_lock;
bool ksmbd_conn_alive(struct ksmbd_conn *conn);
void ksmbd_conn_wait_idle(struct ksmbd_conn *conn);
-int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id);
+int ksmbd_conn_wait_idle_sess(struct ksmbd_conn *curr_conn,
+ struct ksmbd_session *sess);
struct ksmbd_conn *ksmbd_conn_alloc(void);
void ksmbd_conn_free(struct ksmbd_conn *conn);
+struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn);
+void ksmbd_conn_put(struct ksmbd_conn *conn);
+void ksmbd_conn_abort(struct ksmbd_conn *conn);
+int ksmbd_conn_wq_init(void);
+void ksmbd_conn_wq_destroy(void);
bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c);
int ksmbd_conn_write(struct ksmbd_work *work);
+int ksmbd_conn_write_eor(struct ksmbd_work *work);
int ksmbd_conn_rdma_read(struct ksmbd_conn *conn,
void *buf, unsigned int buflen,
struct smbdirect_buffer_descriptor_v1 *desc,
@@ -191,11 +240,25 @@ void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn);
* This is a hack. We will move status to a proper place once we land
* a multi-sessions support.
*/
+static inline bool ksmbd_conn_new(struct ksmbd_conn *conn)
+{
+ return READ_ONCE(conn->status) == KSMBD_SESS_NEW;
+}
+
static inline bool ksmbd_conn_good(struct ksmbd_conn *conn)
{
return READ_ONCE(conn->status) == KSMBD_SESS_GOOD;
}
+static inline unsigned int
+ksmbd_max_allowed_pdu_size(struct ksmbd_conn *conn)
+{
+ if (ksmbd_conn_good(conn))
+ return SMB3_MAX_MSGSIZE + conn->vals->max_write_size;
+
+ return SMB3_MAX_MSGSIZE;
+}
+
static inline bool ksmbd_conn_need_negotiate(struct ksmbd_conn *conn)
{
return READ_ONCE(conn->status) == KSMBD_SESS_NEED_NEGOTIATE;
@@ -256,5 +319,5 @@ static inline void ksmbd_conn_set_releasing(struct ksmbd_conn *conn)
WRITE_ONCE(conn->status, KSMBD_SESS_RELEASING);
}
-void ksmbd_all_conn_set_status(u64 sess_id, u32 status);
+void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status);
#endif /* __CONNECTION_H__ */
diff --git a/fs/smb/server/crypto_ctx.c b/fs/smb/server/crypto_ctx.c
index 8fd9713b00b7..2fe7d3300480 100644
--- a/fs/smb/server/crypto_ctx.c
+++ b/fs/smb/server/crypto_ctx.c
@@ -28,14 +28,6 @@ static inline void free_aead(struct crypto_aead *aead)
crypto_free_aead(aead);
}
-static void free_shash(struct shash_desc *shash)
-{
- if (shash) {
- crypto_free_shash(shash->tfm);
- kfree(shash);
- }
-}
-
static struct crypto_aead *alloc_aead(int id)
{
struct crypto_aead *tfm = NULL;
@@ -60,37 +52,10 @@ static struct crypto_aead *alloc_aead(int id)
return tfm;
}
-static struct shash_desc *alloc_shash_desc(int id)
-{
- struct crypto_shash *tfm = NULL;
- struct shash_desc *shash;
-
- switch (id) {
- case CRYPTO_SHASH_CMACAES:
- tfm = crypto_alloc_shash("cmac(aes)", 0, 0);
- break;
- default:
- return NULL;
- }
-
- if (IS_ERR(tfm))
- return NULL;
-
- shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm),
- KSMBD_DEFAULT_GFP);
- if (!shash)
- crypto_free_shash(tfm);
- else
- shash->tfm = tfm;
- return shash;
-}
-
static void ctx_free(struct ksmbd_crypto_ctx *ctx)
{
int i;
- for (i = 0; i < CRYPTO_SHASH_MAX; i++)
- free_shash(ctx->desc[i]);
for (i = 0; i < CRYPTO_AEAD_MAX; i++)
free_aead(ctx->ccmaes[i]);
kfree(ctx);
@@ -153,29 +118,6 @@ void ksmbd_release_crypto_ctx(struct ksmbd_crypto_ctx *ctx)
ctx_free(ctx);
}
-static struct ksmbd_crypto_ctx *____crypto_shash_ctx_find(int id)
-{
- struct ksmbd_crypto_ctx *ctx;
-
- if (id >= CRYPTO_SHASH_MAX)
- return NULL;
-
- ctx = ksmbd_find_crypto_ctx();
- if (ctx->desc[id])
- return ctx;
-
- ctx->desc[id] = alloc_shash_desc(id);
- if (ctx->desc[id])
- return ctx;
- ksmbd_release_crypto_ctx(ctx);
- return NULL;
-}
-
-struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void)
-{
- return ____crypto_shash_ctx_find(CRYPTO_SHASH_CMACAES);
-}
-
static struct ksmbd_crypto_ctx *____crypto_aead_ctx_find(int id)
{
struct ksmbd_crypto_ctx *ctx;
diff --git a/fs/smb/server/crypto_ctx.h b/fs/smb/server/crypto_ctx.h
index b9476ed520ae..b22c6e086f03 100644
--- a/fs/smb/server/crypto_ctx.h
+++ b/fs/smb/server/crypto_ctx.h
@@ -6,41 +6,24 @@
#ifndef __CRYPTO_CTX_H__
#define __CRYPTO_CTX_H__
-#include <crypto/hash.h>
#include <crypto/aead.h>
enum {
- CRYPTO_SHASH_CMACAES = 0,
- CRYPTO_SHASH_MAX,
-};
-
-enum {
CRYPTO_AEAD_AES_GCM = 16,
CRYPTO_AEAD_AES_CCM,
CRYPTO_AEAD_MAX,
};
-enum {
- CRYPTO_BLK_ECBDES = 32,
- CRYPTO_BLK_MAX,
-};
-
struct ksmbd_crypto_ctx {
struct list_head list;
- struct shash_desc *desc[CRYPTO_SHASH_MAX];
struct crypto_aead *ccmaes[CRYPTO_AEAD_MAX];
};
-#define CRYPTO_CMACAES(c) ((c)->desc[CRYPTO_SHASH_CMACAES])
-
-#define CRYPTO_CMACAES_TFM(c) ((c)->desc[CRYPTO_SHASH_CMACAES]->tfm)
-
#define CRYPTO_GCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_GCM])
#define CRYPTO_CCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_CCM])
void ksmbd_release_crypto_ctx(struct ksmbd_crypto_ctx *ctx);
-struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void);
struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_gcm(void);
struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_ccm(void);
void ksmbd_crypto_destroy(void);
diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index 8ccd57fd904b..2673522c76bc 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -113,7 +113,8 @@ struct ksmbd_startup_request {
__u32 max_connections; /* Number of maximum simultaneous connections */
__s8 bind_interfaces_only;
__u32 max_ip_connections; /* Number of maximum connection per ip address */
- __s8 reserved[499]; /* Reserved room */
+ __s8 aapl_model[32]; /* AAPL model string for Finder icon, e.g. "Xserve" */
+ __s8 reserved[467]; /* Reserved room */
__u32 ifc_list_sz; /* interfaces list size */
__s8 ____payload[];
} __packed;
@@ -285,6 +286,7 @@ struct ksmbd_spnego_authen_response {
* stored in SecurityBuffer of SMB2 SESSION
* SETUP response
*/
+ __u64 session_expiry; /* Kerberos ticket expiry time */
__u8 payload[]; /* session key + AP_REP */
};
@@ -377,6 +379,10 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_SHARE_FLAG_UPDATE BIT(14)
#define KSMBD_SHARE_FLAG_CROSSMNT BIT(15)
#define KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY BIT(16)
+#define KSMBD_SHARE_FLAG_HIDE_UNREADABLE BIT(17)
+#define KSMBD_SHARE_FLAG_TIME_MACHINE BIT(18)
+/* Keep BIT(19) reserved for the existing ksmbd-tools WIDE_LINKS flag. */
+#define KSMBD_SHARE_FLAG_ENCRYPT_DATA BIT(20)
/*
* Tree connect request flags.
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index ab4958dc3eb0..d307aefe0aec 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -11,11 +11,42 @@
#include "server.h"
#include "connection.h"
#include "ksmbd_work.h"
+#include "vfs_cache.h"
#include "mgmt/ksmbd_ida.h"
static struct kmem_cache *work_cache;
static struct workqueue_struct *ksmbd_wq;
+static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
+{
+ struct kvec *new;
+ int new_alloc_cnt = work->iov_alloc_cnt;
+
+ if (work->iov_alloc_cnt >= work->iov_cnt + need_iov_cnt)
+ return 0;
+
+ do {
+ new_alloc_cnt += KSMBD_WORK_INLINE_IOVS;
+ } while (new_alloc_cnt < work->iov_cnt + need_iov_cnt);
+
+ if (work->iov == work->iov_inline) {
+ new = kzalloc_objs(*new, new_alloc_cnt, KSMBD_DEFAULT_GFP);
+ if (!new)
+ return -ENOMEM;
+
+ memcpy(new, work->iov_inline, sizeof(work->iov_inline));
+ } else {
+ new = krealloc(work->iov, sizeof(*new) * new_alloc_cnt,
+ KSMBD_DEFAULT_GFP | __GFP_ZERO);
+ if (!new)
+ return -ENOMEM;
+ }
+
+ work->iov = new;
+ work->iov_alloc_cnt = new_alloc_cnt;
+ return 0;
+}
+
struct ksmbd_work *ksmbd_alloc_work_struct(void)
{
struct ksmbd_work *work = kmem_cache_zalloc(work_cache, KSMBD_DEFAULT_GFP);
@@ -26,14 +57,10 @@ struct ksmbd_work *ksmbd_alloc_work_struct(void)
INIT_LIST_HEAD(&work->request_entry);
INIT_LIST_HEAD(&work->async_request_entry);
INIT_LIST_HEAD(&work->fp_entry);
+ INIT_LIST_HEAD(&work->notify_entry);
INIT_LIST_HEAD(&work->aux_read_list);
- work->iov_alloc_cnt = 4;
- work->iov = kzalloc_objs(struct kvec, work->iov_alloc_cnt,
- KSMBD_DEFAULT_GFP);
- if (!work->iov) {
- kmem_cache_free(work_cache, work);
- work = NULL;
- }
+ work->iov_alloc_cnt = ARRAY_SIZE(work->iov_inline);
+ work->iov = work->iov_inline;
}
return work;
}
@@ -53,11 +80,16 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
}
kfree(work->tr_buf);
+ kvfree(work->compress_buf);
kvfree(work->request_buf);
- kfree(work->iov);
+ if (work->iov != work->iov_inline)
+ kfree(work->iov);
if (work->async_id)
ksmbd_release_id(&work->conn->async_ida, work->async_id);
+ if (work->owns_conn_ref)
+ ksmbd_conn_put(work->conn);
+ ksmbd_fd_put(work, work->request_open);
kmem_cache_free(work_cache, work);
}
@@ -116,19 +148,9 @@ static int __ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len,
return -ENOMEM;
}
- if (work->iov_alloc_cnt < work->iov_cnt + need_iov_cnt) {
- struct kvec *new;
-
- work->iov_alloc_cnt += 4;
- new = krealloc(work->iov,
- sizeof(struct kvec) * work->iov_alloc_cnt,
- KSMBD_DEFAULT_GFP | __GFP_ZERO);
- if (!new) {
- kfree(ar);
- work->iov_alloc_cnt -= 4;
- return -ENOMEM;
- }
- work->iov = new;
+ if (ksmbd_reserve_iov(work, need_iov_cnt)) {
+ kfree(ar);
+ return -ENOMEM;
}
/* Plus rfc_length size on first iov */
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index d36393ff8310..0844aa929f55 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -12,6 +12,9 @@
struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_tree_connect;
+struct ksmbd_file;
+
+#define KSMBD_WORK_INLINE_IOVS 4
enum {
KSMBD_WORK_ACTIVE = 0,
@@ -42,6 +45,7 @@ struct ksmbd_work {
int iov_alloc_cnt;
int iov_cnt;
int iov_idx;
+ struct kvec iov_inline[KSMBD_WORK_INLINE_IOVS];
/* Next cmd hdr in compound req buf*/
int next_smb2_rcv_hdr_off;
@@ -57,25 +61,41 @@ struct ksmbd_work {
u64 compound_fid;
u64 compound_pfid;
u64 compound_sid;
+ __le32 compound_status;
const struct cred *saved_cred;
/* 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;
void *tr_buf;
+ /* Contiguous SMB2 compression transform owned by this work item. */
+ void *compress_buf;
- unsigned char state;
+ unsigned int state;
/* No response for cancelled request */
bool send_no_response:1;
/* Request is encrypted */
bool encrypted:1;
+ /* READ response should be wrapped in a compression transform. */
+ bool compress_response:1;
/* Is this SYNC or ASYNC ksmbd_work */
bool asynchronous:1;
+ /* Work owns a reference to @conn. */
+ bool owns_conn_ref:1;
bool need_invalidate_rkey:1;
+ bool request_open_chseq_tracked:1;
+ bool session_setup_reauth:1;
unsigned int remote_key;
/* cancel works */
@@ -83,12 +103,21 @@ struct ksmbd_work {
void **cancel_argv;
void (*cancel_fn)(void **argv);
+ /*
+ * Refcounted open associated with the SMB2 command currently being
+ * processed.
+ */
+ struct ksmbd_file *request_open;
+ __le16 request_open_chseq;
+
struct work_struct work;
/* List head at conn->requests */
struct list_head request_entry;
/* List head at conn->async_requests */
struct list_head async_request_entry;
struct list_head fp_entry;
+ /* List head at ksmbd_file->notify_pendings */
+ struct list_head notify_entry;
};
/**
diff --git a/fs/smb/server/mgmt/share_config.c b/fs/smb/server/mgmt/share_config.c
index 53f44ff4d376..cc9f18ede80d 100644
--- a/fs/smb/server/mgmt/share_config.c
+++ b/fs/smb/server/mgmt/share_config.c
@@ -9,6 +9,7 @@
#include <linux/rwsem.h>
#include <linux/parser.h>
#include <linux/namei.h>
+#include <linux/fs_struct.h>
#include <linux/sched.h>
#include <linux/mm.h>
@@ -28,6 +29,63 @@ struct ksmbd_veto_pattern {
struct list_head list;
};
+#ifdef CONFIG_PROC_FS
+static const struct ksmbd_const_name ksmbd_share_flag_names[] = {
+ {KSMBD_SHARE_FLAG_AVAILABLE, "available"},
+ {KSMBD_SHARE_FLAG_BROWSEABLE, "browseable"},
+ {KSMBD_SHARE_FLAG_WRITEABLE, "writeable"},
+ {KSMBD_SHARE_FLAG_READONLY, "read-only"},
+ {KSMBD_SHARE_FLAG_GUEST_OK, "guest-ok"},
+ {KSMBD_SHARE_FLAG_GUEST_ONLY, "guest-only"},
+ {KSMBD_SHARE_FLAG_STORE_DOS_ATTRS, "store-dos-attrs"},
+ {KSMBD_SHARE_FLAG_OPLOCKS, "oplocks"},
+ {KSMBD_SHARE_FLAG_PIPE, "pipe"},
+ {KSMBD_SHARE_FLAG_HIDE_DOT_FILES, "hide-dot-files"},
+ {KSMBD_SHARE_FLAG_INHERIT_OWNER, "inherit-owner"},
+ {KSMBD_SHARE_FLAG_STREAMS, "streams"},
+ {KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS, "follow-symlinks"},
+ {KSMBD_SHARE_FLAG_ACL_XATTR, "acl-xattr"},
+ {KSMBD_SHARE_FLAG_UPDATE, "update"},
+ {KSMBD_SHARE_FLAG_CROSSMNT, "crossmnt"},
+ {KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY, "continuous-availability"},
+ {KSMBD_SHARE_FLAG_ENCRYPT_DATA, "encrypt-data"},
+};
+
+static int proc_show_shares(struct seq_file *m, void *v)
+{
+ struct ksmbd_share_config *share;
+ int i;
+
+ down_read(&shares_table_lock);
+ hash_for_each(shares_table, i, share, hlist) {
+ seq_printf(m, "name:\t%s\n", share->name);
+ seq_printf(m, "type:\t%s\n",
+ test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE) ?
+ "pipe" : "disk");
+ seq_printf(m, "tree_connects:\t%d\n",
+ atomic_read(&share->tree_connections));
+ seq_printf(m, "file_mask:\t0%07o\n", share->create_mask);
+ seq_printf(m, "directory_mask:\t0%07o\n", share->directory_mask);
+ seq_puts(m, "flags:\t");
+ ksmbd_proc_show_flag_names(m, ksmbd_share_flag_names,
+ ARRAY_SIZE(ksmbd_share_flag_names),
+ share->flags);
+ seq_puts(m, "\n\n");
+ }
+ up_read(&shares_table_lock);
+ return 0;
+}
+
+int create_proc_shares(void)
+{
+ if (!ksmbd_proc_create("shares", proc_show_shares, NULL))
+ return -ENOMEM;
+ return 0;
+}
+#else
+int create_proc_shares(void) { return 0; }
+#endif
+
static unsigned int share_name_hash(const char *name)
{
return jhash(name, strlen(name), 0);
@@ -88,9 +146,9 @@ static struct ksmbd_share_config *__share_lookup(const char *name)
static int parse_veto_list(struct ksmbd_share_config *share,
char *veto_list,
- int veto_list_sz)
+ size_t veto_list_sz)
{
- int sz = 0;
+ size_t sz;
if (!veto_list_sz)
return 0;
@@ -98,7 +156,7 @@ static int parse_veto_list(struct ksmbd_share_config *share,
while (veto_list_sz > 0) {
struct ksmbd_veto_pattern *p;
- sz = strlen(veto_list);
+ sz = strnlen(veto_list, veto_list_sz);
if (!sz)
break;
@@ -106,7 +164,7 @@ static int parse_veto_list(struct ksmbd_share_config *share,
if (!p)
return -ENOMEM;
- p->pattern = kstrdup(veto_list, KSMBD_DEFAULT_GFP);
+ p->pattern = kstrndup(veto_list, sz, KSMBD_DEFAULT_GFP);
if (!p->pattern) {
kfree(p);
return -ENOMEM;
@@ -114,6 +172,9 @@ static int parse_veto_list(struct ksmbd_share_config *share,
list_add(&p->list, &share->veto_list);
+ if (sz == veto_list_sz)
+ break;
+
veto_list += sz + 1;
veto_list_sz -= (sz + 1);
}
@@ -156,17 +217,37 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
share->flags = resp->flags;
atomic_set(&share->refcount, 1);
+ ksmbd_share_tree_conn_init(share);
INIT_LIST_HEAD(&share->veto_list);
share->name = kstrdup(name, KSMBD_DEFAULT_GFP);
+ if (!share->name) {
+ kill_share(share);
+ share = NULL;
+ goto out;
+ }
if (!test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
- int path_len = PATH_MAX;
+ size_t path_len;
- if (resp->payload_sz)
+ if (resp->payload_sz <= resp->veto_list_sz) {
+ ret = -EINVAL;
+ } else {
path_len = resp->payload_sz - resp->veto_list_sz;
-
- share->path = kstrndup(ksmbd_share_config_path(resp), path_len,
- KSMBD_DEFAULT_GFP);
+ if (resp->veto_list_sz)
+ path_len--;
+
+ if (!path_len) {
+ ret = -EINVAL;
+ } else {
+ share->path = kstrndup(
+ ksmbd_share_config_path(resp),
+ path_len, KSMBD_DEFAULT_GFP);
+ if (!share->path)
+ ret = -ENOMEM;
+ else
+ ret = 0;
+ }
+ }
if (share->path) {
share->path_sz = strlen(share->path);
while (share->path_sz > 1 &&
@@ -179,9 +260,10 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
share->force_directory_mode = resp->force_directory_mode;
share->force_uid = resp->force_uid;
share->force_gid = resp->force_gid;
- ret = parse_veto_list(share,
- KSMBD_SHARE_CONFIG_VETO_LIST(resp),
- resp->veto_list_sz);
+ if (!ret)
+ ret = parse_veto_list(share,
+ KSMBD_SHARE_CONFIG_VETO_LIST(resp),
+ resp->veto_list_sz);
if (!ret && share->path) {
if (__ksmbd_override_fsids(work, share)) {
kill_share(share);
@@ -189,7 +271,8 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
goto out;
}
- ret = kern_path(share->path, 0, &share->vfs_path);
+ scoped_with_init_fs()
+ ret = kern_path(share->path, 0, &share->vfs_path);
ksmbd_revert_fsids(work);
if (ret) {
ksmbd_debug(SMB, "failed to access '%s'\n",
@@ -199,7 +282,7 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
share->path = NULL;
}
}
- if (ret || !share->name) {
+ if (ret) {
kill_share(share);
share = NULL;
goto out;
diff --git a/fs/smb/server/mgmt/share_config.h b/fs/smb/server/mgmt/share_config.h
index d4ac2dd4de20..d157545fe7d1 100644
--- a/fs/smb/server/mgmt/share_config.h
+++ b/fs/smb/server/mgmt/share_config.h
@@ -24,6 +24,9 @@ struct ksmbd_share_config {
struct path vfs_path;
atomic_t refcount;
+#ifdef CONFIG_PROC_FS
+ atomic_t tree_connections;
+#endif
struct hlist_node hlist;
unsigned short create_mask;
unsigned short directory_mask;
@@ -60,6 +63,27 @@ static inline int test_share_config_flag(struct ksmbd_share_config *share,
return share->flags & flag;
}
+#ifdef CONFIG_PROC_FS
+static inline void ksmbd_share_tree_conn_init(struct ksmbd_share_config *share)
+{
+ atomic_set(&share->tree_connections, 0);
+}
+
+static inline void ksmbd_share_tree_conn_inc(struct ksmbd_share_config *share)
+{
+ atomic_inc(&share->tree_connections);
+}
+
+static inline void ksmbd_share_tree_conn_dec(struct ksmbd_share_config *share)
+{
+ atomic_dec(&share->tree_connections);
+}
+#else
+static inline void ksmbd_share_tree_conn_init(struct ksmbd_share_config *share) {}
+static inline void ksmbd_share_tree_conn_inc(struct ksmbd_share_config *share) {}
+static inline void ksmbd_share_tree_conn_dec(struct ksmbd_share_config *share) {}
+#endif
+
void ksmbd_share_config_del(struct ksmbd_share_config *share);
void __ksmbd_share_config_put(struct ksmbd_share_config *share);
@@ -74,4 +98,5 @@ struct ksmbd_share_config *ksmbd_share_config_get(struct ksmbd_work *work,
const char *name);
bool ksmbd_share_veto_filename(struct ksmbd_share_config *share,
const char *filename);
+int create_proc_shares(void);
#endif /* __SHARE_CONFIG_MANAGEMENT_H__ */
diff --git a/fs/smb/server/mgmt/tree_connect.c b/fs/smb/server/mgmt/tree_connect.c
index a72d7e42a6c2..dd1db3554cae 100644
--- a/fs/smb/server/mgmt/tree_connect.c
+++ b/fs/smb/server/mgmt/tree_connect.c
@@ -82,12 +82,15 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
down_write(&sess->tree_conns_lock);
ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn,
KSMBD_DEFAULT_GFP));
+ if (!ret)
+ atomic_inc(&tree_conn->refcount);
up_write(&sess->tree_conns_lock);
if (ret) {
status.ret = -ENOMEM;
goto out_error;
}
ksmbd_counter_inc(KSMBD_COUNTER_TREE_CONNS);
+ ksmbd_share_tree_conn_inc(sc);
kvfree(resp);
return status;
@@ -102,8 +105,10 @@ out_error:
void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon)
{
- if (atomic_dec_and_test(&tcon->refcount))
+ if (atomic_dec_and_test(&tcon->refcount)) {
+ ksmbd_share_config_put(tcon->share_conf);
kfree(tcon);
+ }
}
static int __ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
@@ -113,10 +118,12 @@ static int __ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id);
ksmbd_release_tree_conn_id(sess, tree_conn->id);
- ksmbd_share_config_put(tree_conn->share_conf);
ksmbd_counter_dec(KSMBD_COUNTER_TREE_CONNS);
- if (atomic_dec_and_test(&tree_conn->refcount))
+ ksmbd_share_tree_conn_dec(tree_conn->share_conf);
+ if (atomic_dec_and_test(&tree_conn->refcount)) {
+ ksmbd_share_config_put(tree_conn->share_conf);
kfree(tree_conn);
+ }
return ret;
}
@@ -124,6 +131,12 @@ int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
struct ksmbd_tree_connect *tree_conn)
{
down_write(&sess->tree_conns_lock);
+ if (tree_conn->t_state == TREE_DISCONNECTED ||
+ xa_load(&sess->tree_conns, tree_conn->id) != tree_conn) {
+ up_write(&sess->tree_conns_lock);
+ return -ENOENT;
+ }
+ tree_conn->t_state = TREE_DISCONNECTED;
xa_erase(&sess->tree_conns, tree_conn->id);
up_write(&sess->tree_conns_lock);
diff --git a/fs/smb/server/mgmt/user_config.c b/fs/smb/server/mgmt/user_config.c
index a3183fe5c536..5efc3d7455b9 100644
--- a/fs/smb/server/mgmt/user_config.c
+++ b/fs/smb/server/mgmt/user_config.c
@@ -26,8 +26,9 @@ struct ksmbd_user *ksmbd_login_user(const char *account)
resp_ext = ksmbd_ipc_login_request_ext(account);
user = ksmbd_alloc_user(resp, resp_ext);
+ kvfree(resp_ext);
out:
- kvfree(resp);
+ kvfree_sensitive(resp, sizeof(*resp));
return user;
}
@@ -36,6 +37,17 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
{
struct ksmbd_user *user;
+ /*
+ * resp->hash_sz is a __u16 taken from the mountd IPC login response but
+ * resp->hash[] is only KSMBD_REQ_MAX_HASH_SZ bytes. A malformed or
+ * malicious response can set hash_sz far beyond that (up to 65535),
+ * making the memcpy() below read past the response object
+ * (slab-out-of-bounds in ksmbd_alloc_user()). Reject any oversized
+ * hash rather than trust the length.
+ */
+ if (resp->hash_sz > sizeof(resp->hash))
+ return NULL;
+
user = kmalloc_obj(struct ksmbd_user, KSMBD_DEFAULT_GFP);
if (!user)
return NULL;
@@ -56,12 +68,6 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
goto err_free;
if (resp_ext) {
- if (resp_ext->ngroups > NGROUPS_MAX) {
- pr_err("ngroups(%u) from login response exceeds max groups(%d)\n",
- resp_ext->ngroups, NGROUPS_MAX);
- goto err_free;
- }
-
user->sgid = kmemdup(resp_ext->____payload,
resp_ext->ngroups * sizeof(gid_t),
KSMBD_DEFAULT_GFP);
@@ -76,7 +82,7 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
err_free:
kfree(user->name);
- kfree(user->passkey);
+ kfree_sensitive(user->passkey);
kfree(user);
return NULL;
}
@@ -86,7 +92,7 @@ void ksmbd_free_user(struct ksmbd_user *user)
ksmbd_ipc_logout_request(user->name, user->flags);
kfree(user->sgid);
kfree(user->name);
- kfree(user->passkey);
+ kfree_sensitive(user->passkey);
kfree(user);
}
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index 39be2d2be86c..44dc3f800cd4 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -22,6 +22,7 @@
static DEFINE_IDA(session_ida);
#define SESSION_HASH_BITS 12
+#define KSMBD_MAX_PENDING_SESSIONS 1
static DEFINE_HASHTABLE(sessions_table, SESSION_HASH_BITS);
static DECLARE_RWSEM(sessions_table_lock);
@@ -78,6 +79,28 @@ static const char *session_user_name(struct ksmbd_session *session)
return session->user->name;
}
+static const char *session_account_type(struct ksmbd_session *session)
+{
+ if (user_guest(session->user))
+ return "guest";
+ if (ksmbd_anonymous_user(session->user))
+ return "anonymous";
+ return "user";
+}
+
+static unsigned int session_open_file_count(struct ksmbd_session *session)
+{
+ struct ksmbd_file *fp;
+ unsigned int count = 0;
+ unsigned int id;
+
+ read_lock(&session->file_table.lock);
+ idr_for_each_entry(session->file_table.idr, fp, id)
+ count++;
+ read_unlock(&session->file_table.lock);
+ return count;
+}
+
static int show_proc_session(struct seq_file *m, void *v)
{
struct ksmbd_session *sess;
@@ -90,97 +113,91 @@ static int show_proc_session(struct seq_file *m, void *v)
sess = (struct ksmbd_session *)m->private;
ksmbd_user_session_get(sess);
+ seq_printf(m, "user:\t%s\n", session_user_name(sess));
+ seq_printf(m, "account_type:\t%s\n",
+ session_account_type(sess));
+ seq_printf(m, "id:\t%llu\n", sess->id);
+ seq_printf(m, "state:\t%s\n", session_state_string(sess));
+ seq_printf(m, "dialect:\t0x%04x\n", sess->dialect);
+ seq_printf(m, "last_active_seconds:\t%lu\n",
+ jiffies_to_msecs(jiffies - sess->last_active) / MSEC_PER_SEC);
+ seq_printf(m, "open_files:\t%u\n",
+ session_open_file_count(sess));
+
i = 0;
down_read(&sess->chann_lock);
xa_for_each(&sess->ksmbd_chann_list, id, chan) {
+ const char *name;
+
#if IS_ENABLED(CONFIG_IPV6)
if (chan->conn->inet_addr)
- seq_printf(m, "%-20s\t%pI4\n", "client",
+ seq_printf(m, "client:\t%pI4\n",
&chan->conn->inet_addr);
else
- seq_printf(m, "%-20s\t%pI6c\n", "client",
+ seq_printf(m, "client:\t%pI6c\n",
&chan->conn->inet6_addr);
#else
- seq_printf(m, "%-20s\t%pI4\n", "client",
+ seq_printf(m, "client:\t%pI4\n",
&chan->conn->inet_addr);
#endif
- seq_printf(m, "%-20s\t%s\n", "user", session_user_name(sess));
- seq_printf(m, "%-20s\t%llu\n", "id", sess->id);
- seq_printf(m, "%-20s\t%s\n", "state",
- session_state_string(sess));
-
- seq_printf(m, "%-20s\t", "capabilities");
+ seq_puts(m, "capabilities:\t");
ksmbd_proc_show_flag_names(m,
ksmbd_sess_cap_const_names,
ARRAY_SIZE(ksmbd_sess_cap_const_names),
chan->conn->vals->req_capabilities);
+ seq_putc(m, '\n');
+ seq_printf(m, "posix_extensions:\t%s\n",
+ chan->conn->posix_ext_supported ? "yes" : "no");
if (sess->sign) {
- seq_printf(m, "%-20s\t", "signing");
- ksmbd_proc_show_const_name(m, "%s\t",
- ksmbd_signing_const_names,
- ARRAY_SIZE(ksmbd_signing_const_names),
- le16_to_cpu(chan->conn->signing_algorithm));
- } else if (sess->enc) {
- seq_printf(m, "%-20s\t", "encryption");
- ksmbd_proc_show_const_name(m, "%s\t",
- ksmbd_cipher_const_names,
- ARRAY_SIZE(ksmbd_cipher_const_names),
- le16_to_cpu(chan->conn->cipher_type));
+ unsigned int algorithm =
+ le16_to_cpu(chan->conn->signing_algorithm);
+
+ name = ksmbd_proc_const_name(ksmbd_signing_const_names,
+ ARRAY_SIZE(ksmbd_signing_const_names),
+ algorithm);
+ if (name)
+ seq_printf(m, "signing:\t%s\n", name);
+ else
+ seq_printf(m, "signing:\t0x%04x\n",
+ algorithm);
+ }
+ if (sess->enc) {
+ unsigned int cipher = le16_to_cpu(chan->conn->cipher_type);
+
+ name = ksmbd_proc_const_name(ksmbd_cipher_const_names,
+ ARRAY_SIZE(ksmbd_cipher_const_names),
+ cipher);
+ if (name)
+ seq_printf(m, "encryption:\t%s\n", name);
+ else
+ seq_printf(m, "encryption:\t0x%04x\n",
+ cipher);
}
i++;
}
up_read(&sess->chann_lock);
- seq_printf(m, "%-20s\t%d\n", "channels", i);
+ seq_printf(m, "channels:\t%d\n", i);
i = 0;
down_read(&sess->tree_conns_lock);
xa_for_each(&sess->tree_conns, id, tree_conn) {
share_conf = tree_conn->share_conf;
- seq_printf(m, "%-20s\t%s\t%8d", "share",
- share_conf->name, tree_conn->id);
- if (test_share_config_flag(share_conf, KSMBD_SHARE_FLAG_PIPE))
- seq_printf(m, " %s ", "pipe");
- else
- seq_printf(m, " %s ", "disk");
- seq_putc(m, '\n');
+ seq_printf(m, "share:\t%s\n", share_conf->name);
+ seq_printf(m, "tree_id:\t%d\n", tree_conn->id);
+ seq_printf(m, "share_type:\t%s\n",
+ test_share_config_flag(share_conf, KSMBD_SHARE_FLAG_PIPE) ?
+ "pipe" : "disk");
+ i++;
}
up_read(&sess->tree_conns_lock);
+ seq_printf(m, "tree_connects:\t%d\n", i);
ksmbd_user_session_put(sess);
return 0;
}
-void ksmbd_proc_show_flag_names(struct seq_file *m,
- const struct ksmbd_const_name *table,
- int count,
- unsigned int flags)
-{
- int i;
-
- for (i = 0; i < count; i++) {
- if (table[i].const_value & flags)
- seq_printf(m, "0x%08x\t", table[i].const_value);
- }
- seq_putc(m, '\n');
-}
-
-void ksmbd_proc_show_const_name(struct seq_file *m,
- const char *format,
- const struct ksmbd_const_name *table,
- int count,
- unsigned int const_value)
-{
- int i;
-
- for (i = 0; i < count; i++) {
- if (table[i].const_value & const_value)
- seq_printf(m, format, table[i].name);
- }
- seq_putc(m, '\n');
-}
-
static int create_proc_session(struct ksmbd_session *sess)
{
char name[30];
@@ -188,6 +205,8 @@ static int create_proc_session(struct ksmbd_session *sess)
snprintf(name, sizeof(name), "sessions/%llu", sess->id);
sess->proc_entry = ksmbd_proc_create(name,
show_proc_session, sess);
+ if (!sess->proc_entry)
+ return -ENOMEM;
return 0;
}
@@ -204,9 +223,6 @@ static int show_proc_sessions(struct seq_file *m, void *v)
int i;
unsigned long id;
- seq_printf(m, "#%-40s %-15s %-10s %-10s\n",
- "<client>", "<user>", "<sess_id>", "<state>");
-
down_read(&sessions_table_lock);
hash_for_each(sessions_table, i, session, hlist) {
down_read(&session->chann_lock);
@@ -216,13 +232,13 @@ static int show_proc_sessions(struct seq_file *m, void *v)
#if IS_ENABLED(CONFIG_IPV6)
if (!chan->conn->inet_addr)
- seq_printf(m, " %-40pI6c", &chan->conn->inet6_addr);
+ seq_printf(m, "client:\t%pI6c\n", &chan->conn->inet6_addr);
else
#endif
- seq_printf(m, " %-40pI4", &chan->conn->inet_addr);
- seq_printf(m, " %-15s %-10llu %-10s\n",
- session_user_name(session),
- session->id,
+ seq_printf(m, "client:\t%pI4\n", &chan->conn->inet_addr);
+ seq_printf(m, "user:\t%s\n", session_user_name(session));
+ seq_printf(m, "id:\t%llu\n", session->id);
+ seq_printf(m, "state:\t%s\n\n",
session_state_string(session));
ksmbd_user_session_put(session);
@@ -255,7 +271,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);
@@ -308,8 +324,11 @@ static int __rpc_method(char *rpc_name)
if (!strcmp(rpc_name, "\\lsarpc") || !strcmp(rpc_name, "lsarpc"))
return KSMBD_RPC_LSARPC_METHOD_INVOKE;
+ if (!strcmp(rpc_name, "\\mdssvc") || !strcmp(rpc_name, "mdssvc"))
+ return -ENOENT;
+
pr_err("Unsupported RPC: %s\n", rpc_name);
- return 0;
+ return -ENOENT;
}
int ksmbd_session_rpc_open(struct ksmbd_session *sess, char *rpc_name)
@@ -319,8 +338,8 @@ int ksmbd_session_rpc_open(struct ksmbd_session *sess, char *rpc_name)
int method, id;
method = __rpc_method(rpc_name);
- if (!method)
- return -EINVAL;
+ if (method < 0)
+ return method;
entry = kzalloc_obj(struct ksmbd_session_rpc, KSMBD_DEFAULT_GFP);
if (!entry)
@@ -382,18 +401,23 @@ void ksmbd_session_destroy(struct ksmbd_session *sess)
return;
delete_proc_session(sess);
-
+ ksmbd_tree_conn_session_logoff(sess);
+ ksmbd_destroy_file_table(sess);
if (sess->user)
ksmbd_free_user(sess->user);
-
- ksmbd_tree_conn_session_logoff(sess);
- ksmbd_destroy_file_table(&sess->file_table);
ksmbd_launch_ksmbd_durable_scavenger();
ksmbd_session_rpc_clear_list(sess);
free_channel_list(sess);
- kfree(sess->Preauth_HashValue);
+ kfree_sensitive(sess->Preauth_HashValue);
ksmbd_release_id(&session_ida, sess->id);
- kfree(sess);
+ ida_destroy(&sess->tree_conn_ida);
+ kfree_sensitive(sess);
+}
+
+static void ksmbd_session_remove_from_table(struct ksmbd_session *sess)
+{
+ hash_del(&sess->hlist);
+ ksmbd_counter_dec(KSMBD_COUNTER_SESSIONS);
}
struct ksmbd_session *__session_lookup(unsigned long long id)
@@ -409,35 +433,153 @@ struct ksmbd_session *__session_lookup(unsigned long long id)
return NULL;
}
-static void ksmbd_expire_session(struct ksmbd_conn *conn)
+static bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn)
{
unsigned long id;
struct ksmbd_session *sess;
+ unsigned int pending = 0;
down_write(&sessions_table_lock);
down_write(&conn->session_lock);
xa_for_each(&conn->sessions, id, sess) {
+ if (READ_ONCE(sess->state) != SMB2_SESSION_IN_PROGRESS)
+ continue;
+
if (atomic_read(&sess->refcnt) <= 1 &&
- (sess->state != SMB2_SESSION_VALID ||
- time_after(jiffies,
- sess->last_active + SMB2_SESSION_TIMEOUT))) {
+ time_after(jiffies, sess->last_active +
+ KSMBD_UNAUTHENTICATED_CONN_TIMEOUT)) {
xa_erase(&conn->sessions, sess->id);
- hash_del(&sess->hlist);
+ ksmbd_session_remove_from_table(sess);
ksmbd_session_destroy(sess);
continue;
}
+ pending++;
}
up_write(&conn->session_lock);
up_write(&sessions_table_lock);
+ return pending >= KSMBD_MAX_PENDING_SESSIONS;
}
int ksmbd_session_register(struct ksmbd_conn *conn,
struct ksmbd_session *sess)
{
+ int ret;
+
sess->dialect = conn->dialect;
memcpy(sess->ClientGUID, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
- ksmbd_expire_session(conn);
- return xa_err(xa_store(&conn->sessions, sess->id, sess, KSMBD_DEFAULT_GFP));
+ /* Bound abandoned SessionId-zero authentication exchanges. */
+ if (ksmbd_too_many_session_setups(conn))
+ ret = -ENOSPC;
+ else
+ ret = xa_err(xa_store(&conn->sessions, sess->id, sess,
+ KSMBD_DEFAULT_GFP));
+ if (ret) {
+ down_write(&sessions_table_lock);
+ ksmbd_session_remove_from_table(sess);
+ up_write(&sessions_table_lock);
+ ksmbd_user_session_put(sess);
+ }
+
+ return ret;
+}
+
+void ksmbd_session_unregister(struct ksmbd_conn *conn,
+ struct ksmbd_session *sess)
+{
+ struct ksmbd_conn *session_conns[KSMBD_MAX_CHANNELS];
+ struct channel *chann;
+ unsigned long index;
+ unsigned int nr_conns = 0, i;
+ bool removed = false;
+
+ down_write(&sessions_table_lock);
+ if (!hlist_unhashed(&sess->hlist)) {
+ /* Keep each channel connection stable under sessions_table_lock. */
+ down_read(&sess->chann_lock);
+ xa_for_each(&sess->ksmbd_chann_list, index, chann) {
+ if (nr_conns == ARRAY_SIZE(session_conns))
+ break;
+ session_conns[nr_conns++] = chann->conn;
+ }
+ up_read(&sess->chann_lock);
+
+ ksmbd_session_remove_from_table(sess);
+ removed = true;
+ }
+
+ down_write(&conn->session_lock);
+ if (xa_load(&conn->sessions, sess->id) == sess)
+ xa_erase(&conn->sessions, sess->id);
+ up_write(&conn->session_lock);
+ for (i = 0; i < nr_conns; i++) {
+ if (session_conns[i] == conn)
+ continue;
+ down_write(&session_conns[i]->session_lock);
+ if (xa_load(&session_conns[i]->sessions, sess->id) == sess)
+ xa_erase(&session_conns[i]->sessions, sess->id);
+ up_write(&session_conns[i]->session_lock);
+ }
+ up_write(&sessions_table_lock);
+
+ if (removed)
+ ksmbd_user_session_put(sess);
+}
+
+bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn)
+{
+ struct ksmbd_session *sess;
+ unsigned long id;
+ int state, bkt;
+ bool found = false;
+
+ down_read(&conn->session_lock);
+ xa_for_each(&conn->sessions, id, sess) {
+ state = READ_ONCE(sess->state);
+ if (state == SMB2_SESSION_VALID ||
+ state == SMB2_SESSION_EXPIRED) {
+ found = true;
+ break;
+ }
+ }
+ up_read(&conn->session_lock);
+ if (found)
+ return true;
+
+ /* A session bound through SMB3 multichannel is not in conn->sessions. */
+ down_read(&sessions_table_lock);
+ hash_for_each(sessions_table, bkt, sess, hlist) {
+ state = READ_ONCE(sess->state);
+ if (state != SMB2_SESSION_VALID &&
+ state != SMB2_SESSION_EXPIRED)
+ continue;
+
+ down_read(&sess->chann_lock);
+ found = xa_load(&sess->ksmbd_chann_list, (long)conn);
+ up_read(&sess->chann_lock);
+ if (found)
+ break;
+ }
+ up_read(&sessions_table_lock);
+ return found;
+}
+
+void ksmbd_expire_sessions(void)
+{
+ struct ksmbd_session *sess;
+ u64 now = ktime_get_real_seconds();
+ int bkt;
+
+ down_read(&sessions_table_lock);
+ hash_for_each(sessions_table, bkt, sess, hlist) {
+ if (READ_ONCE(sess->state) != SMB2_SESSION_VALID ||
+ !sess->kerberos_expiry || now < sess->kerberos_expiry)
+ continue;
+
+ if (cmpxchg(&sess->state, SMB2_SESSION_VALID,
+ SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID)
+ ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS);
+ }
+ up_read(&sessions_table_lock);
}
static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
@@ -450,47 +592,36 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
if (!chann)
return -ENOENT;
- kfree(chann);
+ kfree_sensitive(chann);
return 0;
}
-void ksmbd_sessions_deregister(struct ksmbd_conn *conn)
+void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn)
{
struct ksmbd_session *sess;
unsigned long id;
+ struct hlist_node *tmp;
+ int bkt;
down_write(&sessions_table_lock);
- if (conn->binding) {
- int bkt;
- struct hlist_node *tmp;
-
- hash_for_each_safe(sessions_table, bkt, tmp, sess, hlist) {
- if (!ksmbd_chann_del(conn, sess) &&
- xa_empty(&sess->ksmbd_chann_list)) {
- hash_del(&sess->hlist);
- down_write(&conn->session_lock);
- xa_erase(&conn->sessions, sess->id);
- up_write(&conn->session_lock);
- if (atomic_dec_and_test(&sess->refcnt))
- ksmbd_session_destroy(sess);
- }
+ hash_for_each_safe(sessions_table, bkt, tmp, sess, hlist) {
+ if (!ksmbd_chann_del(conn, sess) &&
+ xa_empty(&sess->ksmbd_chann_list)) {
+ ksmbd_session_remove_from_table(sess);
+ down_write(&conn->session_lock);
+ xa_erase(&conn->sessions, sess->id);
+ up_write(&conn->session_lock);
+ if (atomic_dec_and_test(&sess->refcnt))
+ ksmbd_session_destroy(sess);
}
}
down_write(&conn->session_lock);
xa_for_each(&conn->sessions, id, sess) {
- unsigned long chann_id;
- struct channel *chann;
-
- xa_for_each(&sess->ksmbd_chann_list, chann_id, chann) {
- if (chann->conn != conn)
- ksmbd_conn_set_exiting(chann->conn);
- }
-
ksmbd_chann_del(conn, sess);
if (xa_empty(&sess->ksmbd_chann_list)) {
xa_erase(&conn->sessions, sess->id);
- hash_del(&sess->hlist);
+ ksmbd_session_remove_from_table(sess);
if (atomic_dec_and_test(&sess->refcnt))
ksmbd_session_destroy(sess);
}
@@ -543,14 +674,35 @@ struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id)
return sess;
}
-struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
- unsigned long long id)
+struct ksmbd_session *ksmbd_session_lookup_all_states(struct ksmbd_conn *conn,
+ unsigned long long id)
{
struct ksmbd_session *sess;
+ bool channel_found;
sess = ksmbd_session_lookup(conn, id);
- if (!sess && conn->binding)
+ if (!sess) {
sess = ksmbd_session_lookup_slowpath(id);
+ if (!sess)
+ return NULL;
+
+ down_read(&sess->chann_lock);
+ channel_found = xa_load(&sess->ksmbd_chann_list, (long)conn);
+ up_read(&sess->chann_lock);
+ if (!channel_found) {
+ ksmbd_user_session_put(sess);
+ sess = NULL;
+ }
+ }
+ return sess;
+}
+
+struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
+ unsigned long long id)
+{
+ struct ksmbd_session *sess;
+
+ sess = ksmbd_session_lookup_all_states(conn, id);
if (sess && sess->state != SMB2_SESSION_VALID) {
ksmbd_user_session_put(sess);
sess = NULL;
@@ -591,6 +743,17 @@ struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn,
return sess;
}
+void ksmbd_preauth_session_destroy(struct ksmbd_conn *conn)
+{
+ struct preauth_session *sess, *tmp;
+
+ list_for_each_entry_safe(sess, tmp, &conn->preauth_sess_table,
+ preauth_entry) {
+ list_del(&sess->preauth_entry);
+ kfree(sess);
+ }
+}
+
void destroy_previous_session(struct ksmbd_conn *conn,
struct ksmbd_user *user, u64 id)
{
@@ -611,16 +774,28 @@ void destroy_previous_session(struct ksmbd_conn *conn,
memcmp(user->passkey, prev_user->passkey, user->passkey_sz))
goto out;
- ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_RECONNECT);
- err = ksmbd_conn_wait_idle_sess_id(conn, id);
+ down_write(&prev_sess->chann_lock);
+ if (prev_sess->tearing_down) {
+ up_write(&prev_sess->chann_lock);
+ goto out;
+ }
+ prev_sess->tearing_down = true;
+ up_write(&prev_sess->chann_lock);
+
+ ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_RECONNECT);
+ err = ksmbd_conn_wait_idle_sess(conn, prev_sess);
if (err) {
- ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP);
+ down_write(&prev_sess->chann_lock);
+ prev_sess->tearing_down = false;
+ up_write(&prev_sess->chann_lock);
+ ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_GOOD);
goto out;
}
- ksmbd_destroy_file_table(&prev_sess->file_table);
+ ksmbd_destroy_file_table(prev_sess);
+ prev_sess->kerberos_expiry = 0;
prev_sess->state = SMB2_SESSION_EXPIRED;
- ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP);
+ ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP);
ksmbd_launch_ksmbd_durable_scavenger();
out:
up_write(&conn->session_lock);
@@ -667,6 +842,8 @@ static struct ksmbd_session *__session_create(int protocol)
if (!sess)
return NULL;
+ ida_init(&sess->tree_conn_ida);
+
if (ksmbd_init_file_table(&sess->file_table))
goto error;
@@ -686,14 +863,13 @@ static struct ksmbd_session *__session_create(int protocol)
if (ret)
goto error;
- ida_init(&sess->tree_conn_ida);
-
down_write(&sessions_table_lock);
hash_add(sessions_table, &sess->hlist, sess->id);
+ ksmbd_counter_inc(KSMBD_COUNTER_SESSIONS);
up_write(&sessions_table_lock);
- create_proc_session(sess);
- ksmbd_counter_inc(KSMBD_COUNTER_SESSIONS);
+ if (create_proc_session(sess))
+ pr_warn_ratelimited("Unable to create session %llu procfs entry\n", sess->id);
return sess;
error:
diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h
index 6aebd385be84..217258551d6d 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[CIFS_KEY_SIZE];
__u8 smb3signingkey[SMB3_SIGN_KEY_SIZE];
struct ksmbd_conn *conn;
};
@@ -41,11 +42,13 @@ struct ksmbd_session {
bool sign;
bool enc;
+ bool tearing_down;
int state;
__u8 *Preauth_HashValue;
char sess_key[CIFS_KEY_SIZE];
+ u64 kerberos_expiry;
struct hlist_node hlist;
struct rw_semaphore chann_lock;
@@ -69,6 +72,8 @@ struct ksmbd_session {
struct rw_semaphore rpc_lock;
};
+#define KSMBD_MAX_CHANNELS 32
+
static inline int test_session_flag(struct ksmbd_session *sess, int bit)
{
return sess->flags & bit;
@@ -95,14 +100,21 @@ bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn,
unsigned long long id);
int ksmbd_session_register(struct ksmbd_conn *conn,
struct ksmbd_session *sess);
-void ksmbd_sessions_deregister(struct ksmbd_conn *conn);
+void ksmbd_session_unregister(struct ksmbd_conn *conn,
+ struct ksmbd_session *sess);
+void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn);
+bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn);
+void ksmbd_expire_sessions(void);
struct ksmbd_session *__session_lookup(unsigned long long id);
struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
unsigned long long id);
+struct ksmbd_session *ksmbd_session_lookup_all_states(struct ksmbd_conn *conn,
+ unsigned long long id);
void destroy_previous_session(struct ksmbd_conn *conn,
struct ksmbd_user *user, u64 id);
struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn,
u64 sess_id);
+void ksmbd_preauth_session_destroy(struct ksmbd_conn *conn);
struct preauth_session *ksmbd_preauth_session_lookup(struct ksmbd_conn *conn,
unsigned long long id);
diff --git a/fs/smb/server/misc.c b/fs/smb/server/misc.c
index a543ec9d3581..2dd91c9e956a 100644
--- a/fs/smb/server/misc.c
+++ b/fs/smb/server/misc.c
@@ -121,7 +121,9 @@ int parse_stream_name(char *filename, char **stream_name, int *s_type)
char *stream_type;
char *s_name;
int rc = 0;
+ bool has_stream_type = false;
+ *stream_name = NULL;
s_name = filename;
filename = strsep(&s_name, ":");
ksmbd_debug(SMB, "filename : %s, streams : %s\n", filename, s_name);
@@ -137,14 +139,20 @@ int parse_stream_name(char *filename, char **stream_name, int *s_type)
ksmbd_debug(SMB, "stream name : %s, stream type : %s\n", s_name,
stream_type);
- if (!strncasecmp("$data", stream_type, 5))
+ if (!strncasecmp("$data", stream_type, 5)) {
*s_type = DATA_STREAM;
- else if (!strncasecmp("$index_allocation", stream_type, 17))
+ has_stream_type = true;
+ } else if (!strncasecmp("$index_allocation", stream_type, 17)) {
*s_type = DIR_STREAM;
- else
+ has_stream_type = true;
+ } else {
rc = -ENOENT;
+ }
}
+ if (has_stream_type && !s_name[0] && *s_type == DATA_STREAM)
+ goto out;
+
*stream_name = s_name;
out:
return rc;
@@ -283,39 +291,6 @@ char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename)
return ksmbd_casefold_sharename(um, name);
}
-/**
- * convert_to_unix_name() - convert windows name to unix format
- * @share: ksmbd_share_config pointer
- * @name: file name that is relative to share
- *
- * Return: converted name on success, otherwise NULL
- */
-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name)
-{
- int no_slash = 0, name_len, path_len;
- char *new_name;
-
- if (name[0] == '/')
- name++;
-
- path_len = share->path_sz;
- name_len = strlen(name);
- new_name = kmalloc(path_len + name_len + 2, KSMBD_DEFAULT_GFP);
- if (!new_name)
- return new_name;
-
- memcpy(new_name, share->path, path_len);
- if (new_name[path_len - 1] != '/') {
- new_name[path_len] = '/';
- no_slash = 1;
- }
-
- memcpy(new_name + path_len + no_slash, name, name_len);
- path_len += name_len + no_slash;
- new_name[path_len] = 0x00;
- return new_name;
-}
-
char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info,
const struct nls_table *local_nls,
int *conv_len)
diff --git a/fs/smb/server/misc.h b/fs/smb/server/misc.h
index 13423696ae8c..1faaddd0f5f7 100644
--- a/fs/smb/server/misc.h
+++ b/fs/smb/server/misc.h
@@ -25,7 +25,6 @@ void ksmbd_strip_last_slash(char *path);
void ksmbd_conv_path_to_windows(char *path);
char *ksmbd_casefold_sharename(struct unicode_map *um, const char *name);
char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename);
-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name);
#define KSMBD_DIR_INFO_ALIGNMENT 8
struct ksmbd_dir_info;
@@ -44,7 +43,7 @@ struct ksmbd_const_name {
const char *name;
};
-void ksmbd_proc_init(void);
+int ksmbd_proc_init(void);
void ksmbd_proc_cleanup(void);
void ksmbd_proc_reset(void);
struct proc_dir_entry *ksmbd_proc_create(const char *name,
@@ -54,13 +53,10 @@ void ksmbd_proc_show_flag_names(struct seq_file *m,
const struct ksmbd_const_name *table,
int count,
unsigned int flags);
-void ksmbd_proc_show_const_name(struct seq_file *m,
- const char *format,
- const struct ksmbd_const_name *table,
- int count,
- unsigned int const_value);
+const char *ksmbd_proc_const_name(const struct ksmbd_const_name *table,
+ int count, unsigned int const_value);
#else
-static inline void ksmbd_proc_init(void) {}
+static inline int ksmbd_proc_init(void) { return 0; }
static inline void ksmbd_proc_cleanup(void) {}
static inline void ksmbd_proc_reset(void) {}
#endif
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 09d9878db9cb..1b8c3482d1e4 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -5,6 +5,7 @@
*/
#include <linux/moduleparam.h>
+#include <linux/err.h>
#include "glob.h"
#include "oplock.h"
@@ -15,10 +16,58 @@
#include "mgmt/user_session.h"
#include "mgmt/share_config.h"
#include "mgmt/tree_connect.h"
+#include "server.h"
static LIST_HEAD(lease_table_list);
static DEFINE_RWLOCK(lease_list_lock);
+#define SMB2_LEASE_STATE_MASK_LE (SMB2_LEASE_READ_CACHING_LE | \
+ SMB2_LEASE_HANDLE_CACHING_LE | \
+ SMB2_LEASE_WRITE_CACHING_LE)
+
+static bool lease_state_valid(__le32 state)
+{
+ return !(state & ~SMB2_LEASE_STATE_MASK_LE);
+}
+
+static __le32 lease_state_grantable(__le32 state)
+{
+ if (state == SMB2_LEASE_READ_CACHING_LE ||
+ state == (SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE) ||
+ state == (SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_WRITE_CACHING_LE) ||
+ state == SMB2_LEASE_STATE_MASK_LE)
+ return state;
+
+ return 0;
+}
+
+static bool lease_v2_flags_valid(__le32 flags)
+{
+ return !(flags & ~SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE);
+}
+
+static bool lease_has_parent_key(struct lease *lease)
+{
+ return lease->flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
+}
+
+static bool lease_break_in_progress(struct lease *lease)
+{
+ struct oplock_info *opinfo;
+ bool ret = false;
+
+ spin_lock(&lease->lock);
+ list_for_each_entry(opinfo, &lease->open_list, lease_entry) {
+ if (opinfo->op_state == OPLOCK_ACK_WAIT) {
+ ret = true;
+ break;
+ }
+ }
+ spin_unlock(&lease->lock);
+
+ return ret;
+}
+
/**
* alloc_opinfo() - allocate a new opinfo object for oplock info
* @work: smb work
@@ -30,7 +79,6 @@ static DEFINE_RWLOCK(lease_list_lock);
static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
u64 id, __u16 Tid)
{
- struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
struct oplock_info *opinfo;
@@ -39,63 +87,96 @@ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
return NULL;
opinfo->sess = sess;
- opinfo->conn = conn;
+ opinfo->conn = ksmbd_conn_get(work->conn);
opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
opinfo->op_state = OPLOCK_STATE_NONE;
+ spin_lock_init(&opinfo->state_lock);
opinfo->pending_break = 0;
opinfo->fid = id;
opinfo->Tid = Tid;
INIT_LIST_HEAD(&opinfo->op_entry);
+ INIT_LIST_HEAD(&opinfo->lease_entry);
init_waitqueue_head(&opinfo->oplock_q);
init_waitqueue_head(&opinfo->oplock_brk);
atomic_set(&opinfo->refcount, 1);
atomic_set(&opinfo->breaking_cnt, 0);
- atomic_inc(&opinfo->conn->refcnt);
return opinfo;
}
-static void lease_add_list(struct oplock_info *opinfo)
+static void lease_get(struct lease *lease)
+{
+ atomic_inc(&lease->refcount);
+}
+
+static void lease_put(struct lease *lease)
{
- struct lease_table *lb = opinfo->o_lease->l_lb;
+ if (lease && atomic_dec_and_test(&lease->refcount))
+ kfree(lease);
+}
+static void lease_add_table(struct lease *lease, struct lease_table *lb)
+{
+ lease_get(lease);
+ lease->l_lb = lb;
spin_lock(&lb->lb_lock);
- list_add_rcu(&opinfo->lease_entry, &lb->lease_list);
+ list_add_rcu(&lease->l_entry, &lb->lease_list);
spin_unlock(&lb->lb_lock);
}
-static void lease_del_list(struct oplock_info *opinfo)
+static void lease_del_table(struct lease *lease)
{
- struct lease_table *lb = opinfo->o_lease->l_lb;
+ struct lease_table *lb = lease->l_lb;
if (!lb)
return;
spin_lock(&lb->lb_lock);
- if (list_empty(&opinfo->lease_entry)) {
+ if (list_empty(&lease->l_entry)) {
spin_unlock(&lb->lb_lock);
return;
}
- list_del_init(&opinfo->lease_entry);
- opinfo->o_lease->l_lb = NULL;
+ list_del_init(&lease->l_entry);
+ lease->l_lb = NULL;
spin_unlock(&lb->lb_lock);
+
+ lease_put(lease);
}
-static void lb_add(struct lease_table *lb)
+static struct lease_table *alloc_lease_table(struct oplock_info *opinfo)
{
- write_lock(&lease_list_lock);
- list_add(&lb->l_entry, &lease_table_list);
- write_unlock(&lease_list_lock);
+ struct lease_table *lb;
+
+ lb = kmalloc_obj(struct lease_table, KSMBD_DEFAULT_GFP);
+ if (!lb)
+ return NULL;
+
+ memcpy(lb->client_guid, opinfo->conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE);
+ lb->conn = ksmbd_conn_get(opinfo->conn);
+ INIT_LIST_HEAD(&lb->lease_list);
+ spin_lock_init(&lb->lb_lock);
+ return lb;
+}
+
+static void free_lease_table(struct lease_table *lb)
+{
+ if (!lb)
+ return;
+
+ ksmbd_conn_put(lb->conn);
+ kfree(lb);
}
-static int alloc_lease(struct oplock_info *opinfo, struct lease_ctx_info *lctx)
+static struct lease *alloc_lease(struct lease_ctx_info *lctx,
+ struct ksmbd_inode *ci)
{
struct lease *lease;
lease = kmalloc_obj(struct lease, KSMBD_DEFAULT_GFP);
if (!lease)
- return -ENOMEM;
+ return NULL;
memcpy(lease->lease_key, lctx->lease_key, SMB2_LEASE_KEY_SIZE);
lease->state = lctx->req_state;
@@ -105,30 +186,87 @@ static int alloc_lease(struct oplock_info *opinfo, struct lease_ctx_info *lctx)
lease->is_dir = lctx->is_dir;
memcpy(lease->parent_lease_key, lctx->parent_lease_key, SMB2_LEASE_KEY_SIZE);
lease->version = lctx->version;
- lease->epoch = le16_to_cpu(lctx->epoch) + 1;
- INIT_LIST_HEAD(&opinfo->lease_entry);
- opinfo->o_lease = lease;
+ lease->epoch = lctx->version == 2 ? le16_to_cpu(lctx->epoch) + 1 : 0;
+ lease->ci = ci;
+ lease->reuse_epoch = false;
+ lease->l_lb = NULL;
+ INIT_LIST_HEAD(&lease->l_entry);
+ INIT_LIST_HEAD(&lease->open_list);
+ spin_lock_init(&lease->lock);
+ atomic_set(&lease->refcount, 1);
+
+ return lease;
+}
- return 0;
+static void lease_add_open(struct lease *lease, struct oplock_info *opinfo)
+{
+ spin_lock(&lease->lock);
+ list_add(&opinfo->lease_entry, &lease->open_list);
+ spin_unlock(&lease->lock);
}
-static void free_lease(struct oplock_info *opinfo)
+static void lease_del_open(struct oplock_info *opinfo)
{
- struct lease *lease;
+ struct lease *lease = opinfo->o_lease;
+ bool remove_table = false;
- lease = opinfo->o_lease;
- kfree(lease);
+ if (!lease)
+ return;
+
+ spin_lock(&lease->lock);
+ if (!list_empty(&opinfo->lease_entry)) {
+ list_del_init(&opinfo->lease_entry);
+ remove_table = list_empty(&lease->open_list);
+ }
+ spin_unlock(&lease->lock);
+
+ if (remove_table) {
+ write_lock(&lease_list_lock);
+ lease_del_table(lease);
+ write_unlock(&lease_list_lock);
+ }
}
-static void free_opinfo(struct oplock_info *opinfo)
+static void free_lease(struct oplock_info *opinfo)
+{
+ lease_put(opinfo->o_lease);
+}
+
+static void __free_opinfo(struct oplock_info *opinfo)
{
if (opinfo->is_lease)
free_lease(opinfo);
- if (opinfo->conn && atomic_dec_and_test(&opinfo->conn->refcnt))
- kfree(opinfo->conn);
+ ksmbd_conn_put(opinfo->conn);
kfree(opinfo);
}
+static void free_opinfo_rcu(struct rcu_head *rcu)
+{
+ struct oplock_info *opinfo = container_of(rcu, struct oplock_info, rcu);
+
+ __free_opinfo(opinfo);
+}
+
+static void free_opinfo(struct oplock_info *opinfo)
+{
+ call_rcu(&opinfo->rcu, free_opinfo_rcu);
+}
+
+void lease_update_oplock_levels(struct lease *lease)
+{
+ struct oplock_info *opinfo;
+ __u8 level;
+
+ if (!lease)
+ return;
+
+ level = smb2_map_lease_to_oplock(lease->state);
+ spin_lock(&lease->lock);
+ list_for_each_entry(opinfo, &lease->open_list, lease_entry)
+ opinfo->level = level;
+ spin_unlock(&lease->lock);
+}
+
struct oplock_info *opinfo_get(struct ksmbd_file *fp)
{
struct oplock_info *opinfo;
@@ -142,10 +280,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);
@@ -159,6 +311,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);
@@ -176,9 +338,21 @@ void opinfo_put(struct oplock_info *opinfo)
free_opinfo(opinfo);
}
-static void opinfo_add(struct oplock_info *opinfo)
+static bool ksmbd_inode_has_lease(struct ksmbd_inode *ci)
{
- struct ksmbd_inode *ci = opinfo->o_fp->f_ci;
+ struct oplock_info *opinfo = opinfo_get_list(ci, NULL, NULL);
+ bool is_lease;
+
+ if (!opinfo)
+ return false;
+ is_lease = opinfo->is_lease;
+ opinfo_put(opinfo);
+ return is_lease;
+}
+
+static void opinfo_add(struct oplock_info *opinfo, struct ksmbd_file *fp)
+{
+ struct ksmbd_inode *ci = fp->f_ci;
down_write(&ci->m_lock);
list_add(&opinfo->op_entry, &ci->m_op_list);
@@ -189,11 +363,9 @@ static void opinfo_del(struct oplock_info *opinfo)
{
struct ksmbd_inode *ci = opinfo->o_fp->f_ci;
- if (opinfo->is_lease) {
- write_lock(&lease_list_lock);
- lease_del_list(opinfo);
- write_unlock(&lease_list_lock);
- }
+ if (opinfo->is_lease)
+ lease_del_open(opinfo);
+
down_write(&ci->m_lock);
list_del(&opinfo->op_entry);
up_write(&ci->m_lock);
@@ -242,8 +414,10 @@ int opinfo_write_to_read(struct oplock_info *opinfo)
}
opinfo->level = SMB2_OPLOCK_LEVEL_II;
- if (opinfo->is_lease)
+ if (opinfo->is_lease) {
lease->state = lease->new_state;
+ lease_update_oplock_levels(lease);
+ }
return 0;
}
@@ -258,7 +432,7 @@ int opinfo_read_handle_to_read(struct oplock_info *opinfo)
struct lease *lease = opinfo->o_lease;
lease->state = lease->new_state;
- opinfo->level = SMB2_OPLOCK_LEVEL_II;
+ lease_update_oplock_levels(lease);
return 0;
}
@@ -280,8 +454,10 @@ int opinfo_write_to_none(struct oplock_info *opinfo)
return -EINVAL;
}
opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
- if (opinfo->is_lease)
+ if (opinfo->is_lease) {
lease->state = lease->new_state;
+ lease_update_oplock_levels(lease);
+ }
return 0;
}
@@ -302,8 +478,10 @@ int opinfo_read_to_none(struct oplock_info *opinfo)
return -EINVAL;
}
opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
- if (opinfo->is_lease)
+ if (opinfo->is_lease) {
lease->state = lease->new_state;
+ lease_update_oplock_levels(lease);
+ }
return 0;
}
@@ -324,10 +502,7 @@ int lease_read_to_write(struct oplock_info *opinfo)
lease->new_state = SMB2_LEASE_NONE_LE;
lease->state |= SMB2_LEASE_WRITE_CACHING_LE;
- if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
- opinfo->level = SMB2_OPLOCK_LEVEL_BATCH;
- else
- opinfo->level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ lease_update_oplock_levels(lease);
return 0;
}
@@ -349,15 +524,7 @@ static int lease_none_upgrade(struct oplock_info *opinfo, __le32 new_state)
lease->new_state = SMB2_LEASE_NONE_LE;
lease->state = new_state;
- if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
- if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
- opinfo->level = SMB2_OPLOCK_LEVEL_BATCH;
- else
- opinfo->level = SMB2_OPLOCK_LEVEL_II;
- else if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
- opinfo->level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
- else if (lease->state & SMB2_LEASE_READ_CACHING_LE)
- opinfo->level = SMB2_OPLOCK_LEVEL_II;
+ lease_update_oplock_levels(lease);
return 0;
}
@@ -380,14 +547,23 @@ void close_id_del_oplock(struct ksmbd_file *fp)
opinfo_del(opinfo);
rcu_assign_pointer(fp->f_opinfo, NULL);
- if (opinfo->op_state == OPLOCK_ACK_WAIT) {
- opinfo->op_state = OPLOCK_CLOSING;
- wake_up_interruptible_all(&opinfo->oplock_q);
- if (opinfo->is_lease) {
- atomic_set(&opinfo->breaking_cnt, 0);
- wake_up_interruptible_all(&opinfo->oplock_brk);
- }
- }
+ spin_lock(&opinfo->state_lock);
+ if (opinfo->op_state == OPLOCK_ACK_WAIT && opinfo->is_lease)
+ atomic_set(&opinfo->breaking_cnt, 0);
+ /*
+ * An opinfo that has been removed from the inode list is terminal. Keep
+ * this transition and releasing pending_break under state_lock. a breaker
+ * takes the same lock before it acquires pending_break or sets ACK_WAIT.
+ */
+ opinfo->op_state = OPLOCK_CLOSING;
+ clear_bit_unlock(0, &opinfo->pending_break);
+ spin_unlock(&opinfo->state_lock);
+ wake_up_interruptible_all(&opinfo->oplock_q);
+ if (opinfo->is_lease)
+ wake_up_interruptible_all(&opinfo->oplock_brk);
+ /* memory barrier is needed for wake_up_bit() */
+ smp_mb__after_atomic();
+ wake_up_bit(&opinfo->pending_break, 0);
opinfo_count_dec(fp);
atomic_dec(&opinfo->refcount);
@@ -464,8 +640,12 @@ static inline int compare_guid_key(struct oplock_info *opinfo,
const char *guid1, const char *key1)
{
const char *guid2, *key2;
+ struct ksmbd_conn *conn;
- guid2 = opinfo->conn->ClientGUID;
+ conn = READ_ONCE(opinfo->conn);
+ if (!conn)
+ return 0;
+ guid2 = conn->ClientGUID;
key2 = opinfo->o_lease->lease_key;
if (!memcmp(guid1, guid2, SMB2_CLIENT_GUID_SIZE) &&
!memcmp(key1, key2, SMB2_LEASE_KEY_SIZE))
@@ -484,7 +664,7 @@ static inline int compare_guid_key(struct oplock_info *opinfo,
* Return: oplock(lease) object on success, otherwise NULL
*/
static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci,
- char *client_guid,
+ const char *client_guid,
struct lease_ctx_info *lctx)
{
int ret;
@@ -507,7 +687,12 @@ static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci,
ret = compare_guid_key(opinfo, client_guid, lctx->lease_key);
if (ret) {
+ if (!atomic_inc_not_zero(&opinfo->refcount))
+ continue;
+ if (m_opinfo)
+ opinfo_put(m_opinfo);
m_opinfo = opinfo;
+
/* skip upgrading lease about breaking lease */
if (atomic_read(&opinfo->breaking_cnt))
continue;
@@ -529,8 +714,11 @@ static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci,
if (lctx->req_state ==
(SMB2_LEASE_READ_CACHING_LE |
SMB2_LEASE_HANDLE_CACHING_LE)) {
- lease->epoch++;
- lease->state = lctx->req_state;
+ if (lease->state != lctx->req_state) {
+ lease->epoch++;
+ lease->state = lctx->req_state;
+ lease_update_oplock_levels(lease);
+ }
}
}
@@ -546,7 +734,7 @@ static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci,
return m_opinfo;
}
-static void wait_for_break_ack(struct oplock_info *opinfo)
+static bool wait_for_break_ack(struct oplock_info *opinfo)
{
int rc = 0;
@@ -557,11 +745,22 @@ static void wait_for_break_ack(struct oplock_info *opinfo)
/* is this a timeout ? */
if (!rc) {
- if (opinfo->is_lease)
+ spin_lock(&opinfo->state_lock);
+ if (opinfo->op_state == OPLOCK_CLOSING) {
+ spin_unlock(&opinfo->state_lock);
+ return false;
+ }
+ if (opinfo->is_lease) {
opinfo->o_lease->state = SMB2_LEASE_NONE_LE;
+ lease_update_oplock_levels(opinfo->o_lease);
+ }
opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
opinfo->op_state = OPLOCK_STATE_NONE;
+ spin_unlock(&opinfo->state_lock);
+ return true;
}
+
+ return false;
}
static void wake_up_oplock_break(struct oplock_info *opinfo)
@@ -572,17 +771,49 @@ static void wake_up_oplock_break(struct oplock_info *opinfo)
wake_up_bit(&opinfo->pending_break, 0);
}
+static bool oplock_break_set_ack_wait(struct oplock_info *opinfo)
+{
+ bool ret = false;
+
+ spin_lock(&opinfo->state_lock);
+ if (opinfo->op_state != OPLOCK_CLOSING) {
+ opinfo->op_state = OPLOCK_ACK_WAIT;
+ ret = true;
+ }
+ spin_unlock(&opinfo->state_lock);
+
+ return ret;
+}
+
static int oplock_break_pending(struct oplock_info *opinfo, int req_op_level)
{
- while (test_and_set_bit(0, &opinfo->pending_break)) {
+ for (;;) {
+ bool closing;
+
+ spin_lock(&opinfo->state_lock);
+ closing = opinfo->op_state == OPLOCK_CLOSING;
+ if (!closing && !test_and_set_bit(0, &opinfo->pending_break)) {
+ spin_unlock(&opinfo->state_lock);
+ break;
+ }
+ spin_unlock(&opinfo->state_lock);
+ if (closing)
+ return -ENOENT;
+
+ if (opinfo->is_lease)
+ opinfo->o_lease->reuse_epoch = true;
+
wait_on_bit(&opinfo->pending_break, 0, TASK_UNINTERRUPTIBLE);
/* Not immediately break to none. */
opinfo->open_trunc = 0;
- if (opinfo->op_state == OPLOCK_CLOSING)
+ spin_lock(&opinfo->state_lock);
+ closing = opinfo->op_state == OPLOCK_CLOSING;
+ spin_unlock(&opinfo->state_lock);
+ if (closing)
return -ENOENT;
- else if (opinfo->level <= req_op_level) {
+ if (opinfo->level <= req_op_level) {
if (opinfo->is_lease == false)
return 1;
@@ -608,6 +839,17 @@ static int oplock_break_pending(struct oplock_info *opinfo, int req_op_level)
return 0;
}
+static bool lease_break_needed(struct oplock_info *opinfo, int req_op_level,
+ bool open_trunc)
+{
+ struct lease *lease = opinfo->o_lease;
+
+ if (open_trunc)
+ return lease->state != SMB2_LEASE_NONE_LE;
+
+ return opinfo->level > req_op_level;
+}
+
/**
* __smb2_oplock_break_noti() - send smb2 oplock break cmd from conn
* to client
@@ -679,28 +921,72 @@ static void __smb2_oplock_break_noti(struct work_struct *wk)
out:
ksmbd_free_work_struct(work);
ksmbd_conn_r_count_dec(conn);
+ ksmbd_conn_put(conn);
+}
+
+/*
+ * Select and pin the connection used for an oplock break before doing any
+ * allocations which may sleep. The caller of oplock_break() holds a live
+ * reference on ci (a file being opened, a file being operated on, or an
+ * explicit ksmbd_inode_lookup_lock() reference in the parent lease break
+ * paths), so the inode cannot be freed during the call and its lock is
+ * reachable without dereferencing opinfo->o_fp, which is not pinned by
+ * the oplock reference and may be freed by a concurrent close.
+ *
+ * opinfo->conn is cleared under ci->m_lock by session_fd_check() when the
+ * durable handle owning the oplock is disconnected, reassigned by
+ * ksmbd_reopen_durable_fd() under the same lock, and the last
+ * ksmbd_conn_put() of the old connection frees it. Holding the read lock
+ * excludes both writers, so the connection cannot be freed while it is
+ * selected.
+ */
+static struct ksmbd_conn *smb2_oplock_break_conn_get(struct oplock_info *opinfo,
+ struct ksmbd_inode *ci)
+{
+ struct ksmbd_conn *conn;
+
+ down_read(&ci->m_lock);
+ conn = READ_ONCE(opinfo->conn);
+ if (conn && !ksmbd_conn_releasing(conn))
+ conn = ksmbd_conn_get(conn);
+ else
+ conn = NULL;
+ up_read(&ci->m_lock);
+
+ return conn;
}
/**
* smb2_oplock_break_noti() - send smb2 exclusive/batch to level2 oplock
* break command from server to client
* @opinfo: oplock info object
+ * @ci: inode owning the break target's oplock list, pinned by
+ * the caller
*
* Return: 0 on success, otherwise error
*/
-static int smb2_oplock_break_noti(struct oplock_info *opinfo)
+static int smb2_oplock_break_noti(struct oplock_info *opinfo,
+ struct ksmbd_inode *ci)
{
- struct ksmbd_conn *conn = opinfo->conn;
+ struct ksmbd_conn *conn;
struct oplock_break_info *br_info;
int ret = 0;
- struct ksmbd_work *work = ksmbd_alloc_work_struct();
+ struct ksmbd_work *work;
- if (!work)
+ conn = smb2_oplock_break_conn_get(opinfo, ci);
+ if (!conn)
+ return ksmbd_invalidate_durable_fd(opinfo->fid);
+
+ work = ksmbd_alloc_work_struct();
+ if (!work) {
+ ksmbd_conn_put(conn);
return -ENOMEM;
+ }
br_info = kmalloc_obj(struct oplock_break_info, KSMBD_DEFAULT_GFP);
if (!br_info) {
ksmbd_free_work_struct(work);
+ ksmbd_conn_put(conn);
return -ENOMEM;
}
@@ -709,6 +995,7 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
br_info->open_trunc = opinfo->open_trunc;
work->request_buf = (char *)br_info;
+ /* Transfer the reference acquired by smb2_oplock_break_conn_get(). */
work->conn = conn;
work->sess = opinfo->sess;
@@ -717,7 +1004,8 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
INIT_WORK(&work->work, __smb2_oplock_break_noti);
ksmbd_queue_work(work);
- wait_for_break_ack(opinfo);
+ if (wait_for_break_ack(opinfo))
+ ret = ksmbd_invalidate_durable_fd(opinfo->fid);
} else {
__smb2_oplock_break_noti(&work->work);
if (opinfo->level == SMB2_OPLOCK_LEVEL_II)
@@ -783,54 +1071,106 @@ static void __smb2_lease_break_noti(struct work_struct *wk)
out:
ksmbd_free_work_struct(work);
ksmbd_conn_r_count_dec(conn);
+ ksmbd_conn_put(conn);
+}
+
+/*
+ * Select and pin the connection used for a lease break before doing any
+ * allocations which may sleep. opinfo->conn is cleared under ci->m_lock,
+ * while lease->l_lb and the lease table lifetime are protected by
+ * lease_list_lock.
+ */
+static struct ksmbd_conn *smb2_lease_break_conn_get(struct oplock_info *opinfo)
+{
+ struct lease *lease = opinfo->o_lease;
+ struct lease_table *lb;
+ struct ksmbd_conn *conn;
+
+ /* Keep the connection which owns the open, when it is still active. */
+ down_read(&lease->ci->m_lock);
+ conn = READ_ONCE(opinfo->conn);
+ if (conn && !ksmbd_conn_releasing(conn))
+ conn = ksmbd_conn_get(conn);
+ else
+ conn = NULL;
+ up_read(&lease->ci->m_lock);
+
+ if (conn || lease->version != 2)
+ return conn;
+
+ /* Otherwise route v2 lease breaks through the shared lease channel. */
+ read_lock(&lease_list_lock);
+ lb = lease->l_lb;
+ if (lb && lb->conn && !ksmbd_conn_releasing(lb->conn))
+ conn = ksmbd_conn_get(lb->conn);
+ read_unlock(&lease_list_lock);
+
+ return conn;
}
/**
* smb2_lease_break_noti() - break lease when a new client request
* write lease
* @opinfo: contains lease state information
+ * @sync: send the lease break notification synchronously
+ * @inc_epoch: increment the lease epoch before sending the break
*
* Return: 0 on success, otherwise error
*/
-static int smb2_lease_break_noti(struct oplock_info *opinfo)
+static int smb2_lease_break_noti(struct oplock_info *opinfo, bool sync,
+ bool inc_epoch)
{
- struct ksmbd_conn *conn = opinfo->conn;
+ struct ksmbd_conn *conn;
struct ksmbd_work *work;
struct lease_break_info *br_info;
struct lease *lease = opinfo->o_lease;
+ conn = smb2_lease_break_conn_get(opinfo);
+ if (!conn)
+ return ksmbd_invalidate_durable_fd(opinfo->fid);
+
work = ksmbd_alloc_work_struct();
- if (!work)
+ if (!work) {
+ ksmbd_conn_put(conn);
return -ENOMEM;
+ }
br_info = kmalloc_obj(struct lease_break_info, KSMBD_DEFAULT_GFP);
if (!br_info) {
ksmbd_free_work_struct(work);
+ ksmbd_conn_put(conn);
return -ENOMEM;
}
br_info->curr_state = lease->state;
br_info->new_state = lease->new_state;
- if (lease->version == 2)
- br_info->epoch = cpu_to_le16(++lease->epoch);
- else
+ if (lease->version == 2) {
+ if (inc_epoch)
+ lease->epoch++;
+ br_info->epoch = cpu_to_le16(lease->epoch);
+ } else {
br_info->epoch = 0;
+ }
memcpy(br_info->lease_key, lease->lease_key, SMB2_LEASE_KEY_SIZE);
work->request_buf = (char *)br_info;
+ /* Transfer the reference acquired by smb2_lease_break_conn_get(). */
work->conn = conn;
work->sess = opinfo->sess;
ksmbd_conn_r_count_inc(conn);
if (opinfo->op_state == OPLOCK_ACK_WAIT) {
- INIT_WORK(&work->work, __smb2_lease_break_noti);
- ksmbd_queue_work(work);
- wait_for_break_ack(opinfo);
+ if (sync) {
+ __smb2_lease_break_noti(&work->work);
+ } else {
+ INIT_WORK(&work->work, __smb2_lease_break_noti);
+ ksmbd_queue_work(work);
+ }
} else {
__smb2_lease_break_noti(&work->work);
if (opinfo->o_lease->new_state == SMB2_LEASE_NONE_LE) {
- opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
opinfo->o_lease->state = SMB2_LEASE_NONE_LE;
+ lease_update_oplock_levels(opinfo->o_lease);
}
}
return 0;
@@ -853,10 +1193,12 @@ static void wait_lease_breaking(struct oplock_info *opinfo)
}
}
-static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
- struct ksmbd_work *in_work)
+static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci,
+ int req_op_level, struct ksmbd_work *in_work,
+ bool share_break, bool sync_lease_break)
{
int err = 0;
+ bool sent_interim = false;
/* Need to break exclusive/batch oplock, write lease or overwrite_if */
ksmbd_debug(OPLOCK,
@@ -865,18 +1207,36 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
if (brk_opinfo->is_lease) {
struct lease *lease = brk_opinfo->o_lease;
+ bool open_trunc = brk_opinfo->open_trunc;
+ bool was_pending = test_bit(0, &brk_opinfo->pending_break);
+ bool wait_ack;
+ bool inc_epoch = true;
+
+ if (in_work && was_pending) {
+ setup_async_work(in_work, NULL, NULL);
+ smb2_send_interim_resp(in_work, STATUS_PENDING);
+ release_async_work(in_work);
+ sent_interim = true;
+ }
- atomic_inc(&brk_opinfo->breaking_cnt);
err = oplock_break_pending(brk_opinfo, req_op_level);
if (err)
return err < 0 ? err : 0;
+ if (was_pending)
+ open_trunc = brk_opinfo->open_trunc;
- if (brk_opinfo->open_trunc) {
+again:
+ atomic_inc(&brk_opinfo->breaking_cnt);
+ if (open_trunc) {
/*
* Create overwrite break trigger the lease break to
* none.
*/
lease->new_state = SMB2_LEASE_NONE_LE;
+ } else if (share_break &&
+ lease->state & SMB2_LEASE_HANDLE_CACHING_LE) {
+ lease->new_state =
+ lease->state & ~SMB2_LEASE_HANDLE_CACHING_LE;
} else {
if (lease->state & SMB2_LEASE_WRITE_CACHING_LE) {
if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
@@ -898,44 +1258,131 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
if (lease->state & (SMB2_LEASE_WRITE_CACHING_LE |
SMB2_LEASE_HANDLE_CACHING_LE)) {
- if (in_work) {
- setup_async_work(in_work, NULL, NULL);
- smb2_send_interim_resp(in_work, STATUS_PENDING);
- release_async_work(in_work);
+ if (!oplock_break_set_ack_wait(brk_opinfo)) {
+ atomic_dec_if_positive(&brk_opinfo->breaking_cnt);
+ wake_up_oplock_break(brk_opinfo);
+ return -ENOENT;
}
-
- brk_opinfo->op_state = OPLOCK_ACK_WAIT;
} else
atomic_dec(&brk_opinfo->breaking_cnt);
+
+ wait_ack = !(open_trunc &&
+ lease->state == (SMB2_LEASE_READ_CACHING_LE |
+ SMB2_LEASE_HANDLE_CACHING_LE));
+ if (lease->reuse_epoch) {
+ inc_epoch = false;
+ lease->reuse_epoch = false;
+ }
+ err = smb2_lease_break_noti(brk_opinfo, sync_lease_break, inc_epoch);
+ inc_epoch = false;
+ if (in_work && !sent_interim) {
+ setup_async_work(in_work, NULL, NULL);
+ smb2_send_interim_resp(in_work, STATUS_PENDING);
+ release_async_work(in_work);
+ sent_interim = true;
+ }
+ if (wait_ack && !err && wait_for_break_ack(brk_opinfo))
+ err = ksmbd_invalidate_durable_fd(brk_opinfo->fid);
+
+ ksmbd_debug(OPLOCK, "oplock granted = %d\n", brk_opinfo->level);
+ if (brk_opinfo->op_state == OPLOCK_CLOSING)
+ err = -ENOENT;
+
+ if (wait_ack)
+ wait_lease_breaking(brk_opinfo);
+ /*
+ * A share-mode conflict break only drops the conflicting
+ * caching bit; the triggering open fails with a sharing
+ * violation, so keep it to a single break.
+ *
+ * Otherwise chain another break while the lease is still
+ * incompatible with this open (req_op_level), or while a
+ * truncating waiter that arrived during the break still needs
+ * the lease dropped to none. open_trunc snapshotted for this
+ * break stays cleared, so the next state is computed from the
+ * lease state and the cascade steps down (e.g. RH->R->none)
+ * instead of collapsing straight to none.
+ */
+ if (wait_ack && !err && !share_break &&
+ (lease_break_needed(brk_opinfo, req_op_level, open_trunc) ||
+ (brk_opinfo->open_trunc &&
+ lease->state != SMB2_LEASE_NONE_LE)))
+ goto again;
+
+ wake_up_oplock_break(brk_opinfo);
+ return err;
} else {
err = oplock_break_pending(brk_opinfo, req_op_level);
if (err)
return err < 0 ? err : 0;
if (brk_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
- brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
- brk_opinfo->op_state = OPLOCK_ACK_WAIT;
+ brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
+ if (!oplock_break_set_ack_wait(brk_opinfo)) {
+ wake_up_oplock_break(brk_opinfo);
+ return -ENOENT;
+ }
+ }
+
+ /*
+ * Keep a conflicting CREATE asynchronous while waiting for an
+ * oplock-break acknowledgement. Besides avoiding a blocked client
+ * request, this lets a replay arrive while the original CREATE is
+ * still pending and be rejected with FILE_NOT_AVAILABLE.
+ */
+ if (in_work) {
+ setup_async_work(in_work, NULL, NULL);
+ smb2_send_interim_resp(in_work, STATUS_PENDING);
+ release_async_work(in_work);
+ }
}
- if (brk_opinfo->is_lease)
- err = smb2_lease_break_noti(brk_opinfo);
- else
- err = smb2_oplock_break_noti(brk_opinfo);
+ err = smb2_oplock_break_noti(brk_opinfo, ci);
ksmbd_debug(OPLOCK, "oplock granted = %d\n", brk_opinfo->level);
if (brk_opinfo->op_state == OPLOCK_CLOSING)
- err = -ENOENT;
+ err = -EAGAIN;
wake_up_oplock_break(brk_opinfo);
- wait_lease_breaking(brk_opinfo);
-
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 ksmbd_inode *ci)
+{
+ struct oplock_break_entry *ent, *tmp;
+
+ list_for_each_entry_safe(ent, tmp, head, list) {
+ oplock_break(ent->opinfo, ci, SMB2_OPLOCK_LEVEL_NONE, NULL,
+ false, false);
+ list_del(&ent->list);
+ opinfo_put(ent->opinfo);
+ kfree(ent);
+ }
+}
+
void destroy_lease_table(struct ksmbd_conn *conn)
{
struct lease_table *lb, *lbtmp;
- struct oplock_info *opinfo;
+ struct lease *lease, *ltmp;
write_lock(&lease_list_lock);
if (list_empty(&lease_table_list)) {
@@ -947,25 +1394,18 @@ void destroy_lease_table(struct ksmbd_conn *conn)
if (conn && memcmp(lb->client_guid, conn->ClientGUID,
SMB2_CLIENT_GUID_SIZE))
continue;
-again:
- rcu_read_lock();
- list_for_each_entry_rcu(opinfo, &lb->lease_list,
- lease_entry) {
- rcu_read_unlock();
- lease_del_list(opinfo);
- goto again;
- }
- rcu_read_unlock();
+ list_for_each_entry_safe(lease, ltmp, &lb->lease_list, l_entry)
+ lease_del_table(lease);
list_del(&lb->l_entry);
- kfree(lb);
+ free_lease_table(lb);
}
write_unlock(&lease_list_lock);
}
-int find_same_lease_key(struct ksmbd_session *sess, struct ksmbd_inode *ci,
+int find_same_lease_key(struct ksmbd_conn *conn, struct ksmbd_inode *ci,
struct lease_ctx_info *lctx)
{
- struct oplock_info *opinfo;
+ struct lease *lease;
int err = 0;
struct lease_table *lb;
@@ -979,7 +1419,7 @@ int find_same_lease_key(struct ksmbd_session *sess, struct ksmbd_inode *ci,
}
list_for_each_entry(lb, &lease_table_list, l_entry) {
- if (!memcmp(lb->client_guid, sess->ClientGUID,
+ if (!memcmp(lb->client_guid, conn->ClientGUID,
SMB2_CLIENT_GUID_SIZE))
goto found;
}
@@ -988,76 +1428,42 @@ int find_same_lease_key(struct ksmbd_session *sess, struct ksmbd_inode *ci,
return 0;
found:
- rcu_read_lock();
- list_for_each_entry_rcu(opinfo, &lb->lease_list, lease_entry) {
- if (!atomic_inc_not_zero(&opinfo->refcount))
+ list_for_each_entry(lease, &lb->lease_list, l_entry) {
+ if (lease->ci == ci)
continue;
- rcu_read_unlock();
- if (opinfo->o_fp->f_ci == ci)
- goto op_next;
- err = compare_guid_key(opinfo, sess->ClientGUID,
- lctx->lease_key);
- if (err) {
+ if (!memcmp(lease->lease_key, lctx->lease_key,
+ SMB2_LEASE_KEY_SIZE)) {
err = -EINVAL;
ksmbd_debug(OPLOCK,
"found same lease key is already used in other files\n");
- opinfo_put(opinfo);
goto out;
}
-op_next:
- opinfo_put(opinfo);
- rcu_read_lock();
}
- rcu_read_unlock();
out:
read_unlock(&lease_list_lock);
return err;
}
-static void copy_lease(struct oplock_info *op1, struct oplock_info *op2)
-{
- struct lease *lease1 = op1->o_lease;
- struct lease *lease2 = op2->o_lease;
-
- op2->level = op1->level;
- lease2->state = lease1->state;
- memcpy(lease2->lease_key, lease1->lease_key,
- SMB2_LEASE_KEY_SIZE);
- lease2->duration = lease1->duration;
- lease2->flags = lease1->flags;
- lease2->epoch = lease1->epoch;
- lease2->version = lease1->version;
-}
-
-static int add_lease_global_list(struct oplock_info *opinfo)
+static void add_lease_global_list(struct lease *lease, struct ksmbd_conn *conn,
+ struct lease_table *new_lb)
{
struct lease_table *lb;
- read_lock(&lease_list_lock);
+ write_lock(&lease_list_lock);
list_for_each_entry(lb, &lease_table_list, l_entry) {
- if (!memcmp(lb->client_guid, opinfo->conn->ClientGUID,
+ if (!memcmp(lb->client_guid, conn->ClientGUID,
SMB2_CLIENT_GUID_SIZE)) {
- opinfo->o_lease->l_lb = lb;
- lease_add_list(opinfo);
- read_unlock(&lease_list_lock);
- return 0;
+ lease_add_table(lease, lb);
+ write_unlock(&lease_list_lock);
+ free_lease_table(new_lb);
+ return;
}
}
- read_unlock(&lease_list_lock);
- lb = kmalloc_obj(struct lease_table, KSMBD_DEFAULT_GFP);
- if (!lb)
- return -ENOMEM;
-
- memcpy(lb->client_guid, opinfo->conn->ClientGUID,
- SMB2_CLIENT_GUID_SIZE);
- INIT_LIST_HEAD(&lb->lease_list);
- spin_lock_init(&lb->lb_lock);
- opinfo->o_lease->l_lb = lb;
- lease_add_list(opinfo);
- lb_add(lb);
- return 0;
+ lease_add_table(lease, new_lb);
+ list_add(&new_lb->l_entry, &lease_table_list);
+ write_unlock(&lease_list_lock);
}
static void set_oplock_level(struct oplock_info *opinfo, int level,
@@ -1082,8 +1488,9 @@ 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)
+ if (lctx && lctx->version != 2)
return;
p_ci = ksmbd_inode_lookup_lock(fp->filp->f_path.dentry->d_parent);
@@ -1096,9 +1503,10 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
continue;
if (opinfo->o_lease->state != SMB2_OPLOCK_LEVEL_NONE &&
- (!(lctx->flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE) ||
- !compare_guid_key(opinfo, fp->conn->ClientGUID,
- lctx->parent_lease_key))) {
+ (!lctx ||
+ (!(lctx->flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE) ||
+ !compare_guid_key(opinfo, fp->conn->ClientGUID,
+ lctx->parent_lease_key)))) {
if (!atomic_inc_not_zero(&opinfo->refcount))
continue;
@@ -1107,12 +1515,14 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
continue;
}
- oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL);
- 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, p_ci);
+
ksmbd_inode_put(p_ci);
}
@@ -1120,13 +1530,16 @@ 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);
- rcu_read_unlock();
- if (!opinfo || !opinfo->is_lease || opinfo->o_lease->version != 2)
+ if (!opinfo || !opinfo->is_lease || opinfo->o_lease->version != 2) {
+ rcu_read_unlock();
return;
+ }
+ rcu_read_unlock();
p_ci = ksmbd_inode_lookup_lock(fp->filp->f_path.dentry->d_parent);
if (!p_ci)
@@ -1146,12 +1559,14 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
continue;
}
- oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL);
- 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, p_ci);
+
ksmbd_inode_put(p_ci);
}
@@ -1164,25 +1579,31 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
* @tid: Tree id of connection
* @lctx: lease context information on file open
* @share_ret: share mode
+ * @replay: whether this is a replayed CREATE request
*
* Return: 0 on success, otherwise error
*/
int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
struct ksmbd_file *fp, __u16 tid,
- struct lease_ctx_info *lctx, int share_ret)
+ struct lease_ctx_info *lctx, int share_ret, bool replay)
{
- struct ksmbd_session *sess = work->sess;
int err = 0;
+ int break_level = SMB2_OPLOCK_LEVEL_II;
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;
+ unsigned long long prev_fid = KSMBD_NO_FID;
+ bool new_lease = false;
+ bool break_needed;
__le32 prev_op_state = 0;
/* Only v2 leases handle the directory */
if (S_ISDIR(file_inode(fp->filp)->i_mode)) {
- if (!lctx || lctx->version != 2 ||
- (lctx->flags != SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE &&
- !lctx->epoch))
+ if (!lctx || lctx->version != 2)
return 0;
}
@@ -1191,20 +1612,35 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
return -ENOMEM;
if (lctx) {
- err = alloc_lease(opinfo, lctx);
- if (err)
+ opinfo->o_lease = alloc_lease(lctx, ci);
+ if (!opinfo->o_lease) {
+ err = -ENOMEM;
goto err_out;
+ }
opinfo->is_lease = 1;
+ new_lease = true;
}
/* ci does not have any oplock */
if (!opinfo_count(fp))
goto set_lev;
- /* grant none-oplock if second open is trunc */
- if (fp->attrib_only && fp->cdoption != FILE_OVERWRITE_IF_LE &&
+ /*
+ * A stat open that only requests metadata access must not break the
+ * existing caching state. READ_CONTROL (reading the security
+ * descriptor) does not conflict with a lease, but it does conflict
+ * with an oplock, so only treat a read-control-only open as a stat
+ * open when the existing holder is a lease.
+ */
+ if (fp->cdoption != FILE_OVERWRITE_IF_LE &&
fp->cdoption != FILE_OVERWRITE_LE &&
- fp->cdoption != FILE_SUPERSEDE_LE) {
+ fp->cdoption != FILE_SUPERSEDE_LE &&
+ (fp->attrib_only ||
+ (!(fp->daccess & ~(FILE_READ_ATTRIBUTES_LE |
+ FILE_WRITE_ATTRIBUTES_LE |
+ FILE_SYNCHRONIZE_LE |
+ FILE_READ_CONTROL_LE)) &&
+ ksmbd_inode_has_lease(ci)))) {
req_op_level = SMB2_OPLOCK_LEVEL_NONE;
goto set_lev;
}
@@ -1213,17 +1649,19 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
struct oplock_info *m_opinfo;
/* is lease already granted ? */
- m_opinfo = same_client_has_lease(ci, sess->ClientGUID,
+ m_opinfo = same_client_has_lease(ci, work->conn->ClientGUID,
lctx);
if (m_opinfo) {
- copy_lease(m_opinfo, opinfo);
- if (atomic_read(&m_opinfo->breaking_cnt))
- opinfo->o_lease->flags =
- SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE;
+ lease_put(opinfo->o_lease);
+ lease_get(m_opinfo->o_lease);
+ opinfo->o_lease = m_opinfo->o_lease;
+ opinfo->level = m_opinfo->level;
+ new_lease = false;
+ opinfo_put(m_opinfo);
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);
@@ -1232,6 +1670,20 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
prev_op_has_lease = prev_opinfo->is_lease;
if (prev_op_has_lease)
prev_op_state = prev_opinfo->o_lease->state;
+ /*
+ * A replay received while this open is waiting for an oplock or lease
+ * break must not observe an intermediate level and proceed as a new
+ * open. This check has to precede break_needed. an oplock may already
+ * have been downgraded from Batch to II while its acknowledgement is
+ * still pending.
+ */
+ if (replay &&
+ (test_bit(0, &prev_opinfo->pending_break) ||
+ prev_opinfo->op_state == OPLOCK_ACK_WAIT)) {
+ err = -EINPROGRESS;
+ opinfo_put(prev_opinfo);
+ goto err_out;
+ }
if (share_ret < 0 &&
prev_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
@@ -1240,16 +1692,44 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
goto err_out;
}
- if (prev_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH &&
- prev_opinfo->level != SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
+ break_needed = prev_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
+ prev_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
+ (share_ret < 0 && prev_op_has_lease &&
+ (prev_op_state & SMB2_LEASE_HANDLE_CACHING_LE));
+ if (!break_needed) {
opinfo_put(prev_opinfo);
goto op_break_not_needed;
}
- err = oplock_break(prev_opinfo, SMB2_OPLOCK_LEVEL_II, work);
+ 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, ci, break_level, work,
+ share_ret < 0 && prev_opinfo->is_lease, false);
+ if (prev_durable_detached || (prev_durable_open && err == -ENOENT))
+ ksmbd_invalidate_durable_fd(prev_fid);
opinfo_put(prev_opinfo);
- if (err == -ENOENT)
+ if (err == -EAGAIN) {
+ share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp);
+ if (share_ret < 0) {
+ err = share_ret;
+ goto err_out;
+ }
goto set_lev;
+ }
+ if (err == -ENOENT) {
+ /*
+ * A pending durable CREATE can lose the previous oplock when
+ * its holder closes the file. In that case grant the original
+ * request its full caching state. Other opens still need the
+ * normal shared-open downgrade below.
+ */
+ if (!prev_durable_open &&
+ req_op_level != SMB2_OPLOCK_LEVEL_NONE)
+ req_op_level = SMB2_OPLOCK_LEVEL_II;
+ goto set_lev;
+ }
/* Check all oplock was freed by close */
else if (err < 0)
goto err_out;
@@ -1277,20 +1757,35 @@ set_lev:
set_oplock_level(opinfo, req_op_level, lctx);
out:
- rcu_assign_pointer(fp->f_opinfo, opinfo);
+ /*
+ * Keep the original publication order so concurrent opens can
+ * still observe the in-flight grant via ci->m_op_list, but make
+ * everything after opinfo_add() no-fail by preallocating any new
+ * lease_table first.
+ */
opinfo->o_fp = fp;
-
- opinfo_count_inc(fp);
- opinfo_add(opinfo);
- if (opinfo->is_lease) {
- err = add_lease_global_list(opinfo);
- if (err)
+ if (new_lease) {
+ new_lb = alloc_lease_table(opinfo);
+ if (!new_lb) {
+ err = -ENOMEM;
goto err_out;
+ }
}
+ opinfo_count_inc(fp);
+ opinfo_add(opinfo, fp);
+
+ if (new_lease)
+ add_lease_global_list(opinfo->o_lease, opinfo->conn, new_lb);
+ if (opinfo->is_lease)
+ lease_add_open(opinfo->o_lease, opinfo);
+
+ rcu_assign_pointer(fp->f_opinfo, opinfo);
+
return 0;
err_out:
- free_opinfo(opinfo);
+ kfree(new_lb);
+ opinfo_put(opinfo);
return err;
}
@@ -1300,38 +1795,51 @@ err_out:
* @fp: ksmbd file pointer
* @is_trunc: truncate on open
*/
-static void smb_break_all_write_oplock(struct ksmbd_work *work,
+static bool smb_break_all_write_oplock(struct ksmbd_work *work,
struct ksmbd_file *fp, int is_trunc)
{
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;
+ return false;
if (brk_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH &&
brk_opinfo->level != SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
opinfo_put(brk_opinfo);
- return;
+ return false;
}
brk_opinfo->open_trunc = is_trunc;
- oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work);
+ oplock_break(brk_opinfo, fp->f_ci, SMB2_OPLOCK_LEVEL_II, work, false,
+ false);
+ sent_break = true;
opinfo_put(brk_opinfo);
+
+ return sent_break;
}
/**
- * smb_break_all_levII_oplock() - send level2 oplock or read lease break command
+ * __smb_break_all_levII_oplock() - send level2 oplock or read lease break command
* from server to client
- * @work: smb work
- * @fp: ksmbd file pointer
- * @is_trunc: truncate on open
+ * @work: smb work
+ * @fp: ksmbd file pointer
+ * @is_trunc: truncate on open
+ * @send_interim: send interim response to the client
+ * @send_oplock_break: send oplock break notification to the client
+ * @sync_lease_break: send the lease break notification synchronously
*/
-void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
- int is_trunc)
+static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
+ struct ksmbd_file *fp, int is_trunc,
+ bool send_interim, bool send_oplock_break,
+ bool sync_lease_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))
@@ -1353,14 +1861,8 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
continue;
}
- if (brk_op->is_lease && (brk_op->o_lease->state &
- (~(SMB2_LEASE_READ_CACHING_LE |
- SMB2_LEASE_HANDLE_CACHING_LE)))) {
- ksmbd_debug(OPLOCK, "unexpected lease state(0x%x)\n",
- brk_op->o_lease->state);
- goto next;
- } else if (brk_op->level !=
- SMB2_OPLOCK_LEVEL_II) {
+ if (!brk_op->is_lease &&
+ brk_op->level != SMB2_OPLOCK_LEVEL_II) {
ksmbd_debug(OPLOCK, "unexpected oplock(0x%x)\n",
brk_op->level);
goto next;
@@ -1379,16 +1881,68 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
SMB2_LEASE_KEY_SIZE))
goto next;
brk_op->open_trunc = is_trunc;
- oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE, NULL);
+
+ /*
+ * 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;
+ spin_lock(&brk_op->state_lock);
+ if (brk_op->op_state != OPLOCK_CLOSING)
+ brk_op->op_state = OPLOCK_STATE_NONE;
+ spin_unlock(&brk_op->state_lock);
+ } else {
+ oplock_break(brk_op, ci,
+ brk_op->is_lease && !is_trunc ?
+ SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE,
+ send_interim && !sent_interim ? work : NULL,
+ false, sync_lease_break);
+ }
+ sent_interim = true;
+ list_del(&ent->list);
+ opinfo_put(brk_op);
+ kfree(ent);
+ }
+
if (op)
opinfo_put(op);
}
+void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
+ int is_trunc)
+{
+ __smb_break_all_levII_oplock(work, fp, is_trunc, true, true, false);
+}
+
+void smb_break_all_levII_oplock_rename(struct ksmbd_work *work, struct ksmbd_file *fp)
+{
+ __smb_break_all_levII_oplock(work, fp, 0, true, true, true);
+}
+
+void smb_break_all_levII_oplock_no_interim(struct ksmbd_work *work,
+ struct ksmbd_file *fp, int is_trunc)
+{
+ __smb_break_all_levII_oplock(work, fp, is_trunc, false, true, false);
+}
+
+void smb_break_all_levII_oplock_for_delete(struct ksmbd_work *work,
+ struct ksmbd_file *fp)
+{
+ __smb_break_all_levII_oplock(work, fp, 0, false, false, false);
+}
+
/**
* smb_break_all_oplock() - break both batch/exclusive and level2 oplock
* @work: smb work
@@ -1396,12 +1950,14 @@ next:
*/
void smb_break_all_oplock(struct ksmbd_work *work, struct ksmbd_file *fp)
{
+ bool sent_break;
+
if (!test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_OPLOCKS))
return;
- smb_break_all_write_oplock(work, fp, 1);
- smb_break_all_levII_oplock(work, fp, 1);
+ sent_break = smb_break_all_write_oplock(work, fp, 1);
+ __smb_break_all_levII_oplock(work, fp, 1, !sent_break, true, false);
}
/**
@@ -1412,15 +1968,13 @@ void smb_break_all_oplock(struct ksmbd_work *work, struct ksmbd_file *fp)
*/
__u8 smb2_map_lease_to_oplock(__le32 lease_state)
{
- if (lease_state == (SMB2_LEASE_HANDLE_CACHING_LE |
- SMB2_LEASE_READ_CACHING_LE |
- SMB2_LEASE_WRITE_CACHING_LE)) {
+ if ((lease_state & SMB2_LEASE_WRITE_CACHING_LE) &&
+ (lease_state & SMB2_LEASE_HANDLE_CACHING_LE)) {
return SMB2_OPLOCK_LEVEL_BATCH;
- } else if (lease_state != SMB2_LEASE_WRITE_CACHING_LE &&
- lease_state & SMB2_LEASE_WRITE_CACHING_LE) {
- if (!(lease_state & SMB2_LEASE_HANDLE_CACHING_LE))
- return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
- } else if (lease_state & SMB2_LEASE_READ_CACHING_LE) {
+ } else if (lease_state & SMB2_LEASE_WRITE_CACHING_LE) {
+ return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ } else if (lease_state & (SMB2_LEASE_READ_CACHING_LE |
+ SMB2_LEASE_HANDLE_CACHING_LE)) {
return SMB2_OPLOCK_LEVEL_II;
}
return 0;
@@ -1435,14 +1989,19 @@ void create_lease_buf(u8 *rbuf, struct lease *lease)
{
if (lease->version == 2) {
struct create_lease_v2 *buf = (struct create_lease_v2 *)rbuf;
+ __le32 flags = 0;
memset(buf, 0, sizeof(struct create_lease_v2));
memcpy(buf->lcontext.LeaseKey, lease->lease_key,
SMB2_LEASE_KEY_SIZE);
- buf->lcontext.LeaseFlags = lease->flags;
+ if (lease_has_parent_key(lease))
+ flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
+ if (lease_break_in_progress(lease))
+ flags |= SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE;
+ buf->lcontext.LeaseFlags = flags;
buf->lcontext.Epoch = cpu_to_le16(lease->epoch);
buf->lcontext.LeaseState = lease->state;
- if (lease->flags == SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE)
+ if (lease_has_parent_key(lease))
memcpy(buf->lcontext.ParentLeaseKey, lease->parent_lease_key,
SMB2_LEASE_KEY_SIZE);
buf->ccontext.DataOffset = cpu_to_le16(offsetof
@@ -1460,7 +2019,9 @@ void create_lease_buf(u8 *rbuf, struct lease *lease)
memset(buf, 0, sizeof(struct create_lease));
memcpy(buf->lcontext.LeaseKey, lease->lease_key, SMB2_LEASE_KEY_SIZE);
- buf->lcontext.LeaseFlags = lease->flags;
+ if (lease_break_in_progress(lease))
+ buf->lcontext.LeaseFlags =
+ SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE;
buf->lcontext.LeaseState = lease->state;
buf->ccontext.DataOffset = cpu_to_le16(offsetof
(struct create_lease, lcontext));
@@ -1488,12 +2049,14 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
struct lease_ctx_info *lreq;
cc = smb2_find_context_vals(req, SMB2_CREATE_REQUEST_LEASE, 4);
- if (IS_ERR_OR_NULL(cc))
+ if (IS_ERR(cc))
+ return ERR_CAST(cc);
+ if (!cc)
return NULL;
lreq = kzalloc_obj(struct lease_ctx_info, KSMBD_DEFAULT_GFP);
if (!lreq)
- return NULL;
+ return ERR_PTR(-ENOMEM);
if (sizeof(struct lease_context_v2) == le32_to_cpu(cc->DataLength)) {
struct create_lease_v2 *lc = (struct create_lease_v2 *)cc;
@@ -1507,11 +2070,15 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
lreq->flags = lc->lcontext.LeaseFlags;
lreq->epoch = lc->lcontext.Epoch;
lreq->duration = lc->lcontext.LeaseDuration;
+ if (!lease_state_valid(lreq->req_state) ||
+ !lease_v2_flags_valid(lreq->flags))
+ goto err_out;
+ lreq->req_state = lease_state_grantable(lreq->req_state);
if (lreq->flags == SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE)
memcpy(lreq->parent_lease_key, lc->lcontext.ParentLeaseKey,
SMB2_LEASE_KEY_SIZE);
lreq->version = 2;
- } else {
+ } else if (sizeof(struct lease_context) == le32_to_cpu(cc->DataLength)) {
struct create_lease *lc = (struct create_lease *)cc;
if (le16_to_cpu(cc->DataOffset) + le32_to_cpu(cc->DataLength) <
@@ -1520,14 +2087,18 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
memcpy(lreq->lease_key, lc->lcontext.LeaseKey, SMB2_LEASE_KEY_SIZE);
lreq->req_state = lc->lcontext.LeaseState;
- lreq->flags = lc->lcontext.LeaseFlags;
+ lreq->flags = 0;
lreq->duration = lc->lcontext.LeaseDuration;
+ if (!lease_state_valid(lreq->req_state))
+ goto err_out;
+ lreq->req_state = lease_state_grantable(lreq->req_state);
lreq->version = 1;
- }
+ } else
+ goto err_out;
return lreq;
err_out:
kfree(lreq);
- return NULL;
+ return ERR_PTR(-EINVAL);
}
/**
@@ -1552,6 +2123,9 @@ struct create_context *smb2_find_context_vals(void *open_req, const char *tag, i
* CreateContextsOffset and CreateContextsLength are guaranteed to
* be valid because of ksmbd_smb2_check_message().
*/
+ if (!req->CreateContextsOffset || !req->CreateContextsLength)
+ return NULL;
+
cc = (struct create_context *)((char *)req +
le32_to_cpu(req->CreateContextsOffset));
remain_len = le32_to_cpu(req->CreateContextsLength);
@@ -1620,12 +2194,12 @@ void create_durable_v2_rsp_buf(char *cc, struct ksmbd_file *fp)
struct create_durable_rsp_v2 *buf;
buf = (struct create_durable_rsp_v2 *)cc;
- memset(buf, 0, sizeof(struct create_durable_rsp));
+ memset(buf, 0, sizeof(*buf));
buf->ccontext.DataOffset = cpu_to_le16(offsetof
- (struct create_durable_rsp, Data));
+ (struct create_durable_rsp_v2, dcontext));
buf->ccontext.DataLength = cpu_to_le32(8);
buf->ccontext.NameOffset = cpu_to_le16(offsetof
- (struct create_durable_rsp, Name));
+ (struct create_durable_rsp_v2, Name));
buf->ccontext.NameLength = cpu_to_le16(4);
/* SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2 is "DH2Q" */
buf->Name[0] = 'D';
@@ -1746,6 +2320,90 @@ void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp)
SIDUNIX_GROUP, (struct smb_sid *)&buf->SidBuffer[28]);
}
+/**
+ * create_aapl_rsp_buf() - build AAPL kAAPL_SERVER_QUERY response
+ * @cc: buffer to write the create context into (AAPL_RSP_MAX_SIZE bytes)
+ * @vol_caps: volume capability flags (SMB2_CRTCTX_AAPL_* volume bits)
+ * @req_bitmap: the client's request bitmap, echoed back in reply_bitmap
+ *
+ * Response format follows the layout observed from macOS's own smbd, and
+ * matches the client-side parsing in AAPL's published public client kernel
+ * source (public client behavior reference, kAAPL_SERVER_QUERY
+ * case): reply_bitmap, then server_caps/vol_caps/model-info fields present
+ * only when their reply_bitmap bit is set:
+ * reply_bitmap = req_bitmap masked to the fields we support
+ * server_caps = AAPL_SERVER_CAPS_KSMBD when requested
+ * vol_caps = caller-supplied
+ * model string = server_conf.aapl_model (default "Xserve") in UTF-16LE,
+ * when SMB2_CRTCTX_AAPL_MODEL_INFO requested
+ *
+ * Sending reply_bitmap with MODEL_INFO set but no model string causes
+ * smbfs.kext to enter a broken disconnect path requiring a macOS reboot.
+ * @readdir_attr_v2: advertise SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2
+ * instead of the V1 bit
+ */
+void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap,
+ bool readdir_attr_v2)
+{
+ struct create_aapl_rsp *buf;
+ u64 reply_bitmap;
+ u64 server_caps;
+ u32 data_len;
+
+ buf = (struct create_aapl_rsp *)cc;
+ memset(buf, 0, AAPL_RSP_MAX_SIZE);
+
+ reply_bitmap = req_bitmap & (SMB2_CRTCTX_AAPL_SERVER_CAPS |
+ SMB2_CRTCTX_AAPL_VOLUME_CAPS |
+ SMB2_CRTCTX_AAPL_MODEL_INFO);
+
+ /* base data: cmd(4)+reserved(4)+reply_bitmap(8)+server_caps(8)+vol_caps(8) */
+ data_len = 32;
+ if (reply_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO)
+ data_len += 4 + 4 + AAPL_MODEL_UTF16_BYTES; /* pad2+model_bytes+string */
+
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof(struct create_aapl_rsp, cmd));
+ buf->ccontext.DataLength = cpu_to_le32(data_len);
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof(struct create_aapl_rsp, Name));
+ buf->ccontext.NameLength = cpu_to_le16(SMB2_CREATE_AAPL_LEN);
+ buf->Name[0] = 'A';
+ buf->Name[1] = 'A';
+ buf->Name[2] = 'P';
+ buf->Name[3] = 'L';
+
+ buf->cmd = cpu_to_le32(SMB2_CRTCTX_AAPL_SERVER_QUERY);
+ buf->reply_bitmap = cpu_to_le64(reply_bitmap);
+ server_caps = AAPL_SERVER_CAPS_KSMBD;
+ if (readdir_attr_v2)
+ server_caps = (server_caps & ~SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) |
+ SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2;
+ buf->server_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) ?
+ cpu_to_le64(server_caps) : 0;
+ buf->vol_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) ?
+ cpu_to_le64(vol_caps) : 0;
+
+ if (reply_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO) {
+ __le32 *p = (__le32 *)((u8 *)buf + sizeof(*buf));
+ __le16 *model_str = (__le16 *)(p + 2);
+ const char *src = server_conf.aapl_model[0] ?
+ server_conf.aapl_model : "Xserve";
+ int i, model_bytes = 0;
+
+ /* Convert ASCII model string to UTF-16LE in-place */
+ for (i = 0; src[i] && i < AAPL_MODEL_MAX_CHARS; i++) {
+ model_str[i] = cpu_to_le16((unsigned char)src[i]);
+ model_bytes += 2;
+ }
+
+ p[0] = 0; /* pad2 */
+ p[1] = cpu_to_le32(model_bytes);
+
+ /* Update DataLength to reflect actual model string size */
+ buf->ccontext.DataLength =
+ cpu_to_le32(data_len - AAPL_MODEL_UTF16_BYTES + model_bytes);
+ }
+}
+
/*
* Find lease object(opinfo) for given lease key/fid from lease
* break/file close path.
@@ -1761,8 +2419,8 @@ struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
char *lease_key)
{
struct oplock_info *opinfo = NULL, *ret_op = NULL;
+ struct lease *lease;
struct lease_table *lt;
- int ret;
read_lock(&lease_list_lock);
list_for_each_entry(lt, &lease_table_list, l_entry) {
@@ -1775,29 +2433,29 @@ struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
return NULL;
found:
- rcu_read_lock();
- list_for_each_entry_rcu(opinfo, &lt->lease_list, lease_entry) {
- if (!atomic_inc_not_zero(&opinfo->refcount))
+ list_for_each_entry(lease, &lt->lease_list, l_entry) {
+ if (memcmp(lease->lease_key, lease_key, SMB2_LEASE_KEY_SIZE))
continue;
- rcu_read_unlock();
- if (!opinfo->op_state || opinfo->op_state == OPLOCK_CLOSING)
- goto op_next;
- if (!(opinfo->o_lease->state &
- (SMB2_LEASE_HANDLE_CACHING_LE |
- SMB2_LEASE_WRITE_CACHING_LE)))
- goto op_next;
- ret = compare_guid_key(opinfo, conn->ClientGUID,
- lease_key);
- if (ret) {
- ksmbd_debug(OPLOCK, "found opinfo\n");
+ if (!(lease->state & (SMB2_LEASE_HANDLE_CACHING_LE |
+ SMB2_LEASE_WRITE_CACHING_LE)))
+ break;
+
+ spin_lock(&lease->lock);
+ list_for_each_entry(opinfo, &lease->open_list, lease_entry) {
+ if (!opinfo->op_state ||
+ opinfo->op_state == OPLOCK_CLOSING)
+ continue;
+ if (!atomic_inc_not_zero(&opinfo->refcount))
+ continue;
ret_op = opinfo;
+ }
+ spin_unlock(&lease->lock);
+ if (ret_op) {
+ ksmbd_debug(OPLOCK, "found opinfo\n");
goto out;
}
-op_next:
- opinfo_put(opinfo);
- rcu_read_lock();
+ break;
}
- rcu_read_unlock();
out:
read_unlock(&lease_list_lock);
@@ -1808,6 +2466,7 @@ int smb2_check_durable_oplock(struct ksmbd_conn *conn,
struct ksmbd_share_config *share,
struct ksmbd_file *fp,
struct lease_ctx_info *lctx,
+ struct ksmbd_user *user,
char *name)
{
struct oplock_info *opinfo = opinfo_get(fp);
@@ -1816,6 +2475,18 @@ int smb2_check_durable_oplock(struct ksmbd_conn *conn,
if (!opinfo)
return 0;
+ if (ksmbd_has_other_active_fd(fp)) {
+ ksmbd_debug(SMB, "Durable handle reconnect failed: competing open\n");
+ ret = -EBADF;
+ goto out;
+ }
+
+ if (ksmbd_vfs_compare_durable_owner(fp, user) == false) {
+ ksmbd_debug(SMB, "Durable handle reconnect failed: owner mismatch\n");
+ ret = -EBADF;
+ goto out;
+ }
+
if (opinfo->is_lease == false) {
if (lctx) {
pr_err("create context include lease\n");
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index 9a56eaadd0dd..b08d21758e07 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -34,6 +34,7 @@ struct lease_ctx_info {
struct lease_table {
char client_guid[SMB2_CLIENT_GUID_SIZE];
+ struct ksmbd_conn *conn;
struct list_head lease_list;
struct list_head l_entry;
spinlock_t lb_lock;
@@ -49,7 +50,13 @@ struct lease {
int version;
unsigned short epoch;
bool is_dir;
+ bool reuse_epoch;
+ struct ksmbd_inode *ci;
struct lease_table *l_lb;
+ struct list_head l_entry;
+ struct list_head open_list;
+ spinlock_t lock;
+ atomic_t refcount;
};
struct oplock_info {
@@ -59,6 +66,7 @@ struct oplock_info {
struct ksmbd_file *o_fp;
int level;
int op_state;
+ spinlock_t state_lock;
unsigned long pending_break;
u64 fid;
atomic_t breaking_cnt;
@@ -69,8 +77,9 @@ struct oplock_info {
struct lease *o_lease;
struct list_head op_entry;
struct list_head lease_entry;
- wait_queue_head_t oplock_q; /* Other server threads */
- wait_queue_head_t oplock_brk; /* oplock breaking wait */
+ wait_queue_head_t oplock_q; /* Other server threads */
+ wait_queue_head_t oplock_brk; /* oplock breaking wait */
+ struct rcu_head rcu;
};
struct lease_break_info {
@@ -88,9 +97,14 @@ struct oplock_break_info {
int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
u64 pid, struct ksmbd_file *fp, __u16 tid,
- struct lease_ctx_info *lctx, int share_ret);
+ struct lease_ctx_info *lctx, int share_ret, bool replay);
void smb_break_all_levII_oplock(struct ksmbd_work *work,
- struct ksmbd_file *fp, int is_trunc);
+ struct ksmbd_file *fp, int is_trunc);
+void smb_break_all_levII_oplock_rename(struct ksmbd_work *work, struct ksmbd_file *fp);
+void smb_break_all_levII_oplock_no_interim(struct ksmbd_work *work,
+ struct ksmbd_file *fp, int is_trunc);
+void smb_break_all_levII_oplock_for_delete(struct ksmbd_work *work,
+ struct ksmbd_file *fp);
int opinfo_write_to_read(struct oplock_info *opinfo);
int opinfo_read_handle_to_read(struct oplock_info *opinfo);
int opinfo_write_to_none(struct oplock_info *opinfo);
@@ -104,6 +118,7 @@ void opinfo_put(struct oplock_info *opinfo);
void create_lease_buf(u8 *rbuf, struct lease *lease);
struct lease_ctx_info *parse_lease_state(void *open_req);
__u8 smb2_map_lease_to_oplock(__le32 lease_state);
+void lease_update_oplock_levels(struct lease *lease);
int lease_read_to_write(struct oplock_info *opinfo);
/* Durable related functions */
@@ -112,10 +127,12 @@ void create_durable_v2_rsp_buf(char *cc, struct ksmbd_file *fp);
void create_mxac_rsp_buf(char *cc, int maximal_access);
void create_disk_id_rsp_buf(char *cc, __u64 file_id, __u64 vol_id);
void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp);
+void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap,
+ bool readdir_attr_v2);
struct create_context *smb2_find_context_vals(void *open_req, const char *tag, int tag_len);
struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
char *lease_key);
-int find_same_lease_key(struct ksmbd_session *sess, struct ksmbd_inode *ci,
+int find_same_lease_key(struct ksmbd_conn *conn, struct ksmbd_inode *ci,
struct lease_ctx_info *lctx);
void destroy_lease_table(struct ksmbd_conn *conn);
void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
@@ -125,5 +142,6 @@ int smb2_check_durable_oplock(struct ksmbd_conn *conn,
struct ksmbd_share_config *share,
struct ksmbd_file *fp,
struct lease_ctx_info *lctx,
+ struct ksmbd_user *user,
char *name);
#endif /* __KSMBD_OPLOCK_H */
diff --git a/fs/smb/server/proc.c b/fs/smb/server/proc.c
index 101a2cc45a44..19f0f2cfbf54 100644
--- a/fs/smb/server/proc.c
+++ b/fs/smb/server/proc.c
@@ -11,10 +11,12 @@
#include <linux/seq_file.h>
#include "misc.h"
+#include "connection.h"
#include "server.h"
#include "stats.h"
#include "smb_common.h"
#include "smb2pdu.h"
+#include "vfs_cache.h"
static struct proc_dir_entry *ksmbd_proc_fs;
struct ksmbd_counters ksmbd_counters;
@@ -27,6 +29,42 @@ struct proc_dir_entry *ksmbd_proc_create(const char *name,
show, v);
}
+void ksmbd_proc_show_flag_names(struct seq_file *m,
+ const struct ksmbd_const_name *table,
+ int count, unsigned int flags)
+{
+ unsigned int remaining = flags;
+ bool separator = false;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ unsigned int flag = table[i].const_value;
+
+ if (!flag || (remaining & flag) != flag)
+ continue;
+ seq_printf(m, "%s%s", separator ? "," : "", table[i].name);
+ separator = true;
+ remaining &= ~flag;
+ }
+
+ if (remaining)
+ seq_printf(m, "%s0x%08x", separator ? "," : "", remaining);
+ else if (!separator)
+ seq_puts(m, "none");
+}
+
+const char *ksmbd_proc_const_name(const struct ksmbd_const_name *table,
+ int count, unsigned int const_value)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (table[i].const_value == const_value)
+ return table[i].name;
+ }
+ return NULL;
+}
+
struct ksmbd_const_smb2_process_req {
unsigned int const_value;
const char *name;
@@ -54,32 +92,129 @@ static const struct ksmbd_const_smb2_process_req smb2_process_req[KSMBD_COUNTER_
{le16_to_cpu(SMB2_OPLOCK_BREAK), "SMB2_OPLOCK_BREAK"},
};
+static const char *ksmbd_server_state_string(void)
+{
+ switch (READ_ONCE(server_conf.state)) {
+ case SERVER_STATE_STARTING_UP:
+ return "starting";
+ case SERVER_STATE_RUNNING:
+ return "running";
+ case SERVER_STATE_RESETTING:
+ return "resetting";
+ case SERVER_STATE_SHUTTING_DOWN:
+ return "shutdown";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *ksmbd_signing_mode_string(void)
+{
+ switch (server_conf.signing) {
+ case KSMBD_CONFIG_OPT_DISABLED:
+ return "disabled";
+ case KSMBD_CONFIG_OPT_MANDATORY:
+ return "mandatory";
+ case KSMBD_CONFIG_OPT_AUTO:
+ return "auto";
+ default:
+ return "unknown";
+ }
+}
+
+static void proc_show_runtime_totals(struct seq_file *m)
+{
+ struct ksmbd_conn *conn;
+ unsigned int clients = 0;
+ unsigned int open_files = 0;
+ int i;
+
+ down_read(&conn_list_lock);
+ hash_for_each(conn_list, i, conn, hlist) {
+ clients++;
+ open_files += atomic_read(&conn->stats.open_files_count);
+ }
+ up_read(&conn_list_lock);
+
+ seq_printf(m, "clients:\t%u\n", clients);
+ seq_printf(m, "open_files:\t%u\n", open_files);
+}
+
static int proc_show_ksmbd_stats(struct seq_file *m, void *v)
{
int i;
seq_puts(m, "Server\n");
- seq_printf(m, "name: %s\n", ksmbd_server_string());
- seq_printf(m, "netbios: %s\n", ksmbd_netbios_name());
- seq_printf(m, "work group: %s\n", ksmbd_work_group());
- seq_printf(m, "min protocol: %s\n", ksmbd_get_protocol_string(server_conf.min_protocol));
- seq_printf(m, "max protocol: %s\n", ksmbd_get_protocol_string(server_conf.max_protocol));
- seq_printf(m, "flags: 0x%08x\n", server_conf.flags);
- seq_printf(m, "share_fake_fscaps: 0x%08x\n",
+ seq_printf(m, "state:\t%s\n", ksmbd_server_state_string());
+ seq_printf(m, "name:\t%s\n", ksmbd_server_string());
+ seq_printf(m, "netbios:\t%s\n", ksmbd_netbios_name());
+ seq_printf(m, "work_group:\t%s\n", ksmbd_work_group());
+ seq_printf(m, "min_protocol:\t%s\n", ksmbd_get_protocol_string(server_conf.min_protocol));
+ seq_printf(m, "max_protocol:\t%s\n", ksmbd_get_protocol_string(server_conf.max_protocol));
+ seq_printf(m, "flags:\t0x%08x\n", server_conf.flags);
+ seq_printf(m, "tcp_port:\t%u\n", server_conf.tcp_port);
+ seq_printf(m, "signing:\t%s\n", ksmbd_signing_mode_string());
+ seq_printf(m, "signing_enforced:\t%s\n",
+ server_conf.enforced_signing ? "yes" : "no");
+ seq_printf(m, "bind_interfaces_only:\t%s\n",
+ server_conf.bind_interfaces_only ? "yes" : "no");
+ seq_printf(m, "max_connections:\t%u\n", server_conf.max_connections);
+ seq_printf(m, "max_connections_per_ip:\t%u\n",
+ server_conf.max_ip_connections);
+ seq_printf(m, "max_inflight_requests:\t%u\n",
+ server_conf.max_inflight_req);
+ seq_printf(m, "deadtime_seconds:\t%lu\n", server_conf.deadtime / HZ);
+ seq_printf(m, "ipc_timeout_seconds:\t%u\n", server_conf.ipc_timeout / HZ);
+ if (server_conf.ipc_last_active)
+ seq_printf(m, "ipc_last_active_seconds:\t%lu\n",
+ jiffies_to_msecs(jiffies - server_conf.ipc_last_active) /
+ MSEC_PER_SEC);
+ else
+ seq_puts(m, "ipc_last_active_seconds:\tnever\n");
+ seq_printf(m, "durable_scavenger:\t%s\n",
+ ksmbd_durable_scavenger_active() ? "running" : "stopped");
+ seq_printf(m, "share_fake_fscaps:\t0x%08x\n",
server_conf.share_fake_fscaps);
- seq_printf(m, "sessions: %lld\n",
+ proc_show_runtime_totals(m);
+ seq_printf(m, "sessions:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_SESSIONS));
- seq_printf(m, "tree connects: %lld\n",
+ seq_printf(m, "session_timeouts:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_SESSION_TIMEOUTS));
+ seq_printf(m, "tree_connects:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_TREE_CONNS));
- seq_printf(m, "read bytes: %lld\n",
+ seq_printf(m, "requests:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_REQUESTS));
+ seq_printf(m, "read_bytes:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_READ_BYTES));
- seq_printf(m, "written bytes: %lld\n",
+ seq_printf(m, "written_bytes:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_WRITE_BYTES));
seq_puts(m, "\nSMB2\n");
for (i = 0; i < KSMBD_COUNTER_MAX_REQS; i++)
- seq_printf(m, "%-20s:\t%lld\n", smb2_process_req[i].name,
+ seq_printf(m, "%s:\t%lld\n", smb2_process_req[i].name,
ksmbd_counter_sum(KSMBD_COUNTER_FIRST_REQ + i));
+
+ seq_puts(m, "\nSMB2 status\n");
+ seq_printf(m, "success:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_STATUS_SUCCESS));
+ seq_printf(m, "informational:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_STATUS_INFORMATIONAL));
+ seq_printf(m, "warning:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_STATUS_WARNING));
+ seq_printf(m, "error:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_STATUS_ERROR));
+ seq_printf(m, "access_denied:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_ACCESS_DENIED));
+ seq_printf(m, "not_found:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_NOT_FOUND));
+ seq_printf(m, "invalid_parameter:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_INVALID_PARAMETER));
+ seq_printf(m, "sharing_violation:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_SHARING_VIOLATION));
+ seq_printf(m, "not_supported:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_NOT_SUPPORTED));
+ seq_printf(m, "other:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_ERROR_OTHER));
return 0;
}
@@ -106,14 +241,14 @@ void ksmbd_proc_reset(void)
percpu_counter_set(&ksmbd_counters.counters[i], 0);
}
-void ksmbd_proc_init(void)
+int ksmbd_proc_init(void)
{
int i;
- int retval;
+ int retval = -ENOMEM;
ksmbd_proc_fs = proc_mkdir("fs/ksmbd", NULL);
if (!ksmbd_proc_fs)
- return;
+ return retval;
if (!proc_mkdir_mode("sessions", 0400, ksmbd_proc_fs))
goto err_out;
@@ -124,11 +259,14 @@ void ksmbd_proc_init(void)
goto err_out;
}
- if (!ksmbd_proc_create("server", proc_show_ksmbd_stats, NULL))
+ if (!ksmbd_proc_create("server", proc_show_ksmbd_stats, NULL)) {
+ retval = -ENOMEM;
goto err_out;
+ }
ksmbd_proc_reset();
- return;
+ return 0;
err_out:
ksmbd_proc_cleanup();
+ return retval;
}
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 422d9d978285..0827c8c51006 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -15,6 +15,7 @@
#include "server.h"
#include "smb_common.h"
+#include "smb2pdu.h"
#include "../common/smb2status.h"
#include "connection.h"
#include "transport_ipc.h"
@@ -22,6 +23,9 @@
#include "crypto_ctx.h"
#include "auth.h"
#include "stats.h"
+#include "compress.h"
+#include "mgmt/share_config.h"
+#include "mgmt/tree_connect.h"
int ksmbd_debug_types;
@@ -111,6 +115,7 @@ static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn,
{
struct smb_version_cmds *cmds;
u16 command;
+ bool signed_req;
int ret;
if (check_conn_state(work))
@@ -137,7 +142,14 @@ andx_again:
return SERVER_HANDLER_ABORT;
}
- if (work->sess && conn->ops->is_sign_req(work, command)) {
+ signed_req = conn->ops->is_sign_req && conn->ops->is_sign_req(work, command);
+ if (work->sess && work->sess->sign && !work->encrypted &&
+ !signed_req) {
+ conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED);
+ return SERVER_HANDLER_ABORT;
+ }
+
+ if (work->sess && signed_req) {
ret = conn->ops->check_sign_req(work);
if (!ret) {
conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED);
@@ -146,8 +158,11 @@ andx_again:
}
ret = cmds->proc(work);
- if (conn->ops->inc_reqs)
- conn->ops->inc_reqs(command);
+ if (conn->ops->inc_reqs) {
+ struct smb2_hdr *rsp = ksmbd_resp_buf_curr(work);
+
+ conn->ops->inc_reqs(command, rsp->Status);
+ }
if (ret < 0)
ksmbd_debug(CONN, "Failed to process %u [%d]\n", command, ret);
@@ -173,9 +188,33 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (conn->ops->is_transform_hdr &&
conn->ops->is_transform_hdr(work->request_buf)) {
rc = conn->ops->decrypt_req(work);
- if (rc < 0)
+ if (rc < 0) {
+ ksmbd_conn_abort(conn);
return;
+ }
work->encrypted = true;
+
+ /*
+ * SMB3 applies compression before encryption. The receive loop
+ * handles a plain compression transform before allocating work, but
+ * an encrypted request exposes that transform only after decryption.
+ */
+ if (((struct smb2_hdr *)smb_get_msg(work->request_buf))->ProtocolId ==
+ SMB2_COMPRESSION_TRANSFORM_ID) {
+ rc = ksmbd_decompress_work_request(work);
+ if (rc < 0) {
+ ksmbd_conn_abort(conn);
+ return;
+ }
+ }
+
+ /* The decrypted payload must now be a complete SMB2 request. */
+ if (((struct smb2_hdr *)smb_get_msg(work->request_buf))->ProtocolId !=
+ SMB2_PROTO_NUMBER ||
+ get_rfc1002_len(work->request_buf) < sizeof(struct smb2_pdu)) {
+ ksmbd_conn_abort(conn);
+ return;
+ }
}
if (conn->ops->allocate_rsp_buf(work))
@@ -195,9 +234,22 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (rc == -EINVAL)
conn->ops->set_rsp_status(work,
STATUS_INVALID_PARAMETER);
+ else if (rc == -EKEYEXPIRED)
+ conn->ops->set_rsp_status(work,
+ STATUS_NETWORK_SESSION_EXPIRED);
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);
+ if (rc == -EKEYEXPIRED && work->sess &&
+ conn->ops->set_sign_rsp)
+ conn->ops->set_sign_rsp(work);
+ else
+ rsp_hdr->Flags |= SMB2_FLAGS_SIGNED;
+ }
goto send;
} else if (rc > 0) {
rc = conn->ops->get_ksmbd_tcon(work);
@@ -210,12 +262,23 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
STATUS_NETWORK_NAME_DELETED);
goto send;
}
+
+ if (work->tcon &&
+ test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_ENCRYPT_DATA) &&
+ !work->encrypted) {
+ conn->ops->set_rsp_status(work,
+ STATUS_ACCESS_DENIED);
+ goto send;
+ }
}
}
rc = __process_request(work, conn, &command);
- if (rc == SERVER_HANDLER_ABORT)
+ if (rc == SERVER_HANDLER_ABORT) {
+ smb2_complete_request_open(work);
break;
+ }
/*
* Call smb2_set_rsp_credits() function to set number of credits
@@ -228,22 +291,55 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (rc < 0) {
conn->ops->set_rsp_status(work,
STATUS_INVALID_PARAMETER);
+ smb2_complete_request_open(work);
goto send;
}
}
+ smb2_complete_request_open(work);
+
is_chained = is_chained_smb2_message(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:
+ smb2_complete_request_open(work);
+ /*
+ * 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);
+ /*
+ * Preauthentication hashes cover the original SMB2 response. Apply the
+ * transport compression wrapper only after updating the hash.
+ */
+ if (work->compress_response) {
+ rc = ksmbd_compress_response(work);
+ if (rc < 0)
+ ksmbd_debug(CONN, "Failed to compress response: %d\n", rc);
+ }
if (work->sess && work->sess->enc && work->encrypted &&
conn->ops->encrypt_resp) {
rc = conn->ops->encrypt_resp(work);
@@ -318,7 +414,7 @@ static int ksmbd_server_process_request(struct ksmbd_conn *conn)
static int ksmbd_server_terminate_conn(struct ksmbd_conn *conn)
{
- ksmbd_sessions_deregister(conn);
+ ksmbd_conn_sessions_cleanup(conn);
destroy_lease_table(conn);
return 0;
}
@@ -535,11 +631,16 @@ static int ksmbd_server_shutdown(void)
{
WRITE_ONCE(server_conf.state, SERVER_STATE_SHUTTING_DOWN);
- ksmbd_proc_cleanup();
class_unregister(&ksmbd_control_class);
ksmbd_workqueue_destroy();
ksmbd_ipc_release();
ksmbd_conn_transport_destroy();
+ /*
+ * ksmbd_conn_transport_destroy() calls delete_proc_clients() and destroys
+ * sessions. ksmbd_session_destroy() removes each session's proc entry.
+ * Keep the procfs tree alive until these entries have been removed.
+ */
+ ksmbd_proc_cleanup();
ksmbd_crypto_destroy();
ksmbd_free_global_file_table();
destroy_lease_table(NULL);
@@ -559,18 +660,25 @@ static int __init ksmbd_server_init(void)
return ret;
}
- ksmbd_proc_init();
- create_proc_sessions();
+ ret = ksmbd_proc_init();
+ if (ret)
+ goto err_unregister;
+
+ if (create_proc_sessions())
+ pr_warn("Unable to create sessions procfs entry\n");
+
+ if (create_proc_shares())
+ pr_warn("Unable to create shares procfs entry\n");
ksmbd_server_tcp_callbacks_init();
ret = server_conf_init();
if (ret)
- goto err_unregister;
+ goto err_proc_cleanup;
ret = ksmbd_work_pool_init();
if (ret)
- goto err_unregister;
+ goto err_proc_cleanup;
ret = ksmbd_init_file_cache();
if (ret)
@@ -596,8 +704,14 @@ static int __init ksmbd_server_init(void)
if (ret)
goto err_crypto_destroy;
+ ret = ksmbd_conn_wq_init();
+ if (ret)
+ goto err_workqueue_destroy;
+
return 0;
+err_workqueue_destroy:
+ ksmbd_workqueue_destroy();
err_crypto_destroy:
ksmbd_crypto_destroy();
err_release_inode_hash:
@@ -610,6 +724,8 @@ err_exit_file_cache:
ksmbd_exit_file_cache();
err_destroy_work_pools:
ksmbd_work_pool_destroy();
+err_proc_cleanup:
+ ksmbd_proc_cleanup();
err_unregister:
class_unregister(&ksmbd_control_class);
@@ -623,16 +739,20 @@ static void __exit ksmbd_server_exit(void)
{
ksmbd_server_shutdown();
rcu_barrier();
+ /*
+ * ksmbd_conn_put() defers the final release onto ksmbd_conn_wq,
+ * so drain it after rcu_barrier() has fired any pending RCU
+ * callbacks that may have queued a release.
+ */
+ ksmbd_conn_wq_destroy();
ksmbd_release_inode_hash();
}
MODULE_AUTHOR("Namjae Jeon <linkinjeon@kernel.org>");
MODULE_DESCRIPTION("Linux kernel CIFS/SMB SERVER");
MODULE_LICENSE("GPL");
-MODULE_SOFTDEP("pre: ecb");
MODULE_SOFTDEP("pre: nls");
MODULE_SOFTDEP("pre: aes");
-MODULE_SOFTDEP("pre: cmac");
MODULE_SOFTDEP("pre: aead2");
MODULE_SOFTDEP("pre: ccm");
MODULE_SOFTDEP("pre: gcm");
diff --git a/fs/smb/server/server.h b/fs/smb/server/server.h
index b8a7317be86b..4d4d268b59d5 100644
--- a/fs/smb/server/server.h
+++ b/fs/smb/server/server.h
@@ -48,6 +48,8 @@ struct ksmbd_server_config {
char *conf[SERVER_CONF_WORK_GROUP + 1];
struct task_struct *dh_task;
bool bind_interfaces_only;
+ /* AAPL model string for Finder icon, e.g. "Xserve" */
+ char aapl_model[32];
};
extern struct ksmbd_server_config server_conf;
diff --git a/fs/smb/server/smb2misc.c b/fs/smb/server/smb2misc.c
index a1ddca21c47b..532dea7be0b3 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,14 +362,85 @@ 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);
return ret;
}
+/*
+ * Verify that the sequence number(s) consumed by an incoming request fall
+ * within the connection's command sequence window and are not a replay, then
+ * remove them from the window. Returns 0 if the request
+ * may proceed, or 1 if it is invalid and the connection must be torn down.
+ */
+static int smb2_check_sequence_number(struct ksmbd_work *work,
+ struct smb2_hdr *hdr)
+{
+ struct ksmbd_conn *conn = work->conn;
+ u64 mid = le64_to_cpu(hdr->MessageId);
+ unsigned short charge;
+ u64 i;
+ int ret = 0;
+
+ /* An SMB2 CANCEL consumes no sequence number. */
+ if (hdr->Command == SMB2_CANCEL)
+ return 0;
+
+ /*
+ * A multi-credit request consumes CreditCharge consecutive sequence
+ * numbers; every other request consumes exactly one.
+ */
+ charge = le16_to_cpu(hdr->CreditCharge);
+ if (!(conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) ||
+ charge == 0)
+ charge = 1;
+
+ /* The 64-bit sequence number space must not wrap. */
+ if (mid + charge < mid) {
+ pr_err("SMB2 sequence number wrapped (mid %llu charge %u)\n",
+ mid, charge);
+ return 1;
+ }
+
+ spin_lock(&conn->credits_lock);
+
+ /* The whole range must lie within the granted window... */
+ if (mid < conn->seq_low || mid + charge > conn->seq_high) {
+ ksmbd_debug(SMB,
+ "MessageId %llu (charge %u) outside command window [%llu, %llu)\n",
+ mid, charge, conn->seq_low, conn->seq_high);
+ ret = 1;
+ goto out;
+ }
+
+ /* ...and none of it may have been consumed already (replay). */
+ for (i = mid; i < mid + charge; i++) {
+ if (!test_bit(i & (KSMBD_CMD_SEQ_WINDOW - 1), conn->seq_bitmap)) {
+ ksmbd_debug(SMB,
+ "replayed sequence number %llu (mid %llu charge %u)\n",
+ i, mid, charge);
+ ret = 1;
+ goto out;
+ }
+ }
+
+ /* Consume the sequence numbers and slide the low edge forward. */
+ for (i = mid; i < mid + charge; i++)
+ __clear_bit(i & (KSMBD_CMD_SEQ_WINDOW - 1), conn->seq_bitmap);
+ while (conn->seq_low < conn->seq_high &&
+ !test_bit(conn->seq_low & (KSMBD_CMD_SEQ_WINDOW - 1),
+ conn->seq_bitmap))
+ conn->seq_low++;
+out:
+ spin_unlock(&conn->credits_lock);
+ return ret;
+}
+
int ksmbd_smb2_check_message(struct ksmbd_work *work)
{
struct smb2_pdu *pdu = ksmbd_req_buf_next(work);
@@ -399,6 +476,11 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
return 1;
}
+ if (len < __SMB2_HEADER_STRUCTURE_SIZE + sizeof(__le16)) {
+ ksmbd_debug(SMB, "Message is too small for StructureSize2\n");
+ return 1;
+ }
+
if (smb2_req_struct_sizes[command] != pdu->StructureSize2) {
if (!(command == SMB2_OPLOCK_BREAK_HE &&
(le16_to_cpu(pdu->StructureSize2) == OP_BREAK_STRUCT_SIZE_20 ||
@@ -460,9 +542,19 @@ 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;
+ /*
+ * A sequence number violation (out of window or a replay) is a
+ * protocol error. tear the connection down rather than
+ * keep accepting requests on it.
+ */
+ if (smb2_check_sequence_number(work, hdr)) {
+ ksmbd_conn_set_exiting(work->conn);
+ return 1;
+ }
+
return 0;
}
diff --git a/fs/smb/server/smb2ops.c b/fs/smb/server/smb2ops.c
index c9a32ee096b5..4578291fb172 100644
--- a/fs/smb/server/smb2ops.c
+++ b/fs/smb/server/smb2ops.c
@@ -37,6 +37,7 @@ static struct smb_version_values smb21_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
+ .create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb30_server_values = {
@@ -64,6 +65,7 @@ static struct smb_version_values smb30_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
+ .create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb302_server_values = {
@@ -91,6 +93,7 @@ static struct smb_version_values smb302_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
+ .create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb311_server_values = {
@@ -118,6 +121,7 @@ static struct smb_version_values smb311_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
+ .create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_ops smb2_0_server_ops = {
@@ -266,8 +270,10 @@ void init_smb3_02_server(struct ksmbd_conn *conn)
if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
- if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE)
- conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES;
+ /*
+ * Durable handles are in-memory only. Do not advertise persistent
+ * handles until CA recovery and fencing are implemented.
+ */
}
/**
@@ -290,10 +296,7 @@ int init_smb3_11_server(struct ksmbd_conn *conn)
if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
- if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE)
- conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES;
-
- INIT_LIST_HEAD(&conn->preauth_sess_table);
+ /* See init_smb3_02_server(): persistent handles require CA recovery. */
return 0;
}
@@ -326,6 +329,13 @@ void init_smb2_max_trans_size(unsigned int sz)
void init_smb2_max_credits(unsigned int sz)
{
+ /*
+ * The command sequence window (and its backing bitmap) can track at
+ * most SMB2_MAX_CREDITS outstanding sequence numbers, so the number of
+ * credits granted on a connection must not exceed that.
+ */
+ if (sz > SMB2_MAX_CREDITS)
+ sz = SMB2_MAX_CREDITS;
smb21_server_values.max_credits = sz;
smb30_server_values.max_credits = sz;
smb302_server_values.max_credits = sz;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 743c629fe7ec..6b8809f67b92 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -9,14 +9,18 @@
#include <net/addrconf.h>
#include <linux/syscalls.h>
#include <linux/namei.h>
+#include <linux/fs_struct.h>
#include <linux/statfs.h>
#include <linux/ethtool.h>
#include <linux/falloc.h>
#include <linux/mount.h>
#include <linux/filelock.h>
+#include <linux/fileattr.h>
+#include <linux/timekeeping.h>
+#include <linux/unaligned.h>
#include "glob.h"
-#include "smbfsctl.h"
+#include "../common/smbfsctl.h"
#include "oplock.h"
#include "smbacl.h"
@@ -24,7 +28,6 @@
#include "asn1.h"
#include "connection.h"
#include "transport_ipc.h"
-#include "../common/smbdirect/smbdirect.h"
#include "transport_rdma.h"
#include "vfs.h"
#include "vfs_cache.h"
@@ -42,6 +45,7 @@
#include "ndr.h"
#include "stats.h"
#include "transport_tcp.h"
+#include "compress.h"
static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
{
@@ -54,30 +58,21 @@ static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
}
}
+static struct ksmbd_work *smb2_notify_cancel_claim(void **argv);
+static void smb2_notify_cancel_fn(void **argv);
+static void smb2_complete_notify_cancel(struct ksmbd_work *in_work);
+
#define WORK_BUFFERS(w, rq, rs) __wbuf((w), (void **)&(rq), (void **)&(rs))
-/**
- * check_session_id() - check for valid session id in smb header
- * @conn: connection instance
- * @id: session id from smb header
- *
- * Return: 1 if valid session id, otherwise 0
- */
-static inline bool check_session_id(struct ksmbd_conn *conn, u64 id)
-{
- struct ksmbd_session *sess;
+#define SMB2_CREATE_FILE_ATTRIBUTE_MASK \
+ (FILE_ATTRIBUTE_MASK & ~(FILE_ATTRIBUTE_INTEGRITY_STREAM | \
+ FILE_ATTRIBUTE_NO_SCRUB_DATA))
- if (id == 0 || id == -1)
- return false;
+/* Windows reports automatic write-time updates at roughly 15 ms resolution. */
+#define KSMBD_WRITE_TIME_RESOLUTION (15ULL * 10000)
- sess = ksmbd_session_lookup_all(conn, id);
- if (sess) {
- ksmbd_user_session_put(sess);
- return true;
- }
- pr_err("Invalid user session id: %llu\n", id);
- return false;
-}
+/* MAXFILESIZE in [MS-FSA] 2.1.5.3 Server Requests a Write. */
+#define SMB2_MAX_FILE_SIZE 0xfffffff0000ULL
struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn *conn)
{
@@ -90,6 +85,50 @@ struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn
return chann;
}
+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 (sess->tearing_down) {
+ rc = -ESHUTDOWN;
+ goto out;
+ }
+
+ 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
@@ -126,6 +165,8 @@ int smb2_get_ksmbd_tcon(struct ksmbd_work *work)
pr_err("The first operation in the compound does not have tcon\n");
return -EINVAL;
}
+ if (work->tcon->t_state != TREE_CONNECTED)
+ return -ENOENT;
if (tree_id != UINT_MAX && work->tcon->id != tree_id) {
pr_err("tree id(%u) is different with id(%u) in first operation\n",
tree_id, work->tcon->id);
@@ -243,6 +284,13 @@ void set_smb2_rsp_status(struct ksmbd_work *work, __le32 err)
{
struct smb2_hdr *rsp_hdr;
+ if (work->next_smb2_rcv_hdr_off) {
+ rsp_hdr = ksmbd_resp_buf_next(work);
+ rsp_hdr->Status = err;
+ smb2_set_err_rsp(work);
+ return;
+ }
+
rsp_hdr = smb_get_msg(work->response_buf);
rsp_hdr->Status = err;
@@ -327,6 +375,7 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
struct ksmbd_conn *conn = work->conn;
unsigned short credits_requested, aux_max;
unsigned short credit_charge, credits_granted = 0;
+ u64 window_room, i;
if (work->send_no_response)
return 0;
@@ -349,6 +398,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);
@@ -362,11 +412,26 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
aux_max = 1;
else
aux_max = conn->vals->max_credits - conn->total_credits;
+
+ /*
+ * The command sequence window must not grow beyond
+ * KSMBD_CMD_SEQ_WINDOW sequence numbers ahead of the oldest one still
+ * outstanding. Cap the grant by the room left in the window so that
+ * credits are withheld until the client consumes the low end (and so
+ * that seq_bitmap stays usable as a ring).
+ */
+ window_room = conn->seq_low + KSMBD_CMD_SEQ_WINDOW - conn->seq_high;
+ aux_max = min_t(unsigned short, aux_max, window_room);
credits_granted = min_t(unsigned short, credits_requested, aux_max);
conn->total_credits += credits_granted;
work->credits_granted += credits_granted;
+ /* Extend the sequence window to cover the newly granted credits. */
+ for (i = conn->seq_high; i < conn->seq_high + credits_granted; i++)
+ __set_bit(i & (KSMBD_CMD_SEQ_WINDOW - 1), conn->seq_bitmap);
+ conn->seq_high += credits_granted;
+
if (!req_hdr->NextCommand) {
/* Update CreditRequest in last request */
hdr->CreditRequest = cpu_to_le16(work->credits_granted);
@@ -402,6 +467,59 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
work->compound_fid = ((struct smb2_create_rsp *)rsp)->VolatileFileId;
work->compound_pfid = ((struct smb2_create_rsp *)rsp)->PersistentFileId;
work->compound_sid = le64_to_cpu(rsp->SessionId);
+ work->compound_status = STATUS_SUCCESS;
+ } else if ((req->Command == SMB2_FLUSH ||
+ req->Command == SMB2_READ ||
+ req->Command == SMB2_WRITE) &&
+ rsp->Status == STATUS_SUCCESS) {
+ u64 volatile_id = KSMBD_NO_FID;
+ u64 persistent_id = KSMBD_NO_FID;
+
+ if (req->Command == SMB2_FLUSH) {
+ struct smb2_flush_req *flush_req =
+ (struct smb2_flush_req *)req;
+
+ volatile_id = flush_req->VolatileFileId;
+ persistent_id = flush_req->PersistentFileId;
+ } else if (req->Command == SMB2_READ) {
+ struct smb2_read_req *read_req =
+ (struct smb2_read_req *)req;
+
+ volatile_id = read_req->VolatileFileId;
+ persistent_id = read_req->PersistentFileId;
+ } else {
+ struct smb2_write_req *write_req =
+ (struct smb2_write_req *)req;
+
+ volatile_id = write_req->VolatileFileId;
+ persistent_id = write_req->PersistentFileId;
+ }
+
+ if (has_file_id(volatile_id)) {
+ work->compound_fid = volatile_id;
+ work->compound_pfid = persistent_id;
+ work->compound_sid = le64_to_cpu(rsp->SessionId);
+ work->compound_status = STATUS_SUCCESS;
+ }
+ } else if (req->Command == SMB2_CREATE) {
+ work->compound_fid = KSMBD_NO_FID;
+ work->compound_pfid = KSMBD_NO_FID;
+ work->compound_sid = le64_to_cpu(rsp->SessionId);
+ work->compound_status = rsp->Status;
+ } else if (rsp->Status != STATUS_SUCCESS) {
+ work->compound_sid = le64_to_cpu(rsp->SessionId);
+ /*
+ * Only carry the failed status forward when the failing command
+ * was itself part of the related chain. An unrelated command
+ * that fails (e.g. a standalone request with a bad session id)
+ * must not seed the status for a following related command,
+ * which has to be evaluated on its own (and may legitimately
+ * fail with a different status such as INVALID_PARAMETER). The
+ * compound session id is still tracked so a following related
+ * command can validate it.
+ */
+ if (req->Flags & SMB2_FLAGS_RELATED_OPERATIONS)
+ work->compound_status = rsp->Status;
}
len = get_rfc1002_len(work->response_buf) - work->next_smb2_rsp_hdr_off;
@@ -427,6 +545,7 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
ksmbd_debug(SMB, "related flag should be set\n");
work->compound_fid = KSMBD_NO_FID;
work->compound_pfid = KSMBD_NO_FID;
+ work->compound_status = STATUS_SUCCESS;
}
memset((char *)rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
@@ -438,6 +557,8 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR |
SMB2_FLAGS_RELATED_OPERATIONS);
+ if (rcv_hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION)
+ rsp_hdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION;
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
@@ -446,6 +567,19 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
memcpy(rsp_hdr->Signature, rcv_hdr->Signature, 16);
}
+static bool smb2_compound_has_failed(struct ksmbd_work *work,
+ struct smb2_hdr *rsp)
+{
+ if (!work->next_smb2_rcv_hdr_off ||
+ has_file_id(work->compound_fid) ||
+ work->compound_status == STATUS_SUCCESS)
+ return false;
+
+ rsp->Status = work->compound_status;
+ smb2_set_err_rsp(work);
+ return true;
+}
+
/**
* is_chained_smb2_message() - check for chained command
* @work: smb work containing smb request buffer
@@ -517,6 +651,8 @@ int init_smb2_rsp_hdr(struct ksmbd_work *work)
* Message is response. We don't grant oplock yet.
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
+ if (rcv_hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION)
+ rsp_hdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION;
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
@@ -527,6 +663,192 @@ int init_smb2_rsp_hdr(struct ksmbd_work *work)
return 0;
}
+static __le16 smb3_hdr_channel_sequence(struct smb2_hdr *hdr)
+{
+ return ((struct smb3_hdr_req *)hdr)->ChannelSequence;
+}
+
+static bool smb3_hdr_replay(struct smb2_hdr *hdr)
+{
+ return hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION;
+}
+
+static int smb3_verify_channel_sequence(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct smb2_hdr *hdr,
+ bool allow_stale)
+{
+ __le16 chseq_le;
+ u16 chseq, old_chseq;
+ int ret = 0;
+
+ if (work->conn->dialect < SMB30_PROT_ID)
+ return 0;
+
+ chseq_le = smb3_hdr_channel_sequence(hdr);
+ chseq = le16_to_cpu(chseq_le);
+
+ spin_lock(&fp->f_lock);
+ old_chseq = le16_to_cpu(fp->channel_sequence);
+ if (smb3_hdr_replay(hdr)) {
+ if (chseq == old_chseq && fp->outstanding_pre_requests == 0) {
+ fp->outstanding_requests++;
+ } else if ((u16)(chseq - old_chseq) <= 0x7fff &&
+ fp->outstanding_pre_requests == 0) {
+ fp->outstanding_pre_requests += fp->outstanding_requests;
+ fp->outstanding_requests = 1;
+ fp->channel_sequence = chseq_le;
+ } else if (allow_stale) {
+ fp->outstanding_pre_requests++;
+ } else {
+ ret = -EAGAIN;
+ }
+ } else {
+ if (chseq == old_chseq) {
+ fp->outstanding_requests++;
+ } else if ((u16)(chseq - old_chseq) <= 0x7fff) {
+ fp->outstanding_pre_requests += fp->outstanding_requests;
+ fp->outstanding_requests = 1;
+ fp->channel_sequence = chseq_le;
+ } else if (allow_stale) {
+ fp->outstanding_pre_requests++;
+ } else {
+ ret = -EAGAIN;
+ }
+ }
+ spin_unlock(&fp->f_lock);
+
+ return ret;
+}
+
+static void smb3_complete_channel_sequence(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ __le16 chseq_le)
+{
+ u16 chseq;
+
+ if (work->conn->dialect < SMB30_PROT_ID)
+ return;
+
+ chseq = le16_to_cpu(chseq_le);
+
+ spin_lock(&fp->f_lock);
+ if (chseq == le16_to_cpu(fp->channel_sequence)) {
+ if (fp->outstanding_requests)
+ fp->outstanding_requests--;
+ } else {
+ if (fp->outstanding_pre_requests)
+ fp->outstanding_pre_requests--;
+ }
+ spin_unlock(&fp->f_lock);
+}
+
+static int smb2_set_request_open(struct ksmbd_work *work, struct ksmbd_file *fp,
+ struct smb2_hdr *hdr, bool verify_chseq,
+ bool allow_stale_chseq)
+{
+ struct ksmbd_file *open;
+ int ret;
+
+ smb2_complete_request_open(work);
+
+ open = ksmbd_file_get(fp);
+ if (!open)
+ return -ESTALE;
+
+ if (verify_chseq) {
+ ret = smb3_verify_channel_sequence(work, fp, hdr,
+ allow_stale_chseq);
+ if (ret) {
+ ksmbd_fd_put(work, open);
+ return ret;
+ }
+ work->request_open_chseq_tracked = true;
+ }
+
+ work->request_open = open;
+ work->request_open_chseq = smb3_hdr_channel_sequence(hdr);
+ return 0;
+}
+
+void smb2_complete_request_open(struct ksmbd_work *work)
+{
+ struct ksmbd_file *open = work->request_open;
+
+ if (!open)
+ return;
+
+ if (work->request_open_chseq_tracked)
+ smb3_complete_channel_sequence(work, open,
+ work->request_open_chseq);
+
+ work->request_open = NULL;
+ work->request_open_chseq_tracked = false;
+ ksmbd_fd_put(work, open);
+}
+
+static bool smb2_lock_sequence_applicable(struct ksmbd_work *work,
+ struct ksmbd_file *fp)
+{
+ return fp->is_resilient || fp->is_durable || fp->is_persistent ||
+ (work->conn->dialect >= SMB30_PROT_ID &&
+ (work->conn->vals->req_capabilities &
+ SMB2_GLOBAL_CAP_MULTI_CHANNEL));
+}
+
+static bool smb2_verify_lock_sequence(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct smb2_lock_req *req)
+{
+ u32 val, index;
+ u8 sequence;
+ bool replay = false;
+
+ if (work->conn->dialect == SMB20_PROT_ID ||
+ !smb2_lock_sequence_applicable(work, fp))
+ return false;
+
+ val = le32_to_cpu(req->LockSequenceNumber);
+ sequence = val & 0xf;
+ index = val >> 4;
+ if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
+ return false;
+
+ spin_lock(&fp->f_lock);
+ if (fp->lock_seq[index - 1].valid) {
+ if (fp->lock_seq[index - 1].sequence == sequence)
+ replay = true;
+ else
+ fp->lock_seq[index - 1].valid = false;
+ }
+ spin_unlock(&fp->f_lock);
+
+ return replay;
+}
+
+static void smb2_update_lock_sequence(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct smb2_lock_req *req)
+{
+ u32 val, index;
+ u8 sequence;
+
+ if (work->conn->dialect == SMB20_PROT_ID ||
+ !smb2_lock_sequence_applicable(work, fp))
+ return;
+
+ val = le32_to_cpu(req->LockSequenceNumber);
+ sequence = val & 0xf;
+ index = val >> 4;
+ if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
+ return;
+
+ spin_lock(&fp->f_lock);
+ fp->lock_seq[index - 1].valid = true;
+ fp->lock_seq[index - 1].sequence = sequence;
+ spin_unlock(&fp->f_lock);
+}
+
/**
* smb2_allocate_rsp_buf() - allocate smb2 response buffer
* @work: smb work containing smb request buffer
@@ -554,7 +876,8 @@ int smb2_allocate_rsp_buf(struct ksmbd_work *work)
req = smb_get_msg(work->request_buf);
if ((req->InfoType == SMB2_O_INFO_FILE &&
(req->FileInfoClass == FILE_FULL_EA_INFORMATION ||
- req->FileInfoClass == FILE_ALL_INFORMATION)) ||
+ req->FileInfoClass == FILE_ALL_INFORMATION ||
+ req->FileInfoClass == FILE_NORMALIZED_NAME_INFORMATION)) ||
req->InfoType == SMB2_O_INFO_SECURITY)
sz = large_sz;
}
@@ -571,6 +894,53 @@ int smb2_allocate_rsp_buf(struct ksmbd_work *work)
return 0;
}
+static bool smb2_session_expired_cmd_allowed(struct ksmbd_work *work,
+ unsigned int cmd)
+{
+ struct smb2_lock_req *req;
+ unsigned int len, lock_count, i;
+
+ if (cmd == SMB2_CANCEL_HE || cmd == SMB2_CLOSE_HE ||
+ cmd == SMB2_LOGOFF_HE)
+ return true;
+ if (cmd != SMB2_LOCK_HE)
+ return false;
+
+ req = ksmbd_req_buf_next(work);
+ if (req->hdr.NextCommand)
+ len = le32_to_cpu(req->hdr.NextCommand);
+ else {
+ len = get_rfc1002_len(work->request_buf);
+ if (len < work->next_smb2_rcv_hdr_off)
+ return false;
+ len -= work->next_smb2_rcv_hdr_off;
+ }
+
+ lock_count = le16_to_cpu(req->LockCount);
+ if (!lock_count || len < offsetof(struct smb2_lock_req, locks) ||
+ lock_count > (len - offsetof(struct smb2_lock_req, locks)) /
+ sizeof(struct smb2_lock_element))
+ return false;
+
+ for (i = 0; i < lock_count; i++) {
+ if (le32_to_cpu(req->locks[i].Flags) != SMB2_LOCKFLAG_UNLOCK)
+ return false;
+ }
+ return true;
+}
+
+static bool smb2_session_kerberos_expired(struct ksmbd_session *sess)
+{
+ if (!sess->kerberos_expiry ||
+ ktime_get_real_seconds() < sess->kerberos_expiry)
+ return false;
+
+ if (cmpxchg(&sess->state, SMB2_SESSION_VALID,
+ SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID)
+ ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS);
+ return true;
+}
+
/**
* smb2_check_user_session() - check for valid session for a user
* @work: smb work containing smb request buffer
@@ -585,19 +955,36 @@ int smb2_check_user_session(struct ksmbd_work *work)
unsigned long long sess_id;
/*
- * SMB2_ECHO, SMB2_NEGOTIATE, SMB2_SESSION_SETUP command do not
- * require a session id, so no need to validate user session's for
- * these commands.
+ * SMB2_NEGOTIATE and SMB2_SESSION_SETUP do not require a session id.
+ * SMB2_ECHO may omit it, but an echo carrying a session id still needs
+ * the session attached to work so that its signature can be checked and
+ * the response can be signed, including after Kerberos expiry.
*/
- if (cmd == SMB2_ECHO_HE || cmd == SMB2_NEGOTIATE_HE ||
- cmd == SMB2_SESSION_SETUP_HE)
+ if (cmd == SMB2_NEGOTIATE_HE || cmd == SMB2_SESSION_SETUP_HE)
return 0;
+ sess_id = le64_to_cpu(req_hdr->SessionId);
+ if (cmd == SMB2_ECHO_HE) {
+ /*
+ * ECHO remains valid without a live session, including after
+ * LOGOFF. Attach an existing session only to authenticate a signed
+ * ECHO and sign its response; a stale SessionId is not an error.
+ */
+ if (!work->next_smb2_rcv_hdr_off && sess_id)
+ work->sess = ksmbd_session_lookup_all_states(conn, sess_id);
+ if (work->sess) {
+ if (!smb2_session_kerberos_expired(work->sess) &&
+ work->sess->state != SMB2_SESSION_VALID) {
+ ksmbd_user_session_put(work->sess);
+ work->sess = NULL;
+ }
+ }
+ return 0;
+ }
+
if (!ksmbd_conn_good(conn))
return -EIO;
- sess_id = le64_to_cpu(req_hdr->SessionId);
-
/*
* If request is not the first in Compound request,
* Just validate session id in header with work->sess->id.
@@ -612,13 +999,41 @@ int smb2_check_user_session(struct ksmbd_work *work)
sess_id, work->sess->id);
return -EINVAL;
}
+ smb2_session_kerberos_expired(work->sess);
+ if (work->sess->state != SMB2_SESSION_VALID) {
+ pr_err("compound request on a non-valid session (state %d)\n",
+ work->sess->state);
+ if (smb2_session_kerberos_expired(work->sess) &&
+ smb2_session_expired_cmd_allowed(work, cmd))
+ return 1;
+ return smb2_session_kerberos_expired(work->sess) ?
+ -EKEYEXPIRED : -EINVAL;
+ }
return 1;
}
/* Check for validity of user session */
- work->sess = ksmbd_session_lookup_all(conn, sess_id);
- if (work->sess)
+ work->sess = ksmbd_session_lookup_all_states(conn, sess_id);
+ if (work->sess) {
+ if (smb2_session_kerberos_expired(work->sess)) {
+ return smb2_session_expired_cmd_allowed(work, cmd) ?
+ 1 : -EKEYEXPIRED;
+ }
+ if (work->sess->state != SMB2_SESSION_VALID) {
+ /*
+ * Keep the reference for an encrypted request so the caller can
+ * return STATUS_USER_SESSION_DELETED encrypted with the old key.
+ */
+ if (work->encrypted &&
+ work->sess->state == SMB2_SESSION_EXPIRED &&
+ work->sess->enc)
+ return -ENOENT;
+ ksmbd_user_session_put(work->sess);
+ work->sess = NULL;
+ return -ENOENT;
+ }
return 1;
+ }
ksmbd_debug(SMB, "Invalid user session, Uid %llu\n", sess_id);
return -ENOENT;
}
@@ -658,6 +1073,24 @@ smb2_get_name(const char *src, const int maxlen, struct nls_table *local_nls)
return name;
}
+/* Link a fully initialized async work item unless the connection is closing. */
+static bool ksmbd_conn_link_async_request(struct ksmbd_conn *conn,
+ struct ksmbd_work *work)
+{
+ bool linked = false;
+
+ spin_lock(&conn->request_lock);
+ if (!ksmbd_conn_exiting(conn) && !ksmbd_conn_releasing(conn)) {
+ if (list_empty(&work->async_request_entry))
+ list_add_tail(&work->async_request_entry,
+ &conn->async_requests);
+ linked = true;
+ }
+ spin_unlock(&conn->request_lock);
+
+ return linked;
+}
+
int setup_async_work(struct ksmbd_work *work, void (*fn)(void **), void **arg)
{
struct ksmbd_conn *conn = work->conn;
@@ -670,20 +1103,22 @@ int setup_async_work(struct ksmbd_work *work, void (*fn)(void **), void **arg)
}
work->asynchronous = true;
work->async_id = id;
-
- ksmbd_debug(SMB,
- "Send interim Response to inform async request id : %d\n",
- work->async_id);
-
work->cancel_fn = fn;
work->cancel_argv = arg;
- if (list_empty(&work->async_request_entry)) {
- spin_lock(&conn->request_lock);
- list_add_tail(&work->async_request_entry, &conn->async_requests);
- spin_unlock(&conn->request_lock);
+ if (!ksmbd_conn_link_async_request(conn, work)) {
+ work->asynchronous = false;
+ work->async_id = 0;
+ work->cancel_fn = NULL;
+ work->cancel_argv = NULL;
+ ksmbd_release_id(&conn->async_ida, id);
+ return -ESHUTDOWN;
}
+ ksmbd_debug(SMB,
+ "Send interim Response to inform async request id : %d\n",
+ work->async_id);
+
return 0;
}
@@ -705,6 +1140,108 @@ void release_async_work(struct ksmbd_work *work)
}
}
+static int smb2_send_interim_work(struct ksmbd_work *in_work,
+ struct ksmbd_work *work, bool eor)
+{
+ int err = 0;
+
+ in_work->encrypted = work->encrypted;
+ if (work->encrypted && work->sess && work->sess->enc &&
+ work->conn->ops->encrypt_resp) {
+ in_work->sess = work->sess;
+ err = work->conn->ops->encrypt_resp(in_work);
+ in_work->sess = NULL;
+ }
+ if (err)
+ return err;
+
+ return eor ? ksmbd_conn_write_eor(in_work) :
+ ksmbd_conn_write(in_work);
+}
+
+static int smb2_send_interim_prefix_work(struct ksmbd_work *work)
+{
+ struct ksmbd_work *in_work;
+ unsigned int len, copied = 0;
+ char *dst;
+ int err = -ENOMEM;
+ int i;
+
+ len = get_rfc1002_len(work->iov[0].iov_base);
+ in_work = ksmbd_alloc_work_struct();
+ if (!in_work)
+ return err;
+
+ in_work->response_buf = kvzalloc(len + 4, KSMBD_DEFAULT_GFP);
+ if (!in_work->response_buf)
+ goto out;
+ in_work->response_sz = len + 4;
+ in_work->conn = work->conn;
+ dst = in_work->response_buf + 4;
+ for (i = 1; i <= work->iov_idx; i++) {
+ if (work->iov[i].iov_len > len - copied) {
+ err = -EINVAL;
+ goto out;
+ }
+ memcpy(dst + copied, work->iov[i].iov_base,
+ work->iov[i].iov_len);
+ copied += work->iov[i].iov_len;
+ }
+ if (copied != len) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ err = ksmbd_iov_pin_rsp(in_work, dst, len);
+ if (!err)
+ err = smb2_send_interim_work(in_work, work, true);
+out:
+ ksmbd_free_work_struct(in_work);
+ return err;
+}
+
+static void smb2_send_interim_compound_prefix(struct ksmbd_work *work)
+{
+ struct smb2_hdr *req_hdr;
+ struct smb2_hdr *rsp_hdr;
+ int err;
+
+ if (!work->next_smb2_rcv_hdr_off ||
+ !work->next_smb2_rsp_hdr_off ||
+ work->curr_smb2_rsp_hdr_off == work->next_smb2_rsp_hdr_off ||
+ !work->iov_idx)
+ return;
+
+ req_hdr = ksmbd_req_buf_next(work);
+ /* Detach only the final async command from the completed prefix. */
+ if (req_hdr->NextCommand)
+ return;
+
+ /*
+ * The responses before the async command are sent as a standalone
+ * compound response. The last response in this prefix must terminate
+ * the chain.
+ */
+ rsp_hdr = ksmbd_resp_buf_curr(work);
+ rsp_hdr->NextCommand = 0;
+ if ((rsp_hdr->Flags & SMB2_FLAGS_SIGNED) && work->sess &&
+ work->conn->ops->set_sign_rsp)
+ work->conn->ops->set_sign_rsp(work);
+
+ err = smb2_send_interim_prefix_work(work);
+ if (err)
+ ksmbd_debug(SMB, "failed to send compound interim prefix: %d\n",
+ err);
+
+ work->iov_idx = 0;
+ work->iov_cnt = 0;
+ work->curr_smb2_rsp_hdr_off = work->next_smb2_rsp_hdr_off;
+ *(__be32 *)work->response_buf = 0;
+
+ rsp_hdr = ksmbd_resp_buf_next(work);
+ rsp_hdr->Flags &= ~SMB2_FLAGS_RELATED_OPERATIONS;
+}
+
void smb2_send_interim_resp(struct ksmbd_work *work, __le32 status)
{
struct smb2_hdr *rsp_hdr;
@@ -719,6 +1256,9 @@ void smb2_send_interim_resp(struct ksmbd_work *work, __le32 status)
return;
}
+ if (status == STATUS_PENDING)
+ smb2_send_interim_compound_prefix(work);
+
in_work->conn = work->conn;
memcpy(smb_get_msg(in_work->response_buf), ksmbd_resp_buf_next(work),
__SMB2_HEADER_STRUCTURE_SIZE);
@@ -729,7 +1269,8 @@ void smb2_send_interim_resp(struct ksmbd_work *work, __le32 status)
smb2_set_err_rsp(in_work);
rsp_hdr->Status = status;
- ksmbd_conn_write(in_work);
+ if (smb2_send_interim_work(in_work, work, true))
+ ksmbd_debug(SMB, "failed to send interim response\n");
ksmbd_free_work_struct(in_work);
}
@@ -769,9 +1310,6 @@ static int smb2_get_dos_mode(struct kstat *stat, int attribute)
} else {
attr = (attribute & 0x00005137) | FILE_ATTRIBUTE_ARCHIVE;
attr &= ~(FILE_ATTRIBUTE_DIRECTORY);
- if (S_ISREG(stat->mode) && (server_conf.share_fake_fscaps &
- FILE_SUPPORTS_SPARSE_FILES))
- attr |= FILE_ATTRIBUTE_SPARSE_FILE;
if (smb2_get_reparse_tag_special_file(stat->mode))
attr |= FILE_ATTRIBUTE_REPARSE_POINT;
@@ -802,6 +1340,61 @@ static void build_encrypt_ctxt(struct smb2_encryption_neg_context *pneg_ctxt,
pneg_ctxt->Ciphers[0] = cipher_type;
}
+static void build_compress_ctxt(struct smb2_compression_capabilities_context *pneg_ctxt,
+ __le16 compress_algorithm, bool compress_chained,
+ bool compress_pattern)
+{
+ /*
+ * Return only algorithms implemented by ksmbd. Pattern_V1 is advertised
+ * as a second ID when the client also enabled chained transforms.
+ */
+ pneg_ctxt->ContextType = SMB2_COMPRESSION_CAPABILITIES;
+ pneg_ctxt->DataLength = cpu_to_le16(compress_pattern ? 12 : 10);
+ pneg_ctxt->Reserved = cpu_to_le32(0);
+ pneg_ctxt->CompressionAlgorithmCount =
+ cpu_to_le16(compress_pattern ? 2 : 1);
+ pneg_ctxt->Padding = cpu_to_le16(0);
+ pneg_ctxt->Flags = compress_chained ?
+ SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED :
+ SMB2_COMPRESSION_CAPABILITIES_FLAG_NONE;
+ pneg_ctxt->CompressionAlgorithms[0] = compress_algorithm;
+ pneg_ctxt->CompressionAlgorithms[1] = compress_pattern ?
+ SMB3_COMPRESS_PATTERN : 0;
+ pneg_ctxt->CompressionAlgorithms[2] = 0;
+ pneg_ctxt->CompressionAlgorithms[3] = 0;
+}
+
+/**
+ * build_rdma_ctx() - build an RDMA transform negotiate response context
+ * @ctxt: response context header to populate
+ * @transform_ids: bitmap of transforms common to the client and server
+ *
+ * Return: encoded negotiate context length
+ */
+static int build_rdma_ctx(struct smb2_neg_context *ctxt,
+ unsigned long transform_ids)
+{
+ struct smb2_rdma_transform_capabilities_context *pneg_ctxt;
+ int count = 0;
+
+ pneg_ctxt = (void *)ctxt;
+ pneg_ctxt->ContextType = SMB2_RDMA_TRANSFORM_CAPABILITIES;
+ pneg_ctxt->Reserved = 0;
+ pneg_ctxt->Reserved1 = 0;
+ pneg_ctxt->Reserved2 = 0;
+ if (transform_ids & BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION))
+ pneg_ctxt->RDMATransformIds[count++] =
+ cpu_to_le16(SMB2_RDMA_TRANSFORM_ENCRYPTION);
+ if (!count)
+ pneg_ctxt->RDMATransformIds[count++] =
+ cpu_to_le16(SMB2_RDMA_TRANSFORM_NONE);
+
+ pneg_ctxt->TransformCount = cpu_to_le16(count);
+ pneg_ctxt->DataLength = cpu_to_le16(8 + count * sizeof(__le16));
+ return sizeof(struct smb2_neg_context) +
+ le16_to_cpu(pneg_ctxt->DataLength);
+}
+
static void build_sign_cap_ctxt(struct smb2_signing_capabilities *pneg_ctxt,
__le16 sign_algo)
{
@@ -863,8 +1456,31 @@ static unsigned int assemble_neg_contexts(struct ksmbd_conn *conn,
ctxt_size += sizeof(struct smb2_encryption_neg_context) + 2;
}
- /* compression context not yet supported */
- WARN_ON(conn->compress_algorithm != SMB3_COMPRESS_NONE);
+ if (conn->compress_algorithm != SMB3_COMPRESS_NONE) {
+ ctxt_size = round_up(ctxt_size, 8);
+ ksmbd_debug(SMB,
+ "assemble SMB2_COMPRESSION_CAPABILITIES context\n");
+ build_compress_ctxt((struct smb2_compression_capabilities_context *)
+ (pneg_ctxt + ctxt_size),
+ conn->compress_algorithm,
+ conn->compress_chained,
+ conn->compress_pattern);
+ neg_ctxt_cnt++;
+ ctxt_size += sizeof(struct smb2_neg_context) +
+ (conn->compress_pattern ? 12 : 10);
+ }
+
+ if (conn->rdma_transform_negotiated) {
+ struct smb2_neg_context *rdma_ctxt;
+
+ ctxt_size = round_up(ctxt_size, 8);
+ ksmbd_debug(SMB,
+ "assemble SMB2_RDMA_TRANSFORM_CAPABILITIES context\n");
+ rdma_ctxt = (void *)(pneg_ctxt + ctxt_size);
+ ctxt_size += build_rdma_ctx(rdma_ctxt,
+ conn->rdma_transform_ids);
+ neg_ctxt_cnt++;
+ }
if (conn->posix_ext_supported) {
ctxt_size = round_up(ctxt_size, 8);
@@ -968,10 +1584,67 @@ bool smb3_encryption_negotiated(struct ksmbd_conn *conn)
conn->cipher_type;
}
-static void decode_compress_ctxt(struct ksmbd_conn *conn,
- struct smb2_compression_capabilities_context *pneg_ctxt)
+static __le32 decode_compress_ctxt(struct ksmbd_conn *conn,
+ struct smb2_compression_capabilities_context *pneg_ctxt,
+ int ctxt_len)
{
+ int alg_cnt, algs_size, i;
+ __le16 *algs;
+
+ if (sizeof(struct smb2_neg_context) + 10 > ctxt_len) {
+ pr_err("Invalid SMB2_COMPRESSION_CAPABILITIES context length\n");
+ return STATUS_INVALID_PARAMETER;
+ }
+
conn->compress_algorithm = SMB3_COMPRESS_NONE;
+ conn->compress_chained = false;
+ conn->compress_pattern = false;
+
+ alg_cnt = le16_to_cpu(pneg_ctxt->CompressionAlgorithmCount);
+ if (!alg_cnt)
+ return STATUS_INVALID_PARAMETER;
+
+ if (pneg_ctxt->Flags != SMB2_COMPRESSION_CAPABILITIES_FLAG_NONE &&
+ pneg_ctxt->Flags != SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED)
+ return STATUS_INVALID_PARAMETER;
+
+ algs_size = alg_cnt * sizeof(__le16);
+ if (sizeof(struct smb2_neg_context) + 8 + algs_size > ctxt_len) {
+ pr_err("Invalid compression algorithm count(%d)\n", alg_cnt);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * CompressionAlgorithms[] is declared as a fixed 4-element array, but
+ * the actual element count is variable (clients such as Windows may
+ * advertise more). The on-wire length was validated above, so walk the
+ * algorithms through a pointer to avoid a fixed-array bounds check.
+ */
+ algs = pneg_ctxt->CompressionAlgorithms;
+ for (i = 0; i < alg_cnt; i++) {
+ __le16 alg = algs[i];
+
+ /*
+ * LZ77 is the required general-purpose codec. Pattern_V1 is an
+ * optional chained payload type and cannot stand alone.
+ */
+ if (alg == SMB3_COMPRESS_LZ77) {
+ conn->compress_algorithm = alg;
+ conn->compress_chained =
+ pneg_ctxt->Flags ==
+ SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED;
+ ksmbd_debug(SMB, "Compression Algorithm ID = 0x%x\n",
+ le16_to_cpu(alg));
+ } else if (alg == SMB3_COMPRESS_PATTERN) {
+ conn->compress_pattern = true;
+ }
+ }
+
+ if (conn->compress_algorithm == SMB3_COMPRESS_NONE ||
+ !conn->compress_chained)
+ conn->compress_pattern = false;
+
+ return STATUS_SUCCESS;
}
static void decode_sign_cap_ctxt(struct ksmbd_conn *conn,
@@ -1009,6 +1682,46 @@ static void decode_sign_cap_ctxt(struct ksmbd_conn *conn,
}
}
+/**
+ * decode_rdma_ctx() - decode an RDMA transform negotiate request context
+ * @conn: connection being negotiated
+ * @ctxt: request context header to decode
+ * @ctxt_len: total context length, including the negotiate context header
+ *
+ * Record transforms supported by both peers only for SMB Direct connections.
+ *
+ * Return: NT status describing the decode result
+ */
+static __le32 decode_rdma_ctx(struct ksmbd_conn *conn,
+ struct smb2_neg_context *ctxt, int ctxt_len)
+{
+ struct smb2_rdma_transform_capabilities_context *pneg_ctxt;
+ unsigned int count, i;
+
+ pneg_ctxt = (void *)ctxt;
+ /* RDMA transforms are a node capability, not just a transport capability. */
+ if (!ksmbd_rdma_enabled())
+ return STATUS_SUCCESS;
+
+ if (ctxt_len < sizeof(*pneg_ctxt))
+ return STATUS_INVALID_PARAMETER;
+
+ count = le16_to_cpu(pneg_ctxt->TransformCount);
+ if (!count || count >
+ (ctxt_len - sizeof(*pneg_ctxt)) / sizeof(__le16))
+ return STATUS_INVALID_PARAMETER;
+
+ conn->rdma_transform_negotiated = true;
+ conn->rdma_transform_ids = 0;
+ for (i = 0; i < count; i++) {
+ u16 id = le16_to_cpu(pneg_ctxt->RDMATransformIds[i]);
+
+ if (id == SMB2_RDMA_TRANSFORM_ENCRYPTION)
+ conn->rdma_transform_ids |= BIT(id);
+ }
+ return STATUS_SUCCESS;
+}
+
static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
struct smb2_negotiate_req *req,
unsigned int len_of_smb)
@@ -1019,6 +1732,7 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
unsigned int offset = le32_to_cpu(req->NegotiateContextOffset);
unsigned int neg_ctxt_cnt = le16_to_cpu(req->NegotiateContextCount);
__le32 status = STATUS_INVALID_PARAMETER;
+ int compress_ctxt_cnt = 0, rdma_transform_ctxt_cnt = 0;
ksmbd_debug(SMB, "decoding %d negotiate contexts\n", neg_ctxt_cnt);
if (len_of_smb <= offset) {
@@ -1064,14 +1778,30 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
} else if (pctx->ContextType == SMB2_COMPRESSION_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_COMPRESSION_CAPABILITIES context\n");
- if (conn->compress_algorithm)
+ if (compress_ctxt_cnt++) {
+ status = STATUS_INVALID_PARAMETER;
break;
+ }
- decode_compress_ctxt(conn,
- (struct smb2_compression_capabilities_context *)pctx);
+ status = decode_compress_ctxt(conn,
+ (struct smb2_compression_capabilities_context *)
+ pctx, ctxt_len);
+ if (status != STATUS_SUCCESS)
+ break;
} else if (pctx->ContextType == SMB2_NETNAME_NEGOTIATE_CONTEXT_ID) {
ksmbd_debug(SMB,
"deassemble SMB2_NETNAME_NEGOTIATE_CONTEXT_ID context\n");
+ } else if (pctx->ContextType == SMB2_RDMA_TRANSFORM_CAPABILITIES) {
+ ksmbd_debug(SMB,
+ "deassemble SMB2_RDMA_TRANSFORM_CAPABILITIES context\n");
+ if (ksmbd_rdma_enabled() &&
+ rdma_transform_ctxt_cnt++) {
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ status = decode_rdma_ctx(conn, pctx, ctxt_len);
+ if (status != STATUS_SUCCESS)
+ break;
} else if (pctx->ContextType == SMB2_POSIX_EXTENSIONS_AVAILABLE) {
ksmbd_debug(SMB,
"deassemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n");
@@ -1096,6 +1826,8 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
* smb2_handle_negotiate() - handler for smb2 negotiate command
* @work: smb work containing smb request buffer
*
+ * The caller holds conn->srv_mutex.
+ *
* Return: 0
*/
int smb2_handle_negotiate(struct ksmbd_work *work)
@@ -1109,13 +1841,6 @@ int smb2_handle_negotiate(struct ksmbd_work *work)
ksmbd_debug(SMB, "Received negotiate request\n");
conn->need_neg = false;
- if (ksmbd_conn_good(conn)) {
- pr_err("conn->tcp_status is already in CifsGood State\n");
- work->send_no_response = 1;
- return rc;
- }
-
- ksmbd_conn_lock(conn);
smb2_buf_len = get_rfc1002_len(work->request_buf);
smb2_neg_size = offsetof(struct smb2_negotiate_req, Dialects);
if (smb2_neg_size > smb2_buf_len) {
@@ -1169,7 +1894,7 @@ int smb2_handle_negotiate(struct ksmbd_work *work)
KSMBD_DEFAULT_GFP);
if (!conn->preauth_info) {
rc = -ENOMEM;
- rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
goto err_out;
}
@@ -1184,6 +1909,16 @@ int smb2_handle_negotiate(struct ksmbd_work *work)
conn->preauth_info = NULL;
goto err_out;
}
+ if (!conn->cipher_type)
+ conn->rdma_transform_ids &=
+ ~BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION);
+ ksmbd_debug(RDMA,
+ "RDMA transform negotiation: transport=%s context=%s encryption=%s cipher=0x%04x\n",
+ conn->transport->ops->rdma_read ? "rdma" : "tcp",
+ conn->rdma_transform_negotiated ? "present" : "absent",
+ conn->rdma_transform_ids &
+ BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION) ? "enabled" : "disabled",
+ le16_to_cpu(conn->cipher_type));
rc = init_smb3_11_server(conn);
if (rc < 0) {
@@ -1252,11 +1987,9 @@ int smb2_handle_negotiate(struct ksmbd_work *work)
rsp->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED_LE;
conn->use_spnego = true;
- if ((server_conf.signing == KSMBD_CONFIG_OPT_AUTO ||
- server_conf.signing == KSMBD_CONFIG_OPT_DISABLED) &&
- req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED_LE)
+ if (req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED_LE)
conn->sign = true;
- else if (server_conf.signing == KSMBD_CONFIG_OPT_MANDATORY) {
+ if (server_conf.signing == KSMBD_CONFIG_OPT_MANDATORY) {
server_conf.enforced_signing = true;
rsp->SecurityMode |= SMB2_NEGOTIATE_SIGNING_REQUIRED_LE;
conn->sign = true;
@@ -1266,8 +1999,7 @@ int smb2_handle_negotiate(struct ksmbd_work *work)
ksmbd_conn_set_need_setup(conn);
err_out:
- ksmbd_conn_unlock(conn);
- if (rc)
+ if (rc && rsp->hdr.Status == STATUS_SUCCESS)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
if (!rc)
@@ -1459,9 +2191,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[CIFS_KEY_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");
@@ -1486,11 +2220,6 @@ static int ntlm_authenticate(struct ksmbd_work *work,
return -EPERM;
}
- /* Check for previous session */
- prev_id = le64_to_cpu(req->PreviousSessionId);
- if (prev_id && prev_id != sess->id)
- destroy_previous_session(conn, user, prev_id);
-
if (sess->state == SMB2_SESSION_VALID) {
/*
* Reuse session if anonymous try to connect
@@ -1503,7 +2232,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 {
@@ -1520,14 +2249,20 @@ 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;
}
}
+ prev_id = le64_to_cpu(req->PreviousSessionId);
+ if (prev_id && prev_id != sess->id)
+ destroy_previous_session(conn, sess->user, prev_id);
+
/*
* If session state is SMB2_SESSION_VALID, We can assume
* that it is reauthentication. And the user/password
@@ -1559,37 +2294,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
@@ -1600,8 +2328,12 @@ 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[CIFS_KEY_SIZE] = {};
+ char reauth_key[CIFS_KEY_SIZE] = {};
+ char *auth_key = conn->binding ? channel_key :
+ (work->session_setup_reauth ? reauth_key : sess->sess_key);
u64 prev_sess_id;
+ bool binding = conn->binding;
int in_len, out_len;
int retval;
@@ -1610,14 +2342,16 @@ static int krb5_authenticate(struct ksmbd_work *work,
in_len = le16_to_cpu(req->SecurityBufferLength);
out_blob = (char *)&rsp->hdr.ProtocolId +
le16_to_cpu(rsp->SecurityBufferOffset);
- out_len = work->response_sz -
+ out_len = work->response_sz - work->next_smb2_rsp_hdr_off -
(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 = -EPERM;
+ goto out;
}
/* Check previous session */
@@ -1632,12 +2366,21 @@ static int krb5_authenticate(struct ksmbd_work *work,
* that it is reauthentication. And the user/password
* has been verified, so return it here.
*/
- if (sess->state == SMB2_SESSION_VALID) {
+ if (sess->state == SMB2_SESSION_VALID && !work->session_setup_reauth) {
if (conn->binding)
goto binding_session;
return 0;
}
+ /*
+ * Reauthentication verifies the new Kerberos credentials but keeps
+ * the established SMB session keys.
+ */
+ if (work->session_setup_reauth) {
+ retval = 0;
+ goto out;
+ }
+
if ((rsp->SessionFlags != SMB2_SESSION_FLAG_IS_GUEST_LE &&
(conn->sign || server_conf.enforced_signing)) ||
(req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
@@ -1654,37 +2397,31 @@ 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:
+ memzero_explicit(reauth_key, sizeof(reauth_key));
+ if (binding)
+ memzero_explicit(channel_key, sizeof(channel_key));
+ return retval;
}
#else
static int krb5_authenticate(struct ksmbd_work *work,
@@ -1700,7 +2437,7 @@ int smb2_sess_setup(struct ksmbd_work *work)
struct ksmbd_conn *conn = work->conn;
struct smb2_sess_setup_req *req;
struct smb2_sess_setup_rsp *rsp;
- struct ksmbd_session *sess;
+ struct ksmbd_session *sess = NULL;
struct negotiate_message *negblob;
unsigned int negblob_len, negblob_off;
int rc = 0;
@@ -1748,12 +2485,27 @@ int smb2_sess_setup(struct ksmbd_work *work)
goto out_err;
}
+ if (conn->dialect == SMB311_PROT_ID) {
+ struct channel *chann;
+ unsigned long index;
+
+ down_read(&sess->chann_lock);
+ xa_for_each(&sess->ksmbd_chann_list, index, chann) {
+ if (conn->cipher_type != chann->conn->cipher_type)
+ rc = -EINVAL;
+ break;
+ }
+ up_read(&sess->chann_lock);
+ if (rc)
+ goto out_err;
+ }
+
if (!(req->hdr.Flags & SMB2_FLAGS_SIGNED)) {
rc = -EINVAL;
goto out_err;
}
- if (strncmp(conn->ClientGUID, sess->ClientGUID,
+ if (memcmp(conn->ClientGUID, sess->ClientGUID,
SMB2_CLIENT_GUID_SIZE)) {
rc = -ENOENT;
goto out_err;
@@ -1790,20 +2542,48 @@ 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;
}
if (sess->state == SMB2_SESSION_EXPIRED) {
- rc = -EFAULT;
- goto out_err;
+ if (sess->kerberos_expiry &&
+ ktime_get_real_seconds() >= sess->kerberos_expiry) {
+ work->session_setup_reauth = true;
+ } else {
+ rc = -EFAULT;
+ goto out_err;
+ }
}
if (ksmbd_conn_need_reconnect(conn)) {
@@ -1813,6 +2593,9 @@ int smb2_sess_setup(struct ksmbd_work *work)
goto out_err;
}
+ if (work->session_setup_reauth)
+ WRITE_ONCE(sess->state, SMB2_SESSION_IN_PROGRESS);
+
conn->binding = false;
}
work->sess = sess;
@@ -1847,10 +2630,8 @@ int smb2_sess_setup(struct ksmbd_work *work)
if (conn->preferred_auth_mech &
(KSMBD_AUTH_KRB5 | KSMBD_AUTH_MSKRB5)) {
rc = krb5_authenticate(work, req, rsp);
- if (rc) {
- rc = -EINVAL;
+ if (rc)
goto out_err;
- }
if (!ksmbd_conn_need_reconnect(conn)) {
ksmbd_conn_set_good(conn);
@@ -1879,7 +2660,7 @@ int smb2_sess_setup(struct ksmbd_work *work)
ksmbd_preauth_session_lookup(conn, sess->id);
if (preauth_sess) {
list_del(&preauth_sess->preauth_entry);
- kfree(preauth_sess);
+ kfree_sensitive(preauth_sess);
}
}
} else {
@@ -1906,19 +2687,45 @@ 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->use_spnego && conn->mechToken) {
+ if (conn->mechToken) {
kfree(conn->mechToken);
conn->mechToken = NULL;
}
if (rc < 0) {
+ bool setup_in_progress = sess &&
+ READ_ONCE(sess->state) == SMB2_SESSION_IN_PROGRESS &&
+ !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING);
+
+ /* Authentication errors must not leave the new session published. */
+ if (setup_in_progress)
+ ksmbd_session_unregister(conn, sess);
+
+ 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_sensitive(preauth_sess);
+ }
+ }
+
/*
* SecurityBufferOffset should be set to zero
* in session setup error response.
@@ -1937,10 +2744,26 @@ out_err:
if (sess->user && sess->user->flags & KSMBD_USER_FLAG_DELAY_SESSION)
try_delay = true;
- sess->last_active = jiffies;
- sess->state = SMB2_SESSION_EXPIRED;
- ksmbd_user_session_put(sess);
- work->sess = NULL;
+ /*
+ * For binding requests, session belongs to another
+ * connection. Do not expire it.
+ */
+ if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) &&
+ !setup_in_progress) {
+ sess->last_active = jiffies;
+ sess->kerberos_expiry = 0;
+ sess->state = SMB2_SESSION_EXPIRED;
+ }
+ /*
+ * 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);
@@ -1948,6 +2771,7 @@ out_err:
}
}
smb2_set_err_rsp(work);
+ conn->binding = false;
} else {
unsigned int iov_len;
@@ -1979,6 +2803,7 @@ int smb2_tree_connect(struct ksmbd_work *work)
struct ksmbd_session *sess = work->sess;
char *treename = NULL, *name = NULL;
struct ksmbd_tree_conn_status status;
+ struct ksmbd_tree_connect *tree_conn = NULL;
struct ksmbd_share_config *share = NULL;
int rc = -EINVAL;
@@ -2005,12 +2830,23 @@ int smb2_tree_connect(struct ksmbd_work *work)
name, treename);
status = ksmbd_tree_conn_connect(work, name);
- if (status.ret == KSMBD_TREE_CONN_STATUS_OK)
+ if (status.ret == KSMBD_TREE_CONN_STATUS_OK) {
+ tree_conn = status.tree_conn;
rsp->hdr.Id.SyncId.TreeId = cpu_to_le32(status.tree_conn->id);
- else
+ share = status.tree_conn->share_conf;
+
+ /* A share that requires encryption needs a negotiated SMB3 cipher. */
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_ENCRYPT_DATA) &&
+ !smb3_encryption_negotiated(conn)) {
+ ksmbd_tree_conn_disconnect(sess, status.tree_conn);
+ status.tree_conn = NULL;
+ share = NULL;
+ status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
+ goto out_err1;
+ }
+ } else
goto out_err1;
- share = status.tree_conn->share_conf;
if (test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
ksmbd_debug(SMB, "IPC share path request\n");
rsp->ShareType = SMB2_SHARE_TYPE_PIPE;
@@ -2039,23 +2875,46 @@ int smb2_tree_connect(struct ksmbd_work *work)
status.tree_conn->posix_extensions = true;
down_write(&sess->tree_conns_lock);
- status.tree_conn->t_state = TREE_CONNECTED;
+ if (status.tree_conn->t_state == TREE_DISCONNECTED) {
+ status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
+ share = NULL;
+ } else {
+ status.tree_conn->t_state = TREE_CONNECTED;
+ }
up_write(&sess->tree_conns_lock);
+ if (status.ret != KSMBD_TREE_CONN_STATUS_OK)
+ goto out_err1;
rsp->StructureSize = cpu_to_le16(16);
out_err1:
- if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE && share &&
- test_share_config_flag(share,
- KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY))
- rsp->Capabilities = SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY;
- else
- rsp->Capabilities = 0;
+ /*
+ * A configured CA share is not continuously available until persistent
+ * open recovery, ownership fencing, and failover are implemented.
+ */
+ rsp->Capabilities = 0;
rsp->Reserved = 0;
/* default manual caching */
rsp->ShareFlags = SMB2_SHAREFLAG_MANUAL_CACHING;
+ /* Tell the client that READ requests may request compressed responses. */
+ if (conn->dialect == SMB311_PROT_ID &&
+ conn->compress_algorithm != SMB3_COMPRESS_NONE)
+ rsp->ShareFlags |= cpu_to_le32(SMB2_SHAREFLAG_COMPRESS_DATA);
+ if (share && test_share_config_flag(share,
+ KSMBD_SHARE_FLAG_HIDE_UNREADABLE))
+ rsp->ShareFlags |=
+ cpu_to_le32(SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM);
+ if (share && test_share_config_flag(share,
+ KSMBD_SHARE_FLAG_ENCRYPT_DATA))
+ rsp->ShareFlags |=
+ cpu_to_le32(SMB2_SHAREFLAG_ENCRYPT_DATA);
rc = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_tree_connect_rsp));
- if (rc)
+ if (rc) {
+ if (status.ret == KSMBD_TREE_CONN_STATUS_OK) {
+ ksmbd_tree_conn_disconnect(sess, status.tree_conn);
+ status.tree_conn = NULL;
+ }
status.ret = KSMBD_TREE_CONN_STATUS_NOMEM;
+ }
if (!IS_ERR(treename))
kfree(treename);
@@ -2091,6 +2950,9 @@ out_err1:
if (status.ret != KSMBD_TREE_CONN_STATUS_OK)
smb2_set_err_rsp(work);
+ if (tree_conn)
+ ksmbd_tree_connect_put(tree_conn);
+
return rc;
}
@@ -2194,17 +3056,6 @@ int smb2_tree_disconnect(struct ksmbd_work *work)
ksmbd_close_tree_conn_fds(work);
- down_write(&sess->tree_conns_lock);
- if (tcon->t_state == TREE_DISCONNECTED) {
- up_write(&sess->tree_conns_lock);
- rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
- err = -ENOENT;
- goto err_out;
- }
-
- tcon->t_state = TREE_DISCONNECTED;
- up_write(&sess->tree_conns_lock);
-
err = ksmbd_tree_conn_disconnect(sess, tcon);
if (err) {
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
@@ -2239,7 +3090,6 @@ int smb2_session_logoff(struct ksmbd_work *work)
struct ksmbd_session *sess = work->sess;
struct smb2_logoff_req *req;
struct smb2_logoff_rsp *rsp;
- u64 sess_id;
int err;
WORK_BUFFERS(work, req, rsp);
@@ -2253,25 +3103,52 @@ int smb2_session_logoff(struct ksmbd_work *work)
smb2_set_err_rsp(work);
return -ENOENT;
}
- sess_id = le64_to_cpu(req->hdr.SessionId);
- ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_RECONNECT);
+
+ down_write(&sess->chann_lock);
+ if (sess->tearing_down) {
+ up_write(&sess->chann_lock);
+ ksmbd_conn_unlock(conn);
+ rsp->hdr.Status = STATUS_USER_SESSION_DELETED;
+ smb2_set_err_rsp(work);
+ return -ENOENT;
+ }
+ sess->tearing_down = true;
+ up_write(&sess->chann_lock);
+
+ ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_RECONNECT);
ksmbd_conn_unlock(conn);
+ err = ksmbd_conn_wait_idle_sess(conn, sess);
+ if (err) {
+ down_write(&sess->chann_lock);
+ sess->tearing_down = false;
+ up_write(&sess->chann_lock);
+ ksmbd_all_conn_set_status(sess, KSMBD_SESS_GOOD);
+ rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
+ smb2_set_err_rsp(work);
+ return err;
+ }
+
ksmbd_close_session_fds(work);
- ksmbd_conn_wait_idle(conn);
if (ksmbd_tree_conn_session_logoff(sess)) {
ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId);
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
smb2_set_err_rsp(work);
- return -ENOENT;
+ err = -ENOENT;
+ } else {
+ err = 0;
}
down_write(&conn->session_lock);
+ sess->kerberos_expiry = 0;
sess->state = SMB2_SESSION_EXPIRED;
up_write(&conn->session_lock);
- ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_SETUP);
+ ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_SETUP);
+
+ if (err)
+ return err;
rsp->StructureSize = cpu_to_le16(4);
err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_logoff_rsp));
@@ -2309,7 +3186,16 @@ static noinline int create_smb2_pipe(struct ksmbd_work *work)
id = ksmbd_session_rpc_open(work->sess, name);
if (id < 0) {
- pr_err("Unable to open RPC pipe: %d\n", id);
+ /*
+ * mdssvc (Spotlight) is a routine, expected probe from macOS
+ * that we deliberately don't support -- it's disabled at the
+ * __rpc_method() level (mgmt/user_session.c), but this
+ * generic failure log would otherwise still fire on every
+ * single probe regardless.
+ */
+ if (!(id == -ENOENT && (!strcmp(name, "\\mdssvc") ||
+ !strcmp(name, "mdssvc"))))
+ pr_err("Unable to open RPC pipe: %d\n", id);
err = id;
goto out;
}
@@ -2344,6 +3230,9 @@ out:
case -EINVAL:
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
break;
+ case -ENOENT:
+ rsp->hdr.Status = STATUS_OBJECT_NAME_NOT_FOUND;
+ break;
case -ENOSPC:
case -ENOMEM:
rsp->hdr.Status = STATUS_NO_MEMORY;
@@ -2360,6 +3249,22 @@ out:
return err;
}
+static bool smb2_is_private_ea(const char *name, size_t name_len)
+{
+ if (name_len == SD_PREFIX_LEN &&
+ !strncasecmp(name, SD_PREFIX, SD_PREFIX_LEN))
+ return true;
+ if (name_len == DOS_ATTRIBUTE_PREFIX_LEN &&
+ !strncasecmp(name, DOS_ATTRIBUTE_PREFIX,
+ DOS_ATTRIBUTE_PREFIX_LEN))
+ return true;
+ if (name_len >= STREAM_PREFIX_LEN &&
+ !strncasecmp(name, STREAM_PREFIX, STREAM_PREFIX_LEN))
+ return true;
+
+ return false;
+}
+
/**
* smb2_set_ea() - handler for setting extended attributes using set
* info command
@@ -2401,6 +3306,10 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
rc = -EINVAL;
break;
}
+ if (smb2_is_private_ea(eabuf->name, eabuf->EaNameLength)) {
+ rc = -EACCES;
+ break;
+ }
memcpy(attr_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
memcpy(&attr_name[XATTR_USER_PREFIX_LEN], eabuf->name,
@@ -2494,6 +3403,30 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
return 0;
if (fp->cdoption == FILE_OPEN_LE) {
+ if (!strcmp(stream_name, "AFP_AfpInfo") &&
+ test_share_config_flag(fp->tcon->share_conf,
+ KSMBD_SHARE_FLAG_TIME_MACHINE)) {
+ /*
+ * Synthesize an empty AFP_AfpInfo xattr on first access.
+ * type=0/creator=0 tells macOS to use the file extension
+ * for icon and type detection.
+ *
+ * Scoped to TIME_MACHINE shares, matching the rest of
+ * the AAPL series -- conn->is_aapl alone isn't a safe
+ * gate here, since the pre-existing narrow UniqueId=0
+ * path can also set it on ordinary, non-Time-Machine
+ * shares whenever a Mac client happens to negotiate
+ * AAPL there too.
+ */
+ static const u8 afpinfo_empty[60] = {
+ 0x00, 0x05, 0x16, 0x07, /* magic 0x00051607 BE */
+ 0x00, 0x02, 0x00, 0x00, /* version 0x00020000 BE */
+ };
+ rc = ksmbd_vfs_setxattr(idmap, path, xattr_stream_name,
+ (void *)afpinfo_empty,
+ sizeof(afpinfo_empty), 0, false);
+ return rc < 0 ? rc : 0;
+ }
ksmbd_debug(SMB, "XATTR stream name lookup failed: %d\n", rc);
return -EBADF;
}
@@ -2504,6 +3437,22 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
return 0;
}
+/*
+ * fp->stream.size is the byte length of the mangled xattr *name*
+ * (used as attr_name_len when looking the xattr up), not the size of
+ * the xattr's value. Reporting it as EndOfFile/AllocationSize for a
+ * stream handle is wrong -- query the xattr's actual value length
+ * instead.
+ */
+static loff_t ksmbd_stream_eof(struct ksmbd_file *fp)
+{
+ ssize_t slen = ksmbd_vfs_casexattr_len(file_mnt_idmap(fp->filp),
+ fp->filp->f_path.dentry,
+ fp->stream.name,
+ fp->stream.size);
+ return slen < 0 ? 0 : (loff_t)slen;
+}
+
static int smb2_remove_smb_xattrs(const struct path *path)
{
struct mnt_idmap *idmap = mnt_idmap(path->mnt);
@@ -2578,25 +3527,57 @@ static void smb2_new_xattrs(struct ksmbd_tree_connect *tcon, const struct path *
ksmbd_debug(SMB, "failed to store file attribute into xattr\n");
}
+static bool smb2_parent_compressed(struct ksmbd_tree_connect *tcon,
+ const struct path *path)
+{
+ struct dentry *parent = dget_parent(path->dentry);
+ struct file_kattr fa = { .flags_valid = true };
+ struct xattr_dos_attrib da;
+ bool compressed = false;
+ int rc;
+
+ rc = vfs_fileattr_get(parent, &fa);
+ if (!rc && fa.flags & FS_COMPR_FL) {
+ compressed = true;
+ goto out;
+ }
+
+ rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path->mnt), parent, &da);
+ if (rc > 0 && da.attr & FILE_ATTRIBUTE_COMPRESSED)
+ compressed = true;
+
+out:
+ dput(parent);
+ return compressed;
+}
+
static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
const struct path *path, struct ksmbd_file *fp)
{
- struct xattr_dos_attrib da;
+ struct xattr_dos_attrib da = {};
+ bool store_dos_attrs = test_share_config_flag(tcon->share_conf,
+ KSMBD_SHARE_FLAG_STORE_DOS_ATTRS);
int rc;
fp->f_ci->m_fattr &= ~(FILE_ATTRIBUTE_HIDDEN_LE | FILE_ATTRIBUTE_SYSTEM_LE);
/* get FileAttributes from XATTR_NAME_DOS_ATTRIBUTE */
- if (!test_share_config_flag(tcon->share_conf,
- KSMBD_SHARE_FLAG_STORE_DOS_ATTRS))
- return;
-
rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path->mnt),
path->dentry, &da);
if (rc > 0) {
- fp->f_ci->m_fattr = cpu_to_le32(da.attr);
- fp->create_time = da.create_time;
- fp->itime = da.itime;
+ if (store_dos_attrs) {
+ fp->f_ci->m_fattr = cpu_to_le32(da.attr);
+ fp->create_time = da.create_time;
+ fp->itime = da.itime;
+ } else {
+ fp->f_ci->m_fattr &=
+ ~(FILE_ATTRIBUTE_COMPRESSED_LE |
+ FILE_ATTRIBUTE_SPARSE_FILE_LE);
+ fp->f_ci->m_fattr |=
+ cpu_to_le32(da.attr &
+ (FILE_ATTRIBUTE_COMPRESSED |
+ FILE_ATTRIBUTE_SPARSE_FILE));
+ }
}
}
@@ -2698,10 +3679,89 @@ struct durable_info {
unsigned short int type;
bool persistent;
bool reconnected;
+ bool replay;
+ bool replay_consumed;
+ bool app_instance_id;
+ bool app_instance_version_valid;
unsigned int timeout;
char *CreateGuid;
+ char AppInstanceId[SMB2_CREATE_GUID_SIZE];
+ u64 app_instance_version_high;
+ u64 app_instance_version_low;
};
+static int smb2_check_durable_replay(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct lease_ctx_info *lc,
+ bool persistent)
+{
+ struct oplock_info *opinfo;
+ int ret = 0;
+
+ if (!fp->is_durable && !fp->is_persistent)
+ return -EACCES;
+
+ if (ksmbd_vfs_compare_durable_owner(fp, work->sess->user) == false)
+ return -EACCES;
+
+ if (fp->is_persistent && !persistent)
+ return -EINVAL;
+
+ opinfo = opinfo_get(fp);
+ if (!opinfo)
+ return 0;
+
+ if (opinfo->sess && opinfo->sess->id != work->sess->id) {
+ ret = -ENOEXEC;
+ goto out;
+ }
+
+ if (opinfo->is_lease) {
+ if (!lc ||
+ memcmp(opinfo->o_lease->lease_key, lc->lease_key,
+ SMB2_LEASE_KEY_SIZE)) {
+ ret = -EACCES;
+ goto out;
+ }
+ } else {
+ if (lc) {
+ ret = -EACCES;
+ goto out;
+ }
+
+ if (fp->is_durable && opinfo->level != SMB2_OPLOCK_LEVEL_BATCH)
+ ret = -EACCES;
+ }
+out:
+ opinfo_put(opinfo);
+ return ret;
+}
+
+static bool smb2_durable_replay_consumed(struct ksmbd_file *fp)
+{
+ bool consumed;
+
+ spin_lock(&fp->f_lock);
+ consumed = fp->durable_replay_consumed;
+ spin_unlock(&fp->f_lock);
+
+ return consumed;
+}
+
+static void smb2_mark_durable_replay_consumed(struct ksmbd_file *fp)
+{
+ spin_lock(&fp->f_lock);
+ fp->durable_replay_consumed = true;
+ spin_unlock(&fp->f_lock);
+}
+
+static bool smb2_durable_replay_differs(struct ksmbd_file *fp,
+ struct smb2_create_req *req)
+{
+ return fp->cdoption != req->CreateDisposition ||
+ fp->create_file_attributes != req->FileAttributes;
+}
+
static int parse_durable_handle_context(struct ksmbd_work *work,
struct smb2_create_req *req,
struct lease_ctx_info *lc,
@@ -2729,6 +3789,7 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
case DURABLE_RECONN_V2:
{
struct create_durable_handle_reconnect_v2 *recon_v2;
+ u32 flags;
if (dh_info->type == DURABLE_RECONN ||
dh_info->type == DURABLE_REQ_V2) {
@@ -2736,14 +3797,19 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
- if (le16_to_cpu(context->DataOffset) +
- le32_to_cpu(context->DataLength) <
- sizeof(struct create_durable_handle_reconnect_v2)) {
+ if (le32_to_cpu(context->DataLength) <
+ sizeof(recon_v2->dcontext)) {
err = -EINVAL;
goto out;
}
recon_v2 = (struct create_durable_handle_reconnect_v2 *)context;
+ flags = le32_to_cpu(recon_v2->dcontext.Flags);
+ if (flags & ~SMB2_DHANDLE_FLAG_PERSISTENT) {
+ err = -EINVAL;
+ goto out;
+ }
+ dh_info->persistent = flags & SMB2_DHANDLE_FLAG_PERSISTENT;
persistent_id = recon_v2->dcontext.Fid.PersistentFileId;
dh_info->fp = ksmbd_lookup_durable_fd(persistent_id);
if (!dh_info->fp) {
@@ -2752,6 +3818,15 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
+ /* A zero VolatileFileId means that the client did not specify it. */
+ if (recon_v2->dcontext.Fid.VolatileFileId &&
+ dh_info->fp->durable_volatile_id !=
+ recon_v2->dcontext.Fid.VolatileFileId) {
+ err = -EBADF;
+ ksmbd_put_durable_fd(dh_info->fp);
+ goto out;
+ }
+
if (memcmp(dh_info->fp->create_guid, recon_v2->dcontext.CreateGuid,
SMB2_CREATE_GUID_SIZE)) {
err = -EBADF;
@@ -2759,6 +3834,13 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
+ /* A persistent reconnect must match the original open type. */
+ if (dh_info->fp->is_persistent != dh_info->persistent) {
+ err = dh_info->persistent ? -EINVAL : -EBADF;
+ ksmbd_put_durable_fd(dh_info->fp);
+ goto out;
+ }
+
dh_info->type = dh_idx;
dh_info->reconnected = true;
ksmbd_debug(SMB,
@@ -2776,9 +3858,8 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
- if (le16_to_cpu(context->DataOffset) +
- le32_to_cpu(context->DataLength) <
- sizeof(create_durable_reconn_t)) {
+ if (le32_to_cpu(context->DataLength) <
+ sizeof(recon->Data)) {
err = -EINVAL;
goto out;
}
@@ -2792,6 +3873,15 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
+ /* A zero VolatileFileId means that the client did not specify it. */
+ if (recon->Data.Fid.VolatileFileId &&
+ dh_info->fp->durable_volatile_id !=
+ recon->Data.Fid.VolatileFileId) {
+ err = -EBADF;
+ ksmbd_put_durable_fd(dh_info->fp);
+ goto out;
+ }
+
dh_info->type = dh_idx;
dh_info->reconnected = true;
ksmbd_debug(SMB, "reconnect Persistent-id from reconnect = %llu\n",
@@ -2808,16 +3898,24 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
- if (le16_to_cpu(context->DataOffset) +
- le32_to_cpu(context->DataLength) <
- sizeof(struct create_durable_req_v2)) {
+ if (le32_to_cpu(context->DataLength) <
+ sizeof(durable_v2_blob->dcontext)) {
err = -EINVAL;
goto out;
}
durable_v2_blob =
(struct create_durable_req_v2 *)context;
+ if (le32_to_cpu(durable_v2_blob->dcontext.Flags) &
+ ~SMB2_DHANDLE_FLAG_PERSISTENT) {
+ err = -EINVAL;
+ goto out;
+ }
ksmbd_debug(SMB, "Request for durable v2 open\n");
+ dh_info->CreateGuid = durable_v2_blob->dcontext.CreateGuid;
+ dh_info->persistent =
+ le32_to_cpu(durable_v2_blob->dcontext.Flags) &
+ SMB2_DHANDLE_FLAG_PERSISTENT;
dh_info->fp = ksmbd_lookup_fd_cguid(durable_v2_blob->dcontext.CreateGuid);
if (dh_info->fp) {
if (!memcmp(conn->ClientGUID, dh_info->fp->client_guid,
@@ -2828,18 +3926,81 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
- dh_info->fp->conn = conn;
- dh_info->reconnected = true;
+ if (dh_info->fp->f_state == FP_NEW) {
+ /* Original CREATE is still pending. */
+ ksmbd_put_durable_fd(dh_info->fp);
+ err = -EAGAIN;
+ goto out;
+ }
+
+ if (!dh_info->fp->is_durable &&
+ !dh_info->fp->is_persistent) {
+ /*
+ * A DurableHandleReqV2 CREATE can complete
+ * without granting durability (for example, if
+ * it requested no oplock). Its CreateGuid still
+ * identifies a completed CREATE for replay.
+ */
+ if (dh_info->fp->conn &&
+ ksmbd_vfs_compare_durable_owner(
+ dh_info->fp, work->sess->user)) {
+ if (smb2_durable_replay_consumed(
+ dh_info->fp)) {
+ ksmbd_put_durable_fd(dh_info->fp);
+ dh_info->fp = NULL;
+ dh_info->type = dh_idx;
+ dh_info->replay_consumed = true;
+ break;
+ }
+ if (smb2_durable_replay_differs(
+ dh_info->fp, req))
+ smb2_mark_durable_replay_consumed(
+ dh_info->fp);
+ dh_info->replay = true;
+ dh_info->type = dh_idx;
+ goto out;
+ }
+ ksmbd_put_durable_fd(dh_info->fp);
+ err = -EACCES;
+ goto out;
+ }
+
+ if (dh_info->fp->conn &&
+ smb2_durable_replay_consumed(dh_info->fp)) {
+ ksmbd_put_durable_fd(dh_info->fp);
+ dh_info->fp = NULL;
+ dh_info->type = dh_idx;
+ dh_info->replay_consumed = true;
+ break;
+ }
+
+ err = smb2_check_durable_replay(work,
+ dh_info->fp,
+ lc,
+ dh_info->persistent);
+ if (err) {
+ ksmbd_put_durable_fd(dh_info->fp);
+ goto out;
+ }
+
+ if (dh_info->fp->conn) {
+ if (smb2_durable_replay_differs(dh_info->fp,
+ req))
+ smb2_mark_durable_replay_consumed(
+ dh_info->fp);
+ dh_info->replay = true;
+ } else {
+ dh_info->reconnected = true;
+ }
+ dh_info->type = dh_idx;
goto out;
}
+ ksmbd_put_durable_fd(dh_info->fp);
+ dh_info->fp = NULL;
}
if ((lc && (lc->req_state & SMB2_LEASE_HANDLE_CACHING_LE)) ||
req_op_level == SMB2_OPLOCK_LEVEL_BATCH) {
- dh_info->CreateGuid =
- durable_v2_blob->dcontext.CreateGuid;
- dh_info->persistent =
- le32_to_cpu(durable_v2_blob->dcontext.Flags);
dh_info->timeout =
le32_to_cpu(durable_v2_blob->dcontext.Timeout);
dh_info->type = dh_idx;
@@ -2867,6 +4028,93 @@ out:
return err;
}
+static int parse_app_instance_id(struct smb2_create_req *req,
+ struct durable_info *dh_info)
+{
+ struct create_context *context;
+ char *data;
+
+ context = smb2_find_context_vals(req, SMB2_CREATE_APP_INSTANCE_ID,
+ SMB2_CREATE_GUID_SIZE);
+ if (IS_ERR(context))
+ return PTR_ERR(context);
+ if (!context)
+ return 0;
+
+ if (le32_to_cpu(context->DataLength) < 20)
+ return -EINVAL;
+
+ data = (char *)context + le16_to_cpu(context->DataOffset);
+ if (data[0] != 20 || data[1])
+ return -EINVAL;
+
+ memcpy(dh_info->AppInstanceId, data + 4, SMB2_CREATE_GUID_SIZE);
+ dh_info->app_instance_id = true;
+ return 0;
+}
+
+static int parse_app_instance_version(struct smb2_create_req *req,
+ struct durable_info *dh_info)
+{
+ struct create_context *context;
+ char *data;
+
+ context = smb2_find_context_vals(req, SMB2_CREATE_APP_INSTANCE_VERSION,
+ SMB2_CREATE_GUID_SIZE);
+ if (IS_ERR(context))
+ return PTR_ERR(context);
+ if (!context)
+ return 0;
+
+ if (le32_to_cpu(context->DataLength) < 24)
+ return -EINVAL;
+
+ data = (char *)context + le16_to_cpu(context->DataOffset);
+ if (get_unaligned_le16(data) != 24 ||
+ get_unaligned_le16(data + 2) != 0)
+ return -EINVAL;
+
+ dh_info->app_instance_version_high = get_unaligned_le64(data + 8);
+ dh_info->app_instance_version_low = get_unaligned_le64(data + 16);
+ dh_info->app_instance_version_valid = true;
+ return 0;
+}
+
+static int smb2_handle_app_instance_id(struct smb2_create_rsp *rsp,
+ struct durable_info *dh_info)
+{
+ struct ksmbd_file *old_fp;
+ bool reject = false;
+
+ if (!dh_info->app_instance_id)
+ return 0;
+
+ old_fp = ksmbd_lookup_fd_app_instance_id(dh_info->AppInstanceId);
+ if (!old_fp)
+ return 0;
+
+ if (dh_info->app_instance_version_valid) {
+ if (old_fp->app_instance_version_valid &&
+ (dh_info->app_instance_version_high <
+ old_fp->app_instance_version_high ||
+ (dh_info->app_instance_version_high ==
+ old_fp->app_instance_version_high &&
+ dh_info->app_instance_version_low <=
+ old_fp->app_instance_version_low)))
+ reject = true;
+ } else if (old_fp->app_instance_version_valid) {
+ reject = true;
+ }
+
+ ksmbd_put_durable_fd(old_fp);
+ if (reject) {
+ rsp->hdr.Status = STATUS_FILE_FORCED_CLOSED;
+ return -EIO;
+ }
+
+ return ksmbd_close_fd_app_instance_id(dh_info->AppInstanceId);
+}
+
/**
* smb2_open() - handler for smb file open request
* @work: smb work containing request buffer
@@ -2896,15 +4144,19 @@ int smb2_open(struct ksmbd_work *work)
int rc = 0;
int contxt_cnt = 0, query_disk_id = 0;
bool maximal_access_ctxt = false, posix_ctxt = false;
+ bool aapl_ctxt = false;
+ bool durable_rsp = true;
+ __u64 aapl_req_bitmap = 0, aapl_client_caps = 0;
int s_type = 0;
int next_off = 0;
char *name = NULL;
char *stream_name = NULL;
bool file_present = false, created = false, already_permitted = false;
int share_ret, need_truncate = 0;
- u64 time;
+ u64 time, alloc_size = 0;
umode_t posix_mode = 0;
__le32 daccess, maximal_access = 0;
+ u32 dos_attr;
int iov_len = 0;
ksmbd_debug(SMB, "Received smb2 create request\n");
@@ -2990,46 +4242,96 @@ int smb2_open(struct ksmbd_work *work)
req_op_level = req->RequestedOplockLevel;
+ if (req->CreateContextsOffset) {
+ rc = parse_app_instance_id(req, &dh_info);
+ if (rc)
+ goto err_out2;
+ rc = parse_app_instance_version(req, &dh_info);
+ if (rc)
+ goto err_out2;
+ }
+
if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE &&
req->CreateContextsOffset) {
lc = parse_lease_state(req);
+ if (IS_ERR(lc)) {
+ rc = PTR_ERR(lc);
+ lc = NULL;
+ goto err_out2;
+ }
+ if (lc && lc->version == 2 && conn->dialect < SMB30_PROT_ID) {
+ kfree(lc);
+ lc = NULL;
+ if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
+ req_op_level = SMB2_OPLOCK_LEVEL_NONE;
+ }
rc = parse_durable_handle_context(work, req, lc, &dh_info);
if (rc) {
ksmbd_debug(SMB, "error parsing durable handle context\n");
goto err_out2;
}
- if (dh_info.reconnected == true) {
- rc = smb2_check_durable_oplock(conn, share, dh_info.fp, lc, name);
- if (rc) {
- ksmbd_put_durable_fd(dh_info.fp);
+ if (dh_info.replay == true) {
+ fp = dh_info.fp;
+ if (ksmbd_override_fsids(work)) {
+ rc = -ENOMEM;
goto err_out2;
}
+ file_info = FILE_OPENED;
+ rc = ksmbd_vfs_getattr(&fp->filp->f_path, &stat);
+ if (rc)
+ goto err_out2;
+
+ goto reconnected_fp;
+ }
+
+ if (dh_info.reconnected == true) {
+ rc = smb2_check_durable_oplock(conn, share, dh_info.fp,
+ lc, sess->user, name);
+ if (rc)
+ goto err_out2;
+
rc = ksmbd_reopen_durable_fd(work, dh_info.fp);
- if (rc) {
- ksmbd_put_durable_fd(dh_info.fp);
+ if (rc)
goto err_out2;
- }
+
+ fp = dh_info.fp;
if (ksmbd_override_fsids(work)) {
rc = -ENOMEM;
- ksmbd_put_durable_fd(dh_info.fp);
goto err_out2;
}
- fp = dh_info.fp;
file_info = FILE_OPENED;
rc = ksmbd_vfs_getattr(&fp->filp->f_path, &stat);
- ksmbd_put_durable_fd(fp);
if (rc)
goto err_out2;
goto reconnected_fp;
}
- } else if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
+
+ } else if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) {
lc = parse_lease_state(req);
+ if (IS_ERR(lc)) {
+ rc = PTR_ERR(lc);
+ lc = NULL;
+ goto err_out2;
+ }
+ if (lc && lc->version == 2 && conn->dialect < SMB30_PROT_ID) {
+ kfree(lc);
+ lc = NULL;
+ req_op_level = SMB2_OPLOCK_LEVEL_NONE;
+ }
+ }
+
+ if (dh_info.app_instance_id && !dh_info.reconnected &&
+ !dh_info.replay) {
+ rc = smb2_handle_app_instance_id(rsp, &dh_info);
+ if (rc)
+ goto err_out2;
+ }
if (le32_to_cpu(req->ImpersonationLevel) > le32_to_cpu(IL_DELEGATE)) {
pr_err("Invalid impersonationlevel : 0x%x\n",
@@ -3047,7 +4349,7 @@ int smb2_open(struct ksmbd_work *work)
} else {
if (req->CreateOptions & FILE_SEQUENTIAL_ONLY_LE &&
req->CreateOptions & FILE_RANDOM_ACCESS_LE)
- req->CreateOptions = ~(FILE_SEQUENTIAL_ONLY_LE);
+ req->CreateOptions &= ~FILE_SEQUENTIAL_ONLY_LE;
if (req->CreateOptions &
(FILE_OPEN_BY_FILE_ID_LE | CREATE_TREE_CONNECTION |
@@ -3060,8 +4362,6 @@ int smb2_open(struct ksmbd_work *work)
if (req->CreateOptions & FILE_NON_DIRECTORY_FILE_LE) {
rc = -EINVAL;
goto err_out2;
- } else if (req->CreateOptions & FILE_NO_COMPRESSION_LE) {
- req->CreateOptions = ~(FILE_NO_COMPRESSION_LE);
}
}
}
@@ -3081,7 +4381,15 @@ int smb2_open(struct ksmbd_work *work)
goto err_out2;
}
- if (req->FileAttributes && !(req->FileAttributes & FILE_ATTRIBUTE_MASK_LE)) {
+ if (req->DesiredAccess == FILE_SYNCHRONIZE_LE &&
+ req->CreateDisposition == FILE_OPEN_IF_LE &&
+ !req->FileAttributes) {
+ rc = -EACCES;
+ goto err_out2;
+ }
+
+ if (req->FileAttributes &&
+ (req->FileAttributes & ~cpu_to_le32(SMB2_CREATE_FILE_ATTRIBUTE_MASK))) {
pr_err("Invalid file attribute : 0x%x\n",
le32_to_cpu(req->FileAttributes));
rc = -EINVAL;
@@ -3139,10 +4447,29 @@ int smb2_open(struct ksmbd_work *work)
rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS,
&path, 1);
+
+ /*
+ * A durable handle opened with delete-on-close is preserved across a
+ * disconnect so it can be reclaimed by a durable reconnect. When a new
+ * delete-on-close open for the same name arrives instead, the
+ * disconnected handle must give way: close it so its delete-on-close
+ * removes the file, then re-resolve so this open can create a fresh one.
+ */
+ if (!rc && (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) &&
+ (req->CreateDisposition == FILE_OVERWRITE_IF_LE ||
+ req->CreateDisposition == FILE_OPEN_IF_LE) &&
+ ksmbd_close_disconnected_durable_delete_on_close(path.dentry)) {
+ path_put(&path);
+ rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS,
+ &path, 1);
+ }
+
if (!rc) {
file_present = true;
if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
+ struct xattr_dos_attrib da;
+
/*
* If file exists with under flags, return access
* denied error.
@@ -3156,6 +4483,16 @@ int smb2_open(struct ksmbd_work *work)
if (!test_tree_conn_flag(tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
ksmbd_debug(SMB,
"User does not have write permission\n");
+ rc = -EACCES;
+ goto err_out;
+ }
+
+ if (test_share_config_flag(tcon->share_conf,
+ KSMBD_SHARE_FLAG_STORE_DOS_ATTRS) &&
+ ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path.mnt),
+ path.dentry, &da) > 0 &&
+ da.attr & FILE_ATTRIBUTE_READONLY) {
+ rsp->hdr.Status = STATUS_CANNOT_DELETE;
rc = -EACCES;
goto err_out;
}
@@ -3173,7 +4510,21 @@ int smb2_open(struct ksmbd_work *work)
rc = 0;
}
- if (stream_name) {
+ if (!file_present && req->CreateOptions & FILE_DELETE_ON_CLOSE_LE &&
+ req->FileAttributes & FILE_ATTRIBUTE_READONLY_LE) {
+ rsp->hdr.Status = STATUS_CANNOT_DELETE;
+ rc = -EACCES;
+ goto err_out;
+ }
+
+ /*
+ * An explicit ::$DATA suffix names the unnamed data stream and is
+ * canonicalized to a NULL stream name (base file), but the request
+ * still has to be validated against the data-stream type, e.g. opening
+ * <dir>::$DATA with FILE_DIRECTORY_FILE must fail with
+ * STATUS_NOT_A_DIRECTORY.
+ */
+ if (stream_name || s_type == DATA_STREAM) {
if (req->CreateOptions & FILE_DIRECTORY_FILE_LE) {
if (s_type == DATA_STREAM) {
rc = -EIO;
@@ -3181,7 +4532,7 @@ int smb2_open(struct ksmbd_work *work)
}
} else {
if (file_present && S_ISDIR(d_inode(path.dentry)->i_mode) &&
- s_type == DATA_STREAM) {
+ !stream_name && s_type == DATA_STREAM) {
rc = -EIO;
rsp->hdr.Status = STATUS_FILE_IS_A_DIRECTORY;
}
@@ -3225,9 +4576,29 @@ int smb2_open(struct ksmbd_work *work)
if (file_present && !(req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
rc = smb_check_perm_dacl(conn, &path, &daccess,
- sess->user->uid);
+ req->DesiredAccess,
+ sess->user->uid, false);
if (rc)
goto err_out;
+
+ if (maximal_access_ctxt) {
+ maximal_access = FILE_MAXIMAL_ACCESS_LE;
+ rc = smb_check_perm_dacl(conn, &path, &maximal_access,
+ 0, sess->user->uid, false);
+ if (rc)
+ goto err_out;
+
+ /*
+ * smb_check_perm_dacl() returns success without
+ * touching *pdaccess when the object has no stored
+ * NT ACL, leaving maximal_access as the
+ * FILE_MAXIMAL_ACCESS_LE request sentinel instead of
+ * a real access mask.
+ */
+ if (maximal_access == FILE_MAXIMAL_ACCESS_LE)
+ ksmbd_vfs_query_maximal_access(idmap, path.dentry,
+ &maximal_access);
+ }
}
if (daccess & FILE_MAXIMAL_ACCESS_LE) {
@@ -3355,7 +4726,34 @@ int smb2_open(struct ksmbd_work *work)
goto err_out;
}
+ /*
+ * Publish the client and create GUID before an oplock/lease break can
+ * make this CREATE pending. A replay of that in-flight CREATE must find
+ * this FP_NEW handle and fail with STATUS_FILE_NOT_AVAILABLE instead of
+ * waiting on the same break again.
+ */
+ memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+ if (dh_info.app_instance_id) {
+ memcpy(fp->app_instance_id, dh_info.AppInstanceId,
+ SMB2_CREATE_GUID_SIZE);
+ fp->has_app_instance_id = true;
+ }
+ if (dh_info.app_instance_version_valid) {
+ fp->app_instance_version_high =
+ dh_info.app_instance_version_high;
+ fp->app_instance_version_low = dh_info.app_instance_version_low;
+ fp->app_instance_version_valid = true;
+ }
+ if (dh_info.CreateGuid) {
+ memcpy(fp->create_guid, dh_info.CreateGuid, SMB2_CREATE_GUID_SIZE);
+ fp->durable_replay_consumed = dh_info.replay_consumed;
+ rc = ksmbd_vfs_set_durable_owner(fp, sess->user);
+ if (rc)
+ goto err_out;
+ }
+
fp->cdoption = req->CreateDisposition;
+ fp->create_file_attributes = req->FileAttributes;
fp->daccess = daccess;
fp->saccess = req->ShareAccess;
fp->coption = req->CreateOptions;
@@ -3371,14 +4769,16 @@ int smb2_open(struct ksmbd_work *work)
if (posix_acl_rc)
ksmbd_debug(SMB, "inherit posix acl failed : %d\n", posix_acl_rc);
- if (test_share_config_flag(work->tcon->share_conf,
- KSMBD_SHARE_FLAG_ACL_XATTR)) {
- rc = smb_inherit_dacl(conn, &path, sess->user->uid,
- sess->user->gid);
- }
+ rc = smb2_create_sd_buffer(work, req, &path);
+ if (rc && rc != -ENOENT)
+ goto err_out;
- if (rc) {
- rc = smb2_create_sd_buffer(work, req, &path);
+ if (rc == -ENOENT) {
+ if (test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_ACL_XATTR)) {
+ rc = smb_inherit_dacl(conn, &path, sess->user->uid,
+ sess->user->gid);
+ }
if (rc) {
if (posix_acl_rc)
ksmbd_vfs_set_init_posix_acl(idmap,
@@ -3388,20 +4788,24 @@ int smb2_open(struct ksmbd_work *work)
KSMBD_SHARE_FLAG_ACL_XATTR)) {
struct smb_fattr fattr;
struct smb_ntsd *pntsd;
- int pntsd_size, ace_num = 0;
+ int pntsd_size;
+ size_t scratch_len;
ksmbd_acls_fattr(&fattr, idmap, inode);
- if (fattr.cf_acls)
- ace_num = fattr.cf_acls->a_count;
- if (fattr.cf_dacls)
- ace_num += fattr.cf_dacls->a_count;
-
- pntsd = kmalloc(sizeof(struct smb_ntsd) +
- sizeof(struct smb_sid) * 3 +
- sizeof(struct smb_acl) +
- sizeof(struct smb_ace) * ace_num * 2,
- KSMBD_DEFAULT_GFP);
+ scratch_len = smb_acl_sec_desc_scratch_len(&fattr,
+ NULL, 0,
+ OWNER_SECINFO | GROUP_SECINFO |
+ DACL_SECINFO);
+ if (!scratch_len || scratch_len == SIZE_MAX) {
+ rc = -EFBIG;
+ posix_acl_release(fattr.cf_acls);
+ posix_acl_release(fattr.cf_dacls);
+ goto err_out;
+ }
+
+ pntsd = kvzalloc(scratch_len, KSMBD_DEFAULT_GFP);
if (!pntsd) {
+ rc = -ENOMEM;
posix_acl_release(fattr.cf_acls);
posix_acl_release(fattr.cf_dacls);
goto err_out;
@@ -3416,7 +4820,7 @@ int smb2_open(struct ksmbd_work *work)
posix_acl_release(fattr.cf_acls);
posix_acl_release(fattr.cf_dacls);
if (rc) {
- kfree(pntsd);
+ kvfree(pntsd);
goto err_out;
}
@@ -3426,7 +4830,7 @@ int smb2_open(struct ksmbd_work *work)
pntsd,
pntsd_size,
false);
- kfree(pntsd);
+ kvfree(pntsd);
if (rc)
pr_err("failed to store ntacl in xattr : %d\n",
rc);
@@ -3465,6 +4869,12 @@ int smb2_open(struct ksmbd_work *work)
goto err_out;
}
+ if (!stream_name && daccess & FILE_DELETE_LE &&
+ ksmbd_has_stream_without_delete_share(fp)) {
+ rc = -EPERM;
+ goto err_out;
+ }
+
if (file_present || created)
path_put(&path);
@@ -3483,6 +4893,9 @@ int smb2_open(struct ksmbd_work *work)
goto err_out1;
}
} else {
+ if (created && !lc)
+ smb_send_parent_lease_break_noti(fp, NULL);
+
if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE && lc) {
if (S_ISDIR(file_inode(filp)->i_mode)) {
lc->req_state &= ~SMB2_LEASE_WRITE_CACHING_LE;
@@ -3500,7 +4913,7 @@ int smb2_open(struct ksmbd_work *work)
ksmbd_debug(SMB,
"lease req for(%s) req oplock state 0x%x, lease state 0x%x\n",
name, req_op_level, lc->req_state);
- rc = find_same_lease_key(sess, fp->f_ci, lc);
+ rc = find_same_lease_key(conn, fp->f_ci, lc);
if (rc)
goto err_out1;
} else if (open_flags == O_RDONLY &&
@@ -3511,13 +4924,16 @@ int smb2_open(struct ksmbd_work *work)
rc = smb_grant_oplock(work, req_op_level,
fp->persistent_id, fp,
le32_to_cpu(req->hdr.Id.SyncId.TreeId),
- lc, share_ret);
+ lc, share_ret,
+ smb3_hdr_replay(&req->hdr));
if (rc < 0)
goto err_out1;
}
- if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)
+ if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
+ smb_break_all_levII_oplock_for_delete(work, fp);
ksmbd_fd_set_delete_on_close(fp, file_info);
+ }
if (need_truncate) {
rc = smb2_create_truncate(&fp->filp->f_path);
@@ -3534,7 +4950,6 @@ int smb2_open(struct ksmbd_work *work)
rc = PTR_ERR(az_req);
goto err_out1;
} else if (az_req) {
- loff_t alloc_size;
int err;
if (le16_to_cpu(az_req->ccontext.DataOffset) +
@@ -3544,16 +4959,26 @@ int smb2_open(struct ksmbd_work *work)
goto err_out1;
}
alloc_size = le64_to_cpu(az_req->AllocationSize);
+ fp->allocation_size_set = true;
ksmbd_debug(SMB,
"request smb2 create allocate size : %llu\n",
alloc_size);
- smb_break_all_levII_oplock(work, fp, 1);
- err = vfs_fallocate(fp->filp, FALLOC_FL_KEEP_SIZE, 0,
- alloc_size);
- if (err < 0)
- ksmbd_debug(SMB,
- "vfs_fallocate is failed : %d\n",
- err);
+ /*
+ * fp->filp is the base file's data fork for a stream
+ * handle (streams are xattr-backed on the same
+ * underlying file) -- fallocate has no meaning for a
+ * stream and would otherwise pre-allocate storage on
+ * the base file's data instead.
+ */
+ if (!ksmbd_stream_fd(fp)) {
+ smb_break_all_levII_oplock(work, fp, 1);
+ err = vfs_fallocate(fp->filp, FALLOC_FL_KEEP_SIZE, 0,
+ alloc_size);
+ if (err < 0)
+ ksmbd_debug(SMB,
+ "vfs_fallocate is failed : %d\n",
+ err);
+ }
}
context = smb2_find_context_vals(req, SMB2_CREATE_QUERY_ON_DISK_ID, 4);
@@ -3565,7 +4990,32 @@ int smb2_open(struct ksmbd_work *work)
query_disk_id = 1;
}
- if (conn->is_aapl == false) {
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_TIME_MACHINE)) {
+ context = smb2_find_context_vals(req, SMB2_CREATE_AAPL, 4);
+ if (IS_ERR(context)) {
+ rc = PTR_ERR(context);
+ goto err_out1;
+ } else if (context) {
+ struct aapl_server_query_req *aapl_req;
+
+ if (le32_to_cpu(context->DataLength) <
+ sizeof(struct aapl_server_query_req)) {
+ rc = -EINVAL;
+ goto err_out1;
+ }
+
+ aapl_req = (struct aapl_server_query_req *)
+ ((char *)context +
+ le16_to_cpu(context->DataOffset));
+ if (le32_to_cpu(aapl_req->cmd) ==
+ SMB2_CRTCTX_AAPL_SERVER_QUERY) {
+ conn->is_aapl = true;
+ aapl_ctxt = true;
+ aapl_req_bitmap = le64_to_cpu(aapl_req->req_bitmap);
+ aapl_client_caps = le64_to_cpu(aapl_req->client_caps);
+ }
+ }
+ } else if (conn->is_aapl == false) {
context = smb2_find_context_vals(req, SMB2_CREATE_AAPL, 4);
if (IS_ERR(context)) {
rc = PTR_ERR(context);
@@ -3583,55 +5033,124 @@ int smb2_open(struct ksmbd_work *work)
fp->create_time = ksmbd_UnixTimeToNT(stat.btime);
else
fp->create_time = ksmbd_UnixTimeToNT(stat.ctime);
- if (req->FileAttributes || fp->f_ci->m_fattr == 0)
+ fp->change_time = ksmbd_UnixTimeToNT(stat.ctime);
+ fp->allocation_size = S_ISDIR(stat.mode) ? 0 :
+ (alloc_size ?: stat.blocks << 9);
+ if (created || fp->f_ci->m_fattr == 0)
fp->f_ci->m_fattr =
cpu_to_le32(smb2_get_dos_mode(&stat, le32_to_cpu(req->FileAttributes)));
if (!created)
smb2_update_xattrs(tcon, &path, fp);
- else
+ if (need_truncate && req->FileAttributes) {
+ dos_attr = le32_to_cpu(req->FileAttributes);
+ fp->f_ci->m_fattr =
+ cpu_to_le32(smb2_get_dos_mode(&stat, dos_attr));
smb2_new_xattrs(tcon, &path, fp);
+ }
- memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+ ksmbd_vfs_update_compressed_fattr(path.dentry, &fp->f_ci->m_fattr);
+
+ if (created) {
+ if (fp->coption & FILE_NO_COMPRESSION_LE) {
+ rc = ksmbd_vfs_set_compression_create(work, fp,
+ COMPRESSION_FORMAT_NONE);
+ if (rc)
+ fp->f_ci->m_fattr &= ~FILE_ATTRIBUTE_COMPRESSED_LE;
+ rc = 0;
+ } else if (smb2_parent_compressed(tcon, &path)) {
+ rc = ksmbd_vfs_set_compression_create(work, fp,
+ COMPRESSION_FORMAT_LZNT1);
+ if (rc)
+ fp->f_ci->m_fattr |= FILE_ATTRIBUTE_COMPRESSED_LE;
+ rc = 0;
+ }
+ }
+
+ if (created)
+ smb2_new_xattrs(tcon, &path, fp);
+
+ fp->create_action = cpu_to_le32(file_info);
if (dh_info.type == DURABLE_REQ_V2 || dh_info.type == DURABLE_REQ) {
if (dh_info.type == DURABLE_REQ_V2 && dh_info.persistent &&
test_share_config_flag(work->tcon->share_conf,
- KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY))
+ KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY) &&
+ (conn->vals->req_capabilities &
+ SMB2_GLOBAL_CAP_PERSISTENT_HANDLES)) {
+ /* MS-SMB2 3.3.5.9.10: a persistent open is durable too. */
+ fp->is_durable = true;
fp->is_persistent = true;
- else
+ } else {
fp->is_durable = true;
-
+ }
if (dh_info.type == DURABLE_REQ_V2) {
- memcpy(fp->create_guid, dh_info.CreateGuid,
- SMB2_CREATE_GUID_SIZE);
+ if (dh_info.app_instance_id)
+ memcpy(fp->app_instance_id,
+ dh_info.AppInstanceId,
+ SMB2_CREATE_GUID_SIZE);
if (dh_info.timeout)
fp->durable_timeout =
min_t(unsigned int, dh_info.timeout,
DURABLE_HANDLE_MAX_TIMEOUT);
else
- fp->durable_timeout = 60;
+ fp->durable_timeout = 60000;
}
}
-reconnected_fp:
+ /*
+ * conn->is_aapl detection above (this function's create-context
+ * parsing) is skipped on the reconnect path below, since a
+ * reconnect always arrives on a fresh connection -- if the client
+ * cares, it sends its own AAPL context on this same CREATE, which
+ * this function's normal (non-reconnect) parsing already handles.
+ */
+ reconnected_fp:
+ if (dh_info.replay)
+ file_info = le32_to_cpu(fp->create_action);
rsp->StructureSize = cpu_to_le16(89);
- rcu_read_lock();
- opinfo = rcu_dereference(fp->f_opinfo);
+ opinfo = opinfo_get(fp);
rsp->OplockLevel = opinfo != NULL ? opinfo->level : 0;
- rcu_read_unlock();
+ /*
+ * A durable CREATE replay does not modify the existing open. When
+ * replayed without an oplock, however, its response reflects that
+ * request and cannot include a new durable-handle response context.
+ */
+ if (dh_info.replay && !lc &&
+ req_op_level == SMB2_OPLOCK_LEVEL_NONE) {
+ rsp->OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+ durable_rsp = false;
+ }
rsp->Flags = 0;
rsp->CreateAction = cpu_to_le32(file_info);
rsp->CreationTime = cpu_to_le64(fp->create_time);
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);
- time = ksmbd_UnixTimeToNT(stat.ctime);
- rsp->ChangeTime = cpu_to_le64(time);
- rsp->AllocationSize = S_ISDIR(stat.mode) ? 0 :
- cpu_to_le64(stat.blocks << 9);
- rsp->EndofFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
+ rsp->ChangeTime = cpu_to_le64(fp->change_time);
+ /*
+ * The cached allocation size hides filesystem rounding for the
+ * requested allocation, but it can go stale when the file grows past
+ * it via writes (e.g. across a durable reconnect). Refresh it once the
+ * file exceeds the cached value, rounding the end of file up to the
+ * volume allocation unit (the filesystem block size, matching the
+ * SectorsPerAllocationUnit/BytesPerSector ksmbd advertises) rather than
+ * using the raw on-disk block count, which can include filesystem
+ * preallocation and metadata rounding.
+ */
+ if (ksmbd_stream_fd(fp)) {
+ loff_t seof = ksmbd_stream_eof(fp);
+
+ rsp->AllocationSize = cpu_to_le64((u64)seof);
+ rsp->EndofFile = cpu_to_le64((u64)seof);
+ } else {
+ if (!S_ISDIR(stat.mode) && stat.size > fp->allocation_size)
+ fp->allocation_size = round_up(stat.size, stat.blksize);
+ rsp->AllocationSize = cpu_to_le64(fp->allocation_size);
+ rsp->EndofFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
+ }
rsp->FileAttributes = fp->f_ci->m_fattr;
rsp->Reserved2 = 0;
@@ -3660,6 +5179,7 @@ reconnected_fp:
next_ptr = &lease_ccontext->Next;
next_off = conn->vals->create_lease_size;
}
+ opinfo_put(opinfo);
if (maximal_access_ctxt) {
struct create_context *mxac_ccontext;
@@ -3701,7 +5221,8 @@ reconnected_fp:
next_off = conn->vals->create_disk_id_size;
}
- if (dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2) {
+ if (durable_rsp &&
+ (dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2)) {
struct create_context *durable_ccontext;
durable_ccontext = (struct create_context *)(rsp->Buffer +
@@ -3725,10 +5246,16 @@ reconnected_fp:
if (next_ptr)
*next_ptr = cpu_to_le32(next_off);
next_ptr = &durable_ccontext->Next;
- next_off = conn->vals->create_durable_size;
+ next_off = dh_info.type == DURABLE_REQ ?
+ conn->vals->create_durable_size :
+ conn->vals->create_durable_v2_size;
}
if (posix_ctxt) {
+ struct create_context *posix_ccontext;
+
+ posix_ccontext = (struct create_context *)(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength));
contxt_cnt++;
create_posix_rsp_buf(rsp->Buffer +
le32_to_cpu(rsp->CreateContextsLength),
@@ -3738,6 +5265,40 @@ reconnected_fp:
iov_len += conn->vals->create_posix_size;
if (next_ptr)
*next_ptr = cpu_to_le32(next_off);
+ next_ptr = &posix_ccontext->Next;
+ next_off = conn->vals->create_posix_size;
+ }
+
+ /*
+ * AAPL create context response: see smb2pdu.h for the capability
+ * rationale. Scoped to TIME_MACHINE shares only.
+ */
+ if (aapl_ctxt) {
+ if (aapl_client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR)
+ conn->aapl_readdir_attr = true;
+ /*
+ * V2 extends the same inline-FinderInfo mechanism (see
+ * smb2pdu.h), so a V2-requesting client also gets
+ * aapl_readdir_attr treatment -- the reply just advertises
+ * the V2 bit instead of the V1 one (create_aapl_rsp_buf).
+ */
+ if (aapl_client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2) {
+ conn->aapl_readdir_attr = true;
+ conn->aapl_readdir_attr_v2 = true;
+ }
+
+ contxt_cnt++;
+ create_aapl_rsp_buf(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength),
+ SMB2_CRTCTX_AAPL_FULL_SYNC,
+ aapl_req_bitmap,
+ conn->aapl_readdir_attr_v2);
+ le32_add_cpu(&rsp->CreateContextsLength,
+ conn->vals->create_aapl_size);
+ iov_len += conn->vals->create_aapl_size;
+ if (next_ptr)
+ *next_ptr = cpu_to_le32(next_off);
+ /* AAPL is last; next_ptr need not be updated */
}
if (contxt_cnt > 0) {
@@ -3754,16 +5315,26 @@ err_out1:
err_out2:
if (!rc) {
- ksmbd_update_fstate(&work->sess->file_table, fp, FP_INITED);
- rc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len);
+ if (!dh_info.replay)
+ rc = ksmbd_update_fstate(&work->sess->file_table, fp,
+ FP_INITED);
+ if (!rc)
+ rc = smb2_set_request_open(work, fp, &req->hdr, false, false);
+ if (!rc)
+ rc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len);
}
if (rc) {
if (rc == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
else if (rc == -EOPNOTSUPP)
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
- else if (rc == -EACCES || rc == -ESTALE || rc == -EXDEV)
- rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ else if ((rc == -EACCES || rc == -ESTALE || rc == -EXDEV) &&
+ !rsp->hdr.Status) {
+ if (req->DesiredAccess & FILE_ACCESS_SYSTEM_SECURITY_LE)
+ rsp->hdr.Status = STATUS_PRIVILEGE_NOT_HELD;
+ else
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ }
else if (rc == -ENOENT)
rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
else if (rc == -EPERM)
@@ -3780,15 +5351,36 @@ err_out2:
rsp->hdr.Status = STATUS_OBJECT_NAME_COLLISION;
else if (rc == -EMFILE)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ else if (rc == -EINPROGRESS)
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ else if (rc == -EAGAIN)
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
if (!rsp->hdr.Status)
rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
- if (fp)
+ if (fp && !dh_info.replay)
ksmbd_fd_put(work, fp);
smb2_set_err_rsp(work);
ksmbd_debug(SMB, "Error response: %x\n", rsp->hdr.Status);
}
+ if (dh_info.replay)
+ ksmbd_put_durable_fd(dh_info.fp);
+
+ if (dh_info.reconnected) {
+ /*
+ * If reconnect succeeded, fp was republished in the
+ * session file table. On a later error, ksmbd_fd_put()
+ * above drops the session reference; drop the durable
+ * lookup reference through the same session-aware path so
+ * final close removes the volatile id before freeing fp.
+ */
+ if (rc && fp == dh_info.fp)
+ ksmbd_fd_put(work, dh_info.fp);
+ else
+ ksmbd_put_durable_fd(dh_info.fp);
+ }
+
kfree(name);
kfree(lc);
@@ -3930,7 +5522,13 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level,
goto free_conv_name;
}
- struct_sz = readdir_info_level_struct_sz(info_level) + conv_len;
+ struct_sz = readdir_info_level_struct_sz(info_level);
+ if (struct_sz == -EOPNOTSUPP) {
+ rc = -EINVAL;
+ goto free_conv_name;
+ }
+
+ struct_sz += conv_len;
next_entry_offset = ALIGN(struct_sz, KSMBD_DIR_INFO_ALIGNMENT);
d_info->last_entry_off_align = next_entry_offset - struct_sz;
@@ -4028,17 +5626,83 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level,
fibdinfo = (struct file_id_both_directory_info *)kstat;
fibdinfo->FileNameLength = cpu_to_le32(conv_len);
- fibdinfo->EaSize =
- smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
- if (fibdinfo->EaSize)
- fibdinfo->ExtFileAttributes = FILE_ATTRIBUTE_REPARSE_POINT_LE;
if (conn->is_aapl)
fibdinfo->UniqueId = 0;
else
fibdinfo->UniqueId = cpu_to_le64(ksmbd_kstat->kstat->ino);
fibdinfo->ShortNameLength = 0;
fibdinfo->Reserved = 0;
- fibdinfo->Reserved2 = cpu_to_le16(0);
+ if (conn->aapl_readdir_attr) {
+ /*
+ * READDIR_ATTR wire format, confirmed against reference server's
+ * reference implementation marshalling (reference implementation behavior):
+ * EaSize = max_access (expanded specific
+ * rights, simplified to "grant all")
+ * ShortNameLength = 24 (fixed; not 0, despite the spec)
+ * ShortName[0..7] = resource fork size (uint64 LE, 0 = no rfork)
+ * ShortName[8..23] = compressed FinderInfo (type+creator+flags+
+ * ext_flags+date_added, 16 bytes LE; all
+ * zeros means type=0/creator=0, i.e. use
+ * the file extension for icon lookup)
+ * Reserved2 = Unix mode bits (uint16 LE)
+ * Reparse-point tag is indicated via ExtFileAttributes, not EaSize.
+ *
+ * V2 (conn->aapl_readdir_attr_v2): ShortNameLength+Reserved
+ * are read as a single flags field instead of being ignored
+ * -- see smb2pdu.h for the wire-format confirmation and
+ * AAPL_READDIR_ATTR_V2_NO_XATTR's meaning.
+ */
+ __le32 reparse_tag =
+ smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
+
+ if (reparse_tag)
+ fibdinfo->ExtFileAttributes = FILE_ATTRIBUTE_REPARSE_POINT_LE;
+ /*
+ * FILE_GENERIC_ALL_LE (0x10000000) is the raw
+ * "generic all" meta-bit -- valid only in a
+ * client's requested access mask, for the server
+ * to expand. It has none of the specific FILE_*
+ * rights bits set (FILE_LIST_DIRECTORY, FILE_TRAVERSE,
+ * etc.), so reporting it here as max_access would make
+ * macOS's bit-by-bit access checks fail on every
+ * entry -> permanent "no entry" badges in Finder.
+ * Report the actual expanded rights instead, same
+ * as smb_map_generic_desired_access() does when
+ * translating a client's GENERIC_ALL request.
+ */
+ fibdinfo->EaSize = cpu_to_le32(GENERIC_ALL_FLAGS);
+ /*
+ * The spec says ShortNameLength should be 0 when
+ * there's no short name; 24 here instead matches
+ * reference implementation marshalling (reference
+ * behavior) for server-to-server wire parity.
+ * V2 repurposes it as a flags field that is
+ * interpreted; V1 doesn't. Either value is safe
+ * here, so keep 24 for parity.
+ */
+ if (conn->aapl_readdir_attr_v2) {
+ /*
+ * V2 repurposes this field as flags (see comment
+ * above) -- 24 is a V1-only convention that real
+ * macOS clients ignore outright, so don't reuse it
+ * here as a base value for a field V2 clients
+ * actually interpret.
+ */
+ fibdinfo->ShortNameLength = 0;
+ if (!ksmbd_kstat->has_ads_stream)
+ fibdinfo->ShortNameLength = AAPL_READDIR_ATTR_V2_NO_XATTR;
+ } else {
+ fibdinfo->ShortNameLength = 24;
+ }
+ memset(fibdinfo->ShortName, 0, sizeof(fibdinfo->ShortName));
+ fibdinfo->Reserved2 = cpu_to_le16(ksmbd_kstat->kstat->mode & 0xffff);
+ } else {
+ fibdinfo->EaSize =
+ smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
+ if (fibdinfo->EaSize)
+ fibdinfo->ExtFileAttributes = FILE_ATTRIBUTE_REPARSE_POINT_LE;
+ fibdinfo->Reserved2 = cpu_to_le16(0);
+ }
if (d_info->hide_dot_file && d_info->name[0] == '.')
fibdinfo->ExtFileAttributes |= FILE_ATTRIBUTE_HIDDEN_LE;
memcpy(fibdinfo->FileName, conv_name, conv_len);
@@ -4141,6 +5805,7 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
for (i = 0; i < priv->d_info->num_entry; i++) {
struct dentry *dent;
+ struct path path;
if (dentry_name(priv->d_info, priv->info_level))
return -EINVAL;
@@ -4163,6 +5828,23 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
continue;
}
+ if (test_share_config_flag(priv->work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_HIDE_UNREADABLE)) {
+ __le32 daccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
+ FILE_READ_ATTRIBUTES_LE;
+
+ path.mnt = priv->dir_fp->filp->f_path.mnt;
+ path.dentry = dent;
+ rc = smb_check_perm_dacl(priv->work->conn, &path,
+ &daccess, daccess,
+ priv->work->sess->user->uid,
+ true);
+ if (rc) {
+ dput(dent);
+ continue;
+ }
+ }
+
ksmbd_kstat.kstat = &kstat;
if (priv->info_level != FILE_NAMES_INFORMATION) {
rc = ksmbd_vfs_fill_dentry_attrs(priv->work,
@@ -4380,11 +6062,28 @@ int smb2_query_dir(struct ksmbd_work *work)
unsigned char srch_flag;
int buffer_sz;
struct smb2_query_dir_private query_dir_private = {NULL, };
+ unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
ksmbd_debug(SMB, "Received smb2 query directory request\n");
WORK_BUFFERS(work, req, rsp);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
+
+ if (work->next_smb2_rcv_hdr_off &&
+ !has_file_id(req->VolatileFileId)) {
+ ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+ work->compound_fid);
+ id = work->compound_fid;
+ pid = work->compound_pfid;
+ }
+
+ if (!has_file_id(id)) {
+ id = req->VolatileFileId;
+ pid = req->PersistentFileId;
+ }
+
if (ksmbd_override_fsids(work)) {
rsp->hdr.Status = STATUS_NO_MEMORY;
smb2_set_err_rsp(work);
@@ -4397,7 +6096,7 @@ int smb2_query_dir(struct ksmbd_work *work)
goto err_out2;
}
- dir_fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
+ dir_fp = ksmbd_lookup_fd_slow(work, id, pid);
if (!dir_fp) {
rc = -EBADF;
goto err_out2;
@@ -4430,6 +6129,8 @@ int smb2_query_dir(struct ksmbd_work *work)
ksmbd_debug(SMB, "Search pattern is %s\n", srch_ptr);
}
+ mutex_lock(&dir_fp->readdir_lock);
+
if (srch_flag & SMB2_REOPEN || srch_flag & SMB2_RESTART_SCANS) {
ksmbd_debug(SMB, "Restart directory scan\n");
generic_file_llseek(dir_fp->filp, 0, SEEK_SET);
@@ -4439,8 +6140,9 @@ int smb2_query_dir(struct ksmbd_work *work)
d_info.wptr = (char *)rsp->Buffer;
d_info.rptr = (char *)rsp->Buffer;
d_info.out_buf_len =
- smb2_calc_max_out_buf_len(work, 8,
- le32_to_cpu(req->OutputBufferLength));
+ smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_query_directory_rsp, Buffer),
+ le32_to_cpu(req->OutputBufferLength));
if (d_info.out_buf_len < 0) {
rc = -EINVAL;
goto err_out;
@@ -4533,6 +6235,7 @@ no_buf_len:
goto err_out;
}
+ mutex_unlock(&dir_fp->readdir_lock);
kfree(srch_ptr);
ksmbd_fd_put(work, dir_fp);
ksmbd_revert_fsids(work);
@@ -4540,6 +6243,7 @@ no_buf_len:
err_out:
pr_err("error while processing smb2 query dir rc = %d\n", rc);
+ mutex_unlock(&dir_fp->readdir_lock);
kfree(srch_ptr);
err_out2:
@@ -4569,26 +6273,31 @@ err_out2:
/**
* buffer_check_err() - helper function to check buffer errors
* @reqOutputBufferLength: max buffer length expected in command response
+ * @fixed_len: minimum fixed response length
* @rsp: query info response buffer contains output buffer length
- * @rsp_org: base response buffer pointer in case of chained response
*
* Return: 0 on success, otherwise error
*/
static int buffer_check_err(int reqOutputBufferLength,
- struct smb2_query_info_rsp *rsp,
- void *rsp_org)
+ unsigned int fixed_len,
+ struct smb2_query_info_rsp *rsp)
{
- if (reqOutputBufferLength < le32_to_cpu(rsp->OutputBufferLength)) {
+ unsigned int output_len = le32_to_cpu(rsp->OutputBufferLength);
+
+ if (reqOutputBufferLength < fixed_len) {
pr_err("Invalid Buffer Size Requested\n");
rsp->hdr.Status = STATUS_INFO_LENGTH_MISMATCH;
- *(__be32 *)rsp_org = cpu_to_be32(sizeof(struct smb2_hdr));
return -EINVAL;
}
+
+ if (reqOutputBufferLength < output_len) {
+ rsp->hdr.Status = STATUS_BUFFER_OVERFLOW;
+ rsp->OutputBufferLength = cpu_to_le32(reqOutputBufferLength);
+ }
return 0;
}
-static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp,
- void *rsp_org)
+static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp)
{
struct smb2_file_standard_info *sinfo;
@@ -4603,8 +6312,7 @@ static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp,
cpu_to_le32(sizeof(struct smb2_file_standard_info));
}
-static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num,
- void *rsp_org)
+static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num)
{
struct smb2_file_internal_info *file_info;
@@ -4618,8 +6326,7 @@ static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num,
static int smb2_get_info_file_pipe(struct ksmbd_session *sess,
struct smb2_query_info_req *req,
- struct smb2_query_info_rsp *rsp,
- void *rsp_org)
+ struct smb2_query_info_rsp *rsp)
{
u64 id;
int rc;
@@ -4644,14 +6351,16 @@ static int smb2_get_info_file_pipe(struct ksmbd_session *sess,
switch (req->FileInfoClass) {
case FILE_STANDARD_INFORMATION:
- get_standard_info_pipe(rsp, rsp_org);
+ get_standard_info_pipe(rsp);
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
- rsp, rsp_org);
+ le32_to_cpu(rsp->OutputBufferLength),
+ rsp);
break;
case FILE_INTERNAL_INFORMATION:
- get_internal_info_pipe(rsp, id, rsp_org);
+ get_internal_info_pipe(rsp, id);
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
- rsp, rsp_org);
+ le32_to_cpu(rsp->OutputBufferLength),
+ rsp);
break;
default:
ksmbd_debug(SMB, "smb2_info_file_pipe for %u not supported\n",
@@ -4698,6 +6407,11 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
ea_req = (struct smb2_ea_info_req *)((char *)req +
le16_to_cpu(req->InputBufferOffset));
+
+ if (le32_to_cpu(req->InputBufferLength) <
+ offsetof(struct smb2_ea_info_req, name) +
+ ea_req->EaNameLength)
+ return -EINVAL;
} else {
/* need to send all EAs, if no specific EA is requested*/
if (le32_to_cpu(req->Flags) & SL_RETURN_SINGLE_ENTRY)
@@ -4707,8 +6421,9 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
}
buf_free_len =
- smb2_calc_max_out_buf_len(work, 8,
- le32_to_cpu(req->OutputBufferLength));
+ smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_query_info_rsp, Buffer),
+ le32_to_cpu(req->OutputBufferLength));
if (buf_free_len < 0)
return -EINVAL;
@@ -4742,17 +6457,13 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
continue;
- if (!strncmp(&name[XATTR_USER_PREFIX_LEN], STREAM_PREFIX,
- STREAM_PREFIX_LEN))
- continue;
-
if (req->InputBufferLength &&
strncmp(&name[XATTR_USER_PREFIX_LEN], ea_req->name,
ea_req->EaNameLength))
continue;
- if (!strncmp(&name[XATTR_USER_PREFIX_LEN],
- DOS_ATTRIBUTE_PREFIX, DOS_ATTRIBUTE_PREFIX_LEN))
+ if (smb2_is_private_ea(&name[XATTR_USER_PREFIX_LEN],
+ name_len - XATTR_USER_PREFIX_LEN))
continue;
if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
@@ -4797,6 +6508,8 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
/* align next xattr entry at 4 byte bundary */
alignment_bytes = ((next_offset + 3) & ~3) - next_offset;
if (alignment_bytes) {
+ if (buf_free_len < alignment_bytes)
+ break;
memset(ptr, '\0', alignment_bytes);
ptr += alignment_bytes;
next_offset += alignment_bytes;
@@ -4839,7 +6552,7 @@ static void get_file_access_info(struct smb2_query_info_rsp *rsp,
static int get_file_basic_info(struct smb2_query_info_rsp *rsp,
struct ksmbd_file *fp, void *rsp_org)
{
- struct smb2_file_basic_info *basic_info;
+ struct file_basic_info *basic_info;
struct kstat stat;
u64 time;
int ret;
@@ -4855,21 +6568,44 @@ static int get_file_basic_info(struct smb2_query_info_rsp *rsp,
if (ret)
return ret;
- basic_info = (struct smb2_file_basic_info *)rsp->Buffer;
+ basic_info = (struct file_basic_info *)rsp->Buffer;
basic_info->CreationTime = cpu_to_le64(fp->create_time);
time = ksmbd_UnixTimeToNT(stat.atime);
basic_info->LastAccessTime = cpu_to_le64(time);
time = ksmbd_UnixTimeToNT(stat.mtime);
basic_info->LastWriteTime = cpu_to_le64(time);
- time = ksmbd_UnixTimeToNT(stat.ctime);
- basic_info->ChangeTime = cpu_to_le64(time);
+ basic_info->ChangeTime = cpu_to_le64(fp->change_time);
basic_info->Attributes = fp->f_ci->m_fattr;
- basic_info->Pad1 = 0;
+ basic_info->Pad = 0;
rsp->OutputBufferLength =
- cpu_to_le32(sizeof(struct smb2_file_basic_info));
+ cpu_to_le32(sizeof(struct file_basic_info));
return 0;
}
+static int get_file_allocation_stat(struct ksmbd_file *fp, struct kstat *stat)
+{
+ int ret;
+
+ /*
+ * Buffered writes can leave delayed allocation in a state where two
+ * consecutive queries report different block counts even when the
+ * second write only overwrites the first one. Complete writeback before
+ * reporting the filesystem allocation for an ordinary open.
+ */
+ if (!fp->allocation_size_set) {
+ ret = file_write_and_wait(fp->filp);
+ if (ret)
+ return ret;
+ }
+
+ ret = vfs_getattr(&fp->filp->f_path, stat, STATX_BASIC_STATS,
+ AT_STATX_SYNC_AS_STAT);
+ if (!ret && !fp->allocation_size_set)
+ fp->allocation_size = S_ISDIR(stat->mode) ? 0 : stat->blocks << 9;
+
+ return ret;
+}
+
static int get_file_standard_info(struct smb2_query_info_rsp *rsp,
struct ksmbd_file *fp, void *rsp_org)
{
@@ -4878,8 +6614,7 @@ static int get_file_standard_info(struct smb2_query_info_rsp *rsp,
struct kstat stat;
int ret;
- ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
- AT_STATX_SYNC_AS_STAT);
+ ret = get_file_allocation_stat(fp, &stat);
if (ret)
return ret;
@@ -4887,11 +6622,13 @@ static int get_file_standard_info(struct smb2_query_info_rsp *rsp,
delete_pending = ksmbd_inode_pending_delete(fp);
if (ksmbd_stream_fd(fp) == false) {
- sinfo->AllocationSize = cpu_to_le64(stat.blocks << 9);
+ sinfo->AllocationSize = cpu_to_le64(fp->allocation_size);
sinfo->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
} else {
- sinfo->AllocationSize = cpu_to_le64(fp->stream.size);
- sinfo->EndOfFile = cpu_to_le64(fp->stream.size);
+ loff_t seof = ksmbd_stream_eof(fp);
+
+ sinfo->AllocationSize = cpu_to_le64((u64)seof);
+ sinfo->EndOfFile = cpu_to_le64((u64)seof);
}
sinfo->NumberOfLinks = cpu_to_le32(get_nlink(&stat) - delete_pending);
sinfo->DeletePending = delete_pending;
@@ -4925,7 +6662,7 @@ static int get_file_all_info(struct ksmbd_work *work,
int conv_len;
char *filename;
u64 time;
- int ret;
+ int ret, buf_free_len, filename_len;
if (!(fp->daccess & FILE_READ_ATTRIBUTES_LE)) {
ksmbd_debug(SMB, "no right to read the attributes : 0x%x\n",
@@ -4937,8 +6674,16 @@ static int get_file_all_info(struct ksmbd_work *work,
if (IS_ERR(filename))
return PTR_ERR(filename);
- ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
- AT_STATX_SYNC_AS_STAT);
+ filename_len = strlen(filename);
+ buf_free_len = smb2_resp_buf_len(work,
+ offsetof(struct smb2_query_info_rsp, Buffer) +
+ offsetof(struct smb2_file_all_info, FileName));
+ if (buf_free_len < (filename_len + 1) * 2) {
+ kfree(filename);
+ return -EINVAL;
+ }
+
+ ret = get_file_allocation_stat(fp, &stat);
if (ret) {
kfree(filename);
return ret;
@@ -4953,17 +6698,17 @@ static int get_file_all_info(struct ksmbd_work *work,
file_info->LastAccessTime = cpu_to_le64(time);
time = ksmbd_UnixTimeToNT(stat.mtime);
file_info->LastWriteTime = cpu_to_le64(time);
- time = ksmbd_UnixTimeToNT(stat.ctime);
- file_info->ChangeTime = cpu_to_le64(time);
+ file_info->ChangeTime = cpu_to_le64(fp->change_time);
file_info->Attributes = fp->f_ci->m_fattr;
file_info->Pad1 = 0;
if (ksmbd_stream_fd(fp) == false) {
- file_info->AllocationSize =
- cpu_to_le64(stat.blocks << 9);
+ file_info->AllocationSize = cpu_to_le64(fp->allocation_size);
file_info->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
} else {
- file_info->AllocationSize = cpu_to_le64(fp->stream.size);
- file_info->EndOfFile = cpu_to_le64(fp->stream.size);
+ loff_t seof = ksmbd_stream_eof(fp);
+
+ file_info->AllocationSize = cpu_to_le64((u64)seof);
+ file_info->EndOfFile = cpu_to_le64((u64)seof);
}
file_info->NumberOfLinks =
cpu_to_le32(get_nlink(&stat) - delete_pending);
@@ -4980,7 +6725,8 @@ static int get_file_all_info(struct ksmbd_work *work,
file_info->Mode = fp->coption;
file_info->AlignmentRequirement = 0;
conv_len = smbConvertToUTF16((__le16 *)file_info->FileName, filename,
- PATH_MAX, conn->local_nls, 0);
+ min(filename_len, PATH_MAX),
+ conn->local_nls, 0);
conv_len *= 2;
file_info->FileNameLength = cpu_to_le32(conv_len);
rsp->OutputBufferLength =
@@ -5010,6 +6756,88 @@ static void get_file_alternate_info(struct ksmbd_work *work,
cpu_to_le32(struct_size(file_info, FileName, conv_len));
}
+static char *smb2_get_normalized_stream_name(struct ksmbd_file *fp)
+{
+ char *name, *stream_name = NULL, *xattr_list = NULL;
+ ssize_t xattr_list_len;
+
+ if (!ksmbd_stream_fd(fp))
+ return NULL;
+
+ xattr_list_len = ksmbd_vfs_listxattr(fp->filp->f_path.dentry,
+ &xattr_list);
+ if (xattr_list_len <= 0)
+ goto out;
+
+ for (name = xattr_list; name - xattr_list < xattr_list_len;
+ name += strlen(name) + 1) {
+ char *type;
+
+ if (strlen(name) + 1 != fp->stream.size ||
+ strncasecmp(name, fp->stream.name, fp->stream.size - 1))
+ continue;
+
+ name += XATTR_NAME_STREAM_LEN;
+ type = strrchr(name, ':');
+ if (type)
+ stream_name = kstrndup(name, type - name,
+ KSMBD_DEFAULT_GFP);
+ break;
+ }
+out:
+ kvfree(xattr_list);
+ return stream_name;
+}
+
+static int get_file_normalized_name_info(struct ksmbd_work *work,
+ struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp)
+{
+ struct smb2_file_alt_name_info *file_info;
+ char *filename, *normalized, *stream_name;
+ int buf_free_len, conv_len, filename_len;
+
+ if (work->conn->dialect < SMB311_PROT_ID) {
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ return -EOPNOTSUPP;
+ }
+
+ filename = convert_to_nt_pathname(work->tcon->share_conf,
+ &fp->filp->f_path);
+ if (IS_ERR(filename))
+ return PTR_ERR(filename);
+ if (filename[0] == '\\')
+ memmove(filename, filename + 1, strlen(filename));
+
+ stream_name = smb2_get_normalized_stream_name(fp);
+ normalized = kasprintf(KSMBD_DEFAULT_GFP, "%s%s%s", filename,
+ stream_name ? ":" : "",
+ stream_name ? stream_name : "");
+ kfree(stream_name);
+ kfree(filename);
+ if (!normalized)
+ return -ENOMEM;
+
+ filename_len = strlen(normalized);
+ buf_free_len = smb2_resp_buf_len(work, sizeof(*rsp) +
+ sizeof(*file_info));
+ if (buf_free_len < 0 ||
+ (size_t)buf_free_len < (filename_len + 1) * sizeof(__le16)) {
+ kfree(normalized);
+ return -EINVAL;
+ }
+
+ file_info = (struct smb2_file_alt_name_info *)rsp->Buffer;
+ conv_len = smbConvertToUTF16((__le16 *)file_info->FileName,
+ normalized, filename_len,
+ work->conn->local_nls, 0);
+ kfree(normalized);
+ conv_len *= 2;
+ file_info->FileNameLength = cpu_to_le32(conv_len);
+ rsp->OutputBufferLength = cpu_to_le32(sizeof(*file_info) + conv_len);
+ return 0;
+}
+
static int get_file_stream_info(struct ksmbd_work *work,
struct smb2_query_info_rsp *rsp,
struct ksmbd_file *fp,
@@ -5021,9 +6849,10 @@ static int get_file_stream_info(struct ksmbd_work *work,
struct kstat stat;
const struct path *path = &fp->filp->f_path;
ssize_t xattr_list_len;
+ ssize_t slen;
+ loff_t ssize;
int nbytes = 0, streamlen, stream_name_len, next, idx = 0;
int buf_free_len;
- struct smb2_query_info_req *req = ksmbd_req_buf_next(work);
int ret;
ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
@@ -5033,9 +6862,8 @@ static int get_file_stream_info(struct ksmbd_work *work,
file_info = (struct smb2_file_stream_info *)rsp->Buffer;
- buf_free_len =
- smb2_calc_max_out_buf_len(work, 8,
- le32_to_cpu(req->OutputBufferLength));
+ buf_free_len = smb2_resp_buf_len(work,
+ offsetof(struct smb2_query_info_rsp, Buffer));
if (buf_free_len < 0)
goto out;
@@ -5084,8 +6912,20 @@ static int get_file_stream_info(struct ksmbd_work *work,
streamlen *= 2;
kfree(stream_buf);
file_info->StreamNameLength = cpu_to_le32(streamlen);
- file_info->StreamSize = cpu_to_le64(stream_name_len);
- file_info->StreamAllocationSize = cpu_to_le64(stream_name_len);
+ /*
+ * stream_name_len is the byte length of the xattr's *name*,
+ * not its value -- same class of bug ksmbd_stream_eof()
+ * (smb2pdu.c) already fixes for EndOfFile/AllocationSize on
+ * a stream handle; this enumeration path needs the same
+ * real xattr value length, not the name length reused as a
+ * size.
+ */
+ slen = ksmbd_vfs_casexattr_len(file_mnt_idmap(fp->filp),
+ path->dentry, stream_name,
+ strlen(stream_name) + 1);
+ ssize = slen < 0 ? 0 : (loff_t)slen;
+ file_info->StreamSize = cpu_to_le64(ssize);
+ file_info->StreamAllocationSize = cpu_to_le64(ssize);
nbytes += next;
buf_free_len -= next;
@@ -5149,8 +6989,7 @@ static int get_file_network_open_info(struct smb2_query_info_rsp *rsp,
return -EACCES;
}
- ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
- AT_STATX_SYNC_AS_STAT);
+ ret = get_file_allocation_stat(fp, &stat);
if (ret)
return ret;
@@ -5161,15 +7000,16 @@ static int get_file_network_open_info(struct smb2_query_info_rsp *rsp,
file_info->LastAccessTime = cpu_to_le64(time);
time = ksmbd_UnixTimeToNT(stat.mtime);
file_info->LastWriteTime = cpu_to_le64(time);
- time = ksmbd_UnixTimeToNT(stat.ctime);
- file_info->ChangeTime = cpu_to_le64(time);
+ file_info->ChangeTime = cpu_to_le64(fp->change_time);
file_info->Attributes = fp->f_ci->m_fattr;
if (ksmbd_stream_fd(fp) == false) {
- file_info->AllocationSize = cpu_to_le64(stat.blocks << 9);
+ file_info->AllocationSize = cpu_to_le64(fp->allocation_size);
file_info->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
} else {
- file_info->AllocationSize = cpu_to_le64(fp->stream.size);
- file_info->EndOfFile = cpu_to_le64(fp->stream.size);
+ loff_t seof = ksmbd_stream_eof(fp);
+
+ file_info->AllocationSize = cpu_to_le64((u64)seof);
+ file_info->EndOfFile = cpu_to_le64((u64)seof);
}
file_info->Reserved = cpu_to_le32(0);
rsp->OutputBufferLength =
@@ -5218,6 +7058,7 @@ static int get_file_compression_info(struct smb2_query_info_rsp *rsp,
{
struct smb2_file_comp_info *file_info;
struct kstat stat;
+ u16 fmt;
int ret;
ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
@@ -5225,9 +7066,13 @@ static int get_file_compression_info(struct smb2_query_info_rsp *rsp,
if (ret)
return ret;
+ ret = ksmbd_vfs_get_compression(fp, &fmt);
+ if (ret)
+ return ret;
+
file_info = (struct smb2_file_comp_info *)rsp->Buffer;
- file_info->CompressedFileSize = cpu_to_le64(stat.blocks << 9);
- file_info->CompressionFormat = COMPRESSION_FORMAT_NONE;
+ file_info->CompressedFileSize = cpu_to_le64(min_t(u64, stat.blocks << 9, stat.size));
+ file_info->CompressionFormat = cpu_to_le16(fmt);
file_info->CompressionUnitShift = 0;
file_info->ChunkShift = 0;
file_info->ClusterShift = 0;
@@ -5271,8 +7116,13 @@ static int find_file_posix_info(struct smb2_query_info_rsp *rsp,
int out_buf_len = sizeof(struct smb311_posix_qinfo) + 32;
int ret;
- ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
- AT_STATX_SYNC_AS_STAT);
+ if (!(fp->daccess & FILE_READ_ATTRIBUTES_LE)) {
+ pr_err("no right to read the attributes : 0x%x\n",
+ fp->daccess);
+ return -EACCES;
+ }
+
+ ret = get_file_allocation_stat(fp, &stat);
if (ret)
return ret;
@@ -5282,16 +7132,17 @@ static int find_file_posix_info(struct smb2_query_info_rsp *rsp,
file_info->LastAccessTime = cpu_to_le64(time);
time = ksmbd_UnixTimeToNT(stat.mtime);
file_info->LastWriteTime = cpu_to_le64(time);
- time = ksmbd_UnixTimeToNT(stat.ctime);
- file_info->ChangeTime = cpu_to_le64(time);
+ file_info->ChangeTime = cpu_to_le64(fp->change_time);
file_info->DosAttributes = fp->f_ci->m_fattr;
file_info->Inode = cpu_to_le64(stat.ino);
if (ksmbd_stream_fd(fp) == false) {
file_info->EndOfFile = cpu_to_le64(stat.size);
- file_info->AllocationSize = cpu_to_le64(stat.blocks << 9);
+ file_info->AllocationSize = cpu_to_le64(fp->allocation_size);
} else {
- file_info->EndOfFile = cpu_to_le64(fp->stream.size);
- file_info->AllocationSize = cpu_to_le64(fp->stream.size);
+ loff_t seof = ksmbd_stream_eof(fp);
+
+ file_info->EndOfFile = cpu_to_le64((u64)seof);
+ file_info->AllocationSize = cpu_to_le64((u64)seof);
}
file_info->HardLinks = cpu_to_le32(stat.nlink);
file_info->Mode = cpu_to_le32(stat.mode & 0777);
@@ -5339,13 +7190,14 @@ static int smb2_get_info_file(struct ksmbd_work *work,
struct ksmbd_file *fp;
int fileinfoclass = 0;
int rc = 0;
+ unsigned int fixed_len;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
if (test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_PIPE)) {
/* smb2 info file called for pipe */
- return smb2_get_info_file_pipe(work->sess, req, rsp,
- work->response_buf);
+ rc = smb2_get_info_file_pipe(work->sess, req, rsp);
+ goto iov_pin_out;
}
if (work->next_smb2_rcv_hdr_off) {
@@ -5392,6 +7244,9 @@ static int smb2_get_info_file(struct ksmbd_work *work,
case FILE_ALTERNATE_NAME_INFORMATION:
get_file_alternate_info(work, rsp, fp, work->response_buf);
break;
+ case FILE_NORMALIZED_NAME_INFORMATION:
+ rc = get_file_normalized_name_info(work, rsp, fp);
+ break;
case FILE_STREAM_INFORMATION:
rc = get_file_stream_info(work, rsp, fp, work->response_buf);
@@ -5441,10 +7296,33 @@ static int smb2_get_info_file(struct ksmbd_work *work,
fileinfoclass);
rc = -EOPNOTSUPP;
}
- if (!rc)
+ if (!rc) {
+ fixed_len = le32_to_cpu(rsp->OutputBufferLength);
+ switch (fileinfoclass) {
+ case FILE_ALL_INFORMATION:
+ fixed_len = FILE_ALL_INFORMATION_SIZE;
+ break;
+ case FILE_ALTERNATE_NAME_INFORMATION:
+ fixed_len = FILE_ALTERNATE_NAME_INFORMATION_SIZE;
+ break;
+ case FILE_NORMALIZED_NAME_INFORMATION:
+ fixed_len = FILE_NORMALIZED_NAME_INFORMATION_SIZE;
+ break;
+ case FILE_STREAM_INFORMATION:
+ fixed_len = FILE_STREAM_INFORMATION_SIZE;
+ break;
+ }
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
- rsp, work->response_buf);
+ fixed_len,
+ rsp);
+ }
ksmbd_fd_put(work, fp);
+
+iov_pin_out:
+ if (!rc)
+ rc = ksmbd_iov_pin_rsp(work, (void *)rsp,
+ offsetof(struct smb2_query_info_rsp, Buffer) +
+ le32_to_cpu(rsp->OutputBufferLength));
return rc;
}
@@ -5452,18 +7330,19 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
struct smb2_query_info_req *req,
struct smb2_query_info_rsp *rsp)
{
- struct ksmbd_session *sess = work->sess;
struct ksmbd_conn *conn = work->conn;
struct ksmbd_share_config *share = work->tcon->share_conf;
int fsinfoclass = 0;
struct kstatfs stfs;
struct path path;
int rc = 0, len;
+ unsigned int fixed_len = 0;
if (!share->path)
return -EIO;
- rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path);
+ scoped_with_init_fs()
+ rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path);
if (rc) {
pr_err("cannot create vfs path\n");
return -EIO;
@@ -5493,21 +7372,41 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
info->DeviceCharacteristics |=
cpu_to_le32(FILE_READ_ONLY_DEVICE);
rsp->OutputBufferLength = cpu_to_le32(8);
+ fixed_len = 8;
break;
}
case FS_ATTRIBUTE_INFORMATION:
{
FILE_SYSTEM_ATTRIBUTE_INFO *info;
+ struct file_kattr fa = {};
size_t sz;
+ u32 attrs;
+ int err;
info = (FILE_SYSTEM_ATTRIBUTE_INFO *)rsp->Buffer;
- info->Attributes = cpu_to_le32(FILE_SUPPORTS_OBJECT_IDS |
- FILE_PERSISTENT_ACLS |
- FILE_UNICODE_ON_DISK |
- FILE_CASE_PRESERVED_NAMES |
- FILE_CASE_SENSITIVE_SEARCH |
- FILE_SUPPORTS_BLOCK_REFCOUNTING);
+ attrs = FILE_SUPPORTS_OBJECT_IDS |
+ FILE_PERSISTENT_ACLS |
+ FILE_UNICODE_ON_DISK |
+ FILE_FILE_COMPRESSION |
+ FILE_SUPPORTS_SPARSE_FILES |
+ FILE_SUPPORTS_BLOCK_REFCOUNTING;
+
+ err = vfs_fileattr_get(path.dentry, &fa);
+ /*
+ * -EINVAL, -EOPNOTSUPP: ntfs-3g and other FUSE
+ * filesystems that lack FS_IOC_FSGETXATTR support.
+ */
+ if (err && err != -ENOIOCTLCMD && err != -ENOTTY &&
+ err != -EINVAL && err != -EOPNOTSUPP) {
+ path_put(&path);
+ return err;
+ }
+ if (!(fa.fsx_xflags & FS_XFLAG_CASEFOLD))
+ attrs |= FILE_CASE_SENSITIVE_SEARCH;
+ if (!(fa.fsx_xflags & FS_XFLAG_CASENONPRESERVING))
+ attrs |= FILE_CASE_PRESERVED_NAMES;
+ info->Attributes = cpu_to_le32(attrs);
info->Attributes |= cpu_to_le32(server_conf.share_fake_fscaps);
if (test_share_config_flag(work->tcon->share_conf,
@@ -5528,6 +7427,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
info->FileSystemNameLen = cpu_to_le32(len);
sz = sizeof(FILE_SYSTEM_ATTRIBUTE_INFO) + len;
rsp->OutputBufferLength = cpu_to_le32(sz);
+ fixed_len = 16;
break;
}
case FS_VOLUME_INFORMATION:
@@ -5545,15 +7445,17 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
serial_crc = crc32_le(serial_crc, ksmbd_netbios_name(),
strlen(ksmbd_netbios_name()));
/* Taking dummy value of serial number*/
- info->SerialNumber = cpu_to_le32(serial_crc);
+ info->VolumeSerialNumber = cpu_to_le32(serial_crc);
len = smbConvertToUTF16((__le16 *)info->VolumeLabel,
share->name, PATH_MAX,
conn->local_nls, 0);
len = len * 2;
- info->VolumeLabelSize = cpu_to_le32(len);
+ info->VolumeLabelLength = cpu_to_le32(len);
info->Reserved = 0;
+ info->SupportsObjects = 0;
sz = sizeof(struct filesystem_vol_info) + len;
rsp->OutputBufferLength = cpu_to_le32(sz);
+ fixed_len = 24;
break;
}
case FS_SIZE_INFORMATION:
@@ -5566,6 +7468,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
info->SectorsPerAllocationUnit = cpu_to_le32(1);
info->BytesPerSector = cpu_to_le32(stfs.f_bsize);
rsp->OutputBufferLength = cpu_to_le32(24);
+ fixed_len = 24;
break;
}
case FS_FULL_SIZE_INFORMATION:
@@ -5581,6 +7484,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
info->SectorsPerAllocationUnit = cpu_to_le32(1);
info->BytesPerSector = cpu_to_le32(stfs.f_bsize);
rsp->OutputBufferLength = cpu_to_le32(32);
+ fixed_len = 32;
break;
}
case FS_OBJECT_ID_INFORMATION:
@@ -5588,11 +7492,13 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
struct object_id_info *info;
info = (struct object_id_info *)(rsp->Buffer);
+ memset(info, 0, sizeof(*info));
- if (!user_guest(sess->user))
- memcpy(info->objid, user_passkey(sess->user), 16);
+ if (path.mnt->mnt_sb->s_uuid_len == 16)
+ memcpy(info->objid, path.mnt->mnt_sb->s_uuid.b,
+ path.mnt->mnt_sb->s_uuid_len);
else
- memset(info->objid, 0, 16);
+ memcpy(info->objid, &stfs.f_fsid, sizeof(stfs.f_fsid));
info->extended_info.magic = cpu_to_le32(EXTENDED_INFO_MAGIC);
info->extended_info.version = cpu_to_le32(1);
@@ -5600,6 +7506,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
info->extended_info.rel_date = 0;
memcpy(info->extended_info.version_string, "1.1.0", strlen("1.1.0"));
rsp->OutputBufferLength = cpu_to_le32(64);
+ fixed_len = 64;
break;
}
case FS_SECTOR_SIZE_INFORMATION:
@@ -5617,10 +7524,12 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
info->FSEffPhysicalBytesPerSectorForAtomicity =
cpu_to_le32(sector_size);
info->Flags = cpu_to_le32(SSINFO_FLAGS_ALIGNED_DEVICE |
- SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE);
+ SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE |
+ SSINFO_FLAGS_TRIM_ENABLED);
info->ByteOffsetForSectorAlignment = 0;
info->ByteOffsetForPartitionAlignment = 0;
rsp->OutputBufferLength = cpu_to_le32(28);
+ fixed_len = 28;
break;
}
case FS_CONTROL_INFORMATION:
@@ -5639,8 +7548,10 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
info->FreeSpaceStopFiltering = 0;
info->DefaultQuotaThreshold = cpu_to_le64(SMB2_NO_FID);
info->DefaultQuotaLimit = cpu_to_le64(SMB2_NO_FID);
+ info->FileSystemControlFlags = 0;
info->Padding = 0;
rsp->OutputBufferLength = cpu_to_le32(48);
+ fixed_len = 48;
break;
}
case FS_POSIX_INFORMATION:
@@ -5660,7 +7571,11 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
info->UserBlocksAvail = cpu_to_le64(stfs.f_bavail);
info->TotalFileNodes = cpu_to_le64(stfs.f_files);
info->FreeFileNodes = cpu_to_le64(stfs.f_ffree);
+ info->FileSysIdentifier =
+ cpu_to_le64((u64)(u32)stfs.f_fsid.val[1] << 32 |
+ (u32)stfs.f_fsid.val[0]);
rsp->OutputBufferLength = cpu_to_le32(56);
+ fixed_len = 56;
}
break;
}
@@ -5669,8 +7584,14 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
return -EOPNOTSUPP;
}
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
- rsp, work->response_buf);
+ fixed_len,
+ rsp);
path_put(&path);
+
+ if (!rc)
+ rc = ksmbd_iov_pin_rsp(work, (void *)rsp,
+ offsetof(struct smb2_query_info_rsp, Buffer) +
+ le32_to_cpu(rsp->OutputBufferLength));
return rc;
}
@@ -5680,13 +7601,14 @@ static int smb2_get_info_sec(struct ksmbd_work *work,
{
struct ksmbd_file *fp;
struct mnt_idmap *idmap;
- struct smb_ntsd *pntsd = (struct smb_ntsd *)rsp->Buffer, *ppntsd = NULL;
+ struct smb_ntsd *pntsd = NULL, *ppntsd = NULL;
struct smb_fattr fattr = {{0}};
struct inode *inode;
__u32 secdesclen = 0;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
int addition_info = le32_to_cpu(req->AdditionalInformation);
- int rc = 0, ppntsd_size = 0;
+ int rc = 0, ppntsd_size = 0, max_len;
+ size_t scratch_len = 0;
if (addition_info & ~(OWNER_SECINFO | GROUP_SECINFO | DACL_SECINFO |
PROTECTED_DACL_SECINFO |
@@ -5694,17 +7616,8 @@ static int smb2_get_info_sec(struct ksmbd_work *work,
ksmbd_debug(SMB, "Unsupported addition info: 0x%x)\n",
addition_info);
- pntsd->revision = cpu_to_le16(1);
- pntsd->type = cpu_to_le16(SELF_RELATIVE | DACL_PROTECTED);
- pntsd->osidoffset = 0;
- pntsd->gsidoffset = 0;
- pntsd->sacloffset = 0;
- pntsd->dacloffset = 0;
-
- secdesclen = sizeof(struct smb_ntsd);
- rsp->OutputBufferLength = cpu_to_le32(secdesclen);
-
- return 0;
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ return -EINVAL;
}
if (work->next_smb2_rcv_hdr_off) {
@@ -5725,6 +7638,18 @@ static int smb2_get_info_sec(struct ksmbd_work *work,
if (!fp)
return -ENOENT;
+ if (addition_info & (OWNER_SECINFO | GROUP_SECINFO | DACL_SECINFO) &&
+ !(fp->daccess & FILE_READ_CONTROL_LE)) {
+ ksmbd_fd_put(work, fp);
+ return -EACCES;
+ }
+
+ if (le32_to_cpu(req->OutputBufferLength) < sizeof(struct smb_ntsd)) {
+ rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL;
+ ksmbd_fd_put(work, fp);
+ return -ENOSPC;
+ }
+
idmap = file_mnt_idmap(fp->filp);
inode = file_inode(fp->filp);
ksmbd_acls_fattr(&fattr, idmap, inode);
@@ -5736,18 +7661,58 @@ static int smb2_get_info_sec(struct ksmbd_work *work,
&ppntsd);
/* Check if sd buffer size exceeds response buffer size */
- if (smb2_resp_buf_len(work, 8) > ppntsd_size)
- rc = build_sec_desc(idmap, pntsd, ppntsd, ppntsd_size,
- addition_info, &secdesclen, &fattr);
+ max_len = smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_query_info_rsp, Buffer),
+ le32_to_cpu(req->OutputBufferLength));
+ if (max_len < 0) {
+ rc = -EINVAL;
+ goto release_acl;
+ }
+
+ scratch_len = smb_acl_sec_desc_scratch_len(&fattr, ppntsd,
+ ppntsd_size, addition_info);
+ if (!scratch_len || scratch_len == SIZE_MAX) {
+ rc = -EFBIG;
+ goto release_acl;
+ }
+
+ pntsd = kvzalloc(scratch_len, KSMBD_DEFAULT_GFP);
+ if (!pntsd) {
+ rc = -ENOMEM;
+ goto release_acl;
+ }
+
+ rc = build_sec_desc(idmap, pntsd, ppntsd, ppntsd_size,
+ addition_info, &secdesclen, &fattr);
+
+release_acl:
posix_acl_release(fattr.cf_acls);
posix_acl_release(fattr.cf_dacls);
kfree(ppntsd);
ksmbd_fd_put(work, fp);
+
+ if (!rc && ALIGN(secdesclen, 8) > scratch_len)
+ rc = -EFBIG;
if (rc)
- return rc;
+ goto err_out;
rsp->OutputBufferLength = cpu_to_le32(secdesclen);
- return 0;
+ rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
+ le32_to_cpu(rsp->OutputBufferLength),
+ rsp);
+ if (rc)
+ goto err_out;
+
+ rc = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
+ offsetof(struct smb2_query_info_rsp, Buffer),
+ pntsd, secdesclen);
+err_out:
+ if (rc) {
+ rsp->OutputBufferLength = 0;
+ kvfree(pntsd);
+ }
+
+ return rc;
}
/**
@@ -5766,11 +7731,17 @@ int smb2_query_info(struct ksmbd_work *work)
WORK_BUFFERS(work, req, rsp);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
+
if (ksmbd_override_fsids(work)) {
rc = -ENOMEM;
goto err_out;
}
+ rsp->StructureSize = cpu_to_le16(9);
+ rsp->OutputBufferOffset = cpu_to_le16(72);
+
switch (req->InfoType) {
case SMB2_O_INFO_FILE:
ksmbd_debug(SMB, "GOT SMB2_O_INFO_FILE\n");
@@ -5791,14 +7762,6 @@ int smb2_query_info(struct ksmbd_work *work)
}
ksmbd_revert_fsids(work);
- if (!rc) {
- rsp->StructureSize = cpu_to_le16(9);
- rsp->OutputBufferOffset = cpu_to_le16(72);
- rc = ksmbd_iov_pin_rsp(work, (void *)rsp,
- offsetof(struct smb2_query_info_rsp, Buffer) +
- le32_to_cpu(rsp->OutputBufferLength));
- }
-
err_out:
if (rc < 0) {
if (rc == -EACCES)
@@ -5809,7 +7772,9 @@ err_out:
rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
else if (rc == -ENOMEM)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
- else if (rc == -EOPNOTSUPP || rsp->hdr.Status == 0)
+ else if (rc == -EINVAL && rsp->hdr.Status == 0)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else if (rsp->hdr.Status == 0)
rsp->hdr.Status = STATUS_INVALID_INFO_CLASS;
smb2_set_err_rsp(work);
@@ -5864,7 +7829,6 @@ int smb2_close(struct ksmbd_work *work)
u64 sess_id;
struct smb2_close_req *req;
struct smb2_close_rsp *rsp;
- struct ksmbd_conn *conn = work->conn;
struct ksmbd_file *fp;
u64 time;
int err = 0;
@@ -5873,6 +7837,9 @@ int smb2_close(struct ksmbd_work *work)
WORK_BUFFERS(work, req, rsp);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
+
if (test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_PIPE)) {
ksmbd_debug(SMB, "IPC pipe close request\n");
@@ -5884,7 +7851,7 @@ int smb2_close(struct ksmbd_work *work)
sess_id = work->compound_sid;
work->compound_sid = 0;
- if (check_session_id(conn, sess_id)) {
+ if (work->sess && work->sess->id == sess_id) {
work->compound_sid = sess_id;
} else {
rsp->hdr.Status = STATUS_USER_SESSION_DELETED;
@@ -5939,17 +7906,18 @@ int smb2_close(struct ksmbd_work *work)
}
rsp->Flags = SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB;
- rsp->AllocationSize = S_ISDIR(stat.mode) ? 0 :
- cpu_to_le64(stat.blocks << 9);
+ rsp->AllocationSize = cpu_to_le64(fp->allocation_size);
rsp->EndOfFile = cpu_to_le64(stat.size);
rsp->Attributes = fp->f_ci->m_fattr;
rsp->CreationTime = cpu_to_le64(fp->create_time);
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);
- time = ksmbd_UnixTimeToNT(stat.ctime);
- rsp->ChangeTime = cpu_to_le64(time);
+ rsp->ChangeTime = cpu_to_le64(fp->change_time);
ksmbd_fd_put(work, fp);
} else {
rsp->Flags = 0;
@@ -6045,9 +8013,8 @@ static int smb2_rename(struct ksmbd_work *work,
pr_err("failed to store stream name in xattr: %d\n",
rc);
rc = -EINVAL;
- goto out;
}
-
+ kfree(xattr_stream_name);
goto out;
}
@@ -6061,9 +8028,12 @@ static int smb2_rename(struct ksmbd_work *work,
if (!file_info->ReplaceIfExists)
flags = RENAME_NOREPLACE;
- rc = ksmbd_vfs_rename(work, &fp->filp->f_path, new_name, flags);
- if (!rc)
- smb_break_all_levII_oplock(work, fp, 0);
+ rc = ksmbd_vfs_check_rename_share(work, &fp->filp->f_path);
+ if (rc)
+ goto out;
+
+ smb_break_all_levII_oplock_rename(work, fp);
+ rc = ksmbd_vfs_rename(work, fp, new_name, flags);
out:
kfree(new_name);
return rc;
@@ -6137,13 +8107,15 @@ out:
}
static int set_file_basic_info(struct ksmbd_file *fp,
- struct smb2_file_basic_info *file_info,
+ struct file_basic_info *file_info,
struct ksmbd_share_config *share)
{
struct iattr attrs;
struct file *filp;
struct inode *inode;
struct mnt_idmap *idmap;
+ __le32 attrs_mask = FILE_ATTRIBUTE_DIRECTORY_LE |
+ FILE_ATTRIBUTE_COMPRESSED_LE;
int rc = 0;
if (!(fp->daccess & FILE_WRITE_ATTRIBUTES_LE))
@@ -6162,9 +8134,11 @@ static int set_file_basic_info(struct ksmbd_file *fp,
attrs.ia_valid |= (ATTR_ATIME | ATTR_ATIME_SET);
}
- if (file_info->ChangeTime)
+ if (file_info->ChangeTime) {
+ fp->change_time = le64_to_cpu(file_info->ChangeTime);
inode_set_ctime_to_ts(inode,
ksmbd_NTtimeToUnix(file_info->ChangeTime));
+ }
if (file_info->LastWriteTime) {
attrs.ia_mtime = ksmbd_NTtimeToUnix(file_info->LastWriteTime);
@@ -6179,8 +8153,9 @@ static int set_file_basic_info(struct ksmbd_file *fp,
}
if (!(S_ISDIR(inode->i_mode) && file_info->Attributes == FILE_ATTRIBUTE_NORMAL_LE))
- fp->f_ci->m_fattr = file_info->Attributes |
- (fp->f_ci->m_fattr & FILE_ATTRIBUTE_DIRECTORY_LE);
+ fp->f_ci->m_fattr =
+ (file_info->Attributes & ~FILE_ATTRIBUTE_COMPRESSED_LE) |
+ (fp->f_ci->m_fattr & attrs_mask);
}
if (test_share_config_flag(share, KSMBD_SHARE_FLAG_STORE_DOS_ATTRS) &&
@@ -6227,6 +8202,7 @@ static int set_file_allocation_info(struct ksmbd_work *work,
*/
loff_t alloc_blks;
+ u64 alloc_size;
struct inode *inode;
struct kstat stat;
int rc;
@@ -6242,7 +8218,19 @@ static int set_file_allocation_info(struct ksmbd_work *work,
if (rc)
return rc;
- alloc_blks = (le64_to_cpu(file_alloc_info->AllocationSize) + 511) >> 9;
+ /*
+ * AllocationSize is fully client-controlled (the caller only
+ * validates the fixed 8-byte buffer length). Reject values that
+ * would overflow the "round up to 512-byte blocks" conversion
+ * below instead of silently wrapping it to a tiny block count,
+ * which would truncate the file to a size the client never
+ * asked for.
+ */
+ alloc_size = le64_to_cpu(file_alloc_info->AllocationSize);
+ if (alloc_size > MAX_LFS_FILESIZE - 511)
+ return -EINVAL;
+
+ alloc_blks = (alloc_size + 511) >> 9;
inode = file_inode(fp->filp);
if (alloc_blks > stat.blocks) {
@@ -6272,6 +8260,9 @@ static int set_file_allocation_info(struct ksmbd_work *work,
if (size < alloc_blks * 512)
i_size_write(inode, size);
}
+
+ fp->allocation_size = le64_to_cpu(file_alloc_info->AllocationSize);
+ fp->allocation_size_set = true;
return 0;
}
@@ -6328,7 +8319,8 @@ static int set_rename_info(struct ksmbd_work *work, struct ksmbd_file *fp,
return smb2_rename(work, fp, rename_info, work->conn->local_nls);
}
-static int set_file_disposition_info(struct ksmbd_file *fp,
+static int set_file_disposition_info(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
struct smb2_file_disposition_info *file_info)
{
struct inode *inode;
@@ -6338,14 +8330,21 @@ static int set_file_disposition_info(struct ksmbd_file *fp,
return -EACCES;
}
+ if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_READONLY_LE)
+ return -EACCES;
+
inode = file_inode(fp->filp);
if (file_info->DeletePending) {
- if (S_ISDIR(inode->i_mode) &&
+ if (ksmbd_has_stream_without_delete_share(fp))
+ return -ESHARE;
+
+ if (S_ISDIR(inode->i_mode) && !ksmbd_stream_fd(fp) &&
ksmbd_vfs_empty_dir(fp) == -ENOTEMPTY)
return -EBUSY;
- ksmbd_set_inode_pending_delete(fp);
+ smb_break_all_levII_oplock_for_delete(work, fp);
+ ksmbd_fd_set_delete_pending(fp);
} else {
- ksmbd_clear_inode_pending_delete(fp);
+ ksmbd_fd_clear_delete_pending(fp);
}
return 0;
}
@@ -6362,7 +8361,7 @@ static int set_file_position_info(struct ksmbd_file *fp,
sector_size = inode->i_sb->s_blocksize;
if (current_byte_offset < 0 ||
- (fp->coption == FILE_NO_INTERMEDIATE_BUFFERING_LE &&
+ (fp->coption & FILE_NO_INTERMEDIATE_BUFFERING_LE &&
current_byte_offset & (sector_size - 1))) {
pr_err("CurrentByteOffset is not valid : %llu\n",
current_byte_offset);
@@ -6419,10 +8418,10 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
switch (req->FileInfoClass) {
case FILE_BASIC_INFORMATION:
{
- if (buf_len < sizeof(struct smb2_file_basic_info))
+ if (buf_len < sizeof(struct file_basic_info))
return -EMSGSIZE;
- return set_file_basic_info(fp, (struct smb2_file_basic_info *)buffer, share);
+ return set_file_basic_info(fp, (struct file_basic_info *)buffer, share);
}
case FILE_ALLOCATION_INFORMATION:
{
@@ -6451,11 +8450,18 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
}
case FILE_LINK_INFORMATION:
{
+ 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);
}
@@ -6464,7 +8470,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
if (buf_len < sizeof(struct smb2_file_disposition_info))
return -EMSGSIZE;
- return set_file_disposition_info(fp,
+ return set_file_disposition_info(work, fp,
(struct smb2_file_disposition_info *)buffer);
}
case FILE_FULL_EA_INFORMATION:
@@ -6508,6 +8514,9 @@ static int smb2_set_info_sec(struct ksmbd_file *fp, int addition_info,
fp->saccess |= FILE_SHARE_DELETE_LE;
+ if (!(fp->daccess & (FILE_WRITE_DAC_LE | FILE_WRITE_OWNER_LE)))
+ return -EACCES;
+
return set_info_sec(fp->conn, fp->tcon, &fp->filp->f_path, pntsd,
buf_len, false, true);
}
@@ -6520,10 +8529,12 @@ static int smb2_set_info_sec(struct ksmbd_file *fp, int addition_info,
*/
int smb2_set_info(struct ksmbd_work *work)
{
+ const struct cred *saved_cred;
struct smb2_set_info_req *req;
struct smb2_set_info_rsp *rsp;
struct ksmbd_file *fp = NULL;
int rc = 0;
+ bool chseq_err = false;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
ksmbd_debug(SMB, "Received smb2 set info request\n");
@@ -6531,6 +8542,8 @@ int smb2_set_info(struct ksmbd_work *work)
if (work->next_smb2_rcv_hdr_off) {
req = ksmbd_req_buf_next(work);
rsp = ksmbd_resp_buf_next(work);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
if (!has_file_id(req->VolatileFileId)) {
ksmbd_debug(SMB, "Compound request set FID = %llu\n",
work->compound_fid);
@@ -6561,6 +8574,14 @@ int smb2_set_info(struct ksmbd_work *work)
goto err_out;
}
+ rc = smb2_set_request_open(work, fp, &req->hdr, true, false);
+ if (rc) {
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ chseq_err = true;
+ goto err_out;
+ }
+
+ saved_cred = override_creds(fp->filp->f_cred);
switch (req->InfoType) {
case SMB2_O_INFO_FILE:
ksmbd_debug(SMB, "GOT SMB2_O_INFO_FILE\n");
@@ -6568,19 +8589,15 @@ int smb2_set_info(struct ksmbd_work *work)
break;
case SMB2_O_INFO_SECURITY:
ksmbd_debug(SMB, "GOT SMB2_O_INFO_SECURITY\n");
- if (ksmbd_override_fsids(work)) {
- rc = -ENOMEM;
- goto err_out;
- }
rc = smb2_set_info_sec(fp,
le32_to_cpu(req->AdditionalInformation),
(char *)req + le16_to_cpu(req->BufferOffset),
le32_to_cpu(req->BufferLength));
- ksmbd_revert_fsids(work);
break;
default:
rc = -EOPNOTSUPP;
}
+ revert_creds(saved_cred);
if (rc < 0)
goto err_out;
@@ -6594,19 +8611,27 @@ int smb2_set_info(struct ksmbd_work *work)
return 0;
err_out:
- if (rc == -EACCES || rc == -EPERM || rc == -EXDEV)
- rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ if (rc == -EACCES || rc == -EPERM || rc == -EXDEV) {
+ if (fp && req->InfoType == SMB2_O_INFO_FILE &&
+ req->FileInfoClass == FILE_DISPOSITION_INFORMATION &&
+ fp->f_ci->m_fattr & FILE_ATTRIBUTE_READONLY_LE)
+ rsp->hdr.Status = STATUS_CANNOT_DELETE;
+ else
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ }
else if (rc == -EINVAL)
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)
rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
else if (rc == -EBUSY || rc == -ENOTEMPTY)
rsp->hdr.Status = STATUS_DIRECTORY_NOT_EMPTY;
- else if (rc == -EAGAIN)
+ else if (rc == -EAGAIN && !chseq_err)
rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
else if (rc == -EBADF || rc == -ESTALE)
rsp->hdr.Status = STATUS_INVALID_HANDLE;
@@ -6648,13 +8673,18 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work)
}
aux_payload_buf =
- kvmalloc(rpc_resp->payload_sz, KSMBD_DEFAULT_GFP);
+ kvmalloc(ALIGN(rpc_resp->payload_sz, 8),
+ KSMBD_DEFAULT_GFP);
if (!aux_payload_buf) {
err = -ENOMEM;
goto out;
}
memcpy(aux_payload_buf, rpc_resp->payload, rpc_resp->payload_sz);
+ if (rpc_resp->payload_sz & 7)
+ memset(aux_payload_buf + rpc_resp->payload_sz, 0,
+ ALIGN(rpc_resp->payload_sz, 8) -
+ rpc_resp->payload_sz);
nbytes = rpc_resp->payload_sz;
err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
@@ -6687,18 +8717,31 @@ out:
return err;
}
-static int smb2_set_remote_key_for_rdma(struct ksmbd_work *work,
- struct smbdirect_buffer_descriptor_v1 *desc,
- __le32 Channel,
- __le16 ChannelInfoLength)
+/**
+ * smb2_set_rdma_key() - validate descriptors and save invalidation state
+ * @work: request work item
+ * @desc: first RDMA buffer descriptor
+ * @Channel: nested RDMA channel type
+ * @channel_info_len: descriptor array length
+ *
+ * Return: 0 on success, otherwise -EINVAL
+ */
+static int smb2_set_rdma_key(struct ksmbd_work *work,
+ struct smbdirect_buffer_descriptor_v1 *desc,
+ __le32 Channel, __le16 channel_info_len)
{
unsigned int i, ch_count;
+ if (Channel != SMB2_CHANNEL_RDMA_V1 &&
+ Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE)
+ return -EINVAL;
if (work->conn->dialect == SMB30_PROT_ID &&
Channel != SMB2_CHANNEL_RDMA_V1)
return -EINVAL;
+ if (le16_to_cpu(channel_info_len) % sizeof(*desc))
+ return -EINVAL;
- ch_count = le16_to_cpu(ChannelInfoLength) / sizeof(*desc);
+ ch_count = le16_to_cpu(channel_info_len) / sizeof(*desc);
if (ksmbd_debug_types & KSMBD_DEBUG_RDMA) {
for (i = 0; i < ch_count; i++) {
pr_info("RDMA r/w request %#x: token %#x, length %#x\n",
@@ -6717,9 +8760,240 @@ static int smb2_set_remote_key_for_rdma(struct ksmbd_work *work,
return 0;
}
-static ssize_t smb2_read_rdma_channel(struct ksmbd_work *work,
- struct smb2_read_req *req, void *data_buf,
- size_t length)
+/**
+ * smb2_prep_rdma_read() - transform an RDMA READ payload
+ * @work: request work item
+ * @req: READ request controlling encryption or signing
+ * @rsp: READ response receiving transform metadata
+ * @data: data that will be transferred through RDMA
+ * @datalen: data length
+ *
+ * Encrypt the payload in place and encode the detached crypto metadata in
+ * the response buffer.
+ *
+ * Return: metadata length, zero when no transform applies, or negative errno
+ */
+static int smb2_prep_rdma_read(struct ksmbd_work *work,
+ struct smb2_read_req *req,
+ struct smb2_read_rsp *rsp,
+ void *data, unsigned int datalen)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_rdma_transform *transform;
+ struct smb2_rdma_crypto_transform *crypto;
+ u8 *nonce;
+ unsigned int nonce_len = 0, transform_len;
+ u16 transform_type;
+ int err;
+
+ if (!work->encrypted ||
+ !(conn->rdma_transform_ids & BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION)))
+ return 0;
+
+ transform_type = SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION;
+ nonce_len = (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
+ SMB3_AES_GCM_NONCE : SMB3_AES_CCM_NONCE;
+
+ transform = (struct smb2_rdma_transform *)rsp->Buffer;
+ crypto = (struct smb2_rdma_crypto_transform *)(transform + 1);
+ memset(transform, 0, sizeof(*transform) + sizeof(*crypto) +
+ SMB2_SIGNATURE_SIZE + nonce_len);
+ transform->Channel = SMB2_CHANNEL_NONE;
+ transform->TransformCount = cpu_to_le16(1);
+
+ crypto->TransformType = cpu_to_le16(transform_type);
+ crypto->SignatureLength = cpu_to_le16(SMB2_SIGNATURE_SIZE);
+ crypto->NonceLength = cpu_to_le16(nonce_len);
+ nonce = crypto->Signature + SMB2_SIGNATURE_SIZE;
+
+ get_random_bytes(nonce, nonce_len);
+ err = ksmbd_crypt_rdma(conn,
+ work->sess->smb3encryptionkey,
+ data, datalen, nonce, nonce_len,
+ crypto->Signature,
+ SMB2_SIGNATURE_SIZE, true);
+ if (err) {
+ pr_err("RDMA READ encryption failed: session=%llu payload=%u rc=%d\n",
+ work->sess->id, datalen, err);
+ return err;
+ }
+
+ transform_len = sizeof(*transform) + sizeof(*crypto) +
+ SMB2_SIGNATURE_SIZE + nonce_len;
+ rsp->Flags = SMB2_READFLAG_RESPONSE_RDMA_TRANSFORM;
+ rsp->DataLength = cpu_to_le32(transform_len);
+ ksmbd_debug(RDMA,
+ "RDMA READ encryption prepared: session=%llu cipher=0x%04x payload=%u transform=%u nonce=%u tag=%u\n",
+ work->sess->id, le16_to_cpu(conn->cipher_type), datalen,
+ transform_len, nonce_len, SMB2_SIGNATURE_SIZE);
+ return transform_len;
+}
+
+struct smb2_rdma_write_transform {
+ struct smbdirect_buffer_descriptor_v1 *desc;
+ struct smb2_rdma_crypto_transform *crypto;
+ u8 *nonce;
+ unsigned int desc_len;
+ unsigned int nonce_len;
+ unsigned int signature_len;
+ u16 type;
+ __le32 channel;
+};
+
+/**
+ * smb2_current_req_len() - return the current compound request element size
+ * @work: request work item
+ * @hdr: current SMB2 header
+ *
+ * Return: current request element length measured from the SMB2 header
+ */
+static unsigned int smb2_current_req_len(struct ksmbd_work *work,
+ struct smb2_hdr *hdr)
+{
+ if (hdr->NextCommand)
+ return le32_to_cpu(hdr->NextCommand);
+ return get_rfc1002_len(work->request_buf) -
+ work->next_smb2_rcv_hdr_off;
+}
+
+/**
+ * check_rdma_desc() - validate an RDMA descriptor array
+ * @desc: descriptor array
+ * @desc_len: descriptor array length
+ * @required_len: minimum aggregate buffer length
+ *
+ * Return: 0 when the descriptors cover the transfer, otherwise -EINVAL
+ */
+static int check_rdma_desc(struct smbdirect_buffer_descriptor_v1 *desc,
+ unsigned int desc_len,
+ unsigned int required_len)
+{
+ unsigned int i, count;
+ u64 described_len = 0;
+
+ if (!desc_len || desc_len % sizeof(*desc))
+ return -EINVAL;
+ count = desc_len / sizeof(*desc);
+ if (!le32_to_cpu(desc[0].length))
+ return -EINVAL;
+ for (i = 0; i < count; i++)
+ described_len += le32_to_cpu(desc[i].length);
+ return described_len < required_len ? -EINVAL : 0;
+}
+
+/**
+ * smb2_parse_rdma_write_transform() - validate RDMA WRITE transform metadata
+ * @work: request work item
+ * @req: WRITE request containing the transform
+ * @info: parsed transform information
+ *
+ * Validate transform counts, crypto fields, descriptor alignment and bounds,
+ * negotiated algorithms, and the nested RDMA channel.
+ *
+ * Return: 0 on success, otherwise a negative errno
+ */
+static int smb2_parse_rdma_write_transform(struct ksmbd_work *work,
+ struct smb2_write_req *req,
+ struct smb2_rdma_write_transform *info)
+{
+ struct smb2_rdma_transform *transform;
+ struct smb2_rdma_crypto_transform *crypto;
+ unsigned int req_len = smb2_current_req_len(work, &req->hdr);
+ unsigned int offset = le16_to_cpu(req->WriteChannelInfoOffset);
+ unsigned int length = le16_to_cpu(req->WriteChannelInfoLength);
+ unsigned int desc_offset, desc_len, crypto_len, expected_desc_offset;
+ int err;
+
+ if (!work->conn->rdma_transform_ids ||
+ offset < offsetof(struct smb2_write_req, Buffer) ||
+ length < sizeof(*transform) || offset > req_len ||
+ length > req_len - offset)
+ return -EINVAL;
+
+ transform = (struct smb2_rdma_transform *)((char *)req + offset);
+ if (le16_to_cpu(transform->TransformCount) != 1 ||
+ (transform->Channel != SMB2_CHANNEL_RDMA_V1 &&
+ transform->Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE))
+ return -EINVAL;
+
+ desc_offset = le16_to_cpu(transform->RdmaDescriptorOffset);
+ desc_len = le16_to_cpu(transform->RdmaDescriptorLength);
+ if (!desc_len || desc_len % sizeof(*info->desc) ||
+ desc_offset < sizeof(*transform) || desc_offset > length ||
+ desc_len > length - desc_offset)
+ return -EINVAL;
+
+ crypto = (struct smb2_rdma_crypto_transform *)(transform + 1);
+ if (length - sizeof(*transform) < sizeof(*crypto))
+ return -EINVAL;
+ info->type = le16_to_cpu(crypto->TransformType);
+ info->signature_len = le16_to_cpu(crypto->SignatureLength);
+ info->nonce_len = le16_to_cpu(crypto->NonceLength);
+ if (!info->signature_len)
+ return info->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION ?
+ -EBADMSG : -EINVAL;
+ if (info->signature_len > SMB2_SIGNATURE_SIZE)
+ return info->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION ?
+ -EBADMSG : -EINVAL;
+ if (info->signature_len > length - sizeof(*transform) - sizeof(*crypto) ||
+ info->nonce_len > length - sizeof(*transform) - sizeof(*crypto) -
+ info->signature_len)
+ return -EINVAL;
+
+ crypto_len = sizeof(*crypto) + info->signature_len + info->nonce_len;
+ expected_desc_offset = ALIGN(sizeof(*transform) + crypto_len, 8);
+ if (desc_offset != expected_desc_offset)
+ return -EINVAL;
+
+ if (info->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION) {
+ unsigned int expected_nonce_len;
+
+ if (!(work->conn->rdma_transform_ids &
+ BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION)) || !work->encrypted)
+ return -EINVAL;
+ expected_nonce_len =
+ (work->conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ work->conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
+ SMB3_AES_GCM_NONCE : SMB3_AES_CCM_NONCE;
+ if (info->nonce_len != expected_nonce_len)
+ return -EBADMSG;
+ } else {
+ return -EINVAL;
+ }
+
+ info->desc = (struct smbdirect_buffer_descriptor_v1 *)
+ ((char *)transform + desc_offset);
+ info->desc_len = desc_len;
+ info->crypto = crypto;
+ info->nonce = crypto->Signature + info->signature_len;
+ info->channel = transform->Channel;
+ err = check_rdma_desc(info->desc, info->desc_len,
+ le32_to_cpu(req->RemainingBytes));
+ if (err)
+ return err;
+
+ ksmbd_debug(RDMA,
+ "RDMA WRITE encryption metadata: session=%llu cipher=0x%04x payload=%u channel=0x%x descriptors=%zu nonce=%u tag=%u\n",
+ work->sess->id, le16_to_cpu(work->conn->cipher_type),
+ le32_to_cpu(req->RemainingBytes), le32_to_cpu(info->channel),
+ info->desc_len / sizeof(*info->desc), info->nonce_len,
+ info->signature_len);
+ return 0;
+}
+
+/**
+ * smb2_read_rdma() - transfer READ data to client RDMA buffers
+ * @work: request work item
+ * @req: READ request containing client descriptors
+ * @data_buf: data to transfer
+ * @length: data length
+ *
+ * Return: transferred length on success, otherwise a negative errno
+ */
+static ssize_t smb2_read_rdma(struct ksmbd_work *work,
+ struct smb2_read_req *req, void *data_buf,
+ size_t length)
{
int err;
@@ -6749,7 +9023,8 @@ int smb2_read(struct ksmbd_work *work)
size_t length, mincount;
ssize_t nbytes = 0, remain_bytes = 0;
int err = 0;
- bool is_rdma_channel = false;
+ int rdma_transform_len = 0;
+ bool is_rdma_channel = false, async_interim = false;
unsigned int max_read_size = conn->vals->max_read_size;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
void *aux_payload_buf;
@@ -6765,6 +9040,8 @@ int smb2_read(struct ksmbd_work *work)
if (work->next_smb2_rcv_hdr_off) {
req = ksmbd_req_buf_next(work);
rsp = ksmbd_resp_buf_next(work);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
if (!has_file_id(req->VolatileFileId)) {
ksmbd_debug(SMB, "Compound request set FID = %llu\n",
work->compound_fid);
@@ -6781,6 +9058,12 @@ int smb2_read(struct ksmbd_work *work)
pid = req->PersistentFileId;
}
+ if (req->Channel != SMB2_CHANNEL_NONE &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1 &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE) {
+ err = -EINVAL;
+ goto out;
+ }
if (req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE ||
req->Channel == SMB2_CHANNEL_RDMA_V1) {
is_rdma_channel = true;
@@ -6793,16 +9076,24 @@ int smb2_read(struct ksmbd_work *work)
if (is_rdma_channel == true) {
unsigned int ch_offset = le16_to_cpu(req->ReadChannelInfoOffset);
+ unsigned int ch_len = le16_to_cpu(req->ReadChannelInfoLength);
+ unsigned int req_len = smb2_current_req_len(work, &req->hdr);
+ struct smbdirect_buffer_descriptor_v1 *desc;
- if (ch_offset < offsetof(struct smb2_read_req, Buffer)) {
+ if (!le32_to_cpu(req->Length) ||
+ ch_offset < offsetof(struct smb2_read_req, Buffer) ||
+ ch_offset > req_len || ch_len > req_len - ch_offset) {
err = -EINVAL;
goto out;
}
- err = smb2_set_remote_key_for_rdma(work,
- (struct smbdirect_buffer_descriptor_v1 *)
- ((char *)req + ch_offset),
- req->Channel,
- req->ReadChannelInfoLength);
+ desc = (struct smbdirect_buffer_descriptor_v1 *)
+ ((char *)req + ch_offset);
+ err = check_rdma_desc(desc, ch_len, le32_to_cpu(req->Length));
+ if (err)
+ goto out;
+ err = smb2_set_rdma_key(work, desc,
+ req->Channel,
+ req->ReadChannelInfoLength);
if (err)
goto out;
}
@@ -6813,12 +9104,24 @@ int smb2_read(struct ksmbd_work *work)
goto out;
}
+ err = smb2_set_request_open(work, fp, &req->hdr, true, true);
+ if (err)
+ goto out;
+
if (!(fp->daccess & (FILE_READ_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
pr_err("Not permitted to read : 0x%x\n", fp->daccess);
err = -EACCES;
goto out;
}
+ if (work->next_smb2_rcv_hdr_off && !req->hdr.NextCommand) {
+ err = setup_async_work(work, NULL, NULL);
+ if (err)
+ goto out;
+ smb2_send_interim_resp(work, STATUS_PENDING);
+ async_interim = true;
+ }
+
offset = le64_to_cpu(req->Offset);
if (offset < 0) {
err = -EINVAL;
@@ -6837,7 +9140,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;
@@ -6850,10 +9153,21 @@ int smb2_read(struct ksmbd_work *work)
goto out;
}
+ /*
+ * ksmbd_vfs_read() fills only nbytes; the [nbytes, ALIGN(nbytes, 8))
+ * tail of the un-zeroed buffer is transmitted as compound-response
+ * alignment padding, leaking uninitialized kernel memory to the
+ * client. Zero just that tail.
+ */
+ if (nbytes & 7)
+ memset(aux_payload_buf + nbytes, 0, ALIGN(nbytes, 8) - nbytes);
+
if ((nbytes == 0 && length != 0) || nbytes < mincount) {
kvfree(aux_payload_buf);
rsp->hdr.Status = STATUS_END_OF_FILE;
smb2_set_err_rsp(work);
+ if (async_interim)
+ release_async_work(work);
ksmbd_fd_put(work, fp);
return -ENODATA;
}
@@ -6862,10 +9176,28 @@ int smb2_read(struct ksmbd_work *work)
nbytes, offset, mincount);
if (is_rdma_channel == true) {
+ rdma_transform_len = smb2_prep_rdma_read(work, req,
+ rsp,
+ aux_payload_buf,
+ nbytes);
+ if (rdma_transform_len < 0) {
+ kvfree(aux_payload_buf);
+ err = rdma_transform_len;
+ goto out;
+ }
/* write data to the client using rdma channel */
- remain_bytes = smb2_read_rdma_channel(work, req,
- aux_payload_buf,
- nbytes);
+ remain_bytes = smb2_read_rdma(work, req,
+ aux_payload_buf,
+ nbytes);
+ if (remain_bytes < 0)
+ pr_err("RDMA READ transfer failed: session=%llu payload=%zu transform=%d rc=%zd\n",
+ work->sess ? work->sess->id : 0, nbytes,
+ rdma_transform_len, remain_bytes);
+ else
+ ksmbd_debug(RDMA,
+ "RDMA READ transfer completed: session=%llu payload=%zu transform=%d\n",
+ work->sess ? work->sess->id : 0, nbytes,
+ rdma_transform_len);
kvfree(aux_payload_buf);
aux_payload_buf = NULL;
nbytes = 0;
@@ -6878,20 +9210,35 @@ int smb2_read(struct ksmbd_work *work)
rsp->StructureSize = cpu_to_le16(17);
rsp->DataOffset = 80;
rsp->Reserved = 0;
- rsp->DataLength = cpu_to_le32(nbytes);
+ rsp->DataLength = cpu_to_le32(rdma_transform_len ?: nbytes);
rsp->DataRemaining = cpu_to_le32(remain_bytes);
- rsp->Flags = 0;
+ rsp->Flags = rdma_transform_len ?
+ SMB2_READFLAG_RESPONSE_RDMA_TRANSFORM : 0;
err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
- offsetof(struct smb2_read_rsp, Buffer),
+ offsetof(struct smb2_read_rsp, Buffer) +
+ rdma_transform_len,
aux_payload_buf, nbytes);
if (err) {
kvfree(aux_payload_buf);
goto out;
}
+ if (async_interim)
+ release_async_work(work);
+ /*
+ * RDMA responses are transferred through channel buffers and encrypted
+ * responses use the encryption transform, so only normal SMB transport
+ * responses are candidates for compression.
+ */
+ if (!is_rdma_channel && nbytes &&
+ (req->Flags & SMB2_READFLAG_REQUEST_COMPRESSED) &&
+ conn->compress_algorithm != SMB3_COMPRESS_NONE)
+ work->compress_response = true;
ksmbd_fd_put(work, fp);
return 0;
out:
+ if (async_interim)
+ release_async_work(work);
if (err) {
if (err == -EISDIR)
rsp->hdr.Status = STATUS_INVALID_DEVICE_REQUEST;
@@ -6981,10 +9328,28 @@ out:
return err;
}
-static ssize_t smb2_write_rdma_channel(struct ksmbd_work *work,
- struct smb2_write_req *req,
- struct ksmbd_file *fp,
- loff_t offset, size_t length, bool sync)
+/**
+ * smb2_write_rdma() - receive and store an RDMA WRITE payload
+ * @work: request work item
+ * @desc: client RDMA buffer descriptors
+ * @desc_len: descriptor array length
+ * @transform: parsed transform, or NULL for an untransformed transfer
+ * @fp: target open file
+ * @offset: target file offset
+ * @length: transfer length
+ * @sync: request synchronous storage completion
+ *
+ * Receive the payload, authenticate or decrypt it when required, and write it
+ * to the target file.
+ *
+ * Return: written byte count on success, otherwise a negative errno
+ */
+static ssize_t smb2_write_rdma(struct ksmbd_work *work,
+ struct smbdirect_buffer_descriptor_v1 *desc,
+ unsigned int desc_len,
+ struct smb2_rdma_write_transform *transform,
+ struct ksmbd_file *fp, loff_t offset,
+ size_t length, bool sync)
{
char *data_buf;
int ret;
@@ -6994,19 +9359,42 @@ static ssize_t smb2_write_rdma_channel(struct ksmbd_work *work,
if (!data_buf)
return -ENOMEM;
- ret = ksmbd_conn_rdma_read(work->conn, data_buf, length,
- (struct smbdirect_buffer_descriptor_v1 *)
- ((char *)req + le16_to_cpu(req->WriteChannelInfoOffset)),
- le16_to_cpu(req->WriteChannelInfoLength));
+ ret = ksmbd_conn_rdma_read(work->conn, data_buf, length, desc,
+ desc_len);
if (ret < 0) {
+ if (transform)
+ pr_err("RDMA WRITE encrypted transfer failed: session=%llu payload=%zu rdma_read_rc=%d\n",
+ work->sess->id, length, ret);
kvfree(data_buf);
return ret;
}
+ if (transform &&
+ transform->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION) {
+ ret = ksmbd_crypt_rdma(work->conn,
+ work->sess->smb3decryptionkey,
+ data_buf, length, transform->nonce,
+ transform->nonce_len,
+ transform->crypto->Signature,
+ transform->signature_len, false);
+ if (ret) {
+ pr_err("RDMA WRITE decryption failed: session=%llu payload=%zu rc=%d\n",
+ work->sess->id, length, ret);
+ kvfree(data_buf);
+ return ret == -ENOMEM ? ret : -EBADMSG;
+ }
+ }
ret = ksmbd_vfs_write(work, fp, data_buf, length, &offset, sync, &nbytes);
kvfree(data_buf);
- if (ret < 0)
+ if (ret < 0) {
+ if (transform)
+ pr_err("RDMA WRITE encrypted file write failed: session=%llu payload=%zu rc=%d\n",
+ work->sess->id, length, ret);
return ret;
+ }
+ ksmbd_debug(RDMA,
+ "RDMA WRITE transfer completed: session=%llu payload=%zu transformed=%u written=%zd\n",
+ work->sess ? work->sess->id : 0, length, !!transform, nbytes);
return nbytes;
}
@@ -7021,31 +9409,69 @@ int smb2_write(struct ksmbd_work *work)
{
struct smb2_write_req *req;
struct smb2_write_rsp *rsp;
+ struct smb2_rdma_write_transform rdma_transform = {};
+ struct smb2_rdma_write_transform *rdma_info = NULL;
+ struct smbdirect_buffer_descriptor_v1 *rdma_desc = NULL;
+ unsigned int rdma_desc_len = 0;
struct ksmbd_file *fp = NULL;
loff_t offset;
size_t length;
ssize_t nbytes;
char *data_buf;
bool writethrough = false, is_rdma_channel = false;
+ bool async_interim = false;
+ bool chseq_err = false;
int err = 0;
unsigned int max_write_size = work->conn->vals->max_write_size;
+ unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
ksmbd_debug(SMB, "Received smb2 write request\n");
WORK_BUFFERS(work, req, rsp);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
+
+ if (work->next_smb2_rcv_hdr_off &&
+ !has_file_id(req->VolatileFileId)) {
+ ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+ work->compound_fid);
+ id = work->compound_fid;
+ pid = work->compound_pfid;
+ }
+
+ if (!has_file_id(id)) {
+ id = req->VolatileFileId;
+ pid = req->PersistentFileId;
+ }
+
if (test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_PIPE)) {
ksmbd_debug(SMB, "IPC pipe write request\n");
return smb2_write_pipe(work);
}
offset = le64_to_cpu(req->Offset);
- if (offset < 0)
- return -EINVAL;
+ if (offset < 0) {
+ err = -EINVAL;
+ goto out;
+ }
length = le32_to_cpu(req->Length);
+ if (req->Channel != SMB2_CHANNEL_NONE &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1 &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE &&
+ req->Channel != SMB2_CHANNEL_RDMA_TRANSFORM) {
+ err = -EINVAL;
+ goto out;
+ }
+ if (req->Channel == SMB2_CHANNEL_RDMA_TRANSFORM &&
+ work->conn->dialect != SMB311_PROT_ID) {
+ err = -EINVAL;
+ goto out;
+ }
if (req->Channel == SMB2_CHANNEL_RDMA_V1 ||
- req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE) {
+ req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE ||
+ req->Channel == SMB2_CHANNEL_RDMA_TRANSFORM) {
is_rdma_channel = true;
max_write_size = get_smbd_max_read_write_size(work->conn->transport);
if (max_write_size == 0) {
@@ -7055,19 +9481,55 @@ int smb2_write(struct ksmbd_work *work)
length = le32_to_cpu(req->RemainingBytes);
}
+ if (length) {
+ u64 end = (u64)offset + length;
+
+ if (end > SMB2_MAX_FILE_SIZE) {
+ err = -EINVAL;
+ goto out;
+ }
+ if (end == SMB2_MAX_FILE_SIZE) {
+ err = -EFBIG;
+ goto out;
+ }
+ }
+
if (is_rdma_channel == true) {
unsigned int ch_offset = le16_to_cpu(req->WriteChannelInfoOffset);
+ unsigned int ch_len = le16_to_cpu(req->WriteChannelInfoLength);
+ unsigned int req_len = smb2_current_req_len(work, &req->hdr);
- if (req->Length != 0 || req->DataOffset != 0 ||
- ch_offset < offsetof(struct smb2_write_req, Buffer)) {
+ if (!length || req->Length != 0 || req->DataOffset != 0 ||
+ ch_offset < offsetof(struct smb2_write_req, Buffer) ||
+ ch_offset > req_len || ch_len > req_len - ch_offset) {
err = -EINVAL;
goto out;
}
- err = smb2_set_remote_key_for_rdma(work,
- (struct smbdirect_buffer_descriptor_v1 *)
- ((char *)req + ch_offset),
- req->Channel,
- req->WriteChannelInfoLength);
+ if (req->Channel == SMB2_CHANNEL_RDMA_TRANSFORM) {
+ err = smb2_parse_rdma_write_transform(work, req,
+ &rdma_transform);
+ if (err) {
+ pr_err("RDMA WRITE encryption metadata rejected: session=%llu rc=%d\n",
+ work->sess ? work->sess->id : 0, err);
+ goto out;
+ }
+ rdma_desc = rdma_transform.desc;
+ rdma_desc_len = rdma_transform.desc_len;
+ rdma_info = &rdma_transform;
+ err = smb2_set_rdma_key(work, rdma_desc,
+ rdma_transform.channel,
+ cpu_to_le16(rdma_desc_len));
+ } else {
+ rdma_desc = (struct smbdirect_buffer_descriptor_v1 *)
+ ((char *)req + ch_offset);
+ rdma_desc_len = ch_len;
+ err = check_rdma_desc(rdma_desc, rdma_desc_len, length);
+ if (err)
+ goto out;
+ err = smb2_set_rdma_key(work, rdma_desc,
+ req->Channel,
+ req->WriteChannelInfoLength);
+ }
if (err)
goto out;
}
@@ -7078,18 +9540,33 @@ int smb2_write(struct ksmbd_work *work)
goto out;
}
- fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
+ fp = ksmbd_lookup_fd_slow(work, id, pid);
if (!fp) {
err = -ENOENT;
goto out;
}
+ err = smb2_set_request_open(work, fp, &req->hdr, true, false);
+ if (err) {
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ chseq_err = true;
+ goto out;
+ }
+
if (!(fp->daccess & (FILE_WRITE_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
pr_err("Not permitted to write : 0x%x\n", fp->daccess);
err = -EACCES;
goto out;
}
+ if (work->next_smb2_rcv_hdr_off && !req->hdr.NextCommand) {
+ err = setup_async_work(work, NULL, NULL);
+ if (err)
+ goto out;
+ smb2_send_interim_resp(work, STATUS_PENDING);
+ async_interim = true;
+ }
+
if (length > max_write_size) {
ksmbd_debug(SMB, "limiting write size to max size(%u)\n",
max_write_size);
@@ -7121,8 +9598,9 @@ int smb2_write(struct ksmbd_work *work)
/* read data from the client using rdma channel, and
* write the data.
*/
- nbytes = smb2_write_rdma_channel(work, req, fp, offset, length,
- writethrough);
+ nbytes = smb2_write_rdma(work, rdma_desc, rdma_desc_len,
+ rdma_info, fp, offset, length,
+ writethrough);
if (nbytes < 0) {
err = (int)nbytes;
goto out;
@@ -7138,11 +9616,16 @@ int smb2_write(struct ksmbd_work *work)
err = ksmbd_iov_pin_rsp(work, rsp, offsetof(struct smb2_write_rsp, Buffer));
if (err)
goto out;
+ if (async_interim)
+ release_async_work(work);
ksmbd_fd_put(work, fp);
return 0;
out:
- if (err == -EAGAIN)
+ if (async_interim)
+ release_async_work(work);
+
+ if (err == -EAGAIN && !chseq_err)
rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
else if (err == -ENOSPC || err == -EFBIG)
rsp->hdr.Status = STATUS_DISK_FULL;
@@ -7154,7 +9637,11 @@ out:
rsp->hdr.Status = STATUS_SHARING_VIOLATION;
else if (err == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
- else
+ else if (err == -EBADMSG)
+ rsp->hdr.Status = STATUS_AUTH_TAG_MISMATCH;
+ else if (err == -EKEYREJECTED)
+ rsp->hdr.Status = STATUS_INVALID_SIGNATURE;
+ else if (rsp->hdr.Status == 0)
rsp->hdr.Status = STATUS_INVALID_HANDLE;
smb2_set_err_rsp(work);
@@ -7172,13 +9659,30 @@ int smb2_flush(struct ksmbd_work *work)
{
struct smb2_flush_req *req;
struct smb2_flush_rsp *rsp;
+ u64 id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
int err;
WORK_BUFFERS(work, req, rsp);
ksmbd_debug(SMB, "Received smb2 flush request(fid : %llu)\n", req->VolatileFileId);
- err = ksmbd_vfs_fsync(work, req->VolatileFileId, req->PersistentFileId);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
+
+ if (work->next_smb2_rcv_hdr_off &&
+ !has_file_id(req->VolatileFileId)) {
+ ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+ work->compound_fid);
+ id = work->compound_fid;
+ pid = work->compound_pfid;
+ }
+
+ if (!has_file_id(id)) {
+ id = req->VolatileFileId;
+ pid = req->PersistentFileId;
+ }
+
+ err = ksmbd_vfs_fsync(work, id, pid);
if (err)
goto out;
@@ -7204,13 +9708,15 @@ int smb2_cancel(struct ksmbd_work *work)
struct smb2_hdr *hdr = smb_get_msg(work->request_buf);
struct smb2_hdr *chdr;
struct ksmbd_work *iter;
+ struct ksmbd_work *cancelled_notify = NULL;
struct list_head *command_list;
if (work->next_smb2_rcv_hdr_off)
hdr = ksmbd_resp_buf_next(work);
ksmbd_debug(SMB, "smb2 cancel called on mid %llu, async flags 0x%x\n",
- hdr->MessageId, hdr->Flags);
+ le64_to_cpu(hdr->MessageId),
+ le32_to_cpu(hdr->Flags));
if (hdr->Flags & SMB2_FLAGS_ASYNC_COMMAND) {
command_list = &conn->async_requests;
@@ -7224,16 +9730,39 @@ int smb2_cancel(struct ksmbd_work *work)
le64_to_cpu(hdr->Id.AsyncId))
continue;
+ /*
+ * Only an ACTIVE deferred work may have its cancel_fn
+ * fired. A CANCELLED or CLOSED work already took the
+ * smb2_lock() non-ACTIVE early-exit that frees the
+ * file_lock and skips release_async_work(), so it is
+ * still on conn->async_requests with a live cancel_fn
+ * pointing at the freed file_lock.
+ */
+ if (cmpxchg(&iter->state, KSMBD_WORK_ACTIVE,
+ KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE)
+ break;
+
ksmbd_debug(SMB,
"smb2 with AsyncId %llu cancelled command = 0x%x\n",
le64_to_cpu(hdr->Id.AsyncId),
le16_to_cpu(chdr->Command));
- iter->state = KSMBD_WORK_CANCELLED;
- if (iter->cancel_fn)
+ if (iter->cancel_fn == smb2_notify_cancel_fn)
+ cancelled_notify =
+ smb2_notify_cancel_claim(iter->cancel_argv);
+ else if (iter->cancel_fn)
iter->cancel_fn(iter->cancel_argv);
break;
}
spin_unlock(&conn->request_lock);
+
+ /*
+ * Complete a cancelled notify before this CANCEL handler returns.
+ * Deferring it to the system workqueue lets a following request and
+ * its response overtake STATUS_CANCELLED, leaving clients waiting
+ * for the original notify even though the cancellation was accepted.
+ */
+ if (cancelled_notify)
+ smb2_complete_notify_cancel(cancelled_notify);
} else {
command_list = &conn->requests;
@@ -7245,11 +9774,16 @@ int smb2_cancel(struct ksmbd_work *work)
iter == work)
continue;
+ if (cmpxchg(&iter->state, KSMBD_WORK_ACTIVE,
+ KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE)
+ break;
+
ksmbd_debug(SMB,
"smb2 with mid %llu cancelled command = 0x%x\n",
le64_to_cpu(hdr->MessageId),
le16_to_cpu(chdr->Command));
- iter->state = KSMBD_WORK_CANCELLED;
+ if (iter->cancel_fn)
+ iter->cancel_fn(iter->cancel_argv);
break;
}
spin_unlock(&conn->request_lock);
@@ -7322,7 +9856,7 @@ static int smb2_set_flock_flags(struct file_lock *flock, int flags)
}
static struct ksmbd_lock *smb2_lock_init(struct file_lock *flock,
- unsigned int cmd, int flags,
+ unsigned int cmd, int flags, bool zero_len,
struct list_head *lock_list)
{
struct ksmbd_lock *lock;
@@ -7336,8 +9870,7 @@ static struct ksmbd_lock *smb2_lock_init(struct file_lock *flock,
lock->start = flock->fl_start;
lock->end = flock->fl_end;
lock->flags = flags;
- if (lock->start == lock->end)
- lock->zero_len = 1;
+ lock->zero_len = zero_len;
INIT_LIST_HEAD(&lock->clist);
INIT_LIST_HEAD(&lock->flist);
INIT_LIST_HEAD(&lock->llist);
@@ -7354,6 +9887,19 @@ static void smb2_remove_blocked_lock(void **argv)
locks_wake_up(flock);
}
+static void smb2_free_lock(struct file_lock *flock)
+{
+ ksmbd_vfs_posix_lock_unblock(flock);
+ locks_free_lock(flock);
+}
+
+static void smb2_free_blocked_lock(struct file_lock *flock)
+{
+ ksmbd_vfs_posix_lock_unblock(flock);
+ locks_wake_up(flock);
+ locks_free_lock(flock);
+}
+
static inline bool lock_defer_pending(struct file_lock *fl)
{
/* check pending lock waiters */
@@ -7385,23 +9931,55 @@ int smb2_lock(struct ksmbd_work *work)
LIST_HEAD(lock_list);
LIST_HEAD(rollback_list);
int prior_lock = 0, bkt;
+ unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
+ bool lock_replayed;
WORK_BUFFERS(work, req, rsp);
ksmbd_debug(SMB, "Received smb2 lock request\n");
- fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
+
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
+
+ if (work->next_smb2_rcv_hdr_off &&
+ !has_file_id(req->VolatileFileId)) {
+ ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+ work->compound_fid);
+ id = work->compound_fid;
+ pid = work->compound_pfid;
+ }
+
+ if (!has_file_id(id)) {
+ id = req->VolatileFileId;
+ pid = req->PersistentFileId;
+ }
+
+ fp = ksmbd_lookup_fd_slow(work, id, pid);
if (!fp) {
ksmbd_debug(SMB, "Invalid file id for lock : %llu\n", req->VolatileFileId);
err = -ENOENT;
goto out2;
}
+ err = smb2_set_request_open(work, fp, &req->hdr, false, false);
+ if (err)
+ goto out2;
+
+ lock_replayed = smb2_verify_lock_sequence(work, fp, req);
+ if (lock_replayed)
+ goto lock_success;
+
filp = fp->filp;
lock_count = le16_to_cpu(req->LockCount);
lock_ele = req->locks;
ksmbd_debug(SMB, "lock count is %d\n", lock_count);
- if (!lock_count) {
+ /*
+ * Cap lock_count at 64. The MS-SMB2 spec defines Open.LockSequenceArray
+ * as exactly 64 entries so 64 is the intended ceiling. No real workload
+ * comes close to this in a single request.
+ */
+ if (!lock_count || lock_count > 64) {
err = -EINVAL;
goto out2;
}
@@ -7417,32 +9995,18 @@ int smb2_lock(struct ksmbd_work *work)
lock_start = le64_to_cpu(lock_ele[i].Offset);
lock_length = le64_to_cpu(lock_ele[i].Length);
- if (lock_start > U64_MAX - lock_length) {
+ if (lock_start > OFFSET_MAX ||
+ (lock_length &&
+ lock_length - 1 > OFFSET_MAX - lock_start)) {
pr_err("Invalid lock range requested\n");
rsp->hdr.Status = STATUS_INVALID_LOCK_RANGE;
locks_free_lock(flock);
goto out;
}
- if (lock_start > OFFSET_MAX)
- flock->fl_start = OFFSET_MAX;
- else
- flock->fl_start = lock_start;
-
- lock_length = le64_to_cpu(lock_ele[i].Length);
- if (lock_length > OFFSET_MAX - flock->fl_start)
- lock_length = OFFSET_MAX - flock->fl_start;
-
- flock->fl_end = flock->fl_start + lock_length;
-
- if (flock->fl_end < flock->fl_start) {
- ksmbd_debug(SMB,
- "the end offset(%llx) is smaller than the start offset(%llx)\n",
- flock->fl_end, flock->fl_start);
- rsp->hdr.Status = STATUS_INVALID_LOCK_RANGE;
- locks_free_lock(flock);
- goto out;
- }
+ flock->fl_start = lock_start;
+ flock->fl_end = lock_length ?
+ flock->fl_start + lock_length - 1 : flock->fl_start;
/* Check conflict locks in one request */
list_for_each_entry(cmp_lock, &lock_list, llist) {
@@ -7458,7 +10022,8 @@ int smb2_lock(struct ksmbd_work *work)
}
}
- smb_lock = smb2_lock_init(flock, cmd, flags, &lock_list);
+ smb_lock = smb2_lock_init(flock, cmd, flags, !lock_length,
+ &lock_list);
if (!smb_lock) {
err = -EINVAL;
locks_free_lock(flock);
@@ -7467,6 +10032,13 @@ int smb2_lock(struct ksmbd_work *work)
}
list_for_each_entry_safe(smb_lock, tmp, &lock_list, llist) {
+ if (lock_count > 1 &&
+ !(le32_to_cpu(lock_ele[0].Flags) & SMB2_LOCKFLAG_UNLOCK) &&
+ !(smb_lock->flags & SMB2_LOCKFLAG_FAIL_IMMEDIATELY)) {
+ err = -EINVAL;
+ goto out;
+ }
+
if (smb_lock->cmd < 0) {
err = -EINVAL;
goto out;
@@ -7507,12 +10079,14 @@ int smb2_lock(struct ksmbd_work *work)
cmp_lock->end == smb_lock->end &&
!lock_defer_pending(cmp_lock->fl)) {
nolock = 0;
- list_del(&cmp_lock->flist);
- list_del(&cmp_lock->clist);
+ list_del_init(&cmp_lock->flist);
+ list_del_init(&cmp_lock->clist);
+ cmp_lock->conn = NULL;
spin_unlock(&conn->llist_lock);
up_read(&conn_list_lock);
- locks_free_lock(cmp_lock->fl);
+ ksmbd_conn_put(conn);
+ smb2_free_lock(cmp_lock->fl);
kfree(cmp_lock);
goto out_check_cl;
}
@@ -7530,7 +10104,7 @@ int smb2_lock(struct ksmbd_work *work)
/* check zero byte lock range */
if (cmp_lock->zero_len && !smb_lock->zero_len &&
cmp_lock->start > smb_lock->start &&
- cmp_lock->start < smb_lock->end) {
+ cmp_lock->start <= smb_lock->end) {
spin_unlock(&conn->llist_lock);
up_read(&conn_list_lock);
pr_err("previous lock conflict with zero byte lock range\n");
@@ -7539,17 +10113,15 @@ int smb2_lock(struct ksmbd_work *work)
if (smb_lock->zero_len && !cmp_lock->zero_len &&
smb_lock->start > cmp_lock->start &&
- smb_lock->start < cmp_lock->end) {
+ smb_lock->start <= cmp_lock->end) {
spin_unlock(&conn->llist_lock);
up_read(&conn_list_lock);
pr_err("current lock conflict with zero byte lock range\n");
goto out;
}
- if (((cmp_lock->start <= smb_lock->start &&
- cmp_lock->end > smb_lock->start) ||
- (cmp_lock->start < smb_lock->end &&
- cmp_lock->end >= smb_lock->end)) &&
+ if (cmp_lock->start <= smb_lock->end &&
+ smb_lock->start <= cmp_lock->end &&
!cmp_lock->zero_len && !smb_lock->zero_len) {
spin_unlock(&conn->llist_lock);
up_read(&conn_list_lock);
@@ -7579,25 +10151,27 @@ retry:
rc = vfs_lock_file(filp, smb_lock->cmd, flock, NULL);
skip:
if (smb_lock->flags & SMB2_LOCKFLAG_UNLOCK) {
+ locks_free_lock(flock);
+ kfree(smb_lock);
if (!rc) {
ksmbd_debug(SMB, "File unlocked\n");
} else if (rc == -ENOENT) {
rsp->hdr.Status = STATUS_NOT_LOCKED;
+ err = rc;
goto out;
}
- locks_free_lock(flock);
- kfree(smb_lock);
} else {
if (rc == FILE_LOCK_DEFERRED) {
void **argv;
ksmbd_debug(SMB,
"would have to wait for getting lock\n");
- list_add(&smb_lock->llist, &rollback_list);
argv = kmalloc(sizeof(void *), KSMBD_DEFAULT_GFP);
if (!argv) {
err = -ENOMEM;
+ smb2_free_blocked_lock(flock);
+ kfree(smb_lock);
goto out;
}
argv[0] = flock;
@@ -7608,8 +10182,11 @@ skip:
if (rc) {
kfree(argv);
err = -ENOMEM;
+ smb2_free_blocked_lock(flock);
+ kfree(smb_lock);
goto out;
}
+ list_add(&smb_lock->llist, &rollback_list);
spin_lock(&fp->f_lock);
list_add(&work->fp_entry, &fp->blocked_works);
spin_unlock(&fp->f_lock);
@@ -7622,39 +10199,38 @@ skip:
list_del(&work->fp_entry);
spin_unlock(&fp->f_lock);
- if (work->state != KSMBD_WORK_ACTIVE) {
- list_del(&smb_lock->llist);
- locks_free_lock(flock);
-
- if (work->state == KSMBD_WORK_CANCELLED) {
- rsp->hdr.Status =
- STATUS_CANCELLED;
- kfree(smb_lock);
- smb2_send_interim_resp(work,
- STATUS_CANCELLED);
- work->send_no_response = 1;
- goto out;
- }
+ list_del(&smb_lock->llist);
- rsp->hdr.Status =
- STATUS_RANGE_NOT_LOCKED;
+ if (work->state == KSMBD_WORK_CANCELLED) {
+ rsp->hdr.Status = STATUS_CANCELLED;
kfree(smb_lock);
- goto out2;
+ smb2_send_interim_resp(work,
+ STATUS_CANCELLED);
+ release_async_work(work);
+ locks_free_lock(flock);
+ work->send_no_response = 1;
+ goto out;
}
- list_del(&smb_lock->llist);
release_async_work(work);
- goto retry;
+
+ if (work->state == KSMBD_WORK_ACTIVE)
+ goto retry;
+
+ locks_free_lock(flock);
+
+ rsp->hdr.Status =
+ STATUS_RANGE_NOT_LOCKED;
+ kfree(smb_lock);
+ /* rollback_list may still hold earlier grants */
+ goto out;
} else if (!rc) {
list_add(&smb_lock->llist, &rollback_list);
- spin_lock(&work->conn->llist_lock);
- list_add_tail(&smb_lock->clist,
- &work->conn->lock_list);
- list_add_tail(&smb_lock->flist,
- &fp->lock_list);
- spin_unlock(&work->conn->llist_lock);
ksmbd_debug(SMB, "successful in taking lock\n");
} else {
+ locks_free_lock(flock);
+ kfree(smb_lock);
+ err = rc;
goto out;
}
}
@@ -7663,6 +10239,7 @@ skip:
if (atomic_read(&fp->f_ci->op_count) > 1)
smb_break_all_oplock(work, fp);
+lock_success:
rsp->StructureSize = cpu_to_le16(4);
ksmbd_debug(SMB, "successful in taking lock\n");
rsp->hdr.Status = STATUS_SUCCESS;
@@ -7671,6 +10248,23 @@ skip:
if (err)
goto out;
+ /* publish only once the whole batch has committed */
+ if (!list_empty(&rollback_list)) {
+ spin_lock(&work->conn->llist_lock);
+ list_for_each_entry_safe(smb_lock, tmp, &rollback_list, llist) {
+ list_del_init(&smb_lock->llist);
+ smb_lock->conn = ksmbd_conn_get(work->conn);
+ list_add_tail(&smb_lock->clist,
+ &work->conn->lock_list);
+ list_add_tail(&smb_lock->flist,
+ &fp->lock_list);
+ }
+ spin_unlock(&work->conn->llist_lock);
+ }
+
+ if (!lock_replayed)
+ smb2_update_lock_sequence(work, fp, req);
+
ksmbd_fd_put(work, fp);
return 0;
@@ -7685,23 +10279,22 @@ out:
struct file_lock *rlock = NULL;
rlock = smb_flock_init(filp);
- rlock->c.flc_type = F_UNLCK;
- rlock->fl_start = smb_lock->start;
- rlock->fl_end = smb_lock->end;
+ if (rlock) {
+ rlock->c.flc_type = F_UNLCK;
+ rlock->fl_start = smb_lock->start;
+ rlock->fl_end = smb_lock->end;
- rc = vfs_lock_file(filp, F_SETLK, rlock, NULL);
- if (rc)
- pr_err("rollback unlock fail : %d\n", rc);
+ rc = vfs_lock_file(filp, F_SETLK, rlock, NULL);
+ if (rc)
+ pr_err("rollback unlock fail : %d\n", rc);
+ } else {
+ pr_err("rollback unlock alloc failed\n");
+ }
list_del(&smb_lock->llist);
- spin_lock(&work->conn->llist_lock);
- if (!list_empty(&smb_lock->flist))
- list_del(&smb_lock->flist);
- list_del(&smb_lock->clist);
- spin_unlock(&work->conn->llist_lock);
-
- locks_free_lock(smb_lock->fl);
- locks_free_lock(rlock);
+ smb2_free_lock(smb_lock->fl);
+ if (rlock)
+ locks_free_lock(rlock);
kfree(smb_lock);
}
out2:
@@ -7751,23 +10344,44 @@ static int fsctl_copychunk(struct ksmbd_work *work,
cpu_to_le32(ksmbd_server_side_copy_max_total_size());
chunk_count = le32_to_cpu(ci_req->ChunkCount);
- if (chunk_count == 0)
+ /*
+ * ChunkCount=0 is the standard SMB2 "query my copy limits" request
+ * (no data copied) -- but macOS Finder's Cmd+D duplicate sends
+ * FSCTL_SRV_COPYCHUNK with ChunkCount=0 meaning "copy the whole
+ * file", relying on the AAPL-negotiated server to do a full copy
+ * instead. Keep the standard no-op behavior for everyone else.
+ *
+ * Gate on the TIME_MACHINE share flag, not just conn->is_aapl:
+ * that flag alone has ambiguous provenance -- the pre-existing
+ * narrow UniqueId=0 path can also set it on ordinary,
+ * non-Time-Machine shares, and this series' stated design keeps
+ * every AAPL-driven behavior opt-in per share.
+ */
+ if (chunk_count == 0 &&
+ !(work->conn->is_aapl &&
+ test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_TIME_MACHINE)))
goto out;
total_size_written = 0;
+ i = 0;
- /* verify the SRV_COPYCHUNK_COPY packet */
- if (chunk_count > ksmbd_server_side_copy_max_chunk_count() ||
- input_count < struct_size(ci_req, Chunks, chunk_count)) {
- rsp->hdr.Status = STATUS_INVALID_PARAMETER;
- return -EINVAL;
- }
+ if (chunk_count) {
+ /* verify the SRV_COPYCHUNK_COPY packet */
+ if (chunk_count > ksmbd_server_side_copy_max_chunk_count() ||
+ input_count < struct_size(ci_req, Chunks, chunk_count)) {
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ return -EINVAL;
+ }
- chunks = &ci_req->Chunks[0];
- for (i = 0; i < chunk_count; i++) {
- if (le32_to_cpu(chunks[i].Length) == 0 ||
- le32_to_cpu(chunks[i].Length) > ksmbd_server_side_copy_max_chunk_size())
- break;
- total_size_written += le32_to_cpu(chunks[i].Length);
+ chunks = &ci_req->Chunks[0];
+ for (i = 0; i < chunk_count; i++) {
+ if (le32_to_cpu(chunks[i].Length) == 0 ||
+ le32_to_cpu(chunks[i].Length) > ksmbd_server_side_copy_max_chunk_size())
+ break;
+ total_size_written += le32_to_cpu(chunks[i].Length);
+ }
+ } else {
+ chunks = &ci_req->Chunks[0];
}
if (i < chunk_count ||
@@ -7793,9 +10407,9 @@ static int fsctl_copychunk(struct ksmbd_work *work,
/*
* FILE_READ_DATA should only be included in
- * the FSCTL_COPYCHUNK case
+ * the FSCTL_SRV_COPYCHUNK case
*/
- if (cnt_code == FSCTL_COPYCHUNK &&
+ if (cnt_code == FSCTL_SRV_COPYCHUNK &&
!(dst_fp->daccess & (FILE_READ_DATA_LE | FILE_GENERIC_READ_LE))) {
rsp->hdr.Status = STATUS_ACCESS_DENIED;
goto out;
@@ -7809,7 +10423,7 @@ static int fsctl_copychunk(struct ksmbd_work *work,
if (ret < 0) {
if (ret == -EACCES)
rsp->hdr.Status = STATUS_ACCESS_DENIED;
- if (ret == -EAGAIN)
+ else if (ret == -EAGAIN)
rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
else if (ret == -EBADF)
rsp->hdr.Status = STATUS_INVALID_HANDLE;
@@ -7982,7 +10596,7 @@ static int fsctl_validate_negotiate_info(struct ksmbd_conn *conn,
goto err_out;
}
- if (strncmp(neg_req->Guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE)) {
+ if (memcmp(neg_req->Guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE)) {
ret = -EINVAL;
goto err_out;
}
@@ -8015,8 +10629,6 @@ static int fsctl_query_allocated_ranges(struct ksmbd_work *work, u64 id,
int ret = 0;
*out_count = 0;
- if (in_count == 0)
- return -EINVAL;
start = le64_to_cpu(qar_req->file_offset);
length = le64_to_cpu(qar_req->length);
@@ -8028,11 +10640,28 @@ static int fsctl_query_allocated_ranges(struct ksmbd_work *work, u64 id,
if (!fp)
return -ENOENT;
- ret = ksmbd_vfs_fqar_lseek(fp, start, length,
- qar_rsp, in_count, out_count);
+ if (!(fp->daccess & FILE_READ_DATA_LE)) {
+ ret = -EACCES;
+ goto out;
+ }
+
+ if (!in_count) {
+ struct file_allocated_range_buffer range;
+
+ ret = ksmbd_vfs_query_allocated_ranges(fp, start, length,
+ &range, 1, out_count);
+ if ((!ret || ret == -E2BIG) && *out_count)
+ ret = -ENOSPC;
+ *out_count = 0;
+ } else {
+ ret = ksmbd_vfs_query_allocated_ranges(fp, start, length,
+ qar_rsp, in_count,
+ out_count);
+ }
if (ret && ret != -E2BIG)
*out_count = 0;
+out:
ksmbd_fd_put(work, fp);
return ret;
}
@@ -8090,31 +10719,62 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
int ret = 0;
__le32 old_fattr;
+ if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB, "User does not have write permission\n");
+ return -EACCES;
+ }
+
fp = ksmbd_lookup_fd_fast(work, id);
if (!fp)
return -ENOENT;
+
+ if (S_ISDIR(file_inode(fp->filp)->i_mode)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!(fp->daccess & (FILE_WRITE_DATA_LE | FILE_APPEND_DATA_LE |
+ FILE_WRITE_ATTRIBUTES_LE))) {
+ ret = -EACCES;
+ goto out;
+ }
+
idmap = file_mnt_idmap(fp->filp);
old_fattr = fp->f_ci->m_fattr;
+ if (!sparse->SetSparse &&
+ (old_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE)) {
+ ret = ksmbd_vfs_zero_holes(fp);
+ if (ret)
+ goto out;
+ }
+
if (sparse->SetSparse)
fp->f_ci->m_fattr |= FILE_ATTRIBUTE_SPARSE_FILE_LE;
else
fp->f_ci->m_fattr &= ~FILE_ATTRIBUTE_SPARSE_FILE_LE;
- if (fp->f_ci->m_fattr != old_fattr &&
- test_share_config_flag(work->tcon->share_conf,
- KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) {
- struct xattr_dos_attrib da;
+ if (fp->f_ci->m_fattr != old_fattr) {
+ const struct cred *saved_cred;
+ struct xattr_dos_attrib da = {0};
ret = ksmbd_vfs_get_dos_attrib_xattr(idmap,
fp->filp->f_path.dentry, &da);
- if (ret <= 0)
- goto out;
+ if (ret <= 0) {
+ da.version = 4;
+ da.itime = fp->itime;
+ da.create_time = fp->create_time;
+ da.flags = XATTR_DOSINFO_CREATE_TIME |
+ XATTR_DOSINFO_ITIME;
+ }
da.attr = le32_to_cpu(fp->f_ci->m_fattr);
+ da.flags |= XATTR_DOSINFO_ATTRIB;
+ saved_cred = override_creds(fp->filp->f_cred);
ret = ksmbd_vfs_set_dos_attrib_xattr(idmap,
&fp->filp->f_path,
&da, true);
+ revert_creds(saved_cred);
if (ret)
fp->f_ci->m_fattr = old_fattr;
}
@@ -8153,28 +10813,35 @@ int smb2_ioctl(struct ksmbd_work *work)
struct smb2_ioctl_req *req;
struct smb2_ioctl_rsp *rsp;
unsigned int cnt_code, nbytes = 0, out_buf_len, in_buf_len;
- u64 id = KSMBD_NO_FID;
+ u64 id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
struct ksmbd_conn *conn = work->conn;
int ret = 0;
char *buffer;
+ bool no_fileid_ioctl = false;
+ bool chseq_err = false;
ksmbd_debug(SMB, "Received smb2 ioctl request\n");
if (work->next_smb2_rcv_hdr_off) {
req = ksmbd_req_buf_next(work);
rsp = ksmbd_resp_buf_next(work);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
if (!has_file_id(req->VolatileFileId)) {
ksmbd_debug(SMB, "Compound request set FID = %llu\n",
work->compound_fid);
id = work->compound_fid;
+ pid = work->compound_pfid;
}
} else {
req = smb_get_msg(work->request_buf);
rsp = smb_get_msg(work->response_buf);
}
- if (!has_file_id(id))
+ if (!has_file_id(id)) {
id = req->VolatileFileId;
+ pid = req->PersistentFileId;
+ }
if (req->Flags != cpu_to_le32(SMB2_0_IOCTL_IS_FSCTL)) {
ret = -EOPNOTSUPP;
@@ -8184,8 +10851,44 @@ int smb2_ioctl(struct ksmbd_work *work)
buffer = (char *)req + le32_to_cpu(req->InputOffset);
cnt_code = le32_to_cpu(req->CtlCode);
- ret = smb2_calc_max_out_buf_len(work, 48,
- le32_to_cpu(req->MaxOutputResponse));
+ switch (cnt_code) {
+ case FSCTL_DFS_GET_REFERRALS:
+ case FSCTL_DFS_GET_REFERRALS_EX:
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ case FSCTL_PIPE_WAIT:
+ case FSCTL_PIPE_TRANSCEIVE:
+ no_fileid_ioctl = true;
+ break;
+ default:
+ break;
+ }
+
+ if (!no_fileid_ioctl && has_file_id(id)) {
+ struct ksmbd_file *fp;
+
+ fp = ksmbd_lookup_fd_slow(work, id, pid);
+ if (!fp) {
+ if (cnt_code == FSCTL_DUPLICATE_EXTENTS_TO_FILE) {
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ goto out2;
+ }
+ ret = -ENOENT;
+ goto out;
+ }
+
+ ret = smb2_set_request_open(work, fp, &req->hdr, true, false);
+ ksmbd_fd_put(work, fp);
+ if (ret) {
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ chseq_err = true;
+ goto out;
+ }
+ }
+
+ ret = smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_ioctl_rsp, Buffer),
+ le32_to_cpu(req->MaxOutputResponse));
if (ret < 0) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
goto out;
@@ -8194,15 +10897,110 @@ int smb2_ioctl(struct ksmbd_work *work)
in_buf_len = le32_to_cpu(req->InputCount);
switch (cnt_code) {
+ case FSCTL_SRV_ENUM_SNAPS: {
+ struct srv_snapshot_array *snap_rsp;
+ struct ksmbd_file *fp;
+
+ if (out_buf_len < sizeof(*snap_rsp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp) {
+ ret = -ENOENT;
+ goto out;
+ }
+ ksmbd_fd_put(work, fp);
+
+ snap_rsp = (struct srv_snapshot_array *)rsp->Buffer;
+ snap_rsp->NumberOfSnapShots = 0;
+ snap_rsp->NumberOfSnapShotsReturned = 0;
+ snap_rsp->SnapShotArraySize = cpu_to_le32(2);
+ snap_rsp->Reserved = 0;
+ nbytes = sizeof(*snap_rsp);
+ break;
+ }
case FSCTL_DFS_GET_REFERRALS:
case FSCTL_DFS_GET_REFERRALS_EX:
/* Not support DFS yet */
ret = -EOPNOTSUPP;
rsp->hdr.Status = STATUS_FS_DRIVER_REQUIRED;
goto out2;
+ case FSCTL_GET_COMPRESSION: {
+ struct compress_ioctl *cmpr_rsp;
+ struct ksmbd_file *fp;
+ u16 fmt;
+
+ if (out_buf_len < sizeof(struct compress_ioctl)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ ret = ksmbd_vfs_get_compression(fp, &fmt);
+ ksmbd_fd_put(work, fp);
+ if (ret < 0)
+ goto out;
+
+ cmpr_rsp = (struct compress_ioctl *)&rsp->Buffer[0];
+ cmpr_rsp->CompressionState = cpu_to_le16(fmt);
+ nbytes = sizeof(struct compress_ioctl);
+ rsp->PersistentFileId = req->PersistentFileId;
+ rsp->VolatileFileId = req->VolatileFileId;
+ break;
+ }
+ case FSCTL_SET_COMPRESSION: {
+ struct compress_ioctl *cmpr_req;
+ struct ksmbd_file *fp;
+
+ if (in_buf_len < sizeof(struct compress_ioctl)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB, "User does not have write permission\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ cmpr_req = (struct compress_ioctl *)buffer;
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ ret = ksmbd_vfs_set_compression(work, fp, le16_to_cpu(cmpr_req->CompressionState));
+ ksmbd_fd_put(work, fp);
+ if (ret)
+ goto out;
+ break;
+ }
case FSCTL_CREATE_OR_GET_OBJECT_ID:
{
struct file_object_buf_type1_ioctl_rsp *obj_buf;
+ struct ksmbd_file *fp;
+
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp) {
+ ret = -EBADF;
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ goto out2;
+ }
+
+ if (out_buf_len < sizeof(struct file_object_buf_type1_ioctl_rsp)) {
+ ksmbd_fd_put(work, fp);
+ ret = -EINVAL;
+ goto out;
+ }
+ ksmbd_fd_put(work, fp);
nbytes = sizeof(struct file_object_buf_type1_ioctl_rsp);
obj_buf = (struct file_object_buf_type1_ioctl_rsp *)
@@ -8252,12 +11050,18 @@ int smb2_ioctl(struct ksmbd_work *work)
rsp->VolatileFileId = SMB2_NO_FID;
break;
case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ if (req->PersistentFileId != SMB2_NO_FID ||
+ req->VolatileFileId != SMB2_NO_FID) {
+ ret = -EINVAL;
+ goto out;
+ }
+
ret = fsctl_query_iface_info_ioctl(conn, rsp, out_buf_len);
if (ret < 0)
goto out;
nbytes = ret;
break;
- case FSCTL_REQUEST_RESUME_KEY:
+ case FSCTL_SRV_REQUEST_RESUME_KEY:
if (out_buf_len < sizeof(struct resume_key_ioctl_rsp)) {
ret = -EINVAL;
goto out;
@@ -8271,8 +11075,8 @@ int smb2_ioctl(struct ksmbd_work *work)
rsp->VolatileFileId = req->VolatileFileId;
nbytes = sizeof(struct resume_key_ioctl_rsp);
break;
- case FSCTL_COPYCHUNK:
- case FSCTL_COPYCHUNK_WRITE:
+ case FSCTL_SRV_COPYCHUNK:
+ case FSCTL_SRV_COPYCHUNK_WRITE:
if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
ksmbd_debug(SMB,
"User does not have write permission\n");
@@ -8280,7 +11084,7 @@ int smb2_ioctl(struct ksmbd_work *work)
goto out;
}
- if (in_buf_len <= sizeof(struct copychunk_ioctl_req)) {
+ if (in_buf_len < offsetof(struct copychunk_ioctl_req, Chunks)) {
ret = -EINVAL;
goto out;
}
@@ -8302,15 +11106,21 @@ int smb2_ioctl(struct ksmbd_work *work)
rsp);
break;
case FSCTL_SET_SPARSE:
- if (in_buf_len < sizeof(struct file_sparse)) {
+ {
+ struct file_sparse sparse = {0};
+
+ if (in_buf_len && in_buf_len < sizeof(struct file_sparse)) {
ret = -EINVAL;
goto out;
}
- ret = fsctl_set_sparse(work, id, (struct file_sparse *)buffer);
+ *(u8 *)&sparse = 1;
+ ret = fsctl_set_sparse(work, id, in_buf_len ?
+ (struct file_sparse *)buffer : &sparse);
if (ret < 0)
goto out;
break;
+ }
case FSCTL_SET_ZERO_DATA:
{
struct file_zero_data_information *zero_data;
@@ -8347,13 +11157,103 @@ int smb2_ioctl(struct ksmbd_work *work)
goto out;
}
+ if (!(fp->daccess & FILE_WRITE_DATA_LE)) {
+ ksmbd_fd_put(work, fp);
+ ret = -EACCES;
+ goto out;
+ }
+
ret = ksmbd_vfs_zero_data(work, fp, off, len);
ksmbd_fd_put(work, fp);
- if (ret < 0)
+ if (ret == -EAGAIN) {
+ rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
+ ret = 0;
+ goto out;
+ } else if (ret < 0) {
goto out;
+ }
}
break;
}
+ case FSCTL_FILE_LEVEL_TRIM:
+ {
+ struct file_level_trim *trim_req;
+ struct file_level_trim_output *trim_rsp;
+ struct ksmbd_file *fp;
+ u32 i, num_ranges;
+
+ if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB,
+ "User does not have write permission\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ if (in_buf_len < offsetof(struct file_level_trim, Ranges)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (out_buf_len < sizeof(struct file_level_trim_output)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ trim_req = (struct file_level_trim *)buffer;
+ num_ranges = le32_to_cpu(trim_req->NumRanges);
+ if (num_ranges >
+ (in_buf_len - offsetof(struct file_level_trim, Ranges)) /
+ sizeof(struct file_level_trim_range)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (!(fp->daccess & FILE_WRITE_DATA_LE)) {
+ ksmbd_fd_put(work, fp);
+ ret = -EACCES;
+ goto out;
+ }
+
+ trim_rsp = (struct file_level_trim_output *)&rsp->Buffer[0];
+ trim_rsp->NumRangesProcessed = 0;
+ for (i = 0; i < num_ranges; i++) {
+ loff_t off = le64_to_cpu(trim_req->Ranges[i].Offset);
+ loff_t len = le64_to_cpu(trim_req->Ranges[i].Length);
+
+ if (off < 0 || len < 0) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (!len) {
+ trim_rsp->NumRangesProcessed =
+ cpu_to_le32(i + 1);
+ continue;
+ }
+
+ ret = ksmbd_vfs_trim_data(work, fp, off, len);
+ if (ret)
+ break;
+ trim_rsp->NumRangesProcessed = cpu_to_le32(i + 1);
+ }
+ ksmbd_fd_put(work, fp);
+ if (ret == -EAGAIN) {
+ rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
+ ret = 0;
+ goto out;
+ } else if (ret < 0) {
+ goto out;
+ }
+
+ nbytes = sizeof(struct file_level_trim_output);
+ break;
+ }
case FSCTL_QUERY_ALLOCATED_RANGES:
if (in_buf_len < sizeof(struct file_allocated_range_buffer)) {
ret = -EINVAL;
@@ -8379,6 +11279,11 @@ int smb2_ioctl(struct ksmbd_work *work)
struct reparse_data_buffer *reparse_ptr;
struct ksmbd_file *fp;
+ if (out_buf_len < sizeof(struct reparse_data_buffer)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
reparse_ptr = (struct reparse_data_buffer *)&rsp->Buffer[0];
fp = ksmbd_lookup_fd_fast(work, id);
if (!fp) {
@@ -8411,35 +11316,64 @@ int smb2_ioctl(struct ksmbd_work *work)
dup_ext->PersistentFileHandle);
if (!fp_in) {
pr_err("not found file handle in duplicate extent to file\n");
- ret = -ENOENT;
- goto out;
+ ret = -EBADF;
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+ goto out2;
}
fp_out = ksmbd_lookup_fd_fast(work, id);
if (!fp_out) {
pr_err("not found fp\n");
- ret = -ENOENT;
+ ret = -EBADF;
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ ksmbd_fd_put(work, fp_in);
+ goto out2;
+ }
+
+ if (!test_tree_conn_flag(work->tcon,
+ KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ret = -EACCES;
+ goto dup_ext_out;
+ }
+
+ if (!(fp_out->daccess & FILE_WRITE_DATA_LE)) {
+ ret = -EACCES;
+ goto dup_ext_out;
+ }
+ if (!(fp_in->daccess & FILE_READ_DATA_LE)) {
+ ret = -EACCES;
goto dup_ext_out;
}
src_off = le64_to_cpu(dup_ext->SourceFileOffset);
dst_off = le64_to_cpu(dup_ext->TargetFileOffset);
length = le64_to_cpu(dup_ext->ByteCount);
- /*
- * XXX: It is not clear if FSCTL_DUPLICATE_EXTENTS_TO_FILE
- * should fall back to vfs_copy_file_range(). This could be
- * beneficial when re-exporting nfs/smb mount, but note that
- * this can result in partial copy that returns an error status.
- * If/when FSCTL_DUPLICATE_EXTENTS_TO_FILE_EX is implemented,
- * fall back to vfs_copy_file_range(), should be avoided when
- * the flag DUPLICATE_EXTENTS_DATA_EX_SOURCE_ATOMIC is set.
- */
- cloned = vfs_clone_file_range(fp_in->filp, src_off,
- fp_out->filp, dst_off, length, 0);
- if (cloned == -EXDEV || cloned == -EOPNOTSUPP) {
+ if (src_off < 0 || dst_off < 0 || length < 0 ||
+ src_off + length < src_off || dst_off + length < dst_off) {
+ ret = -EINVAL;
+ goto dup_ext_out;
+ }
+ if (src_off + length > i_size_read(file_inode(fp_in->filp))) {
+ ret = -EOPNOTSUPP;
+ goto dup_ext_out;
+ }
+ if (dst_off + length > i_size_read(file_inode(fp_out->filp)))
+ goto dup_ext_out;
+ if ((fp_in->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE) &&
+ !(fp_out->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE)) {
ret = -EOPNOTSUPP;
goto dup_ext_out;
- } else if (cloned != length) {
+ }
+ if (file_inode(fp_in->filp) == file_inode(fp_out->filp) &&
+ dst_off + length > src_off &&
+ dst_off < src_off + length) {
+ ret = -EOPNOTSUPP;
+ goto dup_ext_out;
+ }
+
+ cloned = vfs_clone_file_range(fp_in->filp, src_off,
+ fp_out->filp, dst_off, length, 0);
+ if (cloned != length) {
cloned = vfs_copy_file_range(fp_in->filp, src_off,
fp_out->filp, dst_off,
length, 0);
@@ -8487,7 +11421,7 @@ out:
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
else if (ret == -ENOSPC)
rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL;
- else if (ret < 0 || rsp->hdr.Status == 0)
+ else if (!chseq_err && (ret < 0 || rsp->hdr.Status == 0))
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
out2:
@@ -8507,11 +11441,10 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
struct smb2_oplock_break *rsp;
struct ksmbd_file *fp;
struct oplock_info *opinfo = NULL;
- __le32 err = 0;
- int ret = 0;
+ __le32 status = STATUS_SUCCESS;
+ int ret;
u64 volatile_id, persistent_id;
char req_oplevel = 0, rsp_oplevel = 0;
- unsigned int oplock_change_type;
WORK_BUFFERS(work, req, rsp);
@@ -8528,6 +11461,14 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
return;
}
+ ret = smb2_set_request_open(work, fp, &req->hdr, false, false);
+ if (ret) {
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, fp);
+ return;
+ }
+
opinfo = opinfo_get(fp);
if (!opinfo) {
pr_err("unexpected null oplock_info\n");
@@ -8537,71 +11478,74 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
return;
}
- if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE) {
- rsp->hdr.Status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ if (opinfo->op_state != OPLOCK_ACK_WAIT) {
+ ksmbd_debug(SMB, "unexpected oplock state 0x%x\n",
+ opinfo->op_state);
+ if (smb3_hdr_replay(&req->hdr) &&
+ opinfo->op_state == OPLOCK_STATE_NONE) {
+ rsp->StructureSize = cpu_to_le16(24);
+ rsp->OplockLevel = opinfo->level;
+ rsp->Reserved = 0;
+ rsp->Reserved2 = 0;
+ rsp->VolatileFid = volatile_id;
+ rsp->PersistentFid = persistent_id;
+ ret = ksmbd_iov_pin_rsp(work, rsp,
+ sizeof(struct smb2_oplock_break));
+ if (ret)
+ ksmbd_debug(SMB,
+ "failed to pin replayed oplock break response: %d\n",
+ ret);
+ goto out_no_state_change;
+ }
+ if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE)
+ status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ else
+ status = STATUS_INVALID_DEVICE_STATE;
goto err_out;
}
- if (opinfo->op_state == OPLOCK_STATE_NONE) {
- ksmbd_debug(SMB, "unexpected oplock state 0x%x\n", opinfo->op_state);
- rsp->hdr.Status = STATUS_UNSUCCESSFUL;
+ if (req_oplevel == SMB2_OPLOCK_LEVEL_LEASE) {
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ status = STATUS_INVALID_PARAMETER;
goto err_out;
}
- if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
- opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
- (req_oplevel != SMB2_OPLOCK_LEVEL_II &&
- req_oplevel != SMB2_OPLOCK_LEVEL_NONE)) {
- err = STATUS_INVALID_OPLOCK_PROTOCOL;
- oplock_change_type = OPLOCK_WRITE_TO_NONE;
- } else if (opinfo->level == SMB2_OPLOCK_LEVEL_II &&
- req_oplevel != SMB2_OPLOCK_LEVEL_NONE) {
- err = STATUS_INVALID_OPLOCK_PROTOCOL;
- oplock_change_type = OPLOCK_READ_TO_NONE;
- } else if (req_oplevel == SMB2_OPLOCK_LEVEL_II ||
- req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
- err = STATUS_INVALID_DEVICE_STATE;
- if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
- opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
- req_oplevel == SMB2_OPLOCK_LEVEL_II) {
- oplock_change_type = OPLOCK_WRITE_TO_READ;
- } else if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
- opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
- req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
- oplock_change_type = OPLOCK_WRITE_TO_NONE;
- } else if (opinfo->level == SMB2_OPLOCK_LEVEL_II &&
- req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
- oplock_change_type = OPLOCK_READ_TO_NONE;
- } else {
- oplock_change_type = 0;
- }
- } else {
- oplock_change_type = 0;
+ if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE) {
+ status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto err_out;
}
- switch (oplock_change_type) {
- case OPLOCK_WRITE_TO_READ:
- ret = opinfo_write_to_read(opinfo);
- rsp_oplevel = SMB2_OPLOCK_LEVEL_II;
- break;
- case OPLOCK_WRITE_TO_NONE:
- ret = opinfo_write_to_none(opinfo);
- rsp_oplevel = SMB2_OPLOCK_LEVEL_NONE;
- break;
- case OPLOCK_READ_TO_NONE:
- ret = opinfo_read_to_none(opinfo);
- rsp_oplevel = SMB2_OPLOCK_LEVEL_NONE;
- break;
- default:
- pr_err("unknown oplock change 0x%x -> 0x%x\n",
- opinfo->level, rsp_oplevel);
+ if (opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE &&
+ req_oplevel != SMB2_OPLOCK_LEVEL_II &&
+ req_oplevel != SMB2_OPLOCK_LEVEL_NONE) {
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto err_out;
}
- if (ret < 0) {
- rsp->hdr.Status = err;
+ if (opinfo->level == SMB2_OPLOCK_LEVEL_BATCH &&
+ req_oplevel != SMB2_OPLOCK_LEVEL_II &&
+ req_oplevel != SMB2_OPLOCK_LEVEL_NONE &&
+ req_oplevel != SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto err_out;
+ }
+
+ if (opinfo->level == SMB2_OPLOCK_LEVEL_II &&
+ req_oplevel != SMB2_OPLOCK_LEVEL_NONE) {
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ status = STATUS_INVALID_OPLOCK_PROTOCOL;
goto err_out;
}
+ if (req_oplevel == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
+ rsp_oplevel = SMB2_OPLOCK_LEVEL_NONE;
+ else
+ rsp_oplevel = req_oplevel;
+
+ opinfo->level = rsp_oplevel;
+
rsp->StructureSize = cpu_to_le16(24);
rsp->OplockLevel = rsp_oplevel;
rsp->Reserved = 0;
@@ -8609,27 +11553,37 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
rsp->VolatileFid = volatile_id;
rsp->PersistentFid = persistent_id;
ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_oplock_break));
- if (ret) {
+ if (ret)
+ ksmbd_debug(SMB, "failed to pin oplock break response: %d\n",
+ ret);
+ goto out;
+
err_out:
- smb2_set_err_rsp(work);
- }
+ rsp->hdr.Status = status;
+ smb2_set_err_rsp(work);
- opinfo->op_state = OPLOCK_STATE_NONE;
+out:
+ spin_lock(&opinfo->state_lock);
+ if (opinfo->op_state != OPLOCK_CLOSING)
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ spin_unlock(&opinfo->state_lock);
wake_up_interruptible_all(&opinfo->oplock_q);
+out_no_state_change:
opinfo_put(opinfo);
ksmbd_fd_put(work, fp);
}
-static int check_lease_state(struct lease *lease, __le32 req_state)
+static bool smb2_lease_state_valid(__le32 state)
{
- if ((lease->new_state ==
- (SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE)) &&
- !(req_state & SMB2_LEASE_WRITE_CACHING_LE)) {
- lease->new_state = req_state;
- return 0;
- }
+ return !(state & ~(SMB2_LEASE_READ_CACHING_LE |
+ SMB2_LEASE_HANDLE_CACHING_LE |
+ SMB2_LEASE_WRITE_CACHING_LE));
+}
- if (lease->new_state == req_state)
+static int check_lease_state(struct lease *lease, __le32 req_state)
+{
+ if (smb2_lease_state_valid(req_state) &&
+ !(req_state & ~lease->new_state))
return 0;
return 1;
@@ -8647,9 +11601,7 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
struct smb2_lease_ack *req;
struct smb2_lease_ack *rsp;
struct oplock_info *opinfo;
- __le32 err = 0;
int ret = 0;
- unsigned int lease_change_type;
__le32 lease_state;
struct lease *lease;
@@ -8669,84 +11621,31 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
if (opinfo->op_state == OPLOCK_STATE_NONE) {
pr_err("unexpected lease break state 0x%x\n",
opinfo->op_state);
+ if (smb3_hdr_replay(&req->hdr))
+ goto replay_rsp;
rsp->hdr.Status = STATUS_UNSUCCESSFUL;
goto err_out;
}
- if (check_lease_state(lease, req->LeaseState)) {
- rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
- ksmbd_debug(OPLOCK,
- "req lease state: 0x%x, expected state: 0x%x\n",
- req->LeaseState, lease->new_state);
- goto err_out;
- }
-
if (!atomic_read(&opinfo->breaking_cnt)) {
+ if (smb3_hdr_replay(&req->hdr))
+ goto replay_rsp;
rsp->hdr.Status = STATUS_UNSUCCESSFUL;
goto err_out;
}
- /* check for bad lease state */
- if (req->LeaseState &
- (~(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE))) {
- err = STATUS_INVALID_OPLOCK_PROTOCOL;
- if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
- lease_change_type = OPLOCK_WRITE_TO_NONE;
- else
- lease_change_type = OPLOCK_READ_TO_NONE;
- ksmbd_debug(OPLOCK, "handle bad lease state 0x%x -> 0x%x\n",
- le32_to_cpu(lease->state),
- le32_to_cpu(req->LeaseState));
- } else if (lease->state == SMB2_LEASE_READ_CACHING_LE &&
- req->LeaseState != SMB2_LEASE_NONE_LE) {
- err = STATUS_INVALID_OPLOCK_PROTOCOL;
- lease_change_type = OPLOCK_READ_TO_NONE;
- ksmbd_debug(OPLOCK, "handle bad lease state 0x%x -> 0x%x\n",
- le32_to_cpu(lease->state),
- le32_to_cpu(req->LeaseState));
- } else {
- /* valid lease state changes */
- err = STATUS_INVALID_DEVICE_STATE;
- if (req->LeaseState == SMB2_LEASE_NONE_LE) {
- if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
- lease_change_type = OPLOCK_WRITE_TO_NONE;
- else
- lease_change_type = OPLOCK_READ_TO_NONE;
- } else if (req->LeaseState & SMB2_LEASE_READ_CACHING_LE) {
- if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
- lease_change_type = OPLOCK_WRITE_TO_READ;
- else
- lease_change_type = OPLOCK_READ_HANDLE_TO_READ;
- } else {
- lease_change_type = 0;
- }
- }
-
- switch (lease_change_type) {
- case OPLOCK_WRITE_TO_READ:
- ret = opinfo_write_to_read(opinfo);
- break;
- case OPLOCK_READ_HANDLE_TO_READ:
- ret = opinfo_read_handle_to_read(opinfo);
- break;
- case OPLOCK_WRITE_TO_NONE:
- ret = opinfo_write_to_none(opinfo);
- break;
- case OPLOCK_READ_TO_NONE:
- ret = opinfo_read_to_none(opinfo);
- break;
- default:
- ksmbd_debug(OPLOCK, "unknown lease change 0x%x -> 0x%x\n",
- le32_to_cpu(lease->state),
- le32_to_cpu(req->LeaseState));
- }
-
- if (ret < 0) {
- rsp->hdr.Status = err;
+ if (check_lease_state(lease, req->LeaseState)) {
+ rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
+ ksmbd_debug(OPLOCK,
+ "req lease state: 0x%x, expected state: 0x%x\n",
+ req->LeaseState, lease->new_state);
goto err_out;
}
- lease_state = lease->state;
+ lease_state = req->LeaseState;
+ lease->state = lease_state;
+ lease->new_state = SMB2_LEASE_NONE_LE;
+ lease_update_oplock_levels(lease);
rsp->StructureSize = cpu_to_le16(36);
rsp->Reserved = 0;
@@ -8755,16 +11654,36 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
rsp->LeaseState = lease_state;
rsp->LeaseDuration = 0;
ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lease_ack));
- if (ret) {
-err_out:
- smb2_set_err_rsp(work);
- }
+ if (ret)
+ goto err_out;
- opinfo->op_state = OPLOCK_STATE_NONE;
+ spin_lock(&opinfo->state_lock);
+ if (opinfo->op_state != OPLOCK_CLOSING)
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ spin_unlock(&opinfo->state_lock);
wake_up_interruptible_all(&opinfo->oplock_q);
- atomic_dec(&opinfo->breaking_cnt);
+ atomic_dec_if_positive(&opinfo->breaking_cnt);
wake_up_interruptible_all(&opinfo->oplock_brk);
opinfo_put(opinfo);
+ return;
+
+replay_rsp:
+ rsp->StructureSize = cpu_to_le16(36);
+ rsp->Reserved = 0;
+ rsp->Flags = 0;
+ memcpy(rsp->LeaseKey, req->LeaseKey, 16);
+ rsp->LeaseState = lease->state;
+ rsp->LeaseDuration = 0;
+ ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lease_ack));
+ if (ret)
+ goto err_out;
+ opinfo_put(opinfo);
+ return;
+
+err_out:
+ smb2_set_err_rsp(work);
+ opinfo_put(opinfo);
+ return;
}
/**
@@ -8800,6 +11719,139 @@ int smb2_oplock_break(struct ksmbd_work *work)
return 0;
}
+/*
+ * Cancel handler for a deferred CHANGE_NOTIFY. Races against
+ * __ksmbd_close_fd()'s notify_pendings drain (vfs_cache.c), which can run
+ * concurrently on a different connection closing the same handle -- only
+ * one of the two may claim and free in_work, so both sides check
+ * list_empty() under fp->f_lock before touching it (list_del_init()
+ * leaves a node empty, so whichever side removes it first is the owner;
+ * the loser must not touch in_work again, since the winner may already be
+ * freeing it).
+ *
+ * smb2_cancel() holds conn->request_lock (a spinlock) for the entire
+ * time it walks conn->async_requests and calls this function -- so this
+ * runs with preemption disabled and must not sleep or re-acquire that
+ * same lock. release_async_work() does both (it takes conn->request_lock
+ * itself, and frees things that can involve sleeping paths), so calling
+ * it from here would self-deadlock the very thread processing the
+ * client's CANCEL command. ksmbd_conn_write() can also sleep (it takes
+ * conn's write mutex). So: do only the non-sleeping, no-relock cleanup
+ * inline here. smb2_cancel() sends and frees the claimed notify after it
+ * drops request_lock, preserving response order for a client CANCEL. The
+ * connection teardown caller has no such post-unlock path, so its wrapper
+ * defers the send and free to a workqueue.
+ */
+struct notify_cancel_ctx {
+ struct work_struct work;
+ struct ksmbd_work *in_work;
+};
+
+static void smb2_send_notify_cancelled(struct ksmbd_work *work)
+{
+ struct smb2_hdr *hdr = smb_get_msg(work->response_buf);
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_session *sess;
+
+ sess = ksmbd_session_lookup(conn, le64_to_cpu(hdr->SessionId));
+ if (sess) {
+ work->sess = sess;
+ if (work->encrypted && sess->enc && conn->ops->encrypt_resp) {
+ conn->ops->encrypt_resp(work);
+ } else if (conn->ops->is_sign_req && conn->ops->set_sign_rsp &&
+ conn->ops->is_sign_req(work,
+ conn->ops->get_cmd_val(work))) {
+ conn->ops->set_sign_rsp(work);
+ }
+ }
+
+ ksmbd_conn_write(work);
+ if (sess) {
+ ksmbd_user_session_put(sess);
+ work->sess = NULL;
+ }
+}
+
+static void smb2_notify_cancel_deferred(struct work_struct *w)
+{
+ struct notify_cancel_ctx *ctx =
+ container_of(w, struct notify_cancel_ctx, work);
+ struct ksmbd_conn *conn = ctx->in_work->conn;
+
+ smb2_complete_notify_cancel(ctx->in_work);
+ kfree(ctx);
+ /*
+ * The connection teardown waits for r_count before destroying
+ * connection sessions and their proc entries.
+ */
+ ksmbd_conn_r_count_dec(conn);
+}
+
+static struct ksmbd_work *smb2_notify_cancel_claim(void **argv)
+{
+ struct ksmbd_work *in_work = (struct ksmbd_work *)argv[0];
+ struct ksmbd_file *fp = (struct ksmbd_file *)argv[1];
+ bool claimed;
+
+ spin_lock(&fp->f_lock);
+ claimed = !list_empty(&in_work->notify_entry);
+ if (claimed)
+ list_del_init(&in_work->notify_entry);
+ spin_unlock(&fp->f_lock);
+
+ if (!claimed)
+ return NULL;
+
+ /* conn->request_lock is held by smb2_cancel() or connection teardown. */
+ in_work->cancel_fn = NULL;
+ kfree(in_work->cancel_argv);
+ in_work->cancel_argv = NULL;
+ return in_work;
+}
+
+static void smb2_complete_notify_cancel(struct ksmbd_work *in_work)
+{
+ struct smb2_hdr *in_hdr = smb_get_msg(in_work->response_buf);
+
+ in_hdr->Status = STATUS_CANCELLED;
+ smb2_send_notify_cancelled(in_work);
+ release_async_work(in_work);
+ ksmbd_free_work_struct(in_work);
+}
+
+static void smb2_notify_cancel_fn(void **argv)
+{
+ struct ksmbd_work *in_work = smb2_notify_cancel_claim(argv);
+ struct ksmbd_conn *conn;
+ struct notify_cancel_ctx *ctx;
+
+ if (!in_work)
+ return;
+ conn = in_work->conn;
+
+ ctx = kmalloc_obj(*ctx, GFP_ATOMIC);
+ if (!ctx) {
+ /* Can't defer the response -- free without sending one. */
+ list_del_init(&in_work->async_request_entry);
+ in_work->asynchronous = false;
+ if (in_work->async_id) {
+ ksmbd_release_id(&conn->async_ida, in_work->async_id);
+ in_work->async_id = 0;
+ }
+ ksmbd_free_work_struct(in_work);
+ return;
+ }
+ ctx->in_work = in_work;
+ INIT_WORK(&ctx->work, smb2_notify_cancel_deferred);
+ /*
+ * This deferred work can outlive the connection handler's receive loop.
+ * Keep teardown from destroying the connection's sessions until the
+ * deferred response has finished using them.
+ */
+ ksmbd_conn_r_count_inc(conn);
+ schedule_work(&ctx->work);
+}
+
/**
* smb2_notify() - handler for smb2 notify request
* @work: smb work containing notify command buffer
@@ -8810,20 +11862,177 @@ int smb2_notify(struct ksmbd_work *work)
{
struct smb2_change_notify_req *req;
struct smb2_change_notify_rsp *rsp;
+ struct ksmbd_work *in_work;
+ struct smb2_hdr *in_hdr;
+ struct ksmbd_file *fp;
ksmbd_debug(SMB, "Received smb2 notify\n");
WORK_BUFFERS(work, req, rsp);
+ if (smb2_compound_has_failed(work, &rsp->hdr))
+ return -EACCES;
+
if (work->next_smb2_rcv_hdr_off && req->hdr.NextCommand) {
rsp->hdr.Status = STATUS_INTERNAL_ERROR;
smb2_set_err_rsp(work);
return -EIO;
}
- smb2_set_err_rsp(work);
- rsp->hdr.Status = STATUS_NOT_IMPLEMENTED;
- return -EOPNOTSUPP;
+ /*
+ * macOS backupd sends CHANGE_NOTIFY with FileId=FFFF...FFFF (share-root
+ * sentinel) to watch for changes on the share root without holding an
+ * open handle. Respond STATUS_PENDING + STATUS_NOTIFY_CLEANUP immediately;
+ * without this, backupd aborts Time Machine setup on STATUS_FILE_CLOSED.
+ */
+ if (req->VolatileFileId == SMB2_NO_FID &&
+ req->PersistentFileId == SMB2_NO_FID) {
+ in_work = ksmbd_alloc_work_struct();
+ if (!in_work || allocate_interim_rsp_buf(in_work)) {
+ if (in_work)
+ ksmbd_free_work_struct(in_work);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+ if (setup_async_work(work, NULL, NULL)) {
+ ksmbd_free_work_struct(in_work);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+ smb2_send_interim_resp(work, STATUS_PENDING);
+ in_work->conn = work->conn;
+ in_hdr = smb_get_msg(in_work->response_buf);
+ memcpy(in_hdr, ksmbd_resp_buf_next(work),
+ __SMB2_HEADER_STRUCTURE_SIZE);
+ in_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
+ in_hdr->Id.AsyncId = cpu_to_le64(work->async_id);
+ smb2_set_err_rsp(in_work);
+ in_hdr->Status = STATUS_NOTIFY_CLEANUP;
+ in_work->async_id = work->async_id;
+ work->async_id = 0;
+ release_async_work(work);
+ if (smb2_send_interim_work(in_work, work, false))
+ ksmbd_debug(SMB, "failed to send notify cleanup\n");
+ ksmbd_free_work_struct(in_work);
+ work->send_no_response = 1;
+ return 0;
+ }
+
+ /*
+ * KSMBD does not implement a real change-notification backend.
+ * Genuine SMB2 servers (and macOS smbfs) never complete a
+ * CHANGE_NOTIFY spontaneously: it is satisfied only by a real
+ * directory change, or with STATUS_NOTIFY_CLEANUP when the watched
+ * handle is closed. Completing it early (e.g. on a timer) makes
+ * Finder treat the cleanup as "directory changed" and re-enumerate
+ * the directory forever, leaving items unopenable. Returning
+ * STATUS_NOT_IMPLEMENTED here (like stock ksmbd) makes macOS smbfs
+ * hard-freeze on unmount, so this must stay deferred.
+ */
+ fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
+ if (!fp) {
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+
+ in_work = ksmbd_alloc_work_struct();
+ if (!in_work || allocate_interim_rsp_buf(in_work)) {
+ if (in_work)
+ ksmbd_free_work_struct(in_work);
+ ksmbd_fd_put(work, fp);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+ /*
+ * in_work is synthetic (not from the normal request-receiving
+ * pipeline), so it has no request_buf of its own. It gets registered
+ * into conn->async_requests below, and smb2_cancel() unconditionally
+ * computes smb_get_msg(iter->request_buf) for every entry in that
+ * list while searching for a match -- give it its own small buffer
+ * (not an alias of response_buf: ksmbd_free_work_struct() kvfree()s
+ * both separately, so aliasing them would double-free) so that stays
+ * a harmless read instead of a near-NULL dereference.
+ */
+ in_work->request_buf = kzalloc(MAX_CIFS_SMALL_BUFFER_SIZE, KSMBD_DEFAULT_GFP);
+ if (!in_work->request_buf) {
+ ksmbd_free_work_struct(in_work);
+ ksmbd_fd_put(work, fp);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+ memcpy(smb_get_msg(in_work->request_buf), req,
+ __SMB2_HEADER_STRUCTURE_SIZE);
+
+ if (setup_async_work(work, NULL, NULL)) {
+ ksmbd_free_work_struct(in_work);
+ ksmbd_fd_put(work, fp);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+
+ smb2_send_interim_resp(work, STATUS_PENDING);
+
+ /* Keep the async IDA alive until the deferred work is released. */
+ in_work->conn = ksmbd_conn_get(work->conn);
+ in_work->owns_conn_ref = true;
+ in_work->encrypted = work->encrypted;
+ in_hdr = smb_get_msg(in_work->response_buf);
+ memcpy(in_hdr, ksmbd_resp_buf_next(work), __SMB2_HEADER_STRUCTURE_SIZE);
+ in_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
+ in_hdr->Id.AsyncId = cpu_to_le64(work->async_id);
+ smb2_set_err_rsp(in_work);
+ in_hdr->Status = STATUS_NOTIFY_CLEANUP;
+
+ /*
+ * Transfer ownership of the async id to in_work; it stays reserved
+ * until in_work is freed after the deferred response is sent on
+ * close, so it can't be reused for an unrelated async response.
+ */
+ in_work->async_id = work->async_id;
+ work->async_id = 0;
+ release_async_work(work);
+
+ /*
+ * work itself is about to be recycled by the normal request-processing
+ * pipeline, so it can't stay the target of a future CANCEL -- register
+ * in_work instead, reusing the same async_id, so a client-sent CANCEL
+ * for this notify actually finds something to cancel instead of
+ * silently doing nothing until the handle eventually closes.
+ */
+ in_work->asynchronous = true;
+ in_work->cancel_argv = kmalloc_array(2, sizeof(void *), KSMBD_DEFAULT_GFP);
+ if (in_work->cancel_argv) {
+ in_work->cancel_argv[0] = in_work;
+ in_work->cancel_argv[1] = fp;
+ in_work->cancel_fn = smb2_notify_cancel_fn;
+ }
+
+ if (!ksmbd_conn_link_async_request(work->conn, in_work)) {
+ kfree(in_work->cancel_argv);
+ in_work->cancel_argv = NULL;
+ in_work->cancel_fn = NULL;
+ in_work->asynchronous = false;
+ ksmbd_fd_put(work, fp);
+ if (smb2_send_interim_work(in_work, work, false))
+ ksmbd_debug(SMB, "failed to send notify cleanup\n");
+ ksmbd_free_work_struct(in_work);
+ work->send_no_response = 1;
+ return 0;
+ }
+
+ spin_lock(&fp->f_lock);
+ list_add_tail(&in_work->notify_entry, &fp->notify_pendings);
+ spin_unlock(&fp->f_lock);
+
+ ksmbd_fd_put(work, fp);
+ work->send_no_response = 1;
+ return 0;
}
/**
@@ -8838,9 +12047,7 @@ bool smb2_is_sign_req(struct ksmbd_work *work, unsigned int command)
struct smb2_hdr *rcv_hdr2 = smb_get_msg(work->request_buf);
if ((rcv_hdr2->Flags & SMB2_FLAGS_SIGNED) &&
- command != SMB2_NEGOTIATE_HE &&
- command != SMB2_SESSION_SETUP_HE &&
- command != SMB2_OPLOCK_BREAK_HE)
+ command != SMB2_NEGOTIATE_HE)
return true;
return false;
@@ -8890,6 +12097,39 @@ int smb2_check_sign_req(struct ksmbd_work *work)
}
/**
+ * smb2_get_sign_rsp_iov() - get the iovecs used to sign a response
+ * @work: work that has the response iovecs
+ * @hdr: SMB2 header of the response
+ * @n_vec: set to the number of iovecs to sign
+ *
+ * Response data may be in another buffer. In this case, the response uses
+ * more than one iovec. Find the iovec that starts with @hdr. Sign this
+ * iovec and all iovecs after it.
+ *
+ * Return: The first iovec to sign.
+ */
+static struct kvec *smb2_get_sign_rsp_iov(struct ksmbd_work *work,
+ struct smb2_hdr *hdr, int *n_vec)
+{
+ int i;
+
+ /*
+ * iov[0] has the RFC1002 message length. It is not part of the SMB2
+ * message, so do not sign it.
+ */
+ for (i = 1; i <= work->iov_idx; i++) {
+ if (work->iov[i].iov_base == hdr) {
+ *n_vec = work->iov_idx - i + 1;
+ return &work->iov[i];
+ }
+ }
+
+ WARN_ON_ONCE(1);
+ *n_vec = 1;
+ return &work->iov[work->iov_idx];
+}
+
+/**
* smb2_set_sign_rsp() - handler for rsp packet sign processing
* @work: smb work containing notify command buffer
*
@@ -8899,18 +12139,13 @@ void smb2_set_sign_rsp(struct ksmbd_work *work)
struct smb2_hdr *hdr;
char signature[SMB2_HMACSHA256_SIZE];
struct kvec *iov;
- int n_vec = 1;
+ int n_vec;
hdr = ksmbd_resp_buf_curr(work);
hdr->Flags |= SMB2_FLAGS_SIGNED;
memset(hdr->Signature, 0, SMB2_SIGNATURE_SIZE);
- if (hdr->Command == SMB2_READ) {
- iov = &work->iov[work->iov_idx - 1];
- n_vec++;
- } else {
- iov = &work->iov[work->iov_idx];
- }
+ iov = smb2_get_sign_rsp_iov(work, hdr, &n_vec);
ksmbd_sign_smb2_pdu(work->conn, work->sess->sess_key, iov, n_vec,
signature);
@@ -8951,9 +12186,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) {
@@ -8966,8 +12205,7 @@ int smb3_check_sign_req(struct ksmbd_work *work)
iov[0].iov_base = (char *)&hdr->ProtocolId;
iov[0].iov_len = len;
- if (ksmbd_sign_smb3_pdu(conn, signing_key, iov, 1, signature))
- return 0;
+ ksmbd_sign_smb3_pdu(conn, signing_key, iov, 1, signature);
if (crypto_memneq(signature, signature_req, SMB2_SIGNATURE_SIZE)) {
pr_err("bad smb2 signature\n");
@@ -8989,13 +12227,14 @@ void smb3_set_sign_rsp(struct ksmbd_work *work)
struct channel *chann;
char signature[SMB2_CMACAES_SIZE];
struct kvec *iov;
- int n_vec = 1;
+ u16 command = conn->ops->get_cmd_val(work);
+ int n_vec;
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);
@@ -9011,16 +12250,10 @@ void smb3_set_sign_rsp(struct ksmbd_work *work)
hdr->Flags |= SMB2_FLAGS_SIGNED;
memset(hdr->Signature, 0, SMB2_SIGNATURE_SIZE);
- if (hdr->Command == SMB2_READ) {
- iov = &work->iov[work->iov_idx - 1];
- n_vec++;
- } else {
- iov = &work->iov[work->iov_idx];
- }
+ iov = smb2_get_sign_rsp_iov(work, hdr, &n_vec);
- if (!ksmbd_sign_smb3_pdu(conn, signing_key, iov, n_vec,
- signature))
- memcpy(hdr->Signature, signature, SMB2_SIGNATURE_SIZE);
+ ksmbd_sign_smb3_pdu(conn, signing_key, iov, n_vec, signature);
+ memcpy(hdr->Signature, signature, SMB2_SIGNATURE_SIZE);
}
/**
@@ -9039,28 +12272,30 @@ void smb3_preauth_hash_rsp(struct ksmbd_work *work)
WORK_BUFFERS(work, req, rsp);
- if (le16_to_cpu(req->Command) == SMB2_NEGOTIATE_HE &&
- conn->preauth_info)
- ksmbd_gen_preauth_integrity_hash(conn, work->response_buf,
- conn->preauth_info->Preauth_HashValue);
+ if (le16_to_cpu(req->Command) == SMB2_NEGOTIATE_HE) {
+ ksmbd_conn_lock(conn);
+ if (conn->preauth_info)
+ ksmbd_gen_preauth_integrity_hash(conn, work->response_buf,
+ conn->preauth_info->Preauth_HashValue);
+ ksmbd_conn_unlock(conn);
+ }
if (le16_to_cpu(rsp->Command) == SMB2_SESSION_SETUP_HE && sess) {
- __u8 *hash_value;
+ ksmbd_conn_lock(conn);
if (conn->binding) {
struct preauth_session *preauth_sess;
preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id);
- if (!preauth_sess)
- return;
- hash_value = preauth_sess->Preauth_HashValue;
- } else {
- hash_value = sess->Preauth_HashValue;
- if (!hash_value)
- return;
+ if (preauth_sess)
+ ksmbd_gen_preauth_integrity_hash(conn,
+ work->response_buf,
+ preauth_sess->Preauth_HashValue);
+ } else if (sess->Preauth_HashValue) {
+ ksmbd_gen_preauth_integrity_hash(conn, work->response_buf,
+ sess->Preauth_HashValue);
}
- ksmbd_gen_preauth_integrity_hash(conn, work->response_buf,
- hash_value);
+ ksmbd_conn_unlock(conn);
}
}
@@ -9113,33 +12348,33 @@ bool smb3_is_transform_hdr(void *buf)
int smb3_decrypt_req(struct ksmbd_work *work)
{
- struct ksmbd_session *sess;
char *buf = work->request_buf;
unsigned int pdu_length = get_rfc1002_len(buf);
struct kvec iov[2];
- int buf_data_size = pdu_length - sizeof(struct smb2_transform_hdr);
+ unsigned int buf_data_size;
struct smb2_transform_hdr *tr_hdr = smb_get_msg(buf);
+ unsigned int original_msg_size;
int rc = 0;
- if (pdu_length < sizeof(struct smb2_transform_hdr) ||
- buf_data_size < sizeof(struct smb2_hdr)) {
+ if (pdu_length < sizeof(struct smb2_transform_hdr)) {
pr_err("Transform message is too small (%u)\n",
pdu_length);
return -ECONNABORTED;
}
- if (buf_data_size < le32_to_cpu(tr_hdr->OriginalMessageSize)) {
- pr_err("Transform message is broken\n");
+ buf_data_size = pdu_length - sizeof(struct smb2_transform_hdr);
+ original_msg_size = le32_to_cpu(tr_hdr->OriginalMessageSize);
+ if (buf_data_size < sizeof(struct smb2_compression_hdr) ||
+ original_msg_size < sizeof(struct smb2_compression_hdr)) {
+ pr_err("Transform message is too small (%u)\n",
+ pdu_length);
return -ECONNABORTED;
}
- sess = ksmbd_session_lookup_all(work->conn, le64_to_cpu(tr_hdr->SessionId));
- if (!sess) {
- pr_err("invalid session id(%llx) in transform header\n",
- le64_to_cpu(tr_hdr->SessionId));
+ if (buf_data_size < original_msg_size) {
+ pr_err("Transform message is broken\n");
return -ECONNABORTED;
}
- ksmbd_user_session_put(sess);
iov[0].iov_base = buf;
iov[0].iov_len = sizeof(struct smb2_transform_hdr) + 4;
@@ -9149,8 +12384,9 @@ int smb3_decrypt_req(struct ksmbd_work *work)
if (rc)
return rc;
- memmove(buf + 4, iov[1].iov_base, buf_data_size);
- *(__be32 *)buf = cpu_to_be32(buf_data_size);
+ /* Drop the AEAD authentication tag from the inner RFC1002 frame. */
+ memmove(buf + 4, iov[1].iov_base, original_msg_size);
+ *(__be32 *)buf = cpu_to_be32(original_msg_size);
return rc;
}
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index 257c6d26df26..ca8e27f7b712 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -23,7 +23,7 @@
#define MAX_SMB2_HDR_SIZE 0x78 /* 4 len + 64 hdr + (2*24 wct) + 2 bct + 2 pad */
#define SMB21_DEFAULT_IOSIZE (1024 * 1024)
-#define SMB3_DEFAULT_TRANS_SIZE (1024 * 1024)
+#define SMB3_DEFAULT_TRANS_SIZE (4 * 1024 * 1024)
#define SMB3_MIN_IOSIZE (64 * 1024)
#define SMB3_MAX_IOSIZE (8 * 1024 * 1024)
#define SMB3_MAX_MSGSIZE (4 * 4096)
@@ -61,11 +61,83 @@ struct preauth_integrity_info {
#define SMB2_SESSION_IN_PROGRESS BIT(0)
#define SMB2_SESSION_VALID BIT(1)
-#define SMB2_SESSION_TIMEOUT (10 * HZ)
-
/* Apple Defined Contexts */
#define SMB2_CREATE_AAPL "AAPL"
+/*
+ * AAPL SMB2 extension -- kAAPL_SERVER_QUERY create context.
+ *
+ * Command code and bitmap values are the existing
+ * SMB2_CRTCTX_AAPL_* constants in fs/smb/common/smb2pdu.h.
+ *
+ * Omitting the model string when reply_bitmap includes
+ * SMB2_CRTCTX_AAPL_MODEL_INFO causes smbfs.kext to enter a broken
+ * disconnect path requiring a reboot.
+ *
+ * Layout: ccontext(16) + Name[4] + Pad[4] + cmd(4) + reserved(4) +
+ * reply_bitmap(8) + server_caps(8) + vol_caps(8)
+ * When MODEL_INFO requested, appended: pad2(4) + model_bytes(4) + UTF-16LE
+ */
+#define SMB2_CREATE_AAPL_LEN 4
+
+/*
+ * Server capability flags (server_caps field) -- SMB2_CRTCTX_AAPL_UNIX_BASED:
+ * prevents macOS Windows-compat mode (question-mark icons).
+ * SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE: enables server-side file copy via
+ * FSCTL_SRV_COPYCHUNK. SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR: inline
+ * FinderInfo per FIND entry, set when client also advertises the bit;
+ * format: EaSize=max_access, ShortName[0..7]=rfork_size,
+ * ShortName[8..23]=FinderInfo(16B), Reserved2=unix_mode.
+ */
+#define AAPL_SERVER_CAPS_KSMBD (SMB2_CRTCTX_AAPL_UNIX_BASED | \
+ SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE | \
+ SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR)
+
+/*
+ * READDIR_ATTR_V2 (SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2, see
+ * fs/smb/common/smb2pdu.h) extends the same inline-FinderInfo mechanism
+ * above with a flags field, confirmed byte-identical to V1 otherwise
+ * against AAPL's actual public client behavior. When a client's own
+ * client_caps requests V2, the server advertises V2 instead of V1 in
+ * its own server_caps reply; V1 and V2 are mutually exclusive on the
+ * wire, not both set together. The wire format's ShortNameLength+Reserved
+ * (ignored in V1) become a single flags field in V2 --
+ * AAPL_READDIR_ATTR_V2_NO_XATTR is the only flag bit currently defined,
+ * signaling the item has no xattrs/streams so the client can skip a
+ * separate query.
+ */
+#define AAPL_READDIR_ATTR_V2_NO_XATTR 0x01
+
+/* Model string: up to 31 ASCII chars */
+#define AAPL_MODEL_MAX_CHARS 31
+#define AAPL_MODEL_UTF16_BYTES (AAPL_MODEL_MAX_CHARS * 2)
+
+/*
+ * Max AAPL response: header(24) + base data(32) + pad2(4) + model_bytes(4)
+ * + model(62), 8-byte aligned: ALIGN(126, 8) = 128 bytes.
+ */
+#define AAPL_RSP_MAX_SIZE 128
+
+/* AAPL server query request (client->server) */
+struct aapl_server_query_req {
+ __le32 cmd;
+ __le32 reserved;
+ __le64 req_bitmap;
+ __le64 client_caps;
+} __packed;
+
+struct create_aapl_rsp {
+ struct create_context_hdr ccontext;
+ __u8 Name[4];
+ __u8 Pad[4];
+ __le32 cmd;
+ __le32 reserved;
+ __le64 reply_bitmap;
+ __le64 server_caps;
+ __le64 vol_caps;
+ /* when MODEL_INFO requested: __le32 pad2; __le32 model_bytes; __le16 model[] */
+} __packed;
+
#define DURABLE_HANDLE_MAX_TIMEOUT 300000
struct create_alloc_size_req {
@@ -83,7 +155,10 @@ struct create_durable_rsp {
} Data;
} __packed;
-/* equivalent of the contents of SMB3.1.1 POSIX open context response */
+/*
+ * See POSIX-SMB2 2.2.14.2.16
+ * Link: https://gitlab.com/samba-team/smb3-posix-spec/-/blob/master/smb3_posix_extensions.md
+ */
struct create_posix_rsp {
struct create_context_hdr ccontext;
__u8 Name[16];
@@ -137,6 +212,7 @@ struct file_sparse {
#define FILE_ALLOCATION_INFORMATION_SIZE 19
#define FILE_END_OF_FILE_INFORMATION_SIZE 20
#define FILE_ALTERNATE_NAME_INFORMATION_SIZE 8
+#define FILE_NORMALIZED_NAME_INFORMATION_SIZE 8
#define FILE_STREAM_INFORMATION_SIZE 32
#define FILE_PIPE_INFORMATION_SIZE 23
#define FILE_PIPE_LOCAL_INFORMATION_SIZE 24
@@ -183,15 +259,6 @@ struct smb2_file_alignment_info {
__le32 AlignmentRequirement;
} __packed;
-struct smb2_file_basic_info { /* data block encoding of response to level 18 */
- __le64 CreationTime; /* Beginning of FILE_BASIC_INFO equivalent */
- __le64 LastAccessTime;
- __le64 LastWriteTime;
- __le64 ChangeTime;
- __le32 Attributes;
- __u32 Pad1; /* End of FILE_BASIC_INFO_INFO equivalent */
-} __packed;
-
struct smb2_file_alt_name_info {
__le32 FileNameLength;
char FileName[];
@@ -205,6 +272,13 @@ struct smb2_file_stream_info {
char StreamName[];
} __packed;
+struct srv_snapshot_array {
+ __le32 NumberOfSnapShots;
+ __le32 NumberOfSnapShotsReturned;
+ __le32 SnapShotArraySize;
+ __le32 Reserved;
+} __packed;
+
struct smb2_file_standard_info {
__le64 AllocationSize;
__le64 EndOfFile;
@@ -218,10 +292,6 @@ struct smb2_file_ea_info {
__le32 EASize;
} __packed;
-struct smb2_file_alloc_info {
- __le64 AllocationSize;
-} __packed;
-
struct smb2_file_disposition_info {
__u8 DeletePending;
} __packed;
@@ -236,9 +306,6 @@ struct smb2_file_mode_info {
__le32 Mode;
} __packed;
-#define COMPRESSION_FORMAT_NONE 0x0000
-#define COMPRESSION_FORMAT_LZNT1 0x0002
-
struct smb2_file_comp_info {
__le64 CompressedFileSize;
__le16 CompressionFormat;
@@ -360,6 +427,7 @@ bool smb3_encryption_negotiated(struct ksmbd_conn *conn);
/* smb2 misc functions */
int ksmbd_smb2_check_message(struct ksmbd_work *work);
+void smb2_complete_request_open(struct ksmbd_work *work);
/* smb2 command handlers */
int smb2_handle_negotiate(struct ksmbd_work *work);
diff --git a/fs/smb/server/smb_common.c b/fs/smb/server/smb_common.c
index 741aabdfcef5..4c2da65510bc 100644
--- a/fs/smb/server/smb_common.c
+++ b/fs/smb/server/smb_common.c
@@ -102,9 +102,6 @@ static const struct {
int version;
const char *string;
} version_strings[] = {
-#ifdef CONFIG_SMB_INSECURE_SERVER
- {SMB1_PROT, SMB1_VERSION_STRING},
-#endif
{SMB2_PROT, SMB20_VERSION_STRING},
{SMB21_PROT, SMB21_VERSION_STRING},
{SMB30_PROT, SMB30_VERSION_STRING},
@@ -167,7 +164,22 @@ int ksmbd_verify_smb_message(struct ksmbd_work *work)
hdr = smb_get_msg(work->request_buf);
if (*(__le32 *)hdr->Protocol == SMB1_PROTO_NUMBER &&
hdr->Command == SMB_COM_NEGOTIATE) {
- work->conn->outstanding_credits++;
+ struct ksmbd_conn *conn = work->conn;
+
+ conn->outstanding_credits++;
+ /*
+ * A legacy SMB1 multi-protocol negotiate occupies sequence
+ * number 0 but does not pass through
+ * ksmbd_smb2_check_message(). Consume it here so that, after
+ * the connection is upgraded to SMB2, the command sequence
+ * window can advance instead of staying pinned at 0.
+ */
+ spin_lock(&conn->credits_lock);
+ if (conn->seq_low == 0) {
+ __clear_bit(0, conn->seq_bitmap);
+ conn->seq_low = 1;
+ }
+ spin_unlock(&conn->credits_lock);
return 0;
}
@@ -188,11 +200,6 @@ bool ksmbd_smb_request(struct ksmbd_conn *conn)
return false;
proto = (__le32 *)smb_get_msg(conn->request_buf);
- if (*proto == SMB2_COMPRESSION_TRANSFORM_ID) {
- pr_err_ratelimited("smb2 compression not support yet");
- return false;
- }
-
if (*proto != SMB1_PROTO_NUMBER &&
*proto != SMB2_PROTO_NUMBER &&
*proto != SMB2_TRANSFORM_PROTO_NUM)
@@ -616,23 +623,46 @@ int ksmbd_smb_negotiate_common(struct ksmbd_work *work, unsigned int command)
struct ksmbd_conn *conn = work->conn;
int ret;
- conn->dialect =
- ksmbd_negotiate_smb_dialect(work->request_buf);
- ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect);
-
if (command == SMB2_NEGOTIATE_HE) {
+ /*
+ * An SMB2 NEGOTIATE is valid for a new connection, or after an
+ * SMB1 multi-protocol negotiate has selected SMB2. Do not allow
+ * a second SMB2 NEGOTIATE to replace connection-wide state
+ * while a session setup is pending. KSMBD_SESS_NEED_RECONNECT
+ * is a transient session state and does not restart transport
+ * negotiation.
+ */
+ ksmbd_conn_lock(conn);
+ if (!ksmbd_conn_new(conn) &&
+ !ksmbd_conn_need_negotiate(conn)) {
+ work->send_no_response = 1;
+ ksmbd_conn_set_exiting(conn);
+ ksmbd_conn_unlock(conn);
+ return 0;
+ }
+
+ conn->dialect =
+ ksmbd_negotiate_smb_dialect(work->request_buf);
+ ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect);
ret = smb2_handle_negotiate(work);
+ ksmbd_conn_unlock(conn);
return ret;
}
if (command == SMB_COM_NEGOTIATE) {
+ ksmbd_conn_lock(conn);
+ conn->dialect =
+ ksmbd_negotiate_smb_dialect(work->request_buf);
+ ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect);
if (__smb2_negotiate(conn)) {
init_smb3_11_server(conn);
- init_smb2_neg_rsp(work);
+ ret = init_smb2_neg_rsp(work);
ksmbd_debug(SMB, "Upgrade to SMB2 negotiation\n");
- return 0;
+ } else {
+ ret = smb_handle_negotiate(work);
}
- return smb_handle_negotiate(work);
+ ksmbd_conn_unlock(conn);
+ return ret;
}
pr_err("Unknown SMB negotiation command: %u\n", command);
diff --git a/fs/smb/server/smb_common.h b/fs/smb/server/smb_common.h
index ca7e3610d074..7b9c5cfcb63b 100644
--- a/fs/smb/server/smb_common.h
+++ b/fs/smb/server/smb_common.h
@@ -90,14 +90,6 @@ struct smb_negotiate_rsp {
__le16 ByteCount;
} __packed;
-struct filesystem_vol_info {
- __le64 VolumeCreationTime;
- __le32 SerialNumber;
- __le32 VolumeLabelSize;
- __le16 Reserved;
- __le16 VolumeLabel[];
-} __packed;
-
#define EXTENDED_INFO_MAGIC 0x43667364 /* Cfsd */
#define STRING_LENGTH 28
@@ -143,7 +135,7 @@ struct file_id_both_directory_info {
struct smb_version_ops {
u16 (*get_cmd_val)(struct ksmbd_work *swork);
- void (*inc_reqs)(unsigned int cmd);
+ void (*inc_reqs)(unsigned int cmd, __le32 status);
int (*init_rsp_hdr)(struct ksmbd_work *swork);
void (*set_rsp_status)(struct ksmbd_work *swork, __le32 err);
int (*allocate_rsp_buf)(struct ksmbd_work *work);
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index 49c2abb29bf5..1fad6ccf3a72 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -27,6 +27,9 @@ static const struct smb_sid creator_owner = {
/* security id for everyone/world system group */
static const struct smb_sid creator_group = {
1, 1, {0, 0, 0, 0, 0, 3}, {cpu_to_le32(1)} };
+/* security id for owner rights */
+static const struct smb_sid sid_owner_rights = {
+ 1, 1, {0, 0, 0, 0, 0, 3}, {cpu_to_le32(4)} };
/* security id for everyone/world system group */
static const struct smb_sid sid_everyone = {
@@ -258,6 +261,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 +283,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 +300,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));
@@ -367,13 +383,14 @@ void free_acl_state(struct posix_acl_state *state)
kfree(state->groups);
}
-static void parse_dacl(struct mnt_idmap *idmap,
- struct smb_acl *pdacl, char *end_of_acl,
- struct smb_sid *pownersid, struct smb_sid *pgrpsid,
- struct smb_fattr *fattr)
+static int parse_dacl(struct mnt_idmap *idmap,
+ struct smb_acl *pdacl, char *end_of_acl,
+ struct smb_sid *pownersid, struct smb_sid *pgrpsid,
+ struct smb_fattr *fattr)
{
int i, ret;
u16 num_aces = 0;
+ u16 dacl_size;
unsigned int acl_size;
char *acl_base;
struct smb_ace **ppace;
@@ -383,13 +400,13 @@ static void parse_dacl(struct mnt_idmap *idmap,
bool owner_found = false, group_found = false, others_found = false;
if (!pdacl)
- return;
+ return 0;
/* validate that we do not go past end of acl */
if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl) ||
end_of_acl < (char *)pdacl + le16_to_cpu(pdacl->size)) {
pr_err("ACL too small to parse DACL\n");
- return;
+ return -EINVAL;
}
ksmbd_debug(SMB, "DACL revision %d size %d num aces %d\n",
@@ -401,27 +418,31 @@ static void parse_dacl(struct mnt_idmap *idmap,
num_aces = le16_to_cpu(pdacl->num_aces);
if (num_aces <= 0)
- return;
+ return 0;
+
+ dacl_size = le16_to_cpu(pdacl->size);
+ if (dacl_size < sizeof(struct smb_acl))
+ return -EINVAL;
- if (num_aces > (le16_to_cpu(pdacl->size) - sizeof(struct smb_acl)) /
+ if (num_aces > (dacl_size - sizeof(struct smb_acl)) /
(offsetof(struct smb_ace, sid) +
offsetof(struct smb_sid, sub_auth) + sizeof(__le16)))
- return;
+ return -EINVAL;
ret = init_acl_state(&acl_state, num_aces);
if (ret)
- return;
+ return ret;
ret = init_acl_state(&default_acl_state, num_aces);
if (ret) {
free_acl_state(&acl_state);
- return;
+ return ret;
}
ppace = kmalloc_objs(struct smb_ace *, num_aces, KSMBD_DEFAULT_GFP);
if (!ppace) {
free_acl_state(&default_acl_state);
free_acl_state(&acl_state);
- return;
+ return -ENOMEM;
}
/*
@@ -430,8 +451,10 @@ static void parse_dacl(struct mnt_idmap *idmap,
* user/group/other have no permissions
*/
for (i = 0; i < num_aces; ++i) {
- if (end_of_acl - acl_base < acl_size)
- break;
+ if (end_of_acl - acl_base < acl_size) {
+ ret = -EINVAL;
+ goto out;
+ }
ppace[i] = (struct smb_ace *)(acl_base + acl_size);
acl_base = (char *)ppace[i];
@@ -444,14 +467,17 @@ static void parse_dacl(struct mnt_idmap *idmap,
(end_of_acl - acl_base <
acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth) ||
(le16_to_cpu(ppace[i]->size) <
- acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth))
- break;
+ acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth)) {
+ ret = -EINVAL;
+ goto out;
+ }
acl_size = le16_to_cpu(ppace[i]->size);
ppace[i]->access_req =
smb_map_generic_desired_access(ppace[i]->access_req);
- if (!(compare_sids(&ppace[i]->sid, &sid_unix_NFS_mode))) {
+ if (ppace[i]->sid.num_subauth >= 3 &&
+ !(compare_sids(&ppace[i]->sid, &sid_unix_NFS_mode))) {
fattr->cf_mode =
le32_to_cpu(ppace[i]->sid.sub_auth[2]);
break;
@@ -502,8 +528,8 @@ static void parse_dacl(struct mnt_idmap *idmap,
temp_fattr.cf_uid = INVALID_UID;
ret = sid_to_id(idmap, &ppace[i]->sid, SIDOWNER, &temp_fattr);
if (ret || uid_eq(temp_fattr.cf_uid, INVALID_UID)) {
- pr_err("%s: Error %d mapping Owner SID to uid\n",
- __func__, ret);
+ pr_err_ratelimited("%s: Error %d mapping Owner SID to uid\n",
+ __func__, ret);
continue;
}
@@ -519,7 +545,6 @@ static void parse_dacl(struct mnt_idmap *idmap,
((acl_mode & 0700) >> 6) | 0004;
}
}
- kfree(ppace);
if (owner_found) {
/* The owner must be set to at least read-only. */
@@ -562,10 +587,12 @@ static void parse_dacl(struct mnt_idmap *idmap,
fattr->cf_acls =
posix_acl_alloc(acl_state.users->n +
acl_state.groups->n + 4, KSMBD_DEFAULT_GFP);
- if (fattr->cf_acls) {
- cf_pace = fattr->cf_acls->a_entries;
- posix_state_to_acl(&acl_state, cf_pace);
+ if (!fattr->cf_acls) {
+ ret = -ENOMEM;
+ goto out;
}
+ cf_pace = fattr->cf_acls->a_entries;
+ posix_state_to_acl(&acl_state, cf_pace);
}
}
@@ -576,25 +603,33 @@ static void parse_dacl(struct mnt_idmap *idmap,
fattr->cf_dacls =
posix_acl_alloc(default_acl_state.users->n +
default_acl_state.groups->n + 4, KSMBD_DEFAULT_GFP);
- if (fattr->cf_dacls) {
- cf_pdace = fattr->cf_dacls->a_entries;
- posix_state_to_acl(&default_acl_state, cf_pdace);
+ if (!fattr->cf_dacls) {
+ ret = -ENOMEM;
+ goto out;
}
+ cf_pdace = fattr->cf_dacls->a_entries;
+ posix_state_to_acl(&default_acl_state, cf_pdace);
}
}
+ ret = 0;
+out:
+ kfree(ppace);
free_acl_state(&acl_state);
free_acl_state(&default_acl_state);
+ return ret;
}
static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap,
struct smb_ace *pndace,
struct smb_fattr *fattr, u16 *num_aces,
- u16 *size, u32 nt_aces_num)
+ u16 *size, u16 existing_nt_aces,
+ bool had_nt_aces)
{
struct posix_acl_entry *pace;
struct smb_sid *sid;
struct smb_ace *ntace;
int i, j;
+ u16 ace_sz;
if (!fattr->cf_acls)
goto posix_default_acl;
@@ -620,14 +655,14 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap,
gid = posix_acl_gid_translate(idmap, pace);
id_to_sid(gid, SIDUNIX_GROUP, sid);
- } else if (pace->e_tag == ACL_OTHER && !nt_aces_num) {
+ } else if (pace->e_tag == ACL_OTHER && !had_nt_aces) {
smb_copy_sid(sid, &sid_everyone);
} else {
kfree(sid);
continue;
}
ntace = pndace;
- for (j = 0; j < nt_aces_num; j++) {
+ for (j = 0; j < existing_nt_aces; j++) {
if (ntace->sid.sub_auth[ntace->sid.num_subauth - 1] ==
sid->sub_auth[sid->num_subauth - 1])
goto pass_same_sid;
@@ -639,8 +674,13 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap,
flags = 0x03;
ntace = (struct smb_ace *)((char *)pndace + *size);
- *size += fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, flags,
+ ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, flags,
pace->e_perm, 0777);
+ if (check_add_overflow(*size, ace_sz, size)) {
+ *size -= ace_sz;
+ kfree(sid);
+ break;
+ }
(*num_aces)++;
if (pace->e_tag == ACL_USER)
ntace->access_req |=
@@ -649,8 +689,13 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap,
if (S_ISDIR(fattr->cf_mode) &&
(pace->e_tag == ACL_USER || pace->e_tag == ACL_GROUP)) {
ntace = (struct smb_ace *)((char *)pndace + *size);
- *size += fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED,
+ ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED,
0x03, pace->e_perm, 0777);
+ if (check_add_overflow(*size, ace_sz, size)) {
+ *size -= ace_sz;
+ kfree(sid);
+ break;
+ }
(*num_aces)++;
if (pace->e_tag == ACL_USER)
ntace->access_req |=
@@ -661,7 +706,7 @@ pass_same_sid:
kfree(sid);
}
- if (nt_aces_num)
+ if (had_nt_aces)
return;
posix_default_acl:
@@ -690,8 +735,13 @@ posix_default_acl:
}
ntace = (struct smb_ace *)((char *)pndace + *size);
- *size += fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, 0x0b,
+ ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, 0x0b,
pace->e_perm, 0777);
+ if (check_add_overflow(*size, ace_sz, size)) {
+ *size -= ace_sz;
+ kfree(sid);
+ break;
+ }
(*num_aces)++;
if (pace->e_tag == ACL_USER)
ntace->access_req |=
@@ -710,6 +760,7 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap,
{
struct smb_ace *ntace, *pndace;
u16 nt_num_aces = le16_to_cpu(nt_dacl->num_aces), num_aces = 0;
+ u16 copied_nt_aces;
unsigned short size = 0;
int i;
@@ -719,23 +770,41 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap,
for (i = 0; i < nt_num_aces; i++) {
unsigned short nt_ace_size;
- if (offsetof(struct smb_ace, access_req) > aces_size)
+ if (aces_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
break;
nt_ace_size = le16_to_cpu(ntace->size);
- if (nt_ace_size > aces_size)
+ if (nt_ace_size > aces_size ||
+ nt_ace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
break;
+ if (ntace->sid.num_subauth == 0 ||
+ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
+ nt_ace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE +
+ sizeof(__le32) *
+ ntace->sid.num_subauth)
+ goto next_ace;
+
memcpy((char *)pndace + size, ntace, nt_ace_size);
- size += nt_ace_size;
+ if (check_add_overflow(size, nt_ace_size, &size)) {
+ size -= nt_ace_size;
+ break;
+ }
+ num_aces++;
+
+next_ace:
aces_size -= nt_ace_size;
ntace = (struct smb_ace *)((char *)ntace + nt_ace_size);
- num_aces++;
}
}
+ copied_nt_aces = num_aces;
set_posix_acl_entries_dacl(idmap, pndace, fattr,
- &num_aces, &size, nt_num_aces);
+ &num_aces, &size, copied_nt_aces,
+ nt_num_aces != 0);
pndacl->num_aces = cpu_to_le16(num_aces);
pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size);
}
@@ -753,7 +822,7 @@ static void set_mode_dacl(struct mnt_idmap *idmap,
if (fattr->cf_acls) {
set_posix_acl_entries_dacl(idmap, pndace, fattr,
- &num_aces, &size, num_aces);
+ &num_aces, &size, num_aces, false);
goto out;
}
@@ -874,9 +943,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;
}
}
@@ -892,9 +961,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;
}
}
@@ -908,13 +977,58 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
if (dacloffset < sizeof(struct smb_ntsd))
return -EINVAL;
- parse_dacl(idmap, dacl_ptr, end_of_acl,
- owner_sid_ptr, group_sid_ptr, fattr);
+ rc = parse_dacl(idmap, dacl_ptr, end_of_acl,
+ owner_sid_ptr, group_sid_ptr, fattr);
+ if (rc)
+ return rc;
}
return 0;
}
+size_t smb_acl_sec_desc_scratch_len(struct smb_fattr *fattr,
+ struct smb_ntsd *ppntsd, int ppntsd_size, int addition_info)
+{
+ size_t len = sizeof(struct smb_ntsd);
+ size_t tmp;
+
+ if (addition_info & OWNER_SECINFO)
+ len += sizeof(struct smb_sid);
+ if (addition_info & GROUP_SECINFO)
+ len += sizeof(struct smb_sid);
+ if (!(addition_info & DACL_SECINFO))
+ return len;
+
+ len += sizeof(struct smb_acl);
+ if (ppntsd && ppntsd_size > 0) {
+ unsigned int dacl_offset = le32_to_cpu(ppntsd->dacloffset);
+
+ if (dacl_offset < ppntsd_size &&
+ check_add_overflow(len, ppntsd_size - dacl_offset, &len))
+ return 0;
+ }
+
+ if (fattr->cf_acls) {
+ if (check_mul_overflow((size_t)fattr->cf_acls->a_count,
+ 2 * sizeof(struct smb_ace), &tmp) ||
+ check_add_overflow(len, tmp, &len))
+ return 0;
+ } else {
+ /* default/minimum DACL */
+ if (check_add_overflow(len, 5 * sizeof(struct smb_ace), &len))
+ return 0;
+ }
+
+ if (fattr->cf_dacls) {
+ if (check_mul_overflow((size_t)fattr->cf_dacls->a_count,
+ sizeof(struct smb_ace), &tmp) ||
+ check_add_overflow(len, tmp, &len))
+ return 0;
+ }
+
+ return len;
+}
+
/* Convert permission bits from mode to equivalent CIFS ACL */
int build_sec_desc(struct mnt_idmap *idmap,
struct smb_ntsd *pntsd, struct smb_ntsd *ppntsd,
@@ -1016,7 +1130,60 @@ static void smb_set_ace(struct smb_ace *ace, const struct smb_sid *sid, u8 type,
ace->flags = flags;
ace->access_req = access_req;
smb_copy_sid(&ace->sid, sid);
- ace->size = cpu_to_le16(1 + 1 + 2 + 4 + 1 + 1 + 6 + (sid->num_subauth * 4));
+ ace->size = cpu_to_le16(1 + 1 + 2 + 4 + 1 + 1 + 6 +
+ (ace->sid.num_subauth * 4));
+}
+
+static int smb_append_inherited_ace(struct smb_ace **ace, int *nt_size,
+ u16 *ace_cnt, const struct smb_sid *sid,
+ u8 type, u8 flags, __le32 access_req)
+{
+ int ace_size;
+
+ smb_set_ace(*ace, sid, type, flags, access_req);
+ ace_size = le16_to_cpu((*ace)->size);
+ /* pdacl->size is __le16 and includes struct smb_acl. */
+ if (check_add_overflow(*nt_size, ace_size, nt_size) ||
+ *nt_size > U16_MAX - (int)sizeof(struct smb_acl))
+ return -EINVAL;
+
+ (*ace_cnt)++;
+ *ace = (struct smb_ace *)((char *)*ace + ace_size);
+ return 0;
+}
+
+static int smb_validate_ntsd_sid(struct smb_ntsd *pntsd, size_t pntsd_size,
+ unsigned int sid_offset, struct smb_sid **sid,
+ size_t *sid_size)
+{
+ size_t sid_end;
+
+ *sid = NULL;
+ *sid_size = 0;
+
+ if (!sid_offset)
+ return 0;
+
+ if (sid_offset < sizeof(struct smb_ntsd) ||
+ check_add_overflow(sid_offset, (size_t)CIFS_SID_BASE_SIZE,
+ &sid_end) ||
+ sid_end > pntsd_size)
+ return -EINVAL;
+
+ *sid = (struct smb_sid *)((char *)pntsd + sid_offset);
+ if ((*sid)->num_subauth > SID_MAX_SUB_AUTHORITIES)
+ return -EINVAL;
+
+ if (check_add_overflow((size_t)CIFS_SID_BASE_SIZE,
+ sizeof(__le32) * (size_t)(*sid)->num_subauth,
+ &sid_end))
+ return -EINVAL;
+
+ if (sid_offset > pntsd_size || sid_end > pntsd_size - sid_offset)
+ return -EINVAL;
+
+ *sid_size = sid_end;
+ return 0;
}
int smb_inherit_dacl(struct ksmbd_conn *conn,
@@ -1031,28 +1198,28 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
struct dentry *parent = path->dentry->d_parent;
struct mnt_idmap *idmap = mnt_idmap(path->mnt);
int inherited_flags = 0, flags = 0, i, nt_size = 0, pdacl_size;
- int rc = 0, pntsd_type, pntsd_size, acl_len, aces_size;
+ int rc = 0, pntsd_type, ppntsd_size, acl_len, aces_size;
unsigned int dacloffset;
size_t dacl_struct_end;
u16 num_aces, ace_cnt = 0;
char *aces_base;
bool is_dir = S_ISDIR(d_inode(path->dentry)->i_mode);
- pntsd_size = ksmbd_vfs_get_sd_xattr(conn, idmap,
+ ppntsd_size = ksmbd_vfs_get_sd_xattr(conn, idmap,
parent, &parent_pntsd);
- if (pntsd_size <= 0)
+ if (ppntsd_size <= 0)
return -ENOENT;
dacloffset = le32_to_cpu(parent_pntsd->dacloffset);
if (!dacloffset ||
check_add_overflow(dacloffset, sizeof(struct smb_acl), &dacl_struct_end) ||
- dacl_struct_end > (size_t)pntsd_size) {
+ dacl_struct_end > (size_t)ppntsd_size) {
rc = -EINVAL;
goto free_parent_pntsd;
}
parent_pdacl = (struct smb_acl *)((char *)parent_pntsd + dacloffset);
- acl_len = pntsd_size - dacloffset;
+ acl_len = ppntsd_size - dacloffset;
num_aces = le16_to_cpu(parent_pdacl->num_aces);
pntsd_type = le16_to_cpu(parent_pntsd->type);
pdacl_size = le16_to_cpu(parent_pdacl->size);
@@ -1062,8 +1229,24 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
goto free_parent_pntsd;
}
- aces_base = kmalloc(sizeof(struct smb_ace) * num_aces * 2,
- KSMBD_DEFAULT_GFP);
+ aces_size = pdacl_size - sizeof(struct smb_acl);
+
+ /*
+ * Validate num_aces against the DACL payload before allocating.
+ * Each ACE must be at least as large as its fixed-size header
+ * (up to the SID base), so num_aces cannot exceed the payload
+ * divided by the minimum ACE size. This mirrors the existing
+ * check in parse_dacl().
+ */
+ if (num_aces > aces_size / (offsetof(struct smb_ace, sid) +
+ offsetof(struct smb_sid, sub_auth) +
+ sizeof(__le16))) {
+ rc = -EINVAL;
+ goto free_parent_pntsd;
+ }
+
+ aces_base = kmalloc_array(num_aces * 2, sizeof(struct smb_ace),
+ KSMBD_DEFAULT_GFP);
if (!aces_base) {
rc = -ENOMEM;
goto free_parent_pntsd;
@@ -1072,7 +1255,6 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
aces = (struct smb_ace *)aces_base;
parent_aces = (struct smb_ace *)((char *)parent_pdacl +
sizeof(struct smb_acl));
- aces_size = acl_len - sizeof(struct smb_acl);
if (pntsd_type & DACL_AUTO_INHERITED)
inherited_flags = INHERITED_ACE;
@@ -1080,11 +1262,20 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
for (i = 0; i < num_aces; i++) {
int pace_size;
- if (offsetof(struct smb_ace, access_req) > aces_size)
+ if (aces_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
break;
pace_size = le16_to_cpu(parent_aces->size);
- if (pace_size > aces_size)
+ if (pace_size > aces_size ||
+ pace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
+ break;
+
+ if (parent_aces->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
+ pace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE +
+ sizeof(__le32) * parent_aces->sid.num_subauth)
break;
aces_size -= pace_size;
@@ -1116,22 +1307,24 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
}
if (is_dir && creator && flags & CONTAINER_INHERIT_ACE) {
- smb_set_ace(aces, psid, parent_aces->type, inherited_flags,
- parent_aces->access_req);
- nt_size += le16_to_cpu(aces->size);
- ace_cnt++;
- aces = (struct smb_ace *)((char *)aces + le16_to_cpu(aces->size));
+ rc = smb_append_inherited_ace(&aces, &nt_size, &ace_cnt,
+ psid, parent_aces->type,
+ inherited_flags,
+ parent_aces->access_req);
+ if (rc)
+ goto free_aces_base;
flags |= INHERIT_ONLY_ACE;
psid = creator;
} else if (is_dir && !(parent_aces->flags & NO_PROPAGATE_INHERIT_ACE)) {
psid = &parent_aces->sid;
}
- smb_set_ace(aces, psid, parent_aces->type, flags | inherited_flags,
- parent_aces->access_req);
- nt_size += le16_to_cpu(aces->size);
- aces = (struct smb_ace *)((char *)aces + le16_to_cpu(aces->size));
- ace_cnt++;
+ rc = smb_append_inherited_ace(&aces, &nt_size, &ace_cnt, psid,
+ parent_aces->type,
+ flags | inherited_flags,
+ parent_aces->access_req);
+ if (rc)
+ goto free_aces_base;
pass:
parent_aces = (struct smb_ace *)((char *)parent_aces + pace_size);
}
@@ -1140,22 +1333,33 @@ pass:
struct smb_ntsd *pntsd;
struct smb_acl *pdacl;
struct smb_sid *powner_sid = NULL, *pgroup_sid = NULL;
- int powner_sid_size = 0, pgroup_sid_size = 0, pntsd_size;
- int pntsd_alloc_size;
+ size_t powner_sid_size = 0, pgroup_sid_size = 0, pntsd_size;
+ size_t pntsd_alloc_size;
- if (parent_pntsd->osidoffset) {
- powner_sid = (struct smb_sid *)((char *)parent_pntsd +
- le32_to_cpu(parent_pntsd->osidoffset));
- powner_sid_size = 1 + 1 + 6 + (powner_sid->num_subauth * 4);
- }
- if (parent_pntsd->gsidoffset) {
- pgroup_sid = (struct smb_sid *)((char *)parent_pntsd +
- le32_to_cpu(parent_pntsd->gsidoffset));
- pgroup_sid_size = 1 + 1 + 6 + (pgroup_sid->num_subauth * 4);
- }
+ rc = smb_validate_ntsd_sid(parent_pntsd, ppntsd_size,
+ le32_to_cpu(parent_pntsd->osidoffset),
+ &powner_sid, &powner_sid_size);
+ if (rc)
+ goto free_aces_base;
+ rc = smb_validate_ntsd_sid(parent_pntsd, ppntsd_size,
+ le32_to_cpu(parent_pntsd->gsidoffset),
+ &pgroup_sid, &pgroup_sid_size);
+ if (rc)
+ goto free_aces_base;
- pntsd_alloc_size = sizeof(struct smb_ntsd) + powner_sid_size +
- pgroup_sid_size + sizeof(struct smb_acl) + nt_size;
+ if (check_add_overflow(sizeof(struct smb_ntsd),
+ (size_t)powner_sid_size,
+ &pntsd_alloc_size) ||
+ check_add_overflow(pntsd_alloc_size,
+ (size_t)pgroup_sid_size,
+ &pntsd_alloc_size) ||
+ check_add_overflow(pntsd_alloc_size, sizeof(struct smb_acl),
+ &pntsd_alloc_size) ||
+ check_add_overflow(pntsd_alloc_size, (size_t)nt_size,
+ &pntsd_alloc_size)) {
+ rc = -EINVAL;
+ goto free_aces_base;
+ }
pntsd = kzalloc(pntsd_alloc_size, KSMBD_DEFAULT_GFP);
if (!pntsd) {
@@ -1244,7 +1448,8 @@ bool smb_inherit_flags(int flags, bool is_dir)
}
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
- __le32 *pdaccess, int uid)
+ __le32 *pdaccess, __le32 raw_daccess, int uid,
+ bool strict)
{
struct mnt_idmap *idmap = mnt_idmap(path->mnt);
struct smb_ntsd *pntsd = NULL;
@@ -1254,14 +1459,17 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
unsigned int dacl_offset;
size_t dacl_struct_end;
struct smb_sid sid;
- int granted = le32_to_cpu(*pdaccess & ~FILE_MAXIMAL_ACCESS_LE);
+ int requested = le32_to_cpu(*pdaccess & ~FILE_MAXIMAL_ACCESS_LE);
+ int granted = requested;
struct smb_ace *ace;
int i, found = 0;
- unsigned int access_bits = 0;
+ unsigned int access_bits = 0, denied = 0;
struct smb_ace *others_ace = NULL;
struct posix_acl_entry *pa_entry;
unsigned int sid_type = SIDOWNER;
unsigned short ace_size;
+ bool is_owner, owner_rights = false;
+ vfsuid_t vfsuid;
ksmbd_debug(SMB, "check permission using windows acl\n");
pntsd_size = ksmbd_vfs_get_sd_xattr(conn, idmap,
@@ -1291,62 +1499,103 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
goto err_out;
}
- if (*pdaccess & FILE_MAXIMAL_ACCESS_LE) {
- granted = READ_CONTROL | WRITE_DAC | FILE_READ_ATTRIBUTES |
- DELETE;
+ if (!uid)
+ sid_type = SIDUNIX_USER;
+ id_to_sid(uid, sid_type, &sid);
+ vfsuid = i_uid_into_vfsuid(idmap, d_inode(path->dentry));
+ is_owner = uid == from_kuid(&init_user_ns, vfsuid_into_kuid(vfsuid));
+ if (*pdaccess & FILE_MAXIMAL_ACCESS_LE) {
ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
- aces_size = acl_size - sizeof(struct smb_acl);
+ aces_size = pdacl_size - sizeof(struct smb_acl);
for (i = 0; i < le16_to_cpu(pdacl->num_aces); i++) {
- if (offsetof(struct smb_ace, access_req) > aces_size)
+ if (aces_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
break;
ace_size = le16_to_cpu(ace->size);
- if (ace_size > aces_size)
+ if (ace_size > aces_size ||
+ ace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
break;
aces_size -= ace_size;
- granted |= le32_to_cpu(ace->access_req);
+
+ if (ace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
+ ace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE +
+ sizeof(__le32) * ace->sid.num_subauth)
+ break;
+
+ if (!compare_sids(&sid_owner_rights, &ace->sid)) {
+ owner_rights = true;
+ if (!is_owner)
+ goto next_ace;
+ }
+
+ if (ace->flags & INHERIT_ONLY_ACE ||
+ (compare_sids(&sid, &ace->sid) &&
+ compare_sids(&sid_unix_NFS_mode, &ace->sid) &&
+ compare_sids(&sid_everyone, &ace->sid) &&
+ compare_sids(&sid_authusers, &ace->sid) &&
+ compare_sids(&sid_owner_rights, &ace->sid)))
+ goto next_ace;
+
+ switch (ace->type) {
+ case ACCESS_ALLOWED_ACE_TYPE:
+ access_bits |= le32_to_cpu(ace->access_req);
+ break;
+ case ACCESS_DENIED_ACE_TYPE:
+ case ACCESS_DENIED_CALLBACK_ACE_TYPE:
+ denied |= ~access_bits &
+ le32_to_cpu(ace->access_req);
+ break;
+ }
+next_ace:
ace = (struct smb_ace *)((char *)ace + le16_to_cpu(ace->size));
}
+ if (is_owner && !owner_rights)
+ access_bits |= READ_CONTROL | WRITE_DAC |
+ FILE_READ_ATTRIBUTES | DELETE;
+ access_bits &= ~denied;
+ if ((raw_daccess & FILE_GENERIC_EXECUTE_LE) &&
+ S_ISREG(d_inode(path->dentry)->i_mode) &&
+ (access_bits & GENERIC_READ_FLAGS) == GENERIC_READ_FLAGS)
+ access_bits |= FILE_EXECUTE;
+ granted = requested | access_bits;
}
- if (!uid)
- sid_type = SIDUNIX_USER;
- id_to_sid(uid, sid_type, &sid);
-
ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
- aces_size = acl_size - sizeof(struct smb_acl);
+ aces_size = pdacl_size - sizeof(struct smb_acl);
for (i = 0; i < le16_to_cpu(pdacl->num_aces); i++) {
- if (offsetof(struct smb_ace, access_req) > aces_size)
+ if (aces_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
break;
ace_size = le16_to_cpu(ace->size);
- if (ace_size > aces_size)
+ if (ace_size > aces_size ||
+ ace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
break;
aces_size -= ace_size;
+ if (ace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
+ ace_size < offsetof(struct smb_ace, sid) + CIFS_SID_BASE_SIZE +
+ sizeof(__le32) * ace->sid.num_subauth)
+ break;
+
if (!compare_sids(&sid, &ace->sid) ||
!compare_sids(&sid_unix_NFS_mode, &ace->sid)) {
found = 1;
break;
}
- if (!compare_sids(&sid_everyone, &ace->sid))
+ if (!compare_sids(&sid_everyone, &ace->sid) ||
+ !compare_sids(&sid_authusers, &ace->sid))
others_ace = ace;
ace = (struct smb_ace *)((char *)ace + le16_to_cpu(ace->size));
}
- if (*pdaccess & FILE_MAXIMAL_ACCESS_LE && found) {
- granted = READ_CONTROL | WRITE_DAC | FILE_READ_ATTRIBUTES |
- DELETE;
-
- granted |= le32_to_cpu(ace->access_req);
-
- if (!pdacl->num_aces)
- granted = GENERIC_ALL_FLAGS;
- }
-
if (IS_ENABLED(CONFIG_FS_POSIX_ACL)) {
posix_acls = get_inode_acl(d_inode(path->dentry), ACL_TYPE_ACCESS);
- if (!IS_ERR_OR_NULL(posix_acls) && !found) {
+ if (!IS_ERR_OR_NULL(posix_acls) && !found && !others_ace) {
unsigned int id = -1;
pa_entry = posix_acls->a_entries;
@@ -1384,19 +1633,27 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
}
}
- switch (ace->type) {
- case ACCESS_ALLOWED_ACE_TYPE:
- access_bits = le32_to_cpu(ace->access_req);
- break;
- case ACCESS_DENIED_ACE_TYPE:
- case ACCESS_DENIED_CALLBACK_ACE_TYPE:
- access_bits = le32_to_cpu(~ace->access_req);
- break;
+ if (!(*pdaccess & FILE_MAXIMAL_ACCESS_LE)) {
+ switch (ace->type) {
+ case ACCESS_ALLOWED_ACE_TYPE:
+ access_bits = le32_to_cpu(ace->access_req);
+ break;
+ case ACCESS_DENIED_ACE_TYPE:
+ case ACCESS_DENIED_CALLBACK_ACE_TYPE:
+ access_bits = le32_to_cpu(~ace->access_req);
+ break;
+ }
}
check_access_bits:
- if (granted &
- ~(access_bits | FILE_READ_ATTRIBUTES | READ_CONTROL | WRITE_DAC | DELETE)) {
+ if (strict) {
+ access_bits &= granted;
+ } else {
+ access_bits |= FILE_READ_ATTRIBUTES | READ_CONTROL |
+ WRITE_DAC | DELETE;
+ }
+
+ if (granted & ~access_bits) {
ksmbd_debug(SMB, "Access denied with winACL, granted : %x, access_req : %x\n",
granted, le32_to_cpu(ace->access_req));
rc = -EACCES;
diff --git a/fs/smb/server/smbacl.h b/fs/smb/server/smbacl.h
index 355adaee39b8..01810c16cc04 100644
--- a/fs/smb/server/smbacl.h
+++ b/fs/smb/server/smbacl.h
@@ -95,12 +95,15 @@ bool smb_inherit_flags(int flags, bool is_dir);
int smb_inherit_dacl(struct ksmbd_conn *conn, const struct path *path,
unsigned int uid, unsigned int gid);
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
- __le32 *pdaccess, int uid);
+ __le32 *pdaccess, __le32 raw_daccess, int uid,
+ bool strict);
int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
const struct path *path, struct smb_ntsd *pntsd, int ntsd_len,
bool type_check, bool get_write);
void id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid);
void ksmbd_init_domain(u32 *sub_auth);
+size_t smb_acl_sec_desc_scratch_len(struct smb_fattr *fattr,
+ struct smb_ntsd *ppntsd, int ppntsd_size, int addition_info);
static inline uid_t posix_acl_uid_translate(struct mnt_idmap *idmap,
struct posix_acl_entry *pace)
diff --git a/fs/smb/server/smbfsctl.h b/fs/smb/server/smbfsctl.h
deleted file mode 100644
index ecdf8f6e0df4..000000000000
--- a/fs/smb/server/smbfsctl.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-/*
- * fs/smb/server/smbfsctl.h: SMB, CIFS, SMB2 FSCTL definitions
- *
- * Copyright (c) International Business Machines Corp., 2002,2009
- * Author(s): Steve French (sfrench@us.ibm.com)
- */
-
-/* IOCTL information */
-/*
- * List of ioctl/fsctl function codes that are or could be useful in the
- * future to remote clients like cifs or SMB2 client. There is probably
- * a slightly larger set of fsctls that NTFS local filesystem could handle,
- * including the seven below that we do not have struct definitions for.
- * Even with protocol definitions for most of these now available, we still
- * need to do some experimentation to identify which are practical to do
- * remotely. Some of the following, such as the encryption/compression ones
- * could be invoked from tools via a specialized hook into the VFS rather
- * than via the standard vfs entry points
- */
-
-#ifndef __KSMBD_SMBFSCTL_H
-#define __KSMBD_SMBFSCTL_H
-
-#define FSCTL_DFS_GET_REFERRALS 0x00060194
-#define FSCTL_DFS_GET_REFERRALS_EX 0x000601B0
-#define FSCTL_REQUEST_OPLOCK_LEVEL_1 0x00090000
-#define FSCTL_REQUEST_OPLOCK_LEVEL_2 0x00090004
-#define FSCTL_REQUEST_BATCH_OPLOCK 0x00090008
-#define FSCTL_LOCK_VOLUME 0x00090018
-#define FSCTL_UNLOCK_VOLUME 0x0009001C
-#define FSCTL_IS_PATHNAME_VALID 0x0009002C /* BB add struct */
-#define FSCTL_GET_COMPRESSION 0x0009003C /* BB add struct */
-#define FSCTL_SET_COMPRESSION 0x0009C040 /* BB add struct */
-#define FSCTL_QUERY_FAT_BPB 0x00090058 /* BB add struct */
-/* Verify the next FSCTL number, we had it as 0x00090090 before */
-#define FSCTL_FILESYSTEM_GET_STATS 0x00090060 /* BB add struct */
-#define FSCTL_GET_NTFS_VOLUME_DATA 0x00090064 /* BB add struct */
-#define FSCTL_GET_RETRIEVAL_POINTERS 0x00090073 /* BB add struct */
-#define FSCTL_IS_VOLUME_DIRTY 0x00090078 /* BB add struct */
-#define FSCTL_ALLOW_EXTENDED_DASD_IO 0x00090083 /* BB add struct */
-#define FSCTL_REQUEST_FILTER_OPLOCK 0x0009008C
-#define FSCTL_FIND_FILES_BY_SID 0x0009008F /* BB add struct */
-#define FSCTL_SET_OBJECT_ID 0x00090098 /* BB add struct */
-#define FSCTL_GET_OBJECT_ID 0x0009009C /* BB add struct */
-#define FSCTL_DELETE_OBJECT_ID 0x000900A0 /* BB add struct */
-#define FSCTL_SET_REPARSE_POINT 0x000900A4 /* BB add struct */
-#define FSCTL_GET_REPARSE_POINT 0x000900A8 /* BB add struct */
-#define FSCTL_DELETE_REPARSE_POINT 0x000900AC /* BB add struct */
-#define FSCTL_SET_OBJECT_ID_EXTENDED 0x000900BC /* BB add struct */
-#define FSCTL_CREATE_OR_GET_OBJECT_ID 0x000900C0 /* BB add struct */
-#define FSCTL_SET_SPARSE 0x000900C4 /* BB add struct */
-#define FSCTL_SET_ZERO_DATA 0x000980C8 /* BB add struct */
-#define FSCTL_SET_ENCRYPTION 0x000900D7 /* BB add struct */
-#define FSCTL_ENCRYPTION_FSCTL_IO 0x000900DB /* BB add struct */
-#define FSCTL_WRITE_RAW_ENCRYPTED 0x000900DF /* BB add struct */
-#define FSCTL_READ_RAW_ENCRYPTED 0x000900E3 /* BB add struct */
-#define FSCTL_READ_FILE_USN_DATA 0x000900EB /* BB add struct */
-#define FSCTL_WRITE_USN_CLOSE_RECORD 0x000900EF /* BB add struct */
-#define FSCTL_SIS_COPYFILE 0x00090100 /* BB add struct */
-#define FSCTL_RECALL_FILE 0x00090117 /* BB add struct */
-#define FSCTL_QUERY_SPARING_INFO 0x00090138 /* BB add struct */
-#define FSCTL_SET_ZERO_ON_DEALLOC 0x00090194 /* BB add struct */
-#define FSCTL_SET_SHORT_NAME_BEHAVIOR 0x000901B4 /* BB add struct */
-#define FSCTL_QUERY_ALLOCATED_RANGES 0x000940CF /* BB add struct */
-#define FSCTL_SET_DEFECT_MANAGEMENT 0x00098134 /* BB add struct */
-#define FSCTL_DUPLICATE_EXTENTS_TO_FILE 0x00098344
-#define FSCTL_SIS_LINK_FILES 0x0009C104
-#define FSCTL_PIPE_PEEK 0x0011400C /* BB add struct */
-#define FSCTL_PIPE_TRANSCEIVE 0x0011C017 /* BB add struct */
-/* strange that the number for this op is not sequential with previous op */
-#define FSCTL_PIPE_WAIT 0x00110018 /* BB add struct */
-#define FSCTL_REQUEST_RESUME_KEY 0x00140078
-#define FSCTL_LMR_GET_LINK_TRACK_INF 0x001400E8 /* BB add struct */
-#define FSCTL_LMR_SET_LINK_TRACK_INF 0x001400EC /* BB add struct */
-#define FSCTL_VALIDATE_NEGOTIATE_INFO 0x00140204
-#define FSCTL_QUERY_NETWORK_INTERFACE_INFO 0x001401FC
-#define FSCTL_COPYCHUNK 0x001440F2
-#define FSCTL_COPYCHUNK_WRITE 0x001480F2
-
-#define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003
-#define IO_REPARSE_TAG_HSM 0xC0000004
-#define IO_REPARSE_TAG_SIS 0x80000007
-
-/* WSL reparse tags */
-#define IO_REPARSE_TAG_LX_SYMLINK_LE cpu_to_le32(0xA000001D)
-#define IO_REPARSE_TAG_AF_UNIX_LE cpu_to_le32(0x80000023)
-#define IO_REPARSE_TAG_LX_FIFO_LE cpu_to_le32(0x80000024)
-#define IO_REPARSE_TAG_LX_CHR_LE cpu_to_le32(0x80000025)
-#define IO_REPARSE_TAG_LX_BLK_LE cpu_to_le32(0x80000026)
-#endif /* __KSMBD_SMBFSCTL_H */
diff --git a/fs/smb/server/stats.h b/fs/smb/server/stats.h
index b60c30c69077..8b32b8b4e8be 100644
--- a/fs/smb/server/stats.h
+++ b/fs/smb/server/stats.h
@@ -9,12 +9,25 @@
#ifndef __KSMBD_STATS_H__
#define __KSMBD_STATS_H__
+#include "../common/smb2status.h"
+
#define KSMBD_COUNTER_MAX_REQS 19
enum {
KSMBD_COUNTER_SESSIONS = 0,
+ KSMBD_COUNTER_SESSION_TIMEOUTS,
KSMBD_COUNTER_TREE_CONNS,
KSMBD_COUNTER_REQUESTS,
+ KSMBD_COUNTER_STATUS_SUCCESS,
+ KSMBD_COUNTER_STATUS_INFORMATIONAL,
+ KSMBD_COUNTER_STATUS_WARNING,
+ KSMBD_COUNTER_STATUS_ERROR,
+ KSMBD_COUNTER_ERROR_ACCESS_DENIED,
+ KSMBD_COUNTER_ERROR_NOT_FOUND,
+ KSMBD_COUNTER_ERROR_INVALID_PARAMETER,
+ KSMBD_COUNTER_ERROR_SHARING_VIOLATION,
+ KSMBD_COUNTER_ERROR_NOT_SUPPORTED,
+ KSMBD_COUNTER_ERROR_OTHER,
KSMBD_COUNTER_READ_BYTES,
KSMBD_COUNTER_WRITE_BYTES,
KSMBD_COUNTER_FIRST_REQ,
@@ -50,10 +63,49 @@ static inline void ksmbd_counter_sub(int type, s64 value)
percpu_counter_sub(&ksmbd_counters.counters[type], value);
}
-static inline void ksmbd_counter_inc_reqs(unsigned int cmd)
+static inline void ksmbd_counter_inc_reqs(unsigned int cmd, __le32 status)
{
- if (cmd < KSMBD_COUNTER_MAX_REQS)
+ unsigned int severity = le32_to_cpu(status) >> 30;
+ int type;
+
+ switch (severity) {
+ case 0:
+ type = KSMBD_COUNTER_STATUS_SUCCESS;
+ break;
+ case 1:
+ type = KSMBD_COUNTER_STATUS_INFORMATIONAL;
+ break;
+ case 2:
+ type = KSMBD_COUNTER_STATUS_WARNING;
+ break;
+ default:
+ type = KSMBD_COUNTER_STATUS_ERROR;
+ break;
+ }
+ percpu_counter_inc(&ksmbd_counters.counters[type]);
+
+ if (severity == 3) {
+ if (status == STATUS_ACCESS_DENIED)
+ type = KSMBD_COUNTER_ERROR_ACCESS_DENIED;
+ else if (status == STATUS_OBJECT_NAME_NOT_FOUND ||
+ status == STATUS_NO_SUCH_FILE)
+ type = KSMBD_COUNTER_ERROR_NOT_FOUND;
+ else if (status == STATUS_INVALID_PARAMETER)
+ type = KSMBD_COUNTER_ERROR_INVALID_PARAMETER;
+ else if (status == STATUS_SHARING_VIOLATION)
+ type = KSMBD_COUNTER_ERROR_SHARING_VIOLATION;
+ else if (status == STATUS_NOT_SUPPORTED ||
+ status == STATUS_NOT_IMPLEMENTED)
+ type = KSMBD_COUNTER_ERROR_NOT_SUPPORTED;
+ else
+ type = KSMBD_COUNTER_ERROR_OTHER;
+ percpu_counter_inc(&ksmbd_counters.counters[type]);
+ }
+
+ if (cmd < KSMBD_COUNTER_MAX_REQS) {
+ percpu_counter_inc(&ksmbd_counters.counters[KSMBD_COUNTER_REQUESTS]);
percpu_counter_inc(&ksmbd_counters.counters[KSMBD_COUNTER_FIRST_REQ + cmd]);
+ }
}
static inline s64 ksmbd_counter_sum(int type)
@@ -66,7 +118,7 @@ static inline void ksmbd_counter_inc(int type) {}
static inline void ksmbd_counter_dec(int type) {}
static inline void ksmbd_counter_add(int type, s64 value) {}
static inline void ksmbd_counter_sub(int type, s64 value) {}
-static inline void ksmbd_counter_inc_reqs(unsigned int cmd) {}
+static inline void ksmbd_counter_inc_reqs(unsigned int cmd, __le32 status) {}
static inline s64 ksmbd_counter_sum(int type) { return 0; }
#endif
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 2dbabe2d8005..e550aa41ad2c 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -13,6 +13,7 @@
#include <net/genetlink.h>
#include <linux/socket.h>
#include <linux/workqueue.h>
+#include <linux/overflow.h>
#include "vfs_cache.h"
#include "transport_ipc.h"
@@ -55,7 +56,7 @@ static bool ksmbd_ipc_validate_version(struct genl_info *m)
struct ksmbd_ipc_msg {
unsigned int type;
unsigned int sz;
- unsigned char payload[];
+ unsigned char payload[] __counted_by(sz);
};
struct ipc_msg_table_entry {
@@ -242,9 +243,8 @@ static void ipc_update_last_active(void)
static struct ksmbd_ipc_msg *ipc_msg_alloc(size_t sz)
{
struct ksmbd_ipc_msg *msg;
- size_t msg_sz = sz + sizeof(struct ksmbd_ipc_msg);
- msg = kvzalloc(msg_sz, KSMBD_DEFAULT_GFP);
+ msg = kvzalloc_flex(*msg, payload, sz, KSMBD_DEFAULT_GFP);
if (msg)
msg->sz = sz;
return msg;
@@ -322,6 +322,15 @@ static int ipc_server_config_on_startup(struct ksmbd_startup_request *req)
goto out;
}
server_conf.share_fake_fscaps = req->share_fake_fscaps;
+
+ /* AAPL model string for Finder icon */
+ if (req->aapl_model[0])
+ strscpy(server_conf.aapl_model, req->aapl_model,
+ sizeof(server_conf.aapl_model));
+ else
+ strscpy(server_conf.aapl_model, "Xserve",
+ sizeof(server_conf.aapl_model));
+
ksmbd_init_domain(req->sub_auth);
if (req->smb2_max_read)
@@ -497,13 +506,21 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
{
struct ksmbd_rpc_command *resp = entry->response;
- msg_sz = sizeof(struct ksmbd_rpc_command) + resp->payload_sz;
+ if (entry->msg_sz < sizeof(struct ksmbd_rpc_command))
+ return -EINVAL;
+
+ if (check_add_overflow(sizeof(struct ksmbd_rpc_command),
+ resp->payload_sz, &msg_sz))
+ return -EINVAL;
break;
}
case KSMBD_EVENT_SPNEGO_AUTHEN_REQUEST:
{
struct ksmbd_spnego_authen_response *resp = entry->response;
+ if (entry->msg_sz < sizeof(struct ksmbd_spnego_authen_response))
+ return -EINVAL;
+
msg_sz = sizeof(struct ksmbd_spnego_authen_response) +
resp->session_key_len + resp->spnego_blob_len;
break;
@@ -512,20 +529,40 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
{
struct ksmbd_share_config_response *resp = entry->response;
- if (resp->payload_sz) {
- if (resp->payload_sz < resp->veto_list_sz)
- return -EINVAL;
+ if (entry->msg_sz < sizeof(struct ksmbd_share_config_response))
+ return -EINVAL;
- msg_sz = sizeof(struct ksmbd_share_config_response) +
- resp->payload_sz;
- }
+ if (strnlen(resp->share_name, sizeof(resp->share_name)) ==
+ sizeof(resp->share_name))
+ return -EINVAL;
+
+ if (resp->veto_list_sz > resp->payload_sz)
+ return -EINVAL;
+
+ if (resp->flags != KSMBD_SHARE_FLAG_INVALID &&
+ !(resp->flags & KSMBD_SHARE_FLAG_PIPE) &&
+ resp->payload_sz <= resp->veto_list_sz)
+ return -EINVAL;
+
+ if (check_add_overflow(sizeof(struct ksmbd_share_config_response),
+ resp->payload_sz, &msg_sz))
+ return -EINVAL;
break;
}
case KSMBD_EVENT_LOGIN_REQUEST_EXT:
{
struct ksmbd_login_response_ext *resp = entry->response;
+ if (entry->msg_sz < sizeof(struct ksmbd_login_response_ext))
+ return -EINVAL;
+
if (resp->ngroups) {
+ if (resp->ngroups < 0 ||
+ resp->ngroups > NGROUPS_MAX) {
+ pr_err("ngroups(%d) from login response exceeds max groups(%d)\n",
+ resp->ngroups, NGROUPS_MAX);
+ return -EINVAL;
+ }
msg_sz = sizeof(struct ksmbd_login_response_ext) +
resp->ngroups * sizeof(gid_t);
}
@@ -649,7 +686,7 @@ ksmbd_ipc_spnego_authen_request(const char *spnego_blob, int blob_len)
return NULL;
msg = ipc_msg_alloc(sizeof(struct ksmbd_spnego_authen_request) +
- blob_len + 1);
+ blob_len);
if (!msg)
return NULL;
@@ -830,7 +867,7 @@ struct ksmbd_rpc_command *ksmbd_rpc_write(struct ksmbd_session *sess, int handle
if (payload_sz > KSMBD_IPC_MAX_PAYLOAD)
return NULL;
- msg = ipc_msg_alloc(sizeof(struct ksmbd_rpc_command) + payload_sz + 1);
+ msg = ipc_msg_alloc(sizeof(struct ksmbd_rpc_command) + payload_sz);
if (!msg)
return NULL;
@@ -889,7 +926,7 @@ struct ksmbd_rpc_command *ksmbd_rpc_ioctl(struct ksmbd_session *sess, int handle
if (payload_sz > KSMBD_IPC_MAX_PAYLOAD)
return NULL;
- msg = ipc_msg_alloc(sizeof(struct ksmbd_rpc_command) + payload_sz + 1);
+ msg = ipc_msg_alloc(sizeof(struct ksmbd_rpc_command) + payload_sz);
if (!msg)
return NULL;
diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c
index 188572491d53..ee28a4d1cc86 100644
--- a/fs/smb/server/transport_rdma.c
+++ b/fs/smb/server/transport_rdma.c
@@ -11,30 +11,18 @@
#include <linux/kthread.h>
#include <linux/list.h>
-#include <linux/mempool.h>
-#include <linux/highmem.h>
-#include <linux/scatterlist.h>
#include <linux/string_choices.h>
-#include <rdma/ib_verbs.h>
-#include <rdma/rdma_cm.h>
-#include <rdma/rw.h>
-
-#define __SMBDIRECT_SOCKET_DISCONNECT(__sc) smb_direct_disconnect_rdma_connection(__sc)
#include "glob.h"
#include "connection.h"
#include "smb_common.h"
#include "../common/smb2status.h"
-#include "../common/smbdirect/smbdirect.h"
-#include "../common/smbdirect/smbdirect_pdu.h"
-#include "../common/smbdirect/smbdirect_socket.h"
#include "transport_rdma.h"
+
#define SMB_DIRECT_PORT_IWARP 5445
#define SMB_DIRECT_PORT_INFINIBAND 445
-#define SMB_DIRECT_VERSION_LE cpu_to_le16(SMBDIRECT_V1)
-
/* SMB_DIRECT negotiation timeout (for the server) in seconds */
#define SMB_DIRECT_NEGOTIATE_TIMEOUT 5
@@ -50,11 +38,6 @@
*/
#define SMB_DIRECT_CM_INITIATOR_DEPTH 8
-/* Maximum number of retries on data transfer operations */
-#define SMB_DIRECT_CM_RETRY 6
-/* No need to retry on Receiver Not Ready since SMB_DIRECT manages credits */
-#define SMB_DIRECT_CM_RNR_RETRY 0
-
/*
* User configurable initial values per SMB_DIRECT transport connection
* as defined in [MS-SMBD] 3.1.1.1
@@ -93,357 +76,120 @@ static int smb_direct_max_receive_size = 1364;
static int smb_direct_max_read_write_size = SMBD_DEFAULT_IOSIZE;
-static LIST_HEAD(smb_direct_device_list);
-static DEFINE_RWLOCK(smb_direct_device_lock);
-
-struct smb_direct_device {
- struct ib_device *ib_dev;
- struct list_head list;
-};
+static bool smb_direct_enabled;
static struct smb_direct_listener {
int port;
- struct rdma_cm_id *cm_id;
-} smb_direct_ib_listener, smb_direct_iw_listener;
-static struct workqueue_struct *smb_direct_wq;
+ struct task_struct *thread;
+
+ struct smbdirect_socket *socket;
+} smb_direct_ib_listener, smb_direct_iw_listener;
struct smb_direct_transport {
struct ksmbd_transport transport;
- struct smbdirect_socket socket;
+ struct smbdirect_socket *socket;
};
-#define KSMBD_TRANS(t) (&(t)->transport)
-#define SMBD_TRANS(t) (container_of(t, \
- struct smb_direct_transport, transport))
-
-static const struct ksmbd_transport_ops ksmbd_smb_direct_transport_ops;
-
-void init_smbd_max_io_size(unsigned int sz)
-{
- sz = clamp_val(sz, SMBD_MIN_IOSIZE, SMBD_MAX_IOSIZE);
- smb_direct_max_read_write_size = sz;
-}
-
-unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt)
-{
- struct smb_direct_transport *t;
- struct smbdirect_socket *sc;
- struct smbdirect_socket_parameters *sp;
-
- if (kt->ops != &ksmbd_smb_direct_transport_ops)
- return 0;
-
- t = SMBD_TRANS(kt);
- sc = &t->socket;
- sp = &sc->parameters;
-
- return sp->max_read_write_size;
-}
-
-static inline int get_buf_page_count(void *buf, int size)
-{
- return DIV_ROUND_UP((uintptr_t)buf + size, PAGE_SIZE) -
- (uintptr_t)buf / PAGE_SIZE;
-}
-
-static void smb_direct_destroy_pools(struct smbdirect_socket *sc);
-static void smb_direct_post_recv_credits(struct work_struct *work);
-static int smb_direct_post_send_data(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *send_ctx,
- struct kvec *iov, int niov,
- int remaining_data_length);
-
-static inline void
-*smbdirect_recv_io_payload(struct smbdirect_recv_io *recvmsg)
-{
- return (void *)recvmsg->packet;
-}
-
-static struct
-smbdirect_recv_io *get_free_recvmsg(struct smbdirect_socket *sc)
-{
- struct smbdirect_recv_io *recvmsg = NULL;
- unsigned long flags;
-
- spin_lock_irqsave(&sc->recv_io.free.lock, flags);
- if (!list_empty(&sc->recv_io.free.list)) {
- recvmsg = list_first_entry(&sc->recv_io.free.list,
- struct smbdirect_recv_io,
- list);
- list_del(&recvmsg->list);
- }
- spin_unlock_irqrestore(&sc->recv_io.free.lock, flags);
- return recvmsg;
-}
-
-static void put_recvmsg(struct smbdirect_socket *sc,
- struct smbdirect_recv_io *recvmsg)
-{
- unsigned long flags;
-
- if (likely(recvmsg->sge.length != 0)) {
- ib_dma_unmap_single(sc->ib.dev,
- recvmsg->sge.addr,
- recvmsg->sge.length,
- DMA_FROM_DEVICE);
- recvmsg->sge.length = 0;
- }
-
- spin_lock_irqsave(&sc->recv_io.free.lock, flags);
- list_add(&recvmsg->list, &sc->recv_io.free.list);
- spin_unlock_irqrestore(&sc->recv_io.free.lock, flags);
-
- queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
-}
-
-static void enqueue_reassembly(struct smbdirect_socket *sc,
- struct smbdirect_recv_io *recvmsg,
- int data_length)
+static bool smb_direct_logging_needed(struct smbdirect_socket *sc,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls)
{
- unsigned long flags;
-
- spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
- list_add_tail(&recvmsg->list, &sc->recv_io.reassembly.list);
- sc->recv_io.reassembly.queue_length++;
- /*
- * Make sure reassembly_data_length is updated after list and
- * reassembly_queue_length are updated. On the dequeue side
- * reassembly_data_length is checked without a lock to determine
- * if reassembly_queue_length and list is up to date
- */
- virt_wmb();
- sc->recv_io.reassembly.data_length += data_length;
- spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
-}
+ if (lvl <= SMBDIRECT_LOG_ERR)
+ return true;
-static struct smbdirect_recv_io *get_first_reassembly(struct smbdirect_socket *sc)
-{
- if (!list_empty(&sc->recv_io.reassembly.list))
- return list_first_entry(&sc->recv_io.reassembly.list,
- struct smbdirect_recv_io, list);
- else
- return NULL;
-}
+ if (lvl > SMBDIRECT_LOG_INFO)
+ return false;
-static void smb_direct_disconnect_wake_up_all(struct smbdirect_socket *sc)
-{
+ switch (cls) {
/*
- * Wake up all waiters in all wait queues
- * in order to notice the broken connection.
- */
- wake_up_all(&sc->status_wait);
- wake_up_all(&sc->send_io.bcredits.wait_queue);
- wake_up_all(&sc->send_io.lcredits.wait_queue);
- wake_up_all(&sc->send_io.credits.wait_queue);
- wake_up_all(&sc->send_io.pending.zero_wait_queue);
- wake_up_all(&sc->recv_io.reassembly.wait_queue);
- wake_up_all(&sc->rw_io.credits.wait_queue);
-}
-
-static void smb_direct_disconnect_rdma_work(struct work_struct *work)
-{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, disconnect_work);
-
- if (sc->first_error == 0)
- sc->first_error = -ECONNABORTED;
-
+ * These were more or less also logged before
+ * the move to common code.
+ *
+ * SMBDIRECT_LOG_RDMA_MR was not used, but
+ * that's client only code and we should
+ * notice if it's used on the server...
+ */
+ case SMBDIRECT_LOG_RDMA_EVENT:
+ case SMBDIRECT_LOG_RDMA_SEND:
+ case SMBDIRECT_LOG_RDMA_RECV:
+ case SMBDIRECT_LOG_WRITE:
+ case SMBDIRECT_LOG_READ:
+ case SMBDIRECT_LOG_NEGOTIATE:
+ case SMBDIRECT_LOG_OUTGOING:
+ case SMBDIRECT_LOG_RDMA_RW:
+ case SMBDIRECT_LOG_RDMA_MR:
+ return true;
/*
- * make sure this and other work is not queued again
- * but here we don't block and avoid
- * disable[_delayed]_work_sync()
+ * These were not logged before the move
+ * to common code.
*/
- disable_work(&sc->disconnect_work);
- disable_work(&sc->connect.work);
- disable_work(&sc->recv_io.posted.refill_work);
- disable_delayed_work(&sc->idle.timer_work);
- disable_work(&sc->idle.immediate_work);
-
- switch (sc->status) {
- case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
- case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
- case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
- case SMBDIRECT_SOCKET_CONNECTED:
- case SMBDIRECT_SOCKET_ERROR:
- sc->status = SMBDIRECT_SOCKET_DISCONNECTING;
- rdma_disconnect(sc->rdma.cm_id);
- break;
-
- case SMBDIRECT_SOCKET_CREATED:
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
- /*
- * rdma_accept() never reached
- * RDMA_CM_EVENT_ESTABLISHED
- */
- sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
- break;
-
- case SMBDIRECT_SOCKET_DISCONNECTING:
- case SMBDIRECT_SOCKET_DISCONNECTED:
- case SMBDIRECT_SOCKET_DESTROYED:
- break;
+ case SMBDIRECT_LOG_KEEP_ALIVE:
+ case SMBDIRECT_LOG_INCOMING:
+ return false;
}
/*
- * Wake up all waiters in all wait queues
- * in order to notice the broken connection.
+ * Log all unknown messages
*/
- smb_direct_disconnect_wake_up_all(sc);
+ return true;
}
-static void
-smb_direct_disconnect_rdma_connection(struct smbdirect_socket *sc)
+static void smb_direct_logging_vaprintf(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls,
+ struct va_format *vaf)
{
- if (sc->first_error == 0)
- sc->first_error = -ECONNABORTED;
-
- /*
- * make sure other work (than disconnect_work) is
- * not queued again but here we don't block and avoid
- * disable[_delayed]_work_sync()
- */
- disable_work(&sc->connect.work);
- disable_work(&sc->recv_io.posted.refill_work);
- disable_work(&sc->idle.immediate_work);
- disable_delayed_work(&sc->idle.timer_work);
-
- switch (sc->status) {
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
- case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
- case SMBDIRECT_SOCKET_ERROR:
- case SMBDIRECT_SOCKET_DISCONNECTING:
- case SMBDIRECT_SOCKET_DISCONNECTED:
- case SMBDIRECT_SOCKET_DESTROYED:
- /*
- * Keep the current error status
- */
- break;
-
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
- case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED;
- break;
-
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
- case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
- sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED;
- break;
-
- case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
- case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
- sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED;
- break;
-
- case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
- case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED;
- break;
-
- case SMBDIRECT_SOCKET_CREATED:
- sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
- break;
-
- case SMBDIRECT_SOCKET_CONNECTED:
- sc->status = SMBDIRECT_SOCKET_ERROR;
- break;
- }
-
- /*
- * Wake up all waiters in all wait queues
- * in order to notice the broken connection.
- */
- smb_direct_disconnect_wake_up_all(sc);
-
- queue_work(sc->workqueue, &sc->disconnect_work);
+ if (lvl <= SMBDIRECT_LOG_ERR)
+ pr_err("%pV", vaf);
+ else
+ ksmbd_debug(RDMA, "%pV", vaf);
}
-static void smb_direct_send_immediate_work(struct work_struct *work)
-{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, idle.immediate_work);
+#define KSMBD_TRANS(t) (&(t)->transport)
+#define SMBD_TRANS(t) (container_of(t, \
+ struct smb_direct_transport, transport))
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return;
+static const struct ksmbd_transport_ops ksmbd_smb_direct_transport_ops;
- smb_direct_post_send_data(sc, NULL, NULL, 0, 0);
+void init_smbd_max_io_size(unsigned int sz)
+{
+ sz = clamp_val(sz, SMBD_MIN_IOSIZE, SMBD_MAX_IOSIZE);
+ smb_direct_max_read_write_size = sz;
}
-static void smb_direct_idle_connection_timer(struct work_struct *work)
+unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt)
{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, idle.timer_work.work);
- struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smb_direct_transport *t;
+ const struct smbdirect_socket_parameters *sp;
- if (sc->idle.keepalive != SMBDIRECT_KEEPALIVE_NONE) {
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
+ if (kt->ops != &ksmbd_smb_direct_transport_ops)
+ return 0;
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return;
+ t = SMBD_TRANS(kt);
+ sp = smbdirect_socket_get_current_parameters(t->socket);
- /*
- * Now use the keepalive timeout (instead of keepalive interval)
- * in order to wait for a response
- */
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_PENDING;
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->keepalive_timeout_msec));
- queue_work(sc->workqueue, &sc->idle.immediate_work);
+ return sp->max_read_write_size;
}
-static struct smb_direct_transport *alloc_transport(struct rdma_cm_id *cm_id)
+static struct smb_direct_transport *alloc_transport(struct smbdirect_socket *sc)
{
struct smb_direct_transport *t;
- struct smbdirect_socket *sc;
- struct smbdirect_socket_parameters *sp;
struct ksmbd_conn *conn;
t = kzalloc_obj(*t, KSMBD_DEFAULT_GFP);
if (!t)
return NULL;
- sc = &t->socket;
- smbdirect_socket_init(sc);
- sp = &sc->parameters;
-
- sc->workqueue = smb_direct_wq;
-
- INIT_WORK(&sc->disconnect_work, smb_direct_disconnect_rdma_work);
-
- sp->negotiate_timeout_msec = SMB_DIRECT_NEGOTIATE_TIMEOUT * 1000;
- sp->initiator_depth = SMB_DIRECT_CM_INITIATOR_DEPTH;
- sp->responder_resources = 1;
- sp->recv_credit_max = smb_direct_receive_credit_max;
- sp->send_credit_target = smb_direct_send_credit_target;
- sp->max_send_size = smb_direct_max_send_size;
- sp->max_fragmented_recv_size = smb_direct_max_fragmented_recv_size;
- sp->max_recv_size = smb_direct_max_receive_size;
- sp->max_read_write_size = smb_direct_max_read_write_size;
- sp->keepalive_interval_msec = SMB_DIRECT_KEEPALIVE_SEND_INTERVAL * 1000;
- sp->keepalive_timeout_msec = SMB_DIRECT_KEEPALIVE_RECV_TIMEOUT * 1000;
-
- sc->rdma.cm_id = cm_id;
- cm_id->context = sc;
-
- sc->ib.dev = sc->rdma.cm_id->device;
-
- INIT_DELAYED_WORK(&sc->idle.timer_work, smb_direct_idle_connection_timer);
+ t->socket = sc;
conn = ksmbd_conn_alloc();
if (!conn)
- goto err;
+ goto conn_alloc_failed;
down_write(&conn_list_lock);
hash_add(conn_list, &conn->hlist, 0);
@@ -452,1480 +198,61 @@ static struct smb_direct_transport *alloc_transport(struct rdma_cm_id *cm_id)
conn->transport = KSMBD_TRANS(t);
KSMBD_TRANS(t)->conn = conn;
KSMBD_TRANS(t)->ops = &ksmbd_smb_direct_transport_ops;
+
return t;
-err:
+
+conn_alloc_failed:
kfree(t);
return NULL;
}
static void smb_direct_free_transport(struct ksmbd_transport *kt)
{
- kfree(SMBD_TRANS(kt));
+ struct smb_direct_transport *t = SMBD_TRANS(kt);
+
+ smbdirect_socket_release(t->socket);
+ kfree(t);
}
static void free_transport(struct smb_direct_transport *t)
{
- struct smbdirect_socket *sc = &t->socket;
- struct smbdirect_recv_io *recvmsg;
-
- disable_work_sync(&sc->disconnect_work);
- if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING)
- smb_direct_disconnect_rdma_work(&sc->disconnect_work);
- if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED)
- wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
-
- /*
- * Wake up all waiters in all wait queues
- * in order to notice the broken connection.
- *
- * Most likely this was already called via
- * smb_direct_disconnect_rdma_work(), but call it again...
- */
- smb_direct_disconnect_wake_up_all(sc);
-
- disable_work_sync(&sc->connect.work);
- disable_work_sync(&sc->recv_io.posted.refill_work);
- disable_delayed_work_sync(&sc->idle.timer_work);
- disable_work_sync(&sc->idle.immediate_work);
-
- if (sc->rdma.cm_id)
- rdma_lock_handler(sc->rdma.cm_id);
-
- if (sc->ib.qp) {
- ib_drain_qp(sc->ib.qp);
- sc->ib.qp = NULL;
- rdma_destroy_qp(sc->rdma.cm_id);
- }
-
- ksmbd_debug(RDMA, "drain the reassembly queue\n");
- do {
- unsigned long flags;
-
- spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
- recvmsg = get_first_reassembly(sc);
- if (recvmsg) {
- list_del(&recvmsg->list);
- spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
- put_recvmsg(sc, recvmsg);
- } else {
- spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
- }
- } while (recvmsg);
- sc->recv_io.reassembly.data_length = 0;
-
- if (sc->ib.send_cq)
- ib_free_cq(sc->ib.send_cq);
- if (sc->ib.recv_cq)
- ib_free_cq(sc->ib.recv_cq);
- if (sc->ib.pd)
- ib_dealloc_pd(sc->ib.pd);
- if (sc->rdma.cm_id) {
- rdma_unlock_handler(sc->rdma.cm_id);
- rdma_destroy_id(sc->rdma.cm_id);
- }
-
- smb_direct_destroy_pools(sc);
+ smbdirect_socket_shutdown(t->socket);
ksmbd_conn_free(KSMBD_TRANS(t)->conn);
}
-static struct smbdirect_send_io
-*smb_direct_alloc_sendmsg(struct smbdirect_socket *sc)
-{
- struct smbdirect_send_io *msg;
-
- msg = mempool_alloc(sc->send_io.mem.pool, KSMBD_DEFAULT_GFP);
- if (!msg)
- return ERR_PTR(-ENOMEM);
- msg->socket = sc;
- INIT_LIST_HEAD(&msg->sibling_list);
- msg->num_sge = 0;
- return msg;
-}
-
-static void smb_direct_free_sendmsg(struct smbdirect_socket *sc,
- struct smbdirect_send_io *msg)
-{
- int i;
-
- /*
- * The list needs to be empty!
- * The caller should take care of it.
- */
- WARN_ON_ONCE(!list_empty(&msg->sibling_list));
-
- if (msg->num_sge > 0) {
- ib_dma_unmap_single(sc->ib.dev,
- msg->sge[0].addr, msg->sge[0].length,
- DMA_TO_DEVICE);
- for (i = 1; i < msg->num_sge; i++)
- ib_dma_unmap_page(sc->ib.dev,
- msg->sge[i].addr, msg->sge[i].length,
- DMA_TO_DEVICE);
- }
- mempool_free(msg, sc->send_io.mem.pool);
-}
-
-static int smb_direct_check_recvmsg(struct smbdirect_recv_io *recvmsg)
-{
- struct smbdirect_socket *sc = recvmsg->socket;
-
- switch (sc->recv_io.expected) {
- case SMBDIRECT_EXPECT_DATA_TRANSFER: {
- struct smbdirect_data_transfer *req =
- (struct smbdirect_data_transfer *)recvmsg->packet;
- struct smb2_hdr *hdr = (struct smb2_hdr *)(recvmsg->packet
- + le32_to_cpu(req->data_offset));
- ksmbd_debug(RDMA,
- "CreditGranted: %u, CreditRequested: %u, DataLength: %u, RemainingDataLength: %u, SMB: %x, Command: %u\n",
- le16_to_cpu(req->credits_granted),
- le16_to_cpu(req->credits_requested),
- req->data_length, req->remaining_data_length,
- hdr->ProtocolId, hdr->Command);
- return 0;
- }
- case SMBDIRECT_EXPECT_NEGOTIATE_REQ: {
- struct smbdirect_negotiate_req *req =
- (struct smbdirect_negotiate_req *)recvmsg->packet;
- ksmbd_debug(RDMA,
- "MinVersion: %u, MaxVersion: %u, CreditRequested: %u, MaxSendSize: %u, MaxRecvSize: %u, MaxFragmentedSize: %u\n",
- le16_to_cpu(req->min_version),
- le16_to_cpu(req->max_version),
- le16_to_cpu(req->credits_requested),
- le32_to_cpu(req->preferred_send_size),
- le32_to_cpu(req->max_receive_size),
- le32_to_cpu(req->max_fragmented_size));
- if (le16_to_cpu(req->min_version) > 0x0100 ||
- le16_to_cpu(req->max_version) < 0x0100)
- return -EOPNOTSUPP;
- if (le16_to_cpu(req->credits_requested) <= 0 ||
- le32_to_cpu(req->max_receive_size) <= 128 ||
- le32_to_cpu(req->max_fragmented_size) <=
- 128 * 1024)
- return -ECONNABORTED;
-
- return 0;
- }
- case SMBDIRECT_EXPECT_NEGOTIATE_REP:
- /* client only */
- break;
- }
-
- /* This is an internal error */
- return -EINVAL;
-}
-
-static void recv_done(struct ib_cq *cq, struct ib_wc *wc)
-{
- struct smbdirect_recv_io *recvmsg;
- struct smbdirect_socket *sc;
- struct smbdirect_socket_parameters *sp;
-
- recvmsg = container_of(wc->wr_cqe, struct smbdirect_recv_io, cqe);
- sc = recvmsg->socket;
- sp = &sc->parameters;
-
- if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV) {
- put_recvmsg(sc, recvmsg);
- if (wc->status != IB_WC_WR_FLUSH_ERR) {
- pr_err("Recv error. status='%s (%d)' opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->status,
- wc->opcode);
- smb_direct_disconnect_rdma_connection(sc);
- }
- return;
- }
-
- ksmbd_debug(RDMA, "Recv completed. status='%s (%d)', opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->status,
- wc->opcode);
-
- ib_dma_sync_single_for_cpu(wc->qp->device, recvmsg->sge.addr,
- recvmsg->sge.length, DMA_FROM_DEVICE);
-
- /*
- * Reset timer to the keepalive interval in
- * order to trigger our next keepalive message.
- */
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->keepalive_interval_msec));
-
- switch (sc->recv_io.expected) {
- case SMBDIRECT_EXPECT_NEGOTIATE_REQ:
- /* see smb_direct_negotiate_recv_done */
- break;
- case SMBDIRECT_EXPECT_DATA_TRANSFER: {
- struct smbdirect_data_transfer *data_transfer =
- (struct smbdirect_data_transfer *)recvmsg->packet;
- u32 remaining_data_length, data_offset, data_length;
- int current_recv_credits;
- u16 old_recv_credit_target;
-
- if (wc->byte_len <
- offsetof(struct smbdirect_data_transfer, padding)) {
- put_recvmsg(sc, recvmsg);
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
-
- remaining_data_length = le32_to_cpu(data_transfer->remaining_data_length);
- data_length = le32_to_cpu(data_transfer->data_length);
- data_offset = le32_to_cpu(data_transfer->data_offset);
- if (wc->byte_len < data_offset ||
- wc->byte_len < (u64)data_offset + data_length) {
- put_recvmsg(sc, recvmsg);
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
- if (remaining_data_length > sp->max_fragmented_recv_size ||
- data_length > sp->max_fragmented_recv_size ||
- (u64)remaining_data_length + (u64)data_length >
- (u64)sp->max_fragmented_recv_size) {
- put_recvmsg(sc, recvmsg);
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
-
- if (data_length) {
- if (sc->recv_io.reassembly.full_packet_received)
- recvmsg->first_segment = true;
-
- if (le32_to_cpu(data_transfer->remaining_data_length))
- sc->recv_io.reassembly.full_packet_received = false;
- else
- sc->recv_io.reassembly.full_packet_received = true;
- }
-
- atomic_dec(&sc->recv_io.posted.count);
- current_recv_credits = atomic_dec_return(&sc->recv_io.credits.count);
-
- old_recv_credit_target = sc->recv_io.credits.target;
- sc->recv_io.credits.target =
- le16_to_cpu(data_transfer->credits_requested);
- sc->recv_io.credits.target =
- min_t(u16, sc->recv_io.credits.target, sp->recv_credit_max);
- sc->recv_io.credits.target =
- max_t(u16, sc->recv_io.credits.target, 1);
- atomic_add(le16_to_cpu(data_transfer->credits_granted),
- &sc->send_io.credits.count);
-
- if (le16_to_cpu(data_transfer->flags) &
- SMBDIRECT_FLAG_RESPONSE_REQUESTED)
- queue_work(sc->workqueue, &sc->idle.immediate_work);
-
- if (atomic_read(&sc->send_io.credits.count) > 0)
- wake_up(&sc->send_io.credits.wait_queue);
-
- if (data_length) {
- if (current_recv_credits <= (sc->recv_io.credits.target / 4) ||
- sc->recv_io.credits.target > old_recv_credit_target)
- queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
-
- enqueue_reassembly(sc, recvmsg, (int)data_length);
- wake_up(&sc->recv_io.reassembly.wait_queue);
- } else
- put_recvmsg(sc, recvmsg);
-
- return;
- }
- case SMBDIRECT_EXPECT_NEGOTIATE_REP:
- /* client only */
- break;
- }
-
- /*
- * This is an internal error!
- */
- WARN_ON_ONCE(sc->recv_io.expected != SMBDIRECT_EXPECT_DATA_TRANSFER);
- put_recvmsg(sc, recvmsg);
- smb_direct_disconnect_rdma_connection(sc);
-}
-
-static void smb_direct_negotiate_recv_work(struct work_struct *work);
-
-static void smb_direct_negotiate_recv_done(struct ib_cq *cq, struct ib_wc *wc)
-{
- struct smbdirect_recv_io *recv_io =
- container_of(wc->wr_cqe, struct smbdirect_recv_io, cqe);
- struct smbdirect_socket *sc = recv_io->socket;
- unsigned long flags;
-
- /*
- * reset the common recv_done for later reuse.
- */
- recv_io->cqe.done = recv_done;
-
- if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV) {
- put_recvmsg(sc, recv_io);
- if (wc->status != IB_WC_WR_FLUSH_ERR) {
- pr_err("Negotiate Recv error. status='%s (%d)' opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->status,
- wc->opcode);
- smb_direct_disconnect_rdma_connection(sc);
- }
- return;
- }
-
- ksmbd_debug(RDMA, "Negotiate Recv completed. status='%s (%d)', opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->status,
- wc->opcode);
-
- ib_dma_sync_single_for_cpu(sc->ib.dev,
- recv_io->sge.addr,
- recv_io->sge.length,
- DMA_FROM_DEVICE);
-
- /*
- * This is an internal error!
- */
- if (WARN_ON_ONCE(sc->recv_io.expected != SMBDIRECT_EXPECT_NEGOTIATE_REQ)) {
- put_recvmsg(sc, recv_io);
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
-
- /*
- * Don't reset timer to the keepalive interval in
- * this will be done in smb_direct_negotiate_recv_work.
- */
-
- /*
- * Only remember the recv_io if it has enough bytes,
- * this gives smb_direct_negotiate_recv_work enough
- * information in order to disconnect if it was not
- * valid.
- */
- sc->recv_io.reassembly.full_packet_received = true;
- if (wc->byte_len >= sizeof(struct smbdirect_negotiate_req))
- enqueue_reassembly(sc, recv_io, 0);
- else
- put_recvmsg(sc, recv_io);
-
- /*
- * Some drivers (at least mlx5_ib and irdma in roce mode)
- * might post a recv completion before RDMA_CM_EVENT_ESTABLISHED,
- * we need to adjust our expectation in that case.
- *
- * So we defer further processing of the negotiation
- * to smb_direct_negotiate_recv_work().
- *
- * If we are already in SMBDIRECT_SOCKET_NEGOTIATE_NEEDED
- * we queue the work directly otherwise
- * smb_direct_cm_handler() will do it, when
- * RDMA_CM_EVENT_ESTABLISHED arrived.
- */
- spin_lock_irqsave(&sc->connect.lock, flags);
- if (!sc->first_error) {
- INIT_WORK(&sc->connect.work, smb_direct_negotiate_recv_work);
- if (sc->status == SMBDIRECT_SOCKET_NEGOTIATE_NEEDED)
- queue_work(sc->workqueue, &sc->connect.work);
- }
- spin_unlock_irqrestore(&sc->connect.lock, flags);
-}
-
-static void smb_direct_negotiate_recv_work(struct work_struct *work)
-{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, connect.work);
- const struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_recv_io *recv_io;
-
- if (sc->first_error)
- return;
-
- ksmbd_debug(RDMA, "Negotiate Recv Work running\n");
-
- /*
- * Reset timer to the keepalive interval in
- * order to trigger our next keepalive message.
- */
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->keepalive_interval_msec));
-
- /*
- * If smb_direct_negotiate_recv_done() detected an
- * invalid request we want to disconnect.
- */
- recv_io = get_first_reassembly(sc);
- if (!recv_io) {
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
-
- if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_NEGOTIATE_NEEDED)) {
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_RUNNING;
- wake_up(&sc->status_wait);
-}
-
-static int smb_direct_post_recv(struct smbdirect_socket *sc,
- struct smbdirect_recv_io *recvmsg)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct ib_recv_wr wr;
- int ret;
-
- recvmsg->sge.addr = ib_dma_map_single(sc->ib.dev,
- recvmsg->packet,
- sp->max_recv_size,
- DMA_FROM_DEVICE);
- ret = ib_dma_mapping_error(sc->ib.dev, recvmsg->sge.addr);
- if (ret)
- return ret;
- recvmsg->sge.length = sp->max_recv_size;
- recvmsg->sge.lkey = sc->ib.pd->local_dma_lkey;
-
- wr.wr_cqe = &recvmsg->cqe;
- wr.next = NULL;
- wr.sg_list = &recvmsg->sge;
- wr.num_sge = 1;
-
- ret = ib_post_recv(sc->ib.qp, &wr, NULL);
- if (ret) {
- pr_err("Can't post recv: %d\n", ret);
- ib_dma_unmap_single(sc->ib.dev,
- recvmsg->sge.addr, recvmsg->sge.length,
- DMA_FROM_DEVICE);
- recvmsg->sge.length = 0;
- smb_direct_disconnect_rdma_connection(sc);
- return ret;
- }
- return ret;
-}
-
static int smb_direct_read(struct ksmbd_transport *t, char *buf,
unsigned int size, int unused)
{
- struct smbdirect_recv_io *recvmsg;
- struct smbdirect_data_transfer *data_transfer;
- int to_copy, to_read, data_read, offset;
- u32 data_length, remaining_data_length, data_offset;
- int rc;
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = &st->socket;
-
-again:
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
- pr_err("disconnected\n");
- return -ENOTCONN;
- }
-
- /*
- * No need to hold the reassembly queue lock all the time as we are
- * the only one reading from the front of the queue. The transport
- * may add more entries to the back of the queue at the same time
- */
- if (sc->recv_io.reassembly.data_length >= size) {
- int queue_length;
- int queue_removed = 0;
- unsigned long flags;
-
- /*
- * Need to make sure reassembly_data_length is read before
- * reading reassembly_queue_length and calling
- * get_first_reassembly. This call is lock free
- * as we never read at the end of the queue which are being
- * updated in SOFTIRQ as more data is received
- */
- virt_rmb();
- queue_length = sc->recv_io.reassembly.queue_length;
- data_read = 0;
- to_read = size;
- offset = sc->recv_io.reassembly.first_entry_offset;
- while (data_read < size) {
- recvmsg = get_first_reassembly(sc);
- data_transfer = smbdirect_recv_io_payload(recvmsg);
- data_length = le32_to_cpu(data_transfer->data_length);
- remaining_data_length =
- le32_to_cpu(data_transfer->remaining_data_length);
- data_offset = le32_to_cpu(data_transfer->data_offset);
-
- /*
- * The upper layer expects RFC1002 length at the
- * beginning of the payload. Return it to indicate
- * the total length of the packet. This minimize the
- * change to upper layer packet processing logic. This
- * will be eventually remove when an intermediate
- * transport layer is added
- */
- if (recvmsg->first_segment && size == 4) {
- unsigned int rfc1002_len =
- data_length + remaining_data_length;
- *((__be32 *)buf) = cpu_to_be32(rfc1002_len);
- data_read = 4;
- recvmsg->first_segment = false;
- ksmbd_debug(RDMA,
- "returning rfc1002 length %d\n",
- rfc1002_len);
- goto read_rfc1002_done;
- }
-
- to_copy = min_t(int, data_length - offset, to_read);
- memcpy(buf + data_read, (char *)data_transfer + data_offset + offset,
- to_copy);
-
- /* move on to the next buffer? */
- if (to_copy == data_length - offset) {
- queue_length--;
- /*
- * No need to lock if we are not at the
- * end of the queue
- */
- if (queue_length) {
- list_del(&recvmsg->list);
- } else {
- spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
- list_del(&recvmsg->list);
- spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
- }
- queue_removed++;
- put_recvmsg(sc, recvmsg);
- offset = 0;
- } else {
- offset += to_copy;
- }
-
- to_read -= to_copy;
- data_read += to_copy;
- }
-
- spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
- sc->recv_io.reassembly.data_length -= data_read;
- sc->recv_io.reassembly.queue_length -= queue_removed;
- spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
-
- sc->recv_io.reassembly.first_entry_offset = offset;
- ksmbd_debug(RDMA,
- "returning to thread data_read=%d reassembly_data_length=%d first_entry_offset=%d\n",
- data_read, sc->recv_io.reassembly.data_length,
- sc->recv_io.reassembly.first_entry_offset);
-read_rfc1002_done:
- return data_read;
- }
-
- ksmbd_debug(RDMA, "wait_event on more data\n");
- rc = wait_event_interruptible(sc->recv_io.reassembly.wait_queue,
- sc->recv_io.reassembly.data_length >= size ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
- if (rc)
- return -EINTR;
-
- goto again;
-}
-
-static void smb_direct_post_recv_credits(struct work_struct *work)
-{
- struct smbdirect_socket *sc =
- container_of(work, struct smbdirect_socket, recv_io.posted.refill_work);
- struct smbdirect_recv_io *recvmsg;
- int credits = 0;
- int ret;
-
- if (atomic_read(&sc->recv_io.credits.count) < sc->recv_io.credits.target) {
- while (true) {
- recvmsg = get_free_recvmsg(sc);
- if (!recvmsg)
- break;
-
- recvmsg->first_segment = false;
-
- ret = smb_direct_post_recv(sc, recvmsg);
- if (ret) {
- pr_err("Can't post recv: %d\n", ret);
- put_recvmsg(sc, recvmsg);
- break;
- }
- credits++;
-
- atomic_inc(&sc->recv_io.posted.count);
- }
- }
-
- atomic_add(credits, &sc->recv_io.credits.available);
-
- /*
- * If the last send credit is waiting for credits
- * it can grant we need to wake it up
- */
- if (credits &&
- atomic_read(&sc->send_io.bcredits.count) == 0 &&
- atomic_read(&sc->send_io.credits.count) == 0)
- wake_up(&sc->send_io.credits.wait_queue);
-
- if (credits)
- queue_work(sc->workqueue, &sc->idle.immediate_work);
-}
-
-static void send_done(struct ib_cq *cq, struct ib_wc *wc)
-{
- struct smbdirect_send_io *sendmsg, *sibling, *next;
- struct smbdirect_socket *sc;
- int lcredits = 0;
-
- sendmsg = container_of(wc->wr_cqe, struct smbdirect_send_io, cqe);
- sc = sendmsg->socket;
-
- ksmbd_debug(RDMA, "Send completed. status='%s (%d)', opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->status,
- wc->opcode);
-
- if (unlikely(!(sendmsg->wr.send_flags & IB_SEND_SIGNALED))) {
- /*
- * This happens when smbdirect_send_io is a sibling
- * before the final message, it is signaled on
- * error anyway, so we need to skip
- * smbdirect_connection_free_send_io here,
- * otherwise is will destroy the memory
- * of the siblings too, which will cause
- * use after free problems for the others
- * triggered from ib_drain_qp().
- */
- if (wc->status != IB_WC_SUCCESS)
- goto skip_free;
-
- /*
- * This should not happen!
- * But we better just close the
- * connection...
- */
- pr_err("unexpected send completion wc->status=%s (%d) wc->opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->status, wc->opcode);
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
-
- /*
- * Free possible siblings and then the main send_io
- */
- list_for_each_entry_safe(sibling, next, &sendmsg->sibling_list, sibling_list) {
- list_del_init(&sibling->sibling_list);
- smb_direct_free_sendmsg(sc, sibling);
- lcredits += 1;
- }
- /* Note this frees wc->wr_cqe, but not wc */
- smb_direct_free_sendmsg(sc, sendmsg);
- lcredits += 1;
-
- if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_SEND) {
-skip_free:
- pr_err("Send error. status='%s (%d)', opcode=%d\n",
- ib_wc_status_msg(wc->status), wc->status,
- wc->opcode);
- smb_direct_disconnect_rdma_connection(sc);
- return;
- }
-
- atomic_add(lcredits, &sc->send_io.lcredits.count);
- wake_up(&sc->send_io.lcredits.wait_queue);
-
- if (atomic_dec_and_test(&sc->send_io.pending.count))
- wake_up(&sc->send_io.pending.zero_wait_queue);
-}
-
-static int manage_credits_prior_sending(struct smbdirect_socket *sc)
-{
- int missing;
- int available;
- int new_credits;
-
- if (atomic_read(&sc->recv_io.credits.count) >= sc->recv_io.credits.target)
- return 0;
-
- missing = (int)sc->recv_io.credits.target - atomic_read(&sc->recv_io.credits.count);
- available = atomic_xchg(&sc->recv_io.credits.available, 0);
- new_credits = (u16)min3(U16_MAX, missing, available);
- if (new_credits <= 0) {
- /*
- * If credits are available, but not granted
- * we need to re-add them again.
- */
- if (available)
- atomic_add(available, &sc->recv_io.credits.available);
- return 0;
- }
-
- if (new_credits < available) {
- /*
- * Readd the remaining available again.
- */
- available -= new_credits;
- atomic_add(available, &sc->recv_io.credits.available);
- }
-
- /*
- * Remember we granted the credits
- */
- atomic_add(new_credits, &sc->recv_io.credits.count);
- return new_credits;
-}
-
-static int manage_keep_alive_before_sending(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
-
- if (sc->idle.keepalive == SMBDIRECT_KEEPALIVE_PENDING) {
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_SENT;
- /*
- * Now use the keepalive timeout (instead of keepalive interval)
- * in order to wait for a response
- */
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->keepalive_timeout_msec));
- return 1;
- }
- return 0;
-}
-
-static int smb_direct_post_send(struct smbdirect_socket *sc,
- struct ib_send_wr *wr)
-{
+ struct smbdirect_socket *sc = st->socket;
+ struct msghdr msg = { .msg_flags = 0, };
+ struct kvec iov = {
+ .iov_base = buf,
+ .iov_len = size,
+ };
int ret;
- atomic_inc(&sc->send_io.pending.count);
- ret = ib_post_send(sc->ib.qp, wr, NULL);
- if (ret) {
- pr_err("failed to post send: %d\n", ret);
- smb_direct_disconnect_rdma_connection(sc);
- }
- return ret;
-}
-
-static void smb_direct_send_ctx_init(struct smbdirect_send_batch *send_ctx,
- bool need_invalidate_rkey,
- unsigned int remote_key)
-{
- INIT_LIST_HEAD(&send_ctx->msg_list);
- send_ctx->wr_cnt = 0;
- send_ctx->need_invalidate_rkey = need_invalidate_rkey;
- send_ctx->remote_key = remote_key;
- send_ctx->credit = 0;
-}
-
-static int smb_direct_flush_send_list(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *send_ctx,
- bool is_last)
-{
- struct smbdirect_send_io *first, *last;
- int ret = 0;
-
- if (list_empty(&send_ctx->msg_list))
- goto release_credit;
-
- first = list_first_entry(&send_ctx->msg_list,
- struct smbdirect_send_io,
- sibling_list);
- last = list_last_entry(&send_ctx->msg_list,
- struct smbdirect_send_io,
- sibling_list);
-
- if (send_ctx->need_invalidate_rkey) {
- first->wr.opcode = IB_WR_SEND_WITH_INV;
- first->wr.ex.invalidate_rkey = send_ctx->remote_key;
- send_ctx->need_invalidate_rkey = false;
- send_ctx->remote_key = 0;
- }
-
- last->wr.send_flags = IB_SEND_SIGNALED;
- last->wr.wr_cqe = &last->cqe;
-
- /*
- * Remove last from send_ctx->msg_list
- * and splice the rest of send_ctx->msg_list
- * to last->sibling_list.
- *
- * send_ctx->msg_list is a valid empty list
- * at the end.
- */
- list_del_init(&last->sibling_list);
- list_splice_tail_init(&send_ctx->msg_list, &last->sibling_list);
- send_ctx->wr_cnt = 0;
-
- ret = smb_direct_post_send(sc, &first->wr);
- if (ret) {
- struct smbdirect_send_io *sibling, *next;
-
- list_for_each_entry_safe(sibling, next, &last->sibling_list, sibling_list) {
- list_del_init(&sibling->sibling_list);
- smb_direct_free_sendmsg(sc, sibling);
- }
- smb_direct_free_sendmsg(sc, last);
- }
-
-release_credit:
- if (is_last && !ret && send_ctx->credit) {
- atomic_add(send_ctx->credit, &sc->send_io.bcredits.count);
- send_ctx->credit = 0;
- wake_up(&sc->send_io.bcredits.wait_queue);
- }
+ iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, size);
- return ret;
-}
-
-static int wait_for_credits(struct smbdirect_socket *sc,
- wait_queue_head_t *waitq, atomic_t *total_credits,
- int needed)
-{
- int ret;
-
- do {
- if (atomic_sub_return(needed, total_credits) >= 0)
- return 0;
-
- atomic_add(needed, total_credits);
- ret = wait_event_interruptible(*waitq,
- atomic_read(total_credits) >= needed ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return -ENOTCONN;
- else if (ret < 0)
- return ret;
- } while (true);
-}
-
-static int wait_for_send_bcredit(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *send_ctx)
-{
- int ret;
-
- if (send_ctx->credit)
- return 0;
-
- ret = wait_for_credits(sc,
- &sc->send_io.bcredits.wait_queue,
- &sc->send_io.bcredits.count,
- 1);
- if (ret)
- return ret;
-
- send_ctx->credit = 1;
- return 0;
-}
-
-static int wait_for_send_lcredit(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *send_ctx)
-{
- if (send_ctx && (atomic_read(&sc->send_io.lcredits.count) <= 1)) {
- int ret;
-
- ret = smb_direct_flush_send_list(sc, send_ctx, false);
- if (ret)
- return ret;
- }
-
- return wait_for_credits(sc,
- &sc->send_io.lcredits.wait_queue,
- &sc->send_io.lcredits.count,
- 1);
-}
-
-static int wait_for_send_credits(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *send_ctx)
-{
- int ret;
-
- if (send_ctx &&
- (send_ctx->wr_cnt >= 16 || atomic_read(&sc->send_io.credits.count) <= 1)) {
- ret = smb_direct_flush_send_list(sc, send_ctx, false);
- if (ret)
- return ret;
- }
-
- return wait_for_credits(sc, &sc->send_io.credits.wait_queue, &sc->send_io.credits.count, 1);
-}
-
-static int wait_for_rw_credits(struct smbdirect_socket *sc, int credits)
-{
- return wait_for_credits(sc,
- &sc->rw_io.credits.wait_queue,
- &sc->rw_io.credits.count,
- credits);
-}
-
-static int calc_rw_credits(struct smbdirect_socket *sc,
- char *buf, unsigned int len)
-{
- return DIV_ROUND_UP(get_buf_page_count(buf, len),
- sc->rw_io.credits.num_pages);
-}
-
-static int smb_direct_create_header(struct smbdirect_socket *sc,
- int size, int remaining_data_length,
- int new_credits,
- struct smbdirect_send_io **sendmsg_out)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_send_io *sendmsg;
- struct smbdirect_data_transfer *packet;
- int header_length;
- int ret;
-
- sendmsg = smb_direct_alloc_sendmsg(sc);
- if (IS_ERR(sendmsg))
- return PTR_ERR(sendmsg);
-
- /* Fill in the packet header */
- packet = (struct smbdirect_data_transfer *)sendmsg->packet;
- packet->credits_requested = cpu_to_le16(sp->send_credit_target);
- packet->credits_granted = cpu_to_le16(new_credits);
-
- packet->flags = 0;
- if (manage_keep_alive_before_sending(sc))
- packet->flags |= cpu_to_le16(SMBDIRECT_FLAG_RESPONSE_REQUESTED);
-
- packet->reserved = 0;
- if (!size)
- packet->data_offset = 0;
- else
- packet->data_offset = cpu_to_le32(24);
- packet->data_length = cpu_to_le32(size);
- packet->remaining_data_length = cpu_to_le32(remaining_data_length);
- packet->padding = 0;
-
- ksmbd_debug(RDMA,
- "credits_requested=%d credits_granted=%d data_offset=%d data_length=%d remaining_data_length=%d\n",
- le16_to_cpu(packet->credits_requested),
- le16_to_cpu(packet->credits_granted),
- le32_to_cpu(packet->data_offset),
- le32_to_cpu(packet->data_length),
- le32_to_cpu(packet->remaining_data_length));
-
- /* Map the packet to DMA */
- header_length = sizeof(struct smbdirect_data_transfer);
- /* If this is a packet without payload, don't send padding */
- if (!size)
- header_length =
- offsetof(struct smbdirect_data_transfer, padding);
-
- sendmsg->sge[0].addr = ib_dma_map_single(sc->ib.dev,
- (void *)packet,
- header_length,
- DMA_TO_DEVICE);
- ret = ib_dma_mapping_error(sc->ib.dev, sendmsg->sge[0].addr);
- if (ret) {
- smb_direct_free_sendmsg(sc, sendmsg);
- return ret;
- }
-
- sendmsg->num_sge = 1;
- sendmsg->sge[0].length = header_length;
- sendmsg->sge[0].lkey = sc->ib.pd->local_dma_lkey;
-
- *sendmsg_out = sendmsg;
- return 0;
-}
-
-static int get_sg_list(void *buf, int size, struct scatterlist *sg_list, int nentries)
-{
- bool high = is_vmalloc_addr(buf);
- struct page *page;
- int offset, len;
- int i = 0;
-
- if (size <= 0 || nentries < get_buf_page_count(buf, size))
- return -EINVAL;
-
- offset = offset_in_page(buf);
- buf -= offset;
- while (size > 0) {
- len = min_t(int, PAGE_SIZE - offset, size);
- if (high)
- page = vmalloc_to_page(buf);
- else
- page = kmap_to_page(buf);
-
- if (!sg_list)
- return -EINVAL;
- sg_set_page(sg_list, page, len, offset);
- sg_list = sg_next(sg_list);
-
- buf += PAGE_SIZE;
- size -= len;
- offset = 0;
- i++;
- }
- return i;
-}
-
-static int get_mapped_sg_list(struct ib_device *device, void *buf, int size,
- struct scatterlist *sg_list, int nentries,
- enum dma_data_direction dir, int *npages)
-{
- *npages = get_sg_list(buf, size, sg_list, nentries);
- if (*npages < 0)
- return -EINVAL;
- return ib_dma_map_sg(device, sg_list, *npages, dir);
-}
-
-static int post_sendmsg(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *send_ctx,
- struct smbdirect_send_io *msg)
-{
- int i;
-
- for (i = 0; i < msg->num_sge; i++)
- ib_dma_sync_single_for_device(sc->ib.dev,
- msg->sge[i].addr, msg->sge[i].length,
- DMA_TO_DEVICE);
-
- msg->cqe.done = send_done;
- msg->wr.opcode = IB_WR_SEND;
- msg->wr.sg_list = &msg->sge[0];
- msg->wr.num_sge = msg->num_sge;
- msg->wr.next = NULL;
-
- if (send_ctx) {
- msg->wr.wr_cqe = NULL;
- msg->wr.send_flags = 0;
- if (!list_empty(&send_ctx->msg_list)) {
- struct smbdirect_send_io *last;
-
- last = list_last_entry(&send_ctx->msg_list,
- struct smbdirect_send_io,
- sibling_list);
- last->wr.next = &msg->wr;
- }
- list_add_tail(&msg->sibling_list, &send_ctx->msg_list);
- send_ctx->wr_cnt++;
- return 0;
- }
-
- msg->wr.wr_cqe = &msg->cqe;
- msg->wr.send_flags = IB_SEND_SIGNALED;
- return smb_direct_post_send(sc, &msg->wr);
-}
-
-static int smb_direct_post_send_data(struct smbdirect_socket *sc,
- struct smbdirect_send_batch *send_ctx,
- struct kvec *iov, int niov,
- int remaining_data_length)
-{
- int i, j, ret;
- struct smbdirect_send_io *msg;
- int data_length;
- struct scatterlist sg[SMBDIRECT_SEND_IO_MAX_SGE - 1];
- struct smbdirect_send_batch _send_ctx;
- int new_credits;
-
- if (!send_ctx) {
- smb_direct_send_ctx_init(&_send_ctx, false, 0);
- send_ctx = &_send_ctx;
- }
-
- ret = wait_for_send_bcredit(sc, send_ctx);
- if (ret)
- goto bcredit_failed;
-
- ret = wait_for_send_lcredit(sc, send_ctx);
- if (ret)
- goto lcredit_failed;
-
- ret = wait_for_send_credits(sc, send_ctx);
- if (ret)
- goto credit_failed;
-
- new_credits = manage_credits_prior_sending(sc);
- if (new_credits == 0 &&
- atomic_read(&sc->send_io.credits.count) == 0 &&
- atomic_read(&sc->recv_io.credits.count) == 0) {
- queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
- ret = wait_event_interruptible(sc->send_io.credits.wait_queue,
- atomic_read(&sc->send_io.credits.count) >= 1 ||
- atomic_read(&sc->recv_io.credits.available) >= 1 ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- ret = -ENOTCONN;
- if (ret < 0)
- goto credit_failed;
-
- new_credits = manage_credits_prior_sending(sc);
- }
-
- data_length = 0;
- for (i = 0; i < niov; i++)
- data_length += iov[i].iov_len;
-
- ret = smb_direct_create_header(sc, data_length, remaining_data_length,
- new_credits, &msg);
- if (ret)
- goto header_failed;
-
- for (i = 0; i < niov; i++) {
- struct ib_sge *sge;
- int sg_cnt;
- int npages;
-
- sg_init_table(sg, SMBDIRECT_SEND_IO_MAX_SGE - 1);
- sg_cnt = get_mapped_sg_list(sc->ib.dev,
- iov[i].iov_base, iov[i].iov_len,
- sg, SMBDIRECT_SEND_IO_MAX_SGE - 1,
- DMA_TO_DEVICE, &npages);
- if (sg_cnt <= 0) {
- pr_err("failed to map buffer\n");
- ret = -ENOMEM;
- goto err;
- } else if (sg_cnt + msg->num_sge > SMBDIRECT_SEND_IO_MAX_SGE) {
- pr_err("buffer not fitted into sges\n");
- ret = -E2BIG;
- ib_dma_unmap_sg(sc->ib.dev, sg, npages,
- DMA_TO_DEVICE);
- goto err;
- }
-
- for (j = 0; j < sg_cnt; j++) {
- sge = &msg->sge[msg->num_sge];
- sge->addr = sg_dma_address(&sg[j]);
- sge->length = sg_dma_len(&sg[j]);
- sge->lkey = sc->ib.pd->local_dma_lkey;
- msg->num_sge++;
- }
- }
-
- ret = post_sendmsg(sc, send_ctx, msg);
- if (ret)
- goto err;
-
- if (send_ctx == &_send_ctx) {
- ret = smb_direct_flush_send_list(sc, send_ctx, true);
- if (ret)
- goto err;
- }
-
- return 0;
-err:
- smb_direct_free_sendmsg(sc, msg);
-header_failed:
- atomic_inc(&sc->send_io.credits.count);
-credit_failed:
- atomic_inc(&sc->send_io.lcredits.count);
-lcredit_failed:
- atomic_add(send_ctx->credit, &sc->send_io.bcredits.count);
- send_ctx->credit = 0;
-bcredit_failed:
+ ret = smbdirect_connection_recvmsg(sc, &msg, 0);
+ if (ret == -ERESTARTSYS)
+ ret = -EINTR;
return ret;
}
static int smb_direct_writev(struct ksmbd_transport *t,
- struct kvec *iov, int niovs, int buflen,
- bool need_invalidate, unsigned int remote_key)
+ const struct ksmbd_transport_write *tx)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = &st->socket;
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- size_t remaining_data_length;
- size_t iov_idx;
- size_t iov_ofs;
- size_t max_iov_size = sp->max_send_size -
- sizeof(struct smbdirect_data_transfer);
- int ret;
- struct smbdirect_send_batch send_ctx;
- int error = 0;
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return -ENOTCONN;
-
- //FIXME: skip RFC1002 header..
- if (WARN_ON_ONCE(niovs <= 1 || iov[0].iov_len != 4))
- return -EINVAL;
- buflen -= 4;
- iov_idx = 1;
- iov_ofs = 0;
-
- remaining_data_length = buflen;
- ksmbd_debug(RDMA, "Sending smb (RDMA): smb_len=%u\n", buflen);
-
- smb_direct_send_ctx_init(&send_ctx, need_invalidate, remote_key);
- while (remaining_data_length) {
- struct kvec vecs[SMBDIRECT_SEND_IO_MAX_SGE - 1]; /* minus smbdirect hdr */
- size_t possible_bytes = max_iov_size;
- size_t possible_vecs;
- size_t bytes = 0;
- size_t nvecs = 0;
-
- /*
- * For the last message remaining_data_length should be
- * have been 0 already!
- */
- if (WARN_ON_ONCE(iov_idx >= niovs)) {
- error = -EINVAL;
- goto done;
- }
-
- /*
- * We have 2 factors which limit the arguments we pass
- * to smb_direct_post_send_data():
- *
- * 1. The number of supported sges for the send,
- * while one is reserved for the smbdirect header.
- * And we currently need one SGE per page.
- * 2. The number of negotiated payload bytes per send.
- */
- possible_vecs = min_t(size_t, ARRAY_SIZE(vecs), niovs - iov_idx);
-
- while (iov_idx < niovs && possible_vecs && possible_bytes) {
- struct kvec *v = &vecs[nvecs];
- int page_count;
-
- v->iov_base = ((u8 *)iov[iov_idx].iov_base) + iov_ofs;
- v->iov_len = min_t(size_t,
- iov[iov_idx].iov_len - iov_ofs,
- possible_bytes);
- page_count = get_buf_page_count(v->iov_base, v->iov_len);
- if (page_count > possible_vecs) {
- /*
- * If the number of pages in the buffer
- * is to much (because we currently require
- * one SGE per page), we need to limit the
- * length.
- *
- * We know possible_vecs is at least 1,
- * so we always keep the first page.
- *
- * We need to calculate the number extra
- * pages (epages) we can also keep.
- *
- * We calculate the number of bytes in the
- * first page (fplen), this should never be
- * larger than v->iov_len because page_count is
- * at least 2, but adding a limitation feels
- * better.
- *
- * Then we calculate the number of bytes (elen)
- * we can keep for the extra pages.
- */
- size_t epages = possible_vecs - 1;
- size_t fpofs = offset_in_page(v->iov_base);
- size_t fplen = min_t(size_t, PAGE_SIZE - fpofs, v->iov_len);
- size_t elen = min_t(size_t, v->iov_len - fplen, epages*PAGE_SIZE);
-
- v->iov_len = fplen + elen;
- page_count = get_buf_page_count(v->iov_base, v->iov_len);
- if (WARN_ON_ONCE(page_count > possible_vecs)) {
- /*
- * Something went wrong in the above
- * logic...
- */
- error = -EINVAL;
- goto done;
- }
- }
- possible_vecs -= page_count;
- nvecs += 1;
- possible_bytes -= v->iov_len;
- bytes += v->iov_len;
-
- iov_ofs += v->iov_len;
- if (iov_ofs >= iov[iov_idx].iov_len) {
- iov_idx += 1;
- iov_ofs = 0;
- }
- }
-
- remaining_data_length -= bytes;
-
- ret = smb_direct_post_send_data(sc, &send_ctx,
- vecs, nvecs,
- remaining_data_length);
- if (unlikely(ret)) {
- error = ret;
- goto done;
- }
- }
-
-done:
- ret = smb_direct_flush_send_list(sc, &send_ctx, true);
- if (unlikely(!ret && error))
- ret = error;
-
- /*
- * As an optimization, we don't wait for individual I/O to finish
- * before sending the next one.
- * Send them all and wait for pending send count to get to 0
- * that means all the I/Os have been out and we are good to return
- */
-
- wait_event(sc->send_io.pending.zero_wait_queue,
- atomic_read(&sc->send_io.pending.count) == 0 ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED && ret == 0)
- ret = -ENOTCONN;
-
- return ret;
-}
-
-static void smb_direct_free_rdma_rw_msg(struct smb_direct_transport *t,
- struct smbdirect_rw_io *msg,
- enum dma_data_direction dir)
-{
- struct smbdirect_socket *sc = &t->socket;
-
- rdma_rw_ctx_destroy(&msg->rdma_ctx, sc->ib.qp, sc->ib.qp->port,
- msg->sgt.sgl, msg->sgt.nents, dir);
- sg_free_table_chained(&msg->sgt, SG_CHUNK_SIZE);
- kfree(msg);
-}
-
-static void read_write_done(struct ib_cq *cq, struct ib_wc *wc,
- enum dma_data_direction dir)
-{
- struct smbdirect_rw_io *msg =
- container_of(wc->wr_cqe, struct smbdirect_rw_io, cqe);
- struct smbdirect_socket *sc = msg->socket;
-
- if (wc->status != IB_WC_SUCCESS) {
- msg->error = -EIO;
- pr_err("read/write error. opcode = %d, status = %s(%d)\n",
- wc->opcode, ib_wc_status_msg(wc->status), wc->status);
- if (wc->status != IB_WC_WR_FLUSH_ERR)
- smb_direct_disconnect_rdma_connection(sc);
- }
-
- complete(msg->completion);
-}
-
-static void read_done(struct ib_cq *cq, struct ib_wc *wc)
-{
- read_write_done(cq, wc, DMA_FROM_DEVICE);
-}
-
-static void write_done(struct ib_cq *cq, struct ib_wc *wc)
-{
- read_write_done(cq, wc, DMA_TO_DEVICE);
-}
-
-static int smb_direct_rdma_xmit(struct smb_direct_transport *t,
- void *buf, int buf_len,
- struct smbdirect_buffer_descriptor_v1 *desc,
- unsigned int desc_len,
- bool is_read)
-{
- struct smbdirect_socket *sc = &t->socket;
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_rw_io *msg, *next_msg;
- int i, ret;
- DECLARE_COMPLETION_ONSTACK(completion);
- struct ib_send_wr *first_wr;
- LIST_HEAD(msg_list);
- char *desc_buf;
- int credits_needed;
- unsigned int desc_buf_len, desc_num = 0;
-
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return -ENOTCONN;
-
- if (buf_len > sp->max_read_write_size)
- return -EINVAL;
-
- /* calculate needed credits */
- credits_needed = 0;
- desc_buf = buf;
- for (i = 0; i < desc_len / sizeof(*desc); i++) {
- if (!buf_len)
- break;
-
- desc_buf_len = le32_to_cpu(desc[i].length);
- if (!desc_buf_len)
- return -EINVAL;
+ struct smbdirect_socket *sc = st->socket;
+ struct iov_iter iter;
- if (desc_buf_len > buf_len) {
- desc_buf_len = buf_len;
- desc[i].length = cpu_to_le32(desc_buf_len);
- buf_len = 0;
- }
+ iov_iter_kvec(&iter, ITER_SOURCE, tx->iov, tx->iov_cnt,
+ tx->size);
- credits_needed += calc_rw_credits(sc, desc_buf, desc_buf_len);
- desc_buf += desc_buf_len;
- buf_len -= desc_buf_len;
- desc_num++;
- }
-
- ksmbd_debug(RDMA, "RDMA %s, len %#x, needed credits %#x\n",
- str_read_write(is_read), buf_len, credits_needed);
-
- ret = wait_for_rw_credits(sc, credits_needed);
- if (ret < 0)
- return ret;
-
- /* build rdma_rw_ctx for each descriptor */
- desc_buf = buf;
- for (i = 0; i < desc_num; i++) {
- msg = kzalloc_flex(*msg, sg_list, SG_CHUNK_SIZE,
- KSMBD_DEFAULT_GFP);
- if (!msg) {
- ret = -ENOMEM;
- goto out;
- }
-
- desc_buf_len = le32_to_cpu(desc[i].length);
-
- msg->socket = sc;
- msg->cqe.done = is_read ? read_done : write_done;
- msg->completion = &completion;
-
- msg->sgt.sgl = &msg->sg_list[0];
- ret = sg_alloc_table_chained(&msg->sgt,
- get_buf_page_count(desc_buf, desc_buf_len),
- msg->sg_list, SG_CHUNK_SIZE);
- if (ret) {
- ret = -ENOMEM;
- goto free_msg;
- }
-
- ret = get_sg_list(desc_buf, desc_buf_len,
- msg->sgt.sgl, msg->sgt.orig_nents);
- if (ret < 0)
- goto free_table;
-
- ret = rdma_rw_ctx_init(&msg->rdma_ctx, sc->ib.qp, sc->ib.qp->port,
- msg->sgt.sgl,
- get_buf_page_count(desc_buf, desc_buf_len),
- 0,
- le64_to_cpu(desc[i].offset),
- le32_to_cpu(desc[i].token),
- is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
- if (ret < 0) {
- pr_err("failed to init rdma_rw_ctx: %d\n", ret);
- goto free_table;
- }
-
- list_add_tail(&msg->list, &msg_list);
- desc_buf += desc_buf_len;
- }
-
- /* concatenate work requests of rdma_rw_ctxs */
- first_wr = NULL;
- list_for_each_entry_reverse(msg, &msg_list, list) {
- first_wr = rdma_rw_ctx_wrs(&msg->rdma_ctx, sc->ib.qp, sc->ib.qp->port,
- &msg->cqe, first_wr);
- }
-
- ret = ib_post_send(sc->ib.qp, first_wr, NULL);
- if (ret) {
- pr_err("failed to post send wr for RDMA R/W: %d\n", ret);
- goto out;
- }
-
- msg = list_last_entry(&msg_list, struct smbdirect_rw_io, list);
- wait_for_completion(&completion);
- ret = msg->error;
-out:
- list_for_each_entry_safe(msg, next_msg, &msg_list, list) {
- list_del(&msg->list);
- smb_direct_free_rdma_rw_msg(t, msg,
- is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
- }
- atomic_add(credits_needed, &sc->rw_io.credits.count);
- wake_up(&sc->rw_io.credits.wait_queue);
- return ret;
-
-free_table:
- sg_free_table_chained(&msg->sgt, SG_CHUNK_SIZE);
-free_msg:
- kfree(msg);
- goto out;
+ return smbdirect_connection_send_iter(sc, &iter, 0,
+ tx->need_invalidate_rkey,
+ tx->remote_key);
}
static int smb_direct_rdma_write(struct ksmbd_transport *t,
@@ -1933,8 +260,11 @@ static int smb_direct_rdma_write(struct ksmbd_transport *t,
struct smbdirect_buffer_descriptor_v1 *desc,
unsigned int desc_len)
{
- return smb_direct_rdma_xmit(SMBD_TRANS(t), buf, buflen,
- desc, desc_len, false);
+ struct smb_direct_transport *st = SMBD_TRANS(t);
+ struct smbdirect_socket *sc = st->socket;
+
+ return smbdirect_connection_rdma_xmit(sc, buf, buflen,
+ desc, desc_len, false);
}
static int smb_direct_rdma_read(struct ksmbd_transport *t,
@@ -1942,16 +272,19 @@ static int smb_direct_rdma_read(struct ksmbd_transport *t,
struct smbdirect_buffer_descriptor_v1 *desc,
unsigned int desc_len)
{
- return smb_direct_rdma_xmit(SMBD_TRANS(t), buf, buflen,
- desc, desc_len, true);
+ struct smb_direct_transport *st = SMBD_TRANS(t);
+ struct smbdirect_socket *sc = st->socket;
+
+ return smbdirect_connection_rdma_xmit(sc, buf, buflen,
+ desc, desc_len, true);
}
static void smb_direct_disconnect(struct ksmbd_transport *t)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = &st->socket;
+ struct smbdirect_socket *sc = st->socket;
- ksmbd_debug(RDMA, "Disconnecting cm_id=%p\n", sc->rdma.cm_id);
+ ksmbd_debug(RDMA, "Disconnecting sc=%p\n", sc);
free_transport(st);
}
@@ -1959,840 +292,103 @@ static void smb_direct_disconnect(struct ksmbd_transport *t)
static void smb_direct_shutdown(struct ksmbd_transport *t)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = &st->socket;
-
- ksmbd_debug(RDMA, "smb-direct shutdown cm_id=%p\n", sc->rdma.cm_id);
-
- smb_direct_disconnect_rdma_work(&sc->disconnect_work);
-}
-
-static int smb_direct_cm_handler(struct rdma_cm_id *cm_id,
- struct rdma_cm_event *event)
-{
- struct smbdirect_socket *sc = cm_id->context;
- unsigned long flags;
-
- ksmbd_debug(RDMA, "RDMA CM event. cm_id=%p event=%s (%d)\n",
- cm_id, rdma_event_msg(event->event), event->event);
-
- switch (event->event) {
- case RDMA_CM_EVENT_ESTABLISHED: {
- /*
- * Some drivers (at least mlx5_ib and irdma in roce mode)
- * might post a recv completion before RDMA_CM_EVENT_ESTABLISHED,
- * we need to adjust our expectation in that case.
- *
- * If smb_direct_negotiate_recv_done was called first
- * it initialized sc->connect.work only for us to
- * start, so that we turned into
- * SMBDIRECT_SOCKET_NEGOTIATE_NEEDED, before
- * smb_direct_negotiate_recv_work() runs.
- *
- * If smb_direct_negotiate_recv_done didn't happen
- * yet. sc->connect.work is still be disabled and
- * queue_work() is a no-op.
- */
- if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING))
- break;
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_NEEDED;
- spin_lock_irqsave(&sc->connect.lock, flags);
- if (!sc->first_error)
- queue_work(sc->workqueue, &sc->connect.work);
- spin_unlock_irqrestore(&sc->connect.lock, flags);
- wake_up(&sc->status_wait);
- break;
- }
- case RDMA_CM_EVENT_DEVICE_REMOVAL:
- case RDMA_CM_EVENT_DISCONNECTED: {
- sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
- smb_direct_disconnect_rdma_work(&sc->disconnect_work);
- if (sc->ib.qp)
- ib_drain_qp(sc->ib.qp);
- break;
- }
- case RDMA_CM_EVENT_CONNECT_ERROR: {
- sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
- smb_direct_disconnect_rdma_work(&sc->disconnect_work);
- break;
- }
- default:
- pr_err("Unexpected RDMA CM event. cm_id=%p, event=%s (%d)\n",
- cm_id, rdma_event_msg(event->event),
- event->event);
- break;
- }
- return 0;
-}
-
-static void smb_direct_qpair_handler(struct ib_event *event, void *context)
-{
- struct smbdirect_socket *sc = context;
-
- ksmbd_debug(RDMA, "Received QP event. cm_id=%p, event=%s (%d)\n",
- sc->rdma.cm_id, ib_event_msg(event->event), event->event);
-
- switch (event->event) {
- case IB_EVENT_CQ_ERR:
- case IB_EVENT_QP_FATAL:
- smb_direct_disconnect_rdma_connection(sc);
- break;
- default:
- break;
- }
-}
-
-static int smb_direct_send_negotiate_response(struct smbdirect_socket *sc,
- int failed)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_send_io *sendmsg;
- struct smbdirect_negotiate_resp *resp;
- int ret;
-
- sendmsg = smb_direct_alloc_sendmsg(sc);
- if (IS_ERR(sendmsg))
- return -ENOMEM;
-
- resp = (struct smbdirect_negotiate_resp *)sendmsg->packet;
- if (failed) {
- memset(resp, 0, sizeof(*resp));
- resp->min_version = SMB_DIRECT_VERSION_LE;
- resp->max_version = SMB_DIRECT_VERSION_LE;
- resp->status = STATUS_NOT_SUPPORTED;
-
- sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED;
- } else {
- resp->status = STATUS_SUCCESS;
- resp->min_version = SMB_DIRECT_VERSION_LE;
- resp->max_version = SMB_DIRECT_VERSION_LE;
- resp->negotiated_version = SMB_DIRECT_VERSION_LE;
- resp->reserved = 0;
- resp->credits_requested =
- cpu_to_le16(sp->send_credit_target);
- resp->credits_granted = cpu_to_le16(manage_credits_prior_sending(sc));
- resp->max_readwrite_size = cpu_to_le32(sp->max_read_write_size);
- resp->preferred_send_size = cpu_to_le32(sp->max_send_size);
- resp->max_receive_size = cpu_to_le32(sp->max_recv_size);
- resp->max_fragmented_size =
- cpu_to_le32(sp->max_fragmented_recv_size);
-
- atomic_set(&sc->send_io.bcredits.count, 1);
- sc->recv_io.expected = SMBDIRECT_EXPECT_DATA_TRANSFER;
- sc->status = SMBDIRECT_SOCKET_CONNECTED;
- }
-
- sendmsg->sge[0].addr = ib_dma_map_single(sc->ib.dev,
- (void *)resp, sizeof(*resp),
- DMA_TO_DEVICE);
- ret = ib_dma_mapping_error(sc->ib.dev, sendmsg->sge[0].addr);
- if (ret) {
- smb_direct_free_sendmsg(sc, sendmsg);
- return ret;
- }
-
- sendmsg->num_sge = 1;
- sendmsg->sge[0].length = sizeof(*resp);
- sendmsg->sge[0].lkey = sc->ib.pd->local_dma_lkey;
-
- ret = post_sendmsg(sc, NULL, sendmsg);
- if (ret) {
- smb_direct_free_sendmsg(sc, sendmsg);
- return ret;
- }
-
- wait_event(sc->send_io.pending.zero_wait_queue,
- atomic_read(&sc->send_io.pending.count) == 0 ||
- sc->status != SMBDIRECT_SOCKET_CONNECTED);
- if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
- return -ENOTCONN;
-
- return 0;
-}
-
-static int smb_direct_accept_client(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct rdma_conn_param conn_param;
- __be32 ird_ord_hdr[2];
- int ret;
-
- /*
- * smb_direct_handle_connect_request()
- * already negotiated sp->initiator_depth
- * and sp->responder_resources
- */
- memset(&conn_param, 0, sizeof(conn_param));
- conn_param.initiator_depth = sp->initiator_depth;
- conn_param.responder_resources = sp->responder_resources;
-
- if (sc->rdma.legacy_iwarp) {
- ird_ord_hdr[0] = cpu_to_be32(conn_param.responder_resources);
- ird_ord_hdr[1] = cpu_to_be32(conn_param.initiator_depth);
- conn_param.private_data = ird_ord_hdr;
- conn_param.private_data_len = sizeof(ird_ord_hdr);
- } else {
- conn_param.private_data = NULL;
- conn_param.private_data_len = 0;
- }
- conn_param.retry_count = SMB_DIRECT_CM_RETRY;
- conn_param.rnr_retry_count = SMB_DIRECT_CM_RNR_RETRY;
- conn_param.flow_control = 0;
+ struct smbdirect_socket *sc = st->socket;
- /*
- * start with the negotiate timeout and SMBDIRECT_KEEPALIVE_PENDING
- * so that the timer will cause a disconnect.
- */
- sc->idle.keepalive = SMBDIRECT_KEEPALIVE_PENDING;
- mod_delayed_work(sc->workqueue, &sc->idle.timer_work,
- msecs_to_jiffies(sp->negotiate_timeout_msec));
+ ksmbd_debug(RDMA, "smb-direct shutdown sc=%p\n", sc);
- WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED);
- sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING;
- ret = rdma_accept(sc->rdma.cm_id, &conn_param);
- if (ret) {
- pr_err("error at rdma_accept: %d\n", ret);
- return ret;
- }
- return 0;
+ smbdirect_socket_shutdown(sc);
}
-static int smb_direct_prepare_negotiation(struct smbdirect_socket *sc)
+static int smb_direct_new_connection(struct smb_direct_listener *listener,
+ struct smbdirect_socket *client_sc)
{
- struct smbdirect_recv_io *recvmsg;
- bool recv_posted = false;
+ struct smb_direct_transport *t;
+ struct task_struct *handler;
int ret;
- WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED);
- sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED;
-
- sc->recv_io.expected = SMBDIRECT_EXPECT_NEGOTIATE_REQ;
-
- recvmsg = get_free_recvmsg(sc);
- if (!recvmsg)
+ t = alloc_transport(client_sc);
+ if (!t) {
+ smbdirect_socket_release(client_sc);
return -ENOMEM;
- recvmsg->cqe.done = smb_direct_negotiate_recv_done;
-
- ret = smb_direct_post_recv(sc, recvmsg);
- if (ret) {
- pr_err("Can't post recv: %d\n", ret);
- goto out_err;
}
- recv_posted = true;
- ret = smb_direct_accept_client(sc);
- if (ret) {
- pr_err("Can't accept client\n");
+ handler = kthread_run(ksmbd_conn_handler_loop,
+ KSMBD_TRANS(t)->conn, "ksmbd:r%u",
+ listener->port);
+ if (IS_ERR(handler)) {
+ ret = PTR_ERR(handler);
+ pr_err("Can't start thread\n");
goto out_err;
}
return 0;
out_err:
- /*
- * If the recv was never posted, return it to the free list.
- * If it was posted, leave it alone so disconnect teardown can
- * drain the QP and complete it (flush) and the completion path
- * will unmap it exactly once.
- */
- if (!recv_posted)
- put_recvmsg(sc, recvmsg);
- return ret;
-}
-
-static int smb_direct_init_params(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- int max_send_sges;
- unsigned int maxpages;
-
- /* need 3 more sge. because a SMB_DIRECT header, SMB2 header,
- * SMB2 response could be mapped.
- */
- max_send_sges = DIV_ROUND_UP(sp->max_send_size, PAGE_SIZE) + 3;
- if (max_send_sges > SMBDIRECT_SEND_IO_MAX_SGE) {
- pr_err("max_send_size %d is too large\n", sp->max_send_size);
- return -EINVAL;
- }
-
- atomic_set(&sc->send_io.lcredits.count, sp->send_credit_target);
-
- maxpages = DIV_ROUND_UP(sp->max_read_write_size, PAGE_SIZE);
- sc->rw_io.credits.max = rdma_rw_mr_factor(sc->ib.dev,
- sc->rdma.cm_id->port_num,
- maxpages);
- sc->rw_io.credits.num_pages = DIV_ROUND_UP(maxpages, sc->rw_io.credits.max);
- /* add one extra in order to handle unaligned pages */
- sc->rw_io.credits.max += 1;
-
- sc->recv_io.credits.target = 1;
-
- atomic_set(&sc->rw_io.credits.count, sc->rw_io.credits.max);
-
- return 0;
-}
-
-static void smb_direct_destroy_pools(struct smbdirect_socket *sc)
-{
- struct smbdirect_recv_io *recvmsg;
-
- while ((recvmsg = get_free_recvmsg(sc)))
- mempool_free(recvmsg, sc->recv_io.mem.pool);
-
- mempool_destroy(sc->recv_io.mem.pool);
- sc->recv_io.mem.pool = NULL;
-
- kmem_cache_destroy(sc->recv_io.mem.cache);
- sc->recv_io.mem.cache = NULL;
-
- mempool_destroy(sc->send_io.mem.pool);
- sc->send_io.mem.pool = NULL;
-
- kmem_cache_destroy(sc->send_io.mem.cache);
- sc->send_io.mem.cache = NULL;
-}
-
-static int smb_direct_create_pools(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- char name[80];
- int i;
- struct smbdirect_recv_io *recvmsg;
-
- snprintf(name, sizeof(name), "smbdirect_send_io_pool_%p", sc);
- sc->send_io.mem.cache = kmem_cache_create(name,
- sizeof(struct smbdirect_send_io) +
- sizeof(struct smbdirect_negotiate_resp),
- 0, SLAB_HWCACHE_ALIGN, NULL);
- if (!sc->send_io.mem.cache)
- return -ENOMEM;
-
- sc->send_io.mem.pool = mempool_create(sp->send_credit_target,
- mempool_alloc_slab, mempool_free_slab,
- sc->send_io.mem.cache);
- if (!sc->send_io.mem.pool)
- goto err;
-
- snprintf(name, sizeof(name), "smbdirect_recv_io_pool_%p", sc);
- sc->recv_io.mem.cache = kmem_cache_create(name,
- sizeof(struct smbdirect_recv_io) +
- sp->max_recv_size,
- 0, SLAB_HWCACHE_ALIGN, NULL);
- if (!sc->recv_io.mem.cache)
- goto err;
-
- sc->recv_io.mem.pool =
- mempool_create(sp->recv_credit_max, mempool_alloc_slab,
- mempool_free_slab, sc->recv_io.mem.cache);
- if (!sc->recv_io.mem.pool)
- goto err;
-
- for (i = 0; i < sp->recv_credit_max; i++) {
- recvmsg = mempool_alloc(sc->recv_io.mem.pool, KSMBD_DEFAULT_GFP);
- if (!recvmsg)
- goto err;
- recvmsg->socket = sc;
- recvmsg->sge.length = 0;
- list_add(&recvmsg->list, &sc->recv_io.free.list);
- }
-
- return 0;
-err:
- smb_direct_destroy_pools(sc);
- return -ENOMEM;
-}
-
-static u32 smb_direct_rdma_rw_send_wrs(struct ib_device *dev, const struct ib_qp_init_attr *attr)
-{
- /*
- * This could be split out of rdma_rw_init_qp()
- * and be a helper function next to rdma_rw_mr_factor()
- *
- * We can't check unlikely(rdma_rw_force_mr) here,
- * but that is most likely 0 anyway.
- */
- u32 factor;
-
- WARN_ON_ONCE(attr->port_num == 0);
-
- /*
- * Each context needs at least one RDMA READ or WRITE WR.
- *
- * For some hardware we might need more, eventually we should ask the
- * HCA driver for a multiplier here.
- */
- factor = 1;
-
- /*
- * If the device needs MRs to perform RDMA READ or WRITE operations,
- * we'll need two additional MRs for the registrations and the
- * invalidation.
- */
- if (rdma_protocol_iwarp(dev, attr->port_num) || dev->attrs.max_sgl_rd)
- factor += 2; /* inv + reg */
-
- return factor * attr->cap.max_rdma_ctxs;
-}
-
-static int smb_direct_create_qpair(struct smbdirect_socket *sc)
-{
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- int ret;
- struct ib_qp_cap qp_cap;
- struct ib_qp_init_attr qp_attr;
- u32 max_send_wr;
- u32 rdma_send_wr;
-
- /*
- * Note that {rdma,ib}_create_qp() will call
- * rdma_rw_init_qp() if cap->max_rdma_ctxs is not 0.
- * It will adjust cap->max_send_wr to the required
- * number of additional WRs for the RDMA RW operations.
- * It will cap cap->max_send_wr to the device limit.
- *
- * +1 for ib_drain_qp
- */
- qp_cap.max_send_wr = sp->send_credit_target + 1;
- qp_cap.max_recv_wr = sp->recv_credit_max + 1;
- qp_cap.max_send_sge = SMBDIRECT_SEND_IO_MAX_SGE;
- qp_cap.max_recv_sge = SMBDIRECT_RECV_IO_MAX_SGE;
- qp_cap.max_inline_data = 0;
- qp_cap.max_rdma_ctxs = sc->rw_io.credits.max;
-
- /*
- * Find out the number of max_send_wr
- * after rdma_rw_init_qp() adjusted it.
- *
- * We only do it on a temporary variable,
- * as rdma_create_qp() will trigger
- * rdma_rw_init_qp() again.
- */
- memset(&qp_attr, 0, sizeof(qp_attr));
- qp_attr.cap = qp_cap;
- qp_attr.port_num = sc->rdma.cm_id->port_num;
- rdma_send_wr = smb_direct_rdma_rw_send_wrs(sc->ib.dev, &qp_attr);
- max_send_wr = qp_cap.max_send_wr + rdma_send_wr;
-
- if (qp_cap.max_send_wr > sc->ib.dev->attrs.max_cqe ||
- qp_cap.max_send_wr > sc->ib.dev->attrs.max_qp_wr) {
- pr_err("Possible CQE overrun: max_send_wr %d\n",
- qp_cap.max_send_wr);
- pr_err("device %.*s reporting max_cqe %d max_qp_wr %d\n",
- IB_DEVICE_NAME_MAX,
- sc->ib.dev->name,
- sc->ib.dev->attrs.max_cqe,
- sc->ib.dev->attrs.max_qp_wr);
- pr_err("consider lowering send_credit_target = %d\n",
- sp->send_credit_target);
- return -EINVAL;
- }
-
- if (qp_cap.max_rdma_ctxs &&
- (max_send_wr >= sc->ib.dev->attrs.max_cqe ||
- max_send_wr >= sc->ib.dev->attrs.max_qp_wr)) {
- pr_err("Possible CQE overrun: rdma_send_wr %d + max_send_wr %d = %d\n",
- rdma_send_wr, qp_cap.max_send_wr, max_send_wr);
- pr_err("device %.*s reporting max_cqe %d max_qp_wr %d\n",
- IB_DEVICE_NAME_MAX,
- sc->ib.dev->name,
- sc->ib.dev->attrs.max_cqe,
- sc->ib.dev->attrs.max_qp_wr);
- pr_err("consider lowering send_credit_target = %d, max_rdma_ctxs = %d\n",
- sp->send_credit_target, qp_cap.max_rdma_ctxs);
- return -EINVAL;
- }
-
- if (qp_cap.max_recv_wr > sc->ib.dev->attrs.max_cqe ||
- qp_cap.max_recv_wr > sc->ib.dev->attrs.max_qp_wr) {
- pr_err("Possible CQE overrun: max_recv_wr %d\n",
- qp_cap.max_recv_wr);
- pr_err("device %.*s reporting max_cqe %d max_qp_wr %d\n",
- IB_DEVICE_NAME_MAX,
- sc->ib.dev->name,
- sc->ib.dev->attrs.max_cqe,
- sc->ib.dev->attrs.max_qp_wr);
- pr_err("consider lowering receive_credit_max = %d\n",
- sp->recv_credit_max);
- return -EINVAL;
- }
-
- if (qp_cap.max_send_sge > sc->ib.dev->attrs.max_send_sge ||
- qp_cap.max_recv_sge > sc->ib.dev->attrs.max_recv_sge) {
- pr_err("device %.*s max_send_sge/max_recv_sge = %d/%d too small\n",
- IB_DEVICE_NAME_MAX,
- sc->ib.dev->name,
- sc->ib.dev->attrs.max_send_sge,
- sc->ib.dev->attrs.max_recv_sge);
- return -EINVAL;
- }
-
- sc->ib.pd = ib_alloc_pd(sc->ib.dev, 0);
- if (IS_ERR(sc->ib.pd)) {
- pr_err("Can't create RDMA PD\n");
- ret = PTR_ERR(sc->ib.pd);
- sc->ib.pd = NULL;
- return ret;
- }
-
- sc->ib.send_cq = ib_alloc_cq_any(sc->ib.dev, sc,
- max_send_wr,
- IB_POLL_WORKQUEUE);
- if (IS_ERR(sc->ib.send_cq)) {
- pr_err("Can't create RDMA send CQ\n");
- ret = PTR_ERR(sc->ib.send_cq);
- sc->ib.send_cq = NULL;
- goto err;
- }
-
- sc->ib.recv_cq = ib_alloc_cq_any(sc->ib.dev, sc,
- qp_cap.max_recv_wr,
- IB_POLL_WORKQUEUE);
- if (IS_ERR(sc->ib.recv_cq)) {
- pr_err("Can't create RDMA recv CQ\n");
- ret = PTR_ERR(sc->ib.recv_cq);
- sc->ib.recv_cq = NULL;
- goto err;
- }
-
- /*
- * We reset completely here!
- * As the above use was just temporary
- * to calc max_send_wr and rdma_send_wr.
- *
- * rdma_create_qp() will trigger rdma_rw_init_qp()
- * again if max_rdma_ctxs is not 0.
- */
- memset(&qp_attr, 0, sizeof(qp_attr));
- qp_attr.event_handler = smb_direct_qpair_handler;
- qp_attr.qp_context = sc;
- qp_attr.cap = qp_cap;
- qp_attr.sq_sig_type = IB_SIGNAL_REQ_WR;
- qp_attr.qp_type = IB_QPT_RC;
- qp_attr.send_cq = sc->ib.send_cq;
- qp_attr.recv_cq = sc->ib.recv_cq;
- qp_attr.port_num = ~0;
-
- ret = rdma_create_qp(sc->rdma.cm_id, sc->ib.pd, &qp_attr);
- if (ret) {
- pr_err("Can't create RDMA QP: %d\n", ret);
- goto err;
- }
-
- sc->ib.qp = sc->rdma.cm_id->qp;
- sc->rdma.cm_id->event_handler = smb_direct_cm_handler;
-
- return 0;
-err:
- if (sc->ib.qp) {
- sc->ib.qp = NULL;
- rdma_destroy_qp(sc->rdma.cm_id);
- }
- if (sc->ib.recv_cq) {
- ib_destroy_cq(sc->ib.recv_cq);
- sc->ib.recv_cq = NULL;
- }
- if (sc->ib.send_cq) {
- ib_destroy_cq(sc->ib.send_cq);
- sc->ib.send_cq = NULL;
- }
- if (sc->ib.pd) {
- ib_dealloc_pd(sc->ib.pd);
- sc->ib.pd = NULL;
- }
- return ret;
-}
-
-static int smb_direct_prepare(struct ksmbd_transport *t)
-{
- struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = &st->socket;
- struct smbdirect_socket_parameters *sp = &sc->parameters;
- struct smbdirect_recv_io *recvmsg;
- struct smbdirect_negotiate_req *req;
- unsigned long flags;
- int ret;
-
- /*
- * We are waiting to pass the following states:
- *
- * SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED
- * SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING
- * SMBDIRECT_SOCKET_NEGOTIATE_NEEDED
- *
- * To finally get to SMBDIRECT_SOCKET_NEGOTIATE_RUNNING
- * in order to continue below.
- *
- * Everything else is unexpected and an error.
- */
- ksmbd_debug(RDMA, "Waiting for SMB_DIRECT negotiate request\n");
- ret = wait_event_interruptible_timeout(sc->status_wait,
- sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED &&
- sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING &&
- sc->status != SMBDIRECT_SOCKET_NEGOTIATE_NEEDED,
- msecs_to_jiffies(sp->negotiate_timeout_msec));
- if (ret <= 0 || sc->status != SMBDIRECT_SOCKET_NEGOTIATE_RUNNING)
- return ret < 0 ? ret : -ETIMEDOUT;
-
- recvmsg = get_first_reassembly(sc);
- if (!recvmsg)
- return -ECONNABORTED;
-
- ret = smb_direct_check_recvmsg(recvmsg);
- if (ret)
- goto put;
-
- req = (struct smbdirect_negotiate_req *)recvmsg->packet;
- sp->max_recv_size = min_t(u32, sp->max_recv_size,
- le32_to_cpu(req->preferred_send_size));
- sp->max_send_size = min_t(u32, sp->max_send_size,
- le32_to_cpu(req->max_receive_size));
- sp->max_fragmented_send_size =
- le32_to_cpu(req->max_fragmented_size);
- /*
- * The maximum fragmented upper-layer payload receive size supported
- *
- * Assume max_payload_per_credit is
- * smb_direct_receive_credit_max - 24 = 1340
- *
- * The maximum number would be
- * smb_direct_receive_credit_max * max_payload_per_credit
- *
- * 1340 * 255 = 341700 (0x536C4)
- *
- * The minimum value from the spec is 131072 (0x20000)
- *
- * For now we use the logic we used before:
- * (1364 * 255) / 2 = 173910 (0x2A756)
- *
- * We need to adjust this here in case the peer
- * lowered sp->max_recv_size.
- *
- * TODO: instead of adjusting max_fragmented_recv_size
- * we should adjust the number of available buffers,
- * but for now we keep the current logic.
- */
- sp->max_fragmented_recv_size =
- (sp->recv_credit_max * sp->max_recv_size) / 2;
- sc->recv_io.credits.target = le16_to_cpu(req->credits_requested);
- sc->recv_io.credits.target = min_t(u16, sc->recv_io.credits.target, sp->recv_credit_max);
- sc->recv_io.credits.target = max_t(u16, sc->recv_io.credits.target, 1);
-
-put:
- spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
- sc->recv_io.reassembly.queue_length--;
- list_del(&recvmsg->list);
- spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
- put_recvmsg(sc, recvmsg);
-
- if (ret == -ECONNABORTED)
- return ret;
-
- if (ret)
- goto respond;
-
- /*
- * We negotiated with success, so we need to refill the recv queue.
- * We do that with sc->idle.immediate_work still being disabled
- * via smbdirect_socket_init(), so that queue_work(sc->workqueue,
- * &sc->idle.immediate_work) in smb_direct_post_recv_credits()
- * is a no-op.
- *
- * The message that grants the credits to the client is
- * the negotiate response.
- */
- INIT_WORK(&sc->recv_io.posted.refill_work, smb_direct_post_recv_credits);
- smb_direct_post_recv_credits(&sc->recv_io.posted.refill_work);
- if (unlikely(sc->first_error))
- return sc->first_error;
- INIT_WORK(&sc->idle.immediate_work, smb_direct_send_immediate_work);
-
-respond:
- ret = smb_direct_send_negotiate_response(sc, ret);
-
+ free_transport(t);
return ret;
}
-static int smb_direct_connect(struct smbdirect_socket *sc)
+static int smb_direct_listener_kthread_fn(void *p)
{
- struct smbdirect_recv_io *recv_io;
- int ret;
+ struct smb_direct_listener *listener = (struct smb_direct_listener *)p;
+ struct smbdirect_socket *client_sc = NULL;
- ret = smb_direct_init_params(sc);
- if (ret) {
- pr_err("Can't configure RDMA parameters\n");
- return ret;
- }
+ while (!kthread_should_stop()) {
+ struct proto_accept_arg arg = { .err = -EINVAL, };
+ long timeo = MAX_SCHEDULE_TIMEOUT;
- ret = smb_direct_create_pools(sc);
- if (ret) {
- pr_err("Can't init RDMA pool: %d\n", ret);
- return ret;
- }
-
- list_for_each_entry(recv_io, &sc->recv_io.free.list, list)
- recv_io->cqe.done = recv_done;
+ if (!listener->socket)
+ break;
+ client_sc = smbdirect_socket_accept(listener->socket, timeo, &arg);
+ if (!client_sc && arg.err == -EINVAL)
+ break;
+ if (!client_sc)
+ continue;
- ret = smb_direct_create_qpair(sc);
- if (ret) {
- pr_err("Can't accept RDMA client: %d\n", ret);
- return ret;
+ ksmbd_debug(CONN, "connect success: accepted new connection\n");
+ smb_direct_new_connection(listener, client_sc);
}
- ret = smb_direct_prepare_negotiation(sc);
- if (ret) {
- pr_err("Can't negotiate: %d\n", ret);
- return ret;
- }
+ ksmbd_debug(CONN, "releasing socket\n");
return 0;
}
-static bool rdma_frwr_is_supported(struct ib_device_attr *attrs)
+static void smb_direct_listener_destroy(struct smb_direct_listener *listener)
{
- if (!(attrs->device_cap_flags & IB_DEVICE_MEM_MGT_EXTENSIONS))
- return false;
- if (attrs->max_fast_reg_page_list_len == 0)
- return false;
- return true;
-}
-
-static int smb_direct_handle_connect_request(struct rdma_cm_id *new_cm_id,
- struct rdma_cm_event *event)
-{
- struct smb_direct_listener *listener = new_cm_id->context;
- struct smb_direct_transport *t;
- struct smbdirect_socket *sc;
- struct smbdirect_socket_parameters *sp;
- struct task_struct *handler;
- u8 peer_initiator_depth;
- u8 peer_responder_resources;
int ret;
- if (!rdma_frwr_is_supported(&new_cm_id->device->attrs)) {
- ksmbd_debug(RDMA,
- "Fast Registration Work Requests is not supported. device capabilities=%llx\n",
- new_cm_id->device->attrs.device_cap_flags);
- return -EPROTONOSUPPORT;
- }
+ if (listener->socket)
+ smbdirect_socket_shutdown(listener->socket);
- t = alloc_transport(new_cm_id);
- if (!t)
- return -ENOMEM;
- sc = &t->socket;
- sp = &sc->parameters;
-
- peer_initiator_depth = event->param.conn.initiator_depth;
- peer_responder_resources = event->param.conn.responder_resources;
- if (rdma_protocol_iwarp(new_cm_id->device, new_cm_id->port_num) &&
- event->param.conn.private_data_len == 8) {
- /*
- * Legacy clients with only iWarp MPA v1 support
- * need a private blob in order to negotiate
- * the IRD/ORD values.
- */
- const __be32 *ird_ord_hdr = event->param.conn.private_data;
- u32 ird32 = be32_to_cpu(ird_ord_hdr[0]);
- u32 ord32 = be32_to_cpu(ird_ord_hdr[1]);
-
- /*
- * cifs.ko sends the legacy IRD/ORD negotiation
- * event if iWarp MPA v2 was used.
- *
- * Here we check that the values match and only
- * mark the client as legacy if they don't match.
- */
- if ((u32)event->param.conn.initiator_depth != ird32 ||
- (u32)event->param.conn.responder_resources != ord32) {
- /*
- * There are broken clients (old cifs.ko)
- * using little endian and also
- * struct rdma_conn_param only uses u8
- * for initiator_depth and responder_resources,
- * so we truncate the value to U8_MAX.
- *
- * smb_direct_accept_client() will then
- * do the real negotiation in order to
- * select the minimum between client and
- * server.
- */
- ird32 = min_t(u32, ird32, U8_MAX);
- ord32 = min_t(u32, ord32, U8_MAX);
-
- sc->rdma.legacy_iwarp = true;
- peer_initiator_depth = (u8)ird32;
- peer_responder_resources = (u8)ord32;
- }
+ if (listener->thread) {
+ ret = kthread_stop(listener->thread);
+ if (ret)
+ pr_err("failed to stop forker thread\n");
+ listener->thread = NULL;
}
- /*
- * First set what the we as server are able to support
- */
- sp->initiator_depth = min_t(u8, sp->initiator_depth,
- new_cm_id->device->attrs.max_qp_rd_atom);
-
- /*
- * negotiate the value by using the minimum
- * between client and server if the client provided
- * non 0 values.
- */
- if (peer_initiator_depth != 0)
- sp->initiator_depth = min_t(u8, sp->initiator_depth,
- peer_initiator_depth);
- if (peer_responder_resources != 0)
- sp->responder_resources = min_t(u8, sp->responder_resources,
- peer_responder_resources);
-
- ret = smb_direct_connect(sc);
- if (ret)
- goto out_err;
-
- handler = kthread_run(ksmbd_conn_handler_loop,
- KSMBD_TRANS(t)->conn, "ksmbd:r%u",
- listener->port);
- if (IS_ERR(handler)) {
- ret = PTR_ERR(handler);
- pr_err("Can't start thread\n");
- goto out_err;
+ if (listener->socket) {
+ smbdirect_socket_release(listener->socket);
+ listener->socket = NULL;
}
- return 0;
-out_err:
- free_transport(t);
- return ret;
-}
-
-static int smb_direct_listen_handler(struct rdma_cm_id *cm_id,
- struct rdma_cm_event *event)
-{
- switch (event->event) {
- case RDMA_CM_EVENT_CONNECT_REQUEST: {
- int ret = smb_direct_handle_connect_request(cm_id, event);
-
- if (ret) {
- pr_err("Can't create transport: %d\n", ret);
- return ret;
- }
-
- ksmbd_debug(RDMA, "Received connection request. cm_id=%p\n",
- cm_id);
- break;
- }
- default:
- pr_err("Unexpected listen event. cm_id=%p, event=%s (%d)\n",
- cm_id, rdma_event_msg(event->event), event->event);
- break;
- }
- return 0;
+ listener->port = 0;
}
static int smb_direct_listen(struct smb_direct_listener *listener,
int port)
{
- int ret;
- struct rdma_cm_id *cm_id;
- u8 node_type = RDMA_NODE_UNSPECIFIED;
+ struct net *net = current->nsproxy->net_ns;
+ struct task_struct *kthread;
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_ANY),
.sin_port = htons(port),
};
+ struct smbdirect_socket_parameters init_params = {};
+ struct smbdirect_socket_parameters *sp;
+ struct smbdirect_socket *sc;
+ u64 port_flags = 0;
+ int ret;
switch (port) {
case SMB_DIRECT_PORT_IWARP:
@@ -2800,7 +396,7 @@ static int smb_direct_listen(struct smb_direct_listener *listener,
* only allow iWarp devices
* for port 5445.
*/
- node_type = RDMA_NODE_RNIC;
+ port_flags |= SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW;
break;
case SMB_DIRECT_PORT_INFINIBAND:
/*
@@ -2809,119 +405,95 @@ static int smb_direct_listen(struct smb_direct_listener *listener,
*
* (Basically don't allow iWarp devices)
*/
- node_type = RDMA_NODE_IB_CA;
+ port_flags |= SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB;
break;
default:
pr_err("unsupported smbdirect port=%d!\n", port);
return -ENODEV;
}
- cm_id = rdma_create_id(&init_net, smb_direct_listen_handler,
- listener, RDMA_PS_TCP, IB_QPT_RC);
- if (IS_ERR(cm_id)) {
- pr_err("Can't create cm id: %ld\n", PTR_ERR(cm_id));
- return PTR_ERR(cm_id);
+ ret = smbdirect_socket_create_kern(net, &sc);
+ if (ret) {
+ pr_err("smbdirect_socket_create_kern() failed: %d %1pe\n",
+ ret, ERR_PTR(ret));
+ return ret;
}
- ret = rdma_restrict_node_type(cm_id, node_type);
+ /*
+ * Create the initial parameters
+ */
+ sp = &init_params;
+ sp->flags |= port_flags;
+ sp->negotiate_timeout_msec = SMB_DIRECT_NEGOTIATE_TIMEOUT * 1000;
+ sp->initiator_depth = SMB_DIRECT_CM_INITIATOR_DEPTH;
+ sp->responder_resources = 1;
+ sp->recv_credit_max = smb_direct_receive_credit_max;
+ sp->send_credit_target = smb_direct_send_credit_target;
+ sp->max_send_size = smb_direct_max_send_size;
+ sp->max_fragmented_recv_size = smb_direct_max_fragmented_recv_size;
+ sp->max_recv_size = smb_direct_max_receive_size;
+ sp->max_read_write_size = smb_direct_max_read_write_size;
+ sp->keepalive_interval_msec = SMB_DIRECT_KEEPALIVE_SEND_INTERVAL * 1000;
+ sp->keepalive_timeout_msec = SMB_DIRECT_KEEPALIVE_RECV_TIMEOUT * 1000;
+
+ smbdirect_socket_set_logging(sc, NULL,
+ smb_direct_logging_needed,
+ smb_direct_logging_vaprintf);
+ ret = smbdirect_socket_set_initial_parameters(sc, sp);
if (ret) {
- pr_err("rdma_restrict_node_type(%u) failed %d\n",
- node_type, ret);
+ pr_err("Failed smbdirect_socket_set_initial_parameters(): %d %1pe\n",
+ ret, ERR_PTR(ret));
+ goto err;
+ }
+ ret = smbdirect_socket_set_kernel_settings(sc, IB_POLL_WORKQUEUE, KSMBD_DEFAULT_GFP);
+ if (ret) {
+ pr_err("Failed smbdirect_socket_set_kernel_settings(): %d %1pe\n",
+ ret, ERR_PTR(ret));
goto err;
}
- ret = rdma_bind_addr(cm_id, (struct sockaddr *)&sin);
+ ret = smbdirect_socket_bind(sc, (struct sockaddr *)&sin);
if (ret) {
- pr_err("Can't bind: %d\n", ret);
+ pr_err("smbdirect_socket_bind() failed: %d %1pe\n",
+ ret, ERR_PTR(ret));
goto err;
}
- ret = rdma_listen(cm_id, 10);
+ ret = smbdirect_socket_listen(sc, 10);
if (ret) {
- pr_err("Can't listen: %d\n", ret);
+ pr_err("Port[%d] smbdirect_socket_listen() failed: %d %1pe\n",
+ port, ret, ERR_PTR(ret));
goto err;
}
listener->port = port;
- listener->cm_id = cm_id;
+ listener->socket = sc;
+
+ kthread = kthread_run(smb_direct_listener_kthread_fn,
+ listener,
+ "ksmbd-smbdirect-listener-%u", port);
+ if (IS_ERR(kthread)) {
+ ret = PTR_ERR(kthread);
+ pr_err("Can't start ksmbd listen kthread: %d %1pe\n",
+ ret, ERR_PTR(ret));
+ goto err;
+ }
+ listener->thread = kthread;
return 0;
err:
- listener->port = 0;
- listener->cm_id = NULL;
- rdma_destroy_id(cm_id);
+ smb_direct_listener_destroy(listener);
return ret;
}
-static int smb_direct_ib_client_add(struct ib_device *ib_dev)
-{
- struct smb_direct_device *smb_dev;
-
- if (!rdma_frwr_is_supported(&ib_dev->attrs))
- return 0;
-
- smb_dev = kzalloc_obj(*smb_dev, KSMBD_DEFAULT_GFP);
- if (!smb_dev)
- return -ENOMEM;
- smb_dev->ib_dev = ib_dev;
-
- write_lock(&smb_direct_device_lock);
- list_add(&smb_dev->list, &smb_direct_device_list);
- write_unlock(&smb_direct_device_lock);
-
- ksmbd_debug(RDMA, "ib device added: name %s\n", ib_dev->name);
- return 0;
-}
-
-static void smb_direct_ib_client_remove(struct ib_device *ib_dev,
- void *client_data)
-{
- struct smb_direct_device *smb_dev, *tmp;
-
- write_lock(&smb_direct_device_lock);
- list_for_each_entry_safe(smb_dev, tmp, &smb_direct_device_list, list) {
- if (smb_dev->ib_dev == ib_dev) {
- list_del(&smb_dev->list);
- kfree(smb_dev);
- break;
- }
- }
- write_unlock(&smb_direct_device_lock);
-}
-
-static struct ib_client smb_direct_ib_client = {
- .name = "ksmbd_smb_direct_ib",
- .add = smb_direct_ib_client_add,
- .remove = smb_direct_ib_client_remove,
-};
-
int ksmbd_rdma_init(void)
{
int ret;
smb_direct_ib_listener = smb_direct_iw_listener = (struct smb_direct_listener) {
- .cm_id = NULL,
+ .socket = NULL,
};
- ret = ib_register_client(&smb_direct_ib_client);
- if (ret) {
- pr_err("failed to ib_register_client\n");
- return ret;
- }
-
- /* When a client is running out of send credits, the credits are
- * granted by the server's sending a packet using this queue.
- * This avoids the situation that a clients cannot send packets
- * for lack of credits
- */
- smb_direct_wq = alloc_workqueue("ksmbd-smb_direct-wq",
- WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_PERCPU,
- 0);
- if (!smb_direct_wq) {
- ret = -ENOMEM;
- goto err;
- }
-
ret = smb_direct_listen(&smb_direct_ib_listener,
SMB_DIRECT_PORT_INFINIBAND);
if (ret) {
@@ -2929,8 +501,8 @@ int ksmbd_rdma_init(void)
goto err;
}
- ksmbd_debug(RDMA, "InfiniBand/RoCEv1/RoCEv2 RDMA listener. cm_id=%p\n",
- smb_direct_ib_listener.cm_id);
+ ksmbd_debug(RDMA, "InfiniBand/RoCEv1/RoCEv2 RDMA listener. socket=%p\n",
+ smb_direct_ib_listener.socket);
ret = smb_direct_listen(&smb_direct_iw_listener,
SMB_DIRECT_PORT_IWARP);
@@ -2939,107 +511,37 @@ int ksmbd_rdma_init(void)
goto err;
}
- ksmbd_debug(RDMA, "iWarp RDMA listener. cm_id=%p\n",
- smb_direct_iw_listener.cm_id);
+ ksmbd_debug(RDMA, "iWarp RDMA listener. socket=%p\n",
+ smb_direct_iw_listener.socket);
+ WRITE_ONCE(smb_direct_enabled, true);
return 0;
err:
+ WRITE_ONCE(smb_direct_enabled, false);
ksmbd_rdma_stop_listening();
- ksmbd_rdma_destroy();
return ret;
}
void ksmbd_rdma_stop_listening(void)
{
- if (!smb_direct_ib_listener.cm_id && !smb_direct_iw_listener.cm_id)
- return;
-
- ib_unregister_client(&smb_direct_ib_client);
-
- if (smb_direct_ib_listener.cm_id)
- rdma_destroy_id(smb_direct_ib_listener.cm_id);
- if (smb_direct_iw_listener.cm_id)
- rdma_destroy_id(smb_direct_iw_listener.cm_id);
-
- smb_direct_ib_listener = smb_direct_iw_listener = (struct smb_direct_listener) {
- .cm_id = NULL,
- };
+ WRITE_ONCE(smb_direct_enabled, false);
+ smb_direct_listener_destroy(&smb_direct_ib_listener);
+ smb_direct_listener_destroy(&smb_direct_iw_listener);
}
-void ksmbd_rdma_destroy(void)
+bool ksmbd_rdma_enabled(void)
{
- if (smb_direct_wq) {
- destroy_workqueue(smb_direct_wq);
- smb_direct_wq = NULL;
- }
-}
-
-static bool ksmbd_find_rdma_capable_netdev(struct net_device *netdev)
-{
- struct smb_direct_device *smb_dev;
- int i;
- bool rdma_capable = false;
-
- read_lock(&smb_direct_device_lock);
- list_for_each_entry(smb_dev, &smb_direct_device_list, list) {
- for (i = 0; i < smb_dev->ib_dev->phys_port_cnt; i++) {
- struct net_device *ndev;
-
- ndev = ib_device_get_netdev(smb_dev->ib_dev, i + 1);
- if (!ndev)
- continue;
-
- if (ndev == netdev) {
- dev_put(ndev);
- rdma_capable = true;
- goto out;
- }
- dev_put(ndev);
- }
- }
-out:
- read_unlock(&smb_direct_device_lock);
-
- if (rdma_capable == false) {
- struct ib_device *ibdev;
-
- ibdev = ib_device_get_by_netdev(netdev, RDMA_DRIVER_UNKNOWN);
- if (ibdev) {
- rdma_capable = rdma_frwr_is_supported(&ibdev->attrs);
- ib_device_put(ibdev);
- }
- }
-
- ksmbd_debug(RDMA, "netdev(%s) rdma capable : %s\n",
- netdev->name, str_true_false(rdma_capable));
-
- return rdma_capable;
+ return READ_ONCE(smb_direct_enabled);
}
bool ksmbd_rdma_capable_netdev(struct net_device *netdev)
{
- struct net_device *lower_dev;
- struct list_head *iter;
+ u8 node_type = smbdirect_netdev_rdma_capable_node_type(netdev);
- if (ksmbd_find_rdma_capable_netdev(netdev))
- return true;
-
- /* check if netdev is bridge or VLAN */
- if (netif_is_bridge_master(netdev) ||
- netdev->priv_flags & IFF_802_1Q_VLAN)
- netdev_for_each_lower_dev(netdev, lower_dev, iter)
- if (ksmbd_find_rdma_capable_netdev(lower_dev))
- return true;
-
- /* check if netdev is IPoIB safely without layer violation */
- if (netdev->type == ARPHRD_INFINIBAND)
- return true;
-
- return false;
+ return node_type != RDMA_NODE_UNSPECIFIED;
}
static const struct ksmbd_transport_ops ksmbd_smb_direct_transport_ops = {
- .prepare = smb_direct_prepare,
.disconnect = smb_direct_disconnect,
.shutdown = smb_direct_shutdown,
.writev = smb_direct_writev,
@@ -3048,3 +550,5 @@ static const struct ksmbd_transport_ops ksmbd_smb_direct_transport_ops = {
.rdma_write = smb_direct_rdma_write,
.free_transport = smb_direct_free_transport,
};
+
+MODULE_IMPORT_NS("SMBDIRECT");
diff --git a/fs/smb/server/transport_rdma.h b/fs/smb/server/transport_rdma.h
index 3f93c6a9f7e4..23247713b5c3 100644
--- a/fs/smb/server/transport_rdma.h
+++ b/fs/smb/server/transport_rdma.h
@@ -14,17 +14,19 @@
#ifdef CONFIG_SMB_SERVER_SMBDIRECT
int ksmbd_rdma_init(void);
void ksmbd_rdma_stop_listening(void);
-void ksmbd_rdma_destroy(void);
+bool ksmbd_rdma_enabled(void);
bool ksmbd_rdma_capable_netdev(struct net_device *netdev);
void init_smbd_max_io_size(unsigned int sz);
unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt);
#else
static inline int ksmbd_rdma_init(void) { return 0; }
static inline void ksmbd_rdma_stop_listening(void) { }
-static inline void ksmbd_rdma_destroy(void) { }
+static inline bool ksmbd_rdma_enabled(void) { return false; }
static inline bool ksmbd_rdma_capable_netdev(struct net_device *netdev) { return false; }
static inline void init_smbd_max_io_size(unsigned int sz) { }
static inline unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt) { return 0; }
#endif
+#include <linux/smbdirect.h>
+
#endif /* __KSMBD_TRANSPORT_RDMA_H__ */
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 7e29b06820e2..4968cfc1a572 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -39,6 +39,7 @@ struct tcp_transport {
static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops;
static void tcp_stop_kthread(struct task_struct *kthread);
+static void ksmbd_tcp_stop_listener(struct interface *iface);
static struct interface *alloc_iface(char *ifname);
static void ksmbd_tcp_disconnect(struct ksmbd_transport *t);
@@ -183,6 +184,8 @@ static int ksmbd_tcp_new_connection(struct socket *client_sk)
t = alloc_transport(client_sk);
if (!t) {
sock_release(client_sk);
+ if (server_conf.max_connections)
+ atomic_dec(&active_num_conn);
return -ENOMEM;
}
@@ -279,7 +282,7 @@ static int ksmbd_kthread_fn(void *p)
skip_max_ip_conns_limit:
if (server_conf.max_connections &&
- atomic_inc_return(&active_num_conn) >= server_conf.max_connections) {
+ atomic_inc_return(&active_num_conn) > server_conf.max_connections) {
pr_info_ratelimited("Limit the maximum number of connections(%u)\n",
atomic_read(&active_num_conn));
atomic_dec(&active_num_conn);
@@ -290,6 +293,12 @@ skip_max_ip_conns_limit:
ksmbd_debug(CONN, "connect success: accepted new connection\n");
client_sk->sk->sk_rcvtimeo = KSMBD_TCP_RECV_TIMEOUT;
client_sk->sk->sk_sndtimeo = KSMBD_TCP_SEND_TIMEOUT;
+ /*
+ * Detect peers that disappear without sending a FIN or RST.
+ * Otherwise the connection handler can retry receive timeouts
+ * indefinitely and keep the connection in conn_list.
+ */
+ sock_set_keepalive(client_sk->sk);
ksmbd_tcp_new_connection(client_sk);
}
@@ -313,13 +322,20 @@ static int ksmbd_tcp_run_kthread(struct interface *iface)
int rc;
struct task_struct *kthread;
- kthread = kthread_run(ksmbd_kthread_fn, (void *)iface, "ksmbd-%s",
- iface->name);
+ kthread = kthread_create(ksmbd_kthread_fn, (void *)iface, "ksmbd-%s",
+ iface->name);
if (IS_ERR(kthread)) {
rc = PTR_ERR(kthread);
return rc;
}
+
+ /*
+ * The listener can exit after its socket is shutdown, so keep the
+ * task_struct alive until the caller has stopped it.
+ */
+ get_task_struct(kthread);
iface->ksmbd_kthread = kthread;
+ wake_up_process(kthread);
return 0;
}
@@ -415,14 +431,15 @@ static int ksmbd_tcp_read(struct ksmbd_transport *t, char *buf,
return ksmbd_tcp_readv(TCP_TRANS(t), &iov, 1, to_read, max_retries);
}
-static int ksmbd_tcp_writev(struct ksmbd_transport *t, struct kvec *iov,
- int nvecs, int size, bool need_invalidate,
- unsigned int remote_key)
-
+static int ksmbd_tcp_writev(struct ksmbd_transport *t,
+ const struct ksmbd_transport_write *tx)
{
- struct msghdr smb_msg = {.msg_flags = MSG_NOSIGNAL};
+ struct msghdr smb_msg = {
+ .msg_flags = MSG_NOSIGNAL | tx->msg_flags,
+ };
- return kernel_sendmsg(TCP_TRANS(t)->sock, &smb_msg, iov, nvecs, size);
+ return kernel_sendmsg(TCP_TRANS(t)->sock, &smb_msg, tx->iov,
+ tx->iov_cnt, tx->size);
}
static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
@@ -432,6 +449,11 @@ static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
atomic_dec(&active_num_conn);
}
+static void ksmbd_tcp_shutdown(struct ksmbd_transport *t)
+{
+ kernel_sock_shutdown(TCP_TRANS(t)->sock, SHUT_RDWR);
+}
+
static void tcp_destroy_socket(struct socket *ksmbd_socket)
{
int ret;
@@ -509,6 +531,12 @@ static int create_socket(struct interface *iface)
goto out_error;
}
+ /*
+ * Accepted sockets inherit the listener's net reference. Keep TCP
+ * timers alive after a kernel socket is released.
+ */
+ sk_net_refcnt_upgrade(ksmbd_socket->sk);
+
ret = kernel_listen(ksmbd_socket, KSMBD_SOCKET_BACKLOG);
if (ret) {
pr_err("Port listen() error: %d\n", ret);
@@ -578,12 +606,7 @@ static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event,
if (iface && iface->state == IFACE_STATE_CONFIGURED) {
ksmbd_debug(CONN, "netdev-down event: netdev(%s) is going down\n",
iface->name);
- kernel_sock_shutdown(iface->ksmbd_socket, SHUT_RDWR);
- tcp_stop_kthread(iface->ksmbd_kthread);
- iface->ksmbd_kthread = NULL;
- sock_release(iface->ksmbd_socket);
- iface->ksmbd_socket = NULL;
-
+ ksmbd_tcp_stop_listener(iface);
iface->state = IFACE_STATE_DOWN;
break;
}
@@ -611,11 +634,25 @@ static void tcp_stop_kthread(struct task_struct *kthread)
if (!kthread)
return;
- ret = kthread_stop(kthread);
+ ret = kthread_stop_put(kthread);
if (ret)
pr_err("failed to stop forker thread\n");
}
+static void ksmbd_tcp_stop_listener(struct interface *iface)
+{
+ if (iface->ksmbd_socket)
+ kernel_sock_shutdown(iface->ksmbd_socket, SHUT_RDWR);
+
+ tcp_stop_kthread(iface->ksmbd_kthread);
+ iface->ksmbd_kthread = NULL;
+
+ if (iface->ksmbd_socket) {
+ sock_release(iface->ksmbd_socket);
+ iface->ksmbd_socket = NULL;
+ }
+}
+
void ksmbd_tcp_destroy(void)
{
struct interface *iface, *tmp;
@@ -623,6 +660,7 @@ void ksmbd_tcp_destroy(void)
unregister_netdevice_notifier(&ksmbd_netdev_notifier);
list_for_each_entry_safe(iface, tmp, &iface_list, entry) {
+ ksmbd_tcp_stop_listener(iface);
list_del(&iface->entry);
kfree(iface->name);
kfree(iface);
@@ -678,5 +716,6 @@ static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops = {
.read = ksmbd_tcp_read,
.writev = ksmbd_tcp_writev,
.disconnect = ksmbd_tcp_disconnect,
+ .shutdown = ksmbd_tcp_shutdown,
.free_transport = ksmbd_tcp_free_transport,
};
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index d08973b288e5..c2c9aaa5de1b 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -7,6 +7,7 @@
#include <crypto/sha2.h>
#include <linux/kernel.h>
#include <linux/fs.h>
+#include <linux/fs_struct.h>
#include <linux/filelock.h>
#include <linux/uaccess.h>
#include <linux/backing-dev.h>
@@ -16,11 +17,11 @@
#include <linux/fsnotify.h>
#include <linux/dcache.h>
#include <linux/slab.h>
+#include <linux/sizes.h>
#include <linux/vmalloc.h>
#include <linux/sched/xacct.h>
#include <linux/crc32c.h>
-#include <linux/namei.h>
-#include <linux/splice.h>
+#include <linux/fileattr.h>
#include "glob.h"
#include "oplock.h"
@@ -56,7 +57,7 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
{
struct qstr last;
const struct path *root_share_path = &share_conf->vfs_path;
- int err, type;
+ int err;
struct dentry *d;
if (pathname[0] == '\0') {
@@ -67,17 +68,12 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
}
CLASS(filename_kernel, filename)(pathname);
- err = vfs_path_parent_lookup(filename, flags,
- path, &last, &type,
- root_share_path);
+ scoped_with_init_fs()
+ err = vfs_path_parent_lookup(filename, flags, path, &last,
+ root_share_path);
if (err)
return err;
- if (unlikely(type != LAST_NORM)) {
- path_put(path);
- return -ENOENT;
- }
-
if (for_remove) {
err = mnt_want_write(path->mnt);
if (err) {
@@ -254,17 +250,20 @@ out:
static int ksmbd_vfs_stream_read(struct ksmbd_file *fp, char *buf, loff_t *pos,
size_t count)
{
+ const struct cred *saved_cred;
ssize_t v_len;
char *stream_buf = NULL;
ksmbd_debug(VFS, "read stream data pos : %llu, count : %zd\n",
*pos, count);
+ saved_cred = override_creds(fp->filp->f_cred);
v_len = ksmbd_vfs_getcasexattr(file_mnt_idmap(fp->filp),
fp->filp->f_path.dentry,
fp->stream.name,
fp->stream.size,
&stream_buf);
+ revert_creds(saved_cred);
if ((int)v_len <= 0)
return (int)v_len;
@@ -300,9 +299,6 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end,
struct file_lock_context *ctx = locks_inode_context(file_inode(filp));
int error = 0;
- if (start == end)
- return 0;
-
if (!ctx || list_empty_careful(&ctx->flc_posix))
return 0;
@@ -348,7 +344,7 @@ int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
ssize_t nbytes = 0;
struct inode *inode = file_inode(filp);
- if (S_ISDIR(inode->i_mode))
+ if (S_ISDIR(inode->i_mode) && !ksmbd_stream_fd(fp))
return -EISDIR;
if (unlikely(count == 0))
@@ -388,6 +384,7 @@ int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
size_t count)
{
+ const struct cred *saved_cred;
char *stream_buf = NULL, *wbuf;
struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
size_t size;
@@ -408,6 +405,7 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
count = XATTR_SIZE_MAX - *pos;
}
+ saved_cred = override_creds(fp->filp->f_cred);
v_len = ksmbd_vfs_getcasexattr(idmap,
fp->filp->f_path.dentry,
fp->stream.name,
@@ -416,14 +414,14 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
if (v_len < 0) {
pr_err("not found stream in xattr : %zd\n", v_len);
err = v_len;
- goto out;
+ goto out_revert;
}
if (v_len < size) {
wbuf = kvzalloc(size, KSMBD_DEFAULT_GFP);
if (!wbuf) {
err = -ENOMEM;
- goto out;
+ goto out_revert;
}
if (v_len > 0)
@@ -441,6 +439,8 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
size,
0,
true);
+out_revert:
+ revert_creds(saved_cred);
if (err < 0)
goto out;
else
@@ -473,7 +473,8 @@ int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp,
if (work->conn->connection_type) {
if (!(fp->daccess & (FILE_WRITE_DATA_LE | FILE_APPEND_DATA_LE)) ||
- S_ISDIR(file_inode(fp->filp)->i_mode)) {
+ (S_ISDIR(file_inode(fp->filp)->i_mode) &&
+ !ksmbd_stream_fd(fp))) {
pr_err("no right to write(%pD)\n", fp->filp);
err = -EACCES;
goto out;
@@ -622,7 +623,8 @@ int ksmbd_vfs_link(struct ksmbd_work *work, const char *oldname,
if (ksmbd_override_fsids(work))
return -ENOMEM;
- err = kern_path(oldname, LOOKUP_NO_SYMLINKS, &oldpath);
+ scoped_with_init_fs()
+ err = kern_path(oldname, LOOKUP_NO_SYMLINKS, &oldpath);
if (err) {
pr_err("cannot get linux path for %s, err = %d\n",
oldname, err);
@@ -659,16 +661,35 @@ out1:
return err;
}
-int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
- char *newname, int flags)
+int ksmbd_vfs_check_rename_share(struct ksmbd_work *work,
+ const struct path *old_path)
+{
+ struct ksmbd_file *parent_fp;
+ int err = 0;
+
+ parent_fp = ksmbd_lookup_fd_inode(old_path->dentry->d_parent);
+ if (!parent_fp)
+ return 0;
+
+ if ((parent_fp->daccess & FILE_DELETE_LE) ||
+ (!parent_fp->attrib_only &&
+ !(parent_fp->saccess & FILE_SHARE_DELETE_LE))) {
+ ksmbd_debug(VFS, "parent dir blocks delete sharing\n");
+ err = -ESHARE;
+ }
+ ksmbd_fd_put(work, parent_fp);
+ return err;
+}
+
+int ksmbd_vfs_rename(struct ksmbd_work *work, struct ksmbd_file *old_fp,
+ char *newname, int flags)
{
+ const struct path *old_path = &old_fp->filp->f_path;
struct dentry *old_child = old_path->dentry;
struct path new_path;
struct qstr new_last;
struct renamedata rd;
struct ksmbd_share_config *share_conf = work->tcon->share_conf;
- struct ksmbd_file *parent_fp;
- int new_type;
int err, lookup_flags = LOOKUP_NO_SYMLINKS;
if (ksmbd_override_fsids(work))
@@ -678,8 +699,7 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
retry:
err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH,
- &new_path, &new_last, &new_type,
- &share_conf->vfs_path);
+ &new_path, &new_last, &share_conf->vfs_path);
if (err)
goto out1;
@@ -701,17 +721,28 @@ retry:
if (err)
goto out_drop_write;
- parent_fp = ksmbd_lookup_fd_inode(old_child->d_parent);
- if (parent_fp) {
- if (parent_fp->daccess & FILE_DELETE_LE) {
- pr_err("parent dir is opened with delete access\n");
- err = -ESHARE;
- ksmbd_fd_put(work, parent_fp);
- goto out3;
- }
- ksmbd_fd_put(work, parent_fp);
+ if (d_is_dir(old_child) && ksmbd_has_nonposix_open_child(old_fp)) {
+ err = -EACCES;
+ goto out3;
}
+ /*
+ * See MS-FSA 2.1.5.15.12.
+ * An overwrite rename must fail with STATUS_ACCESS_DENIED if the
+ * existing target still has a non-POSIX open.
+ */
+ if (!(flags & (RENAME_NOREPLACE | RENAME_EXCHANGE)) &&
+ d_inode(rd.new_dentry) &&
+ d_inode(rd.new_dentry) != d_inode(old_child) &&
+ ksmbd_has_other_nonposix_open(rd.new_dentry)) {
+ err = -EACCES;
+ goto out3;
+ }
+
+ err = ksmbd_vfs_check_rename_share(work, old_path);
+ if (err)
+ goto out3;
+
if (d_is_symlink(rd.new_dentry)) {
err = -EACCES;
goto out3;
@@ -919,49 +950,206 @@ void ksmbd_vfs_set_fadvise(struct file *filp, __le32 option)
int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
loff_t off, loff_t len)
{
+ const struct cred *saved_cred;
+ loff_t pos = off, size;
+ char *zero_buf = NULL;
+ int err;
+
smb_break_all_levII_oplock(work, fp, 1);
- if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE)
- return vfs_fallocate(fp->filp,
- FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
- off, len);
-
- return vfs_fallocate(fp->filp,
- FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
- off, len);
+ if (!work->tcon->posix_extensions) {
+ loff_t size = i_size_read(file_inode(fp->filp));
+
+ if (off < size) {
+ err = check_lock_range(fp->filp, off,
+ min(off + len, size) - 1,
+ WRITE);
+ if (err)
+ return -EAGAIN;
+ }
+ }
+
+ saved_cred = override_creds(fp->filp->f_cred);
+ if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE) {
+ err = vfs_fallocate(fp->filp,
+ FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ off, len);
+ } else {
+ size = i_size_read(file_inode(fp->filp));
+ if (off >= size) {
+ err = 0;
+ goto out;
+ }
+
+ len = min(len, size - off);
+ zero_buf = kvzalloc(SZ_64K, GFP_KERNEL);
+ if (!zero_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ while (len) {
+ ssize_t written;
+ size_t count = min_t(loff_t, len, SZ_64K);
+
+ written = kernel_write(fp->filp, zero_buf, count, &pos);
+ if (written < 0) {
+ err = written;
+ goto out;
+ }
+ if (!written) {
+ err = -EIO;
+ goto out;
+ }
+ len -= written;
+ }
+ err = 0;
+ }
+out:
+ revert_creds(saved_cred);
+ kvfree(zero_buf);
+ return err;
}
-int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
- struct file_allocated_range_buffer *ranges,
- unsigned int in_count, unsigned int *out_count)
+int ksmbd_vfs_zero_holes(struct ksmbd_file *fp)
{
struct file *f = fp->filp;
- struct inode *inode = file_inode(fp->filp);
- loff_t maxbytes = (u64)inode->i_sb->s_maxbytes, end;
- loff_t extent_start, extent_end;
- int ret = 0;
+ const struct cred *saved_cred;
+ loff_t size, pos = 0;
+ char *zero_buf;
+ int err;
- if (start > maxbytes)
- return -EFBIG;
+ err = file_write_and_wait(f);
+ if (err)
+ return err;
- if (!in_count)
+ size = i_size_read(file_inode(f));
+ if (!size)
return 0;
/*
- * Shrink request scope to what the fs can actually handle.
+ * FALLOC_FL_ZERO_RANGE may leave unwritten extents, which SEEK_DATA
+ * reports as holes. Write zeroes into each hole so that clearing the
+ * sparse attribute leaves the file fully allocated.
*/
- if (length > maxbytes || (maxbytes - length) < start)
- length = maxbytes - start;
+ zero_buf = kvzalloc(SZ_64K, GFP_KERNEL);
+ if (!zero_buf)
+ return -ENOMEM;
- if (start + length > inode->i_size)
- length = inode->i_size - start;
+ saved_cred = override_creds(f->f_cred);
+ while (pos < size) {
+ loff_t data, hole;
+
+ hole = vfs_llseek(f, pos, SEEK_HOLE);
+ if (hole == -ENXIO || hole >= size)
+ break;
+ if (hole < 0) {
+ err = hole;
+ goto out;
+ }
+
+ data = vfs_llseek(f, hole, SEEK_DATA);
+ if (data == -ENXIO) {
+ data = size;
+ } else if (data < 0) {
+ err = data;
+ goto out;
+ }
+ data = min(data, size);
+ if (data <= hole) {
+ err = -EIO;
+ goto out;
+ }
+
+ pos = hole;
+ while (pos < data) {
+ ssize_t written;
+ size_t count = min_t(loff_t, data - pos, SZ_64K);
+
+ written = kernel_write(f, zero_buf, count, &pos);
+ if (written < 0) {
+ err = written;
+ goto out;
+ }
+ if (!written) {
+ err = -EIO;
+ goto out;
+ }
+ }
+ }
+ err = file_write_and_wait(f);
+out:
+ revert_creds(saved_cred);
+ kvfree(zero_buf);
+ return err;
+}
+
+int ksmbd_vfs_trim_data(struct ksmbd_work *work, struct ksmbd_file *fp,
+ loff_t off, loff_t len)
+{
+ const struct cred *saved_cred;
+ int err;
+
+ smb_break_all_levII_oplock(work, fp, 1);
+ if (!work->tcon->posix_extensions) {
+ loff_t size = i_size_read(file_inode(fp->filp));
+
+ if (off < size) {
+ err = check_lock_range(fp->filp, off,
+ min(off + len, size) - 1,
+ WRITE);
+ if (err)
+ return -EAGAIN;
+ }
+ }
+
+ saved_cred = override_creds(fp->filp->f_cred);
+ err = vfs_fallocate(fp->filp,
+ FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ off, len);
+ revert_creds(saved_cred);
+ return err;
+}
+
+int ksmbd_vfs_query_allocated_ranges(struct ksmbd_file *fp, loff_t start,
+ loff_t length,
+ struct file_allocated_range_buffer *ranges,
+ unsigned int in_count,
+ unsigned int *out_count)
+{
+ struct file *f = fp->filp;
+ struct inode *inode = file_inode(fp->filp);
+ loff_t maxbytes = inode->i_sb->s_maxbytes, size;
+ loff_t extent_start, extent_end, end;
+ int ret = 0;
*out_count = 0;
+ if (start < 0 || length < 0)
+ return -EINVAL;
+ if (start > maxbytes)
+ return -EFBIG;
+ if (!in_count)
+ return 0;
+ if (length > maxbytes || maxbytes - length < start)
+ length = maxbytes - start;
+ size = i_size_read(inode);
+ if (!length || start >= size)
+ return 0;
+ if (length > size - start)
+ length = size - start;
+
end = start + length;
+ if ((fp->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE) &&
+ start < end) {
+ ret = file_write_and_wait_range(f, start, end - 1);
+ if (ret)
+ return ret;
+ }
+
while (start < end && *out_count < in_count) {
extent_start = vfs_llseek(f, start, SEEK_DATA);
if (extent_start < 0) {
if (extent_start != -ENXIO)
- ret = (int)extent_start;
+ ret = extent_start;
break;
}
@@ -971,7 +1159,7 @@ int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
extent_end = vfs_llseek(f, extent_start, SEEK_HOLE);
if (extent_end < 0) {
if (extent_end != -ENXIO)
- ret = (int)extent_end;
+ ret = extent_end;
break;
} else if (extent_start >= extent_end) {
break;
@@ -980,10 +1168,12 @@ int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
ranges[*out_count].file_offset = cpu_to_le64(extent_start);
ranges[(*out_count)++].length =
cpu_to_le64(min(extent_end, end) - extent_start);
-
start = extent_end;
}
+ if (!ret && start < end && *out_count == in_count)
+ ret = -E2BIG;
+
return ret;
}
@@ -1009,13 +1199,15 @@ int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
int ksmbd_vfs_unlink(struct file *filp)
{
+ const struct cred *saved_cred;
int err = 0;
struct dentry *dir, *dentry = filp->f_path.dentry;
struct mnt_idmap *idmap = file_mnt_idmap(filp);
+ saved_cred = override_creds(filp->f_cred);
err = mnt_want_write(filp->f_path.mnt);
if (err)
- return err;
+ goto out_revert;
dir = dget_parent(dentry);
dentry = start_removing_dentry(dir, dentry);
@@ -1034,7 +1226,8 @@ int ksmbd_vfs_unlink(struct file *filp)
out:
dput(dir);
mnt_drop_write(filp->f_path.mnt);
-
+out_revert:
+ revert_creds(saved_cred);
return err;
}
@@ -1149,7 +1342,7 @@ int __ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath,
retry:
err = ksmbd_vfs_path_lookup(share_conf, filepath, flags, path, for_remove);
- if (!err || !caseless)
+ if (!err || err != -ENOENT || !caseless)
return err;
path_len = strlen(filepath);
@@ -1251,15 +1444,30 @@ struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
unsigned int flags,
struct path *path)
{
- char *abs_name;
+ struct ksmbd_share_config *share_conf = work->tcon->share_conf;
+ struct qstr last;
struct dentry *dent;
+ int err;
+
+ /* resolve the name beneath the share root so ".." cannot escape */
+ CLASS(filename_kernel, filename)(name);
+
+ err = vfs_path_parent_lookup(filename, flags | LOOKUP_BENEATH,
+ path, &last, &share_conf->vfs_path);
+ if (err)
+ return ERR_PTR(err);
- abs_name = convert_to_unix_name(work->tcon->share_conf, name);
- if (!abs_name)
- return ERR_PTR(-ENOMEM);
+ err = mnt_want_write(path->mnt);
+ if (err) {
+ path_put(path);
+ return ERR_PTR(err);
+ }
- dent = start_creating_path(AT_FDCWD, abs_name, path, flags);
- kfree(abs_name);
+ dent = start_creating_noperm(path->dentry, &last);
+ if (IS_ERR(dent)) {
+ mnt_drop_write(path->mnt);
+ path_put(path);
+ }
return dent;
}
@@ -1456,8 +1664,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);
@@ -1473,7 +1681,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};
@@ -1484,7 +1692,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);
@@ -1510,6 +1718,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;
}
@@ -1529,8 +1738,6 @@ out_free:
kfree(acl.sd_buf);
*pntsd = NULL;
}
-
-free_n_data:
kfree(n.data);
return rc;
}
@@ -1545,14 +1752,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;
}
@@ -1655,6 +1863,35 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
}
}
+ /*
+ * Only pay for this when it'll actually be used: AAPL
+ * READDIR_ATTR_V2's flags field (AAPL_READDIR_ATTR_V2_NO_XATTR) is
+ * the only consumer. XATTR_NAME_STREAM ("user.DosStream.") is a
+ * reliable, distinct prefix for genuine ADS/stream xattrs -- unlike
+ * DOSATTRIB or ACL xattrs, which live under different prefixes, so
+ * this can't false-positive into telling Finder a file has no extra
+ * data when it actually does.
+ */
+ ksmbd_kstat->has_ads_stream = false;
+ if (work->conn->aapl_readdir_attr_v2) {
+ char *xattr_list = NULL, *name;
+ ssize_t xattr_list_len;
+
+ xattr_list_len = ksmbd_vfs_listxattr(dentry, &xattr_list);
+ if (xattr_list_len > 0) {
+ for (name = xattr_list;
+ name - xattr_list < xattr_list_len;
+ name += strlen(name) + 1) {
+ if (!strncmp(name, XATTR_NAME_STREAM,
+ XATTR_NAME_STREAM_LEN)) {
+ ksmbd_kstat->has_ads_stream = true;
+ break;
+ }
+ }
+ }
+ kvfree(xattr_list);
+ }
+
return 0;
}
@@ -1705,6 +1942,76 @@ int ksmbd_vfs_xattr_stream_name(char *stream_name, char **xattr_stream_name,
return 0;
}
+static ssize_t ksmbd_vfs_copy_file_range_buffered(struct ksmbd_work *work,
+ struct ksmbd_file *src_fp,
+ struct ksmbd_file *dst_fp,
+ loff_t src_off,
+ loff_t dst_off, size_t len)
+{
+ size_t buf_size = min_t(size_t, len, SZ_1M);
+ size_t copied = 0;
+ char *buf;
+ ssize_t ret = 0;
+
+ buf = kvmalloc(buf_size, KSMBD_DEFAULT_GFP);
+ if (!buf)
+ return -ENOMEM;
+
+ while (copied < len) {
+ size_t chunk_size = min(buf_size, len - copied);
+ size_t done = 0;
+ loff_t src_pos, dst_pos;
+
+ if (dst_off > src_off) {
+ src_pos = src_off + len - copied - chunk_size;
+ dst_pos = dst_off + len - copied - chunk_size;
+ } else {
+ src_pos = src_off + copied;
+ dst_pos = dst_off + copied;
+ }
+
+ while (done < chunk_size) {
+ loff_t pos = src_pos + done;
+
+ ret = ksmbd_vfs_read(work, src_fp, chunk_size - done,
+ &pos, buf + done);
+ if (ret <= 0) {
+ if (!ret)
+ ret = -EIO;
+ goto out;
+ }
+ done += ret;
+ }
+
+ done = 0;
+ while (done < chunk_size) {
+ loff_t pos = dst_pos + done;
+ ssize_t written = 0;
+
+ ret = ksmbd_vfs_write(work, dst_fp, buf + done,
+ chunk_size - done, &pos, false,
+ &written);
+ if (ret < 0)
+ goto out;
+ if (!written) {
+ ret = -EIO;
+ goto out;
+ }
+ done += written;
+ }
+ copied += chunk_size;
+ }
+ ret = copied;
+out:
+ kvfree(buf);
+ return ret;
+}
+
+static bool ksmbd_vfs_copy_range_valid(loff_t offset, size_t len)
+{
+ return offset >= 0 && (loff_t)len <= MAX_LFS_FILESIZE - offset;
+}
+
int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
struct ksmbd_file *src_fp,
struct ksmbd_file *dst_fp,
@@ -1732,9 +2039,6 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
return -EACCES;
}
- if (ksmbd_stream_fd(src_fp) || ksmbd_stream_fd(dst_fp))
- return -EBADF;
-
smb_break_all_levII_oplock(work, dst_fp, 1);
if (!work->tcon->posix_extensions) {
@@ -1743,6 +2047,10 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
dst_off = le64_to_cpu(chunks[i].TargetOffset);
len = le32_to_cpu(chunks[i].Length);
+ if (!ksmbd_vfs_copy_range_valid(src_off, len) ||
+ !ksmbd_vfs_copy_range_valid(dst_off, len))
+ return -E2BIG;
+
if (check_lock_range(src_fp->filp, src_off,
src_off + len - 1, READ))
return -EAGAIN;
@@ -1752,35 +2060,139 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
}
}
- src_file_size = i_size_read(file_inode(src_fp->filp));
+ if (ksmbd_stream_fd(src_fp)) {
+ const struct cred *saved_cred;
+
+ saved_cred = override_creds(src_fp->filp->f_cred);
+ src_file_size = ksmbd_vfs_casexattr_len(
+ file_mnt_idmap(src_fp->filp),
+ src_fp->filp->f_path.dentry,
+ src_fp->stream.name, src_fp->stream.size);
+ revert_creds(saved_cred);
+ if (src_file_size < 0)
+ return src_file_size;
+ } else {
+ src_file_size = i_size_read(file_inode(src_fp->filp));
+ }
+
+ /*
+ * macOS Finder's Cmd+D duplicate sends FSCTL_SRV_COPYCHUNK with
+ * ChunkCount=0 meaning "copy the whole file/stream", not the
+ * standard SMB2 "query my copy limits, no data" semantics --
+ * fsctl_copychunk() only reaches here with chunk_count == 0 for
+ * AAPL-negotiated connections, so this doesn't affect compliant
+ * non-AAPL clients. Without this, the destination stays at its
+ * just-created 0 bytes / empty stream: the for loop below is a
+ * no-op when chunk_count is 0, since it never has an iteration to
+ * treat as "copy everything".
+ */
+ if (chunk_count == 0 && work->conn->is_aapl) {
+ loff_t off = 0;
+
+ while (off < src_file_size) {
+ size_t remaining = src_file_size - off;
+ ssize_t copied;
+
+ /* Same source/destination offset here: an in-place,
+ * same-inode copy at matching offsets is a degenerate
+ * no-op range, not a real overlap, but vfs_copy_file_range
+ * still doesn't support streams -- route those (and the
+ * same-inode case defensively) through the buffered path.
+ */
+ if (ksmbd_stream_fd(src_fp) || ksmbd_stream_fd(dst_fp) ||
+ file_inode(src_fp->filp) == file_inode(dst_fp->filp)) {
+ copied = ksmbd_vfs_copy_file_range_buffered(work, src_fp, dst_fp,
+ off, off, remaining);
+ } else {
+ copied = vfs_copy_file_range(src_fp->filp, off,
+ dst_fp->filp, off,
+ remaining, 0);
+ if (copied == -EOPNOTSUPP || copied == -EXDEV)
+ copied = vfs_copy_file_range(src_fp->filp, off,
+ dst_fp->filp, off,
+ remaining,
+ COPY_FILE_SPLICE);
+ }
+ if (copied < 0)
+ return copied;
+ if (copied == 0)
+ break;
+ off += copied;
+ }
+
+ /*
+ * This is a synthesized whole-file copy, not a response to
+ * any chunk descriptor the client actually sent (it sent
+ * none -- chunk_count is 0). Report zero chunks/chunk-bytes
+ * rather than inventing a chunk that doesn't correspond to
+ * anything in the request; only total_size_written (bytes
+ * actually copied) is meaningful here.
+ */
+ *chunk_count_written = 0;
+ *chunk_size_written = 0;
+ *total_size_written = off;
+ return 0;
+ }
for (i = 0; i < chunk_count; i++) {
+ bool stream_len_mismatch = false;
+ size_t copy_len;
+
src_off = le64_to_cpu(chunks[i].SourceOffset);
dst_off = le64_to_cpu(chunks[i].TargetOffset);
len = le32_to_cpu(chunks[i].Length);
+ copy_len = len;
- if (src_off + len > src_file_size)
+ if (!ksmbd_vfs_copy_range_valid(src_off, len) ||
+ !ksmbd_vfs_copy_range_valid(dst_off, len))
return -E2BIG;
+ if (src_off > src_file_size || len > src_file_size - src_off) {
+ /*
+ * macOS can reuse the main file's chunk list when copying
+ * streams, so the requested range can exceed the size of
+ * the xattr-backed stream. For an AAPL connection, copy the
+ * available stream data and report the requested length to
+ * avoid a copy length mismatch.
+ */
+ if (!work->conn->is_aapl ||
+ !ksmbd_stream_fd(src_fp) ||
+ !ksmbd_stream_fd(dst_fp))
+ return -E2BIG;
+
+ stream_len_mismatch = true;
+ if (src_off < src_file_size)
+ copy_len = src_file_size - src_off;
+ else
+ copy_len = 0;
+ }
+
/*
- * vfs_copy_file_range does not allow overlapped copying
- * within the same file.
+ * vfs_copy_file_range does not support streams or overlapping
+ * ranges within the same file.
*/
- if (file_inode(src_fp->filp) == file_inode(dst_fp->filp) &&
- dst_off + len > src_off &&
- dst_off < src_off + len)
- ret = do_splice_direct(src_fp->filp, &src_off,
- dst_fp->filp, &dst_off,
- min_t(size_t, len, MAX_RW_COUNT), 0);
- else
- ret = vfs_copy_file_range(src_fp->filp, src_off,
- dst_fp->filp, dst_off, len, 0);
- if (ret == -EOPNOTSUPP || ret == -EXDEV)
+ if (!copy_len) {
+ ret = 0;
+ } else if (ksmbd_stream_fd(src_fp) || ksmbd_stream_fd(dst_fp) ||
+ (file_inode(src_fp->filp) == file_inode(dst_fp->filp) &&
+ dst_off + copy_len > src_off &&
+ dst_off < src_off + copy_len)) {
+ ret = ksmbd_vfs_copy_file_range_buffered(work, src_fp,
+ dst_fp, src_off,
+ dst_off, copy_len);
+ } else {
ret = vfs_copy_file_range(src_fp->filp, src_off,
- dst_fp->filp, dst_off, len,
- COPY_FILE_SPLICE);
+ dst_fp->filp, dst_off, copy_len, 0);
+ if (ret == -EOPNOTSUPP || ret == -EXDEV)
+ ret = vfs_copy_file_range(src_fp->filp, src_off,
+ dst_fp->filp, dst_off,
+ copy_len,
+ COPY_FILE_SPLICE);
+ }
if (ret < 0)
return ret;
+ if (stream_len_mismatch)
+ ret = len;
*chunk_count_written += 1;
*total_size_written += ret;
@@ -1855,10 +2267,6 @@ int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap,
const struct path *path, struct inode *parent_inode)
{
struct posix_acl *acls;
- struct posix_acl_entry *pace;
- struct dentry *dentry = path->dentry;
- struct inode *inode = d_inode(dentry);
- int rc, i;
if (!IS_ENABLED(CONFIG_FS_POSIX_ACL))
return -EOPNOTSUPP;
@@ -1866,27 +2274,148 @@ int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap,
acls = get_inode_acl(parent_inode, ACL_TYPE_DEFAULT);
if (IS_ERR_OR_NULL(acls))
return -ENOENT;
- pace = acls->a_entries;
- for (i = 0; i < acls->a_count; i++, pace++) {
- if (pace->e_tag == ACL_MASK) {
- pace->e_perm = 0x07;
- break;
- }
+ posix_acl_release(acls);
+ return 0;
+}
+
+void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr)
+{
+ int rc;
+ struct file_kattr fa = { .flags_valid = true };
+
+ rc = vfs_fileattr_get(dentry, &fa);
+ if (rc)
+ return;
+
+ if (fa.flags & FS_COMPR_FL)
+ *fattr |= FILE_ATTRIBUTE_COMPRESSED_LE;
+}
+
+int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt)
+{
+ struct file_kattr fa = { .flags_valid = true };
+ int rc;
+
+ rc = vfs_fileattr_get(fp->filp->f_path.dentry, &fa);
+ if (rc == -ENOIOCTLCMD || rc == -ENOTTY || rc == -EINVAL ||
+ rc == -EOPNOTSUPP) {
+ if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_COMPRESSED_LE)
+ *fmt = COMPRESSION_FORMAT_LZNT1;
+ else
+ *fmt = COMPRESSION_FORMAT_NONE;
+ rc = 0;
+ goto out;
}
+ if (rc)
+ goto out;
- rc = set_posix_acl(idmap, dentry, ACL_TYPE_ACCESS, acls);
- if (rc < 0)
- ksmbd_debug(SMB, "Set posix acl(ACL_TYPE_ACCESS) failed, rc : %d\n",
- rc);
- if (S_ISDIR(inode->i_mode)) {
- rc = set_posix_acl(idmap, dentry, ACL_TYPE_DEFAULT,
- acls);
- if (rc < 0)
- ksmbd_debug(SMB, "Set posix acl(ACL_TYPE_DEFAULT) failed, rc : %d\n",
- rc);
+ if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_COMPRESSED_LE)
+ *fmt = COMPRESSION_FORMAT_LZNT1;
+ else
+ *fmt = COMPRESSION_FORMAT_NONE;
+
+out:
+ return rc;
+}
+
+static int __ksmbd_vfs_set_compression(struct ksmbd_work *work,
+ struct ksmbd_file *fp, u16 fmt,
+ bool check_access)
+{
+ const struct cred *saved_cred = NULL;
+ struct file_kattr fa;
+ struct dentry *dentry = fp->filp->f_path.dentry;
+ struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
+ u32 flags;
+ __le32 old_fattr;
+ int rc;
+
+ if (check_access && !(fp->daccess & FILE_WRITE_DATA_LE)) {
+ rc = -EACCES;
+ goto out;
}
- posix_acl_release(acls);
+ if (fmt != COMPRESSION_FORMAT_NONE &&
+ fmt != COMPRESSION_FORMAT_DEFAULT &&
+ fmt != COMPRESSION_FORMAT_LZNT1) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ saved_cred = override_creds(fp->filp->f_cred);
+ rc = vfs_fileattr_get(dentry, &fa);
+ if (rc == -ENOIOCTLCMD || rc == -ENOTTY || rc == -EINVAL ||
+ rc == -EOPNOTSUPP)
+ goto update_fattr;
+ if (rc)
+ goto out;
+
+ flags = fa.flags;
+ if (fmt == COMPRESSION_FORMAT_NONE) {
+ flags &= ~FS_COMPR_FL;
+ } else if (fmt == COMPRESSION_FORMAT_DEFAULT ||
+ fmt == COMPRESSION_FORMAT_LZNT1) {
+ flags |= FS_COMPR_FL;
+ }
+
+ if (flags != fa.flags) {
+ fileattr_fill_flags(&fa, flags);
+ rc = mnt_want_write_file(fp->filp);
+ if (rc)
+ goto out;
+
+ rc = vfs_fileattr_set(idmap, dentry, &fa);
+ mnt_drop_write_file(fp->filp);
+ if (rc == -ENOIOCTLCMD || rc == -ENOTTY || rc == -EINVAL ||
+ rc == -EOPNOTSUPP)
+ goto update_fattr;
+ if (rc)
+ goto out;
+ }
+
+update_fattr:
+ old_fattr = fp->f_ci->m_fattr;
+ if (fmt == COMPRESSION_FORMAT_NONE)
+ fp->f_ci->m_fattr &= ~FILE_ATTRIBUTE_COMPRESSED_LE;
+ else
+ fp->f_ci->m_fattr |= FILE_ATTRIBUTE_COMPRESSED_LE;
+
+ if (fp->f_ci->m_fattr != old_fattr) {
+ struct xattr_dos_attrib da = {0};
+
+ rc = ksmbd_vfs_get_dos_attrib_xattr(idmap, dentry, &da);
+ if (rc <= 0) {
+ da.version = 4;
+ da.itime = fp->itime;
+ da.create_time = fp->create_time;
+ da.flags = XATTR_DOSINFO_CREATE_TIME |
+ XATTR_DOSINFO_ITIME;
+ }
+
+ da.attr = le32_to_cpu(fp->f_ci->m_fattr);
+ da.flags |= XATTR_DOSINFO_ATTRIB;
+ rc = ksmbd_vfs_set_dos_attrib_xattr(idmap,
+ &fp->filp->f_path,
+ &da, true);
+ if (rc)
+ rc = 0;
+ }
+
+out:
+ if (saved_cred)
+ revert_creds(saved_cred);
return rc;
}
+
+int ksmbd_vfs_set_compression(struct ksmbd_work *work,
+ struct ksmbd_file *fp, u16 fmt)
+{
+ return __ksmbd_vfs_set_compression(work, fp, fmt, true);
+}
+
+int ksmbd_vfs_set_compression_create(struct ksmbd_work *work,
+ struct ksmbd_file *fp, u16 fmt)
+{
+ return __ksmbd_vfs_set_compression(work, fp, fmt, false);
+}
diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h
index 16ca29ee16e5..55d099de71f5 100644
--- a/fs/smb/server/vfs.h
+++ b/fs/smb/server/vfs.h
@@ -70,6 +70,7 @@ struct ksmbd_kstat {
struct kstat *kstat;
unsigned long long create_time;
__le32 file_attributes;
+ bool has_ads_stream; /* AAPL READDIR_ATTR V2 xattr-presence flag */
};
int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child);
@@ -87,8 +88,10 @@ int ksmbd_vfs_remove_file(struct ksmbd_work *work, const struct path *path);
int ksmbd_vfs_link(struct ksmbd_work *work,
const char *oldname, const char *newname);
int ksmbd_vfs_getattr(const struct path *path, struct kstat *stat);
-int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
- char *newname, int flags);
+int ksmbd_vfs_rename(struct ksmbd_work *work, struct ksmbd_file *old_fp,
+ char *newname, int flags);
+int ksmbd_vfs_check_rename_share(struct ksmbd_work *work,
+ const struct path *old_path);
int ksmbd_vfs_truncate(struct ksmbd_work *work,
struct ksmbd_file *fp, loff_t size);
struct srv_copychunk;
@@ -132,10 +135,15 @@ int ksmbd_vfs_empty_dir(struct ksmbd_file *fp);
void ksmbd_vfs_set_fadvise(struct file *filp, __le32 option);
int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
loff_t off, loff_t len);
+int ksmbd_vfs_zero_holes(struct ksmbd_file *fp);
+int ksmbd_vfs_trim_data(struct ksmbd_work *work, struct ksmbd_file *fp,
+ loff_t off, loff_t len);
struct file_allocated_range_buffer;
-int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
- struct file_allocated_range_buffer *ranges,
- unsigned int in_count, unsigned int *out_count);
+int ksmbd_vfs_query_allocated_ranges(struct ksmbd_file *fp, loff_t start,
+ loff_t length,
+ struct file_allocated_range_buffer *ranges,
+ unsigned int in_count,
+ unsigned int *out_count);
int ksmbd_vfs_unlink(struct file *filp);
void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat);
int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
@@ -168,4 +176,9 @@ int ksmbd_vfs_set_init_posix_acl(struct mnt_idmap *idmap,
int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap,
const struct path *path,
struct inode *parent_inode);
+void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr);
+int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt);
+int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u16 fmt);
+int ksmbd_vfs_set_compression_create(struct ksmbd_work *work,
+ struct ksmbd_file *fp, u16 fmt);
#endif /* __KSMBD_VFS_H__ */
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index ff4ea412d900..fd2c595f0486 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -10,6 +10,7 @@
#include <linux/vmalloc.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
+#include <linux/dcache.h>
#include "glob.h"
#include "vfs_cache.h"
@@ -19,6 +20,7 @@
#include "misc.h"
#include "mgmt/tree_connect.h"
#include "mgmt/user_session.h"
+#include "mgmt/user_config.h"
#include "smb_common.h"
#include "server.h"
#include "smb2pdu.h"
@@ -36,6 +38,8 @@ static struct ksmbd_file_table global_ft;
static atomic_long_t fd_limit;
static struct kmem_cache *filp_cache;
+static int ksmbd_mark_fp_closed(struct ksmbd_file *fp);
+
#define OPLOCK_NONE 0
#define OPLOCK_EXCLUSIVE 1
#define OPLOCK_BATCH 2
@@ -62,37 +66,73 @@ static const struct ksmbd_const_name ksmbd_lease_const_names[] = {
static const struct ksmbd_const_name ksmbd_oplock_const_names[] = {
{SMB2_OPLOCK_LEVEL_NONE, "OPLOCK_NONE"},
{SMB2_OPLOCK_LEVEL_II, "OPLOCK_II"},
- {SMB2_OPLOCK_LEVEL_EXCLUSIVE, "OPLOCK_EXECL"},
+ {SMB2_OPLOCK_LEVEL_EXCLUSIVE, "OPLOCK_EXCLUSIVE"},
{SMB2_OPLOCK_LEVEL_BATCH, "OPLOCK_BATCH"},
};
+static const struct ksmbd_const_name ksmbd_file_state_names[] = {
+ {FP_NEW, "new"},
+ {FP_INITED, "open"},
+ {FP_CLOSED, "closed"},
+};
+
+#define KSMBD_PROC_FILE_DURABLE BIT(0)
+#define KSMBD_PROC_FILE_PERSISTENT BIT(1)
+#define KSMBD_PROC_FILE_RESILIENT BIT(2)
+#define KSMBD_PROC_FILE_DELETE_ON_CLOSE BIT(3)
+#define KSMBD_PROC_FILE_STREAM BIT(4)
+#define KSMBD_PROC_FILE_POSIX BIT(5)
+#define KSMBD_PROC_FILE_ATTRIB_ONLY BIT(6)
+
+static const struct ksmbd_const_name ksmbd_file_flag_names[] = {
+ {KSMBD_PROC_FILE_DURABLE, "durable"},
+ {KSMBD_PROC_FILE_PERSISTENT, "persistent"},
+ {KSMBD_PROC_FILE_RESILIENT, "resilient"},
+ {KSMBD_PROC_FILE_DELETE_ON_CLOSE, "delete-on-close"},
+ {KSMBD_PROC_FILE_STREAM, "stream"},
+ {KSMBD_PROC_FILE_POSIX, "posix"},
+ {KSMBD_PROC_FILE_ATTRIB_ONLY, "attrib-only"},
+};
+
+static unsigned int ksmbd_proc_file_flags(struct ksmbd_file *fp)
+{
+ unsigned int flags = 0;
+
+ if (fp->is_durable)
+ flags |= KSMBD_PROC_FILE_DURABLE;
+ if (fp->is_persistent)
+ flags |= KSMBD_PROC_FILE_PERSISTENT;
+ if (fp->is_resilient)
+ flags |= KSMBD_PROC_FILE_RESILIENT;
+ if (fp->coption & FILE_DELETE_ON_CLOSE_LE)
+ flags |= KSMBD_PROC_FILE_DELETE_ON_CLOSE;
+ if (fp->stream.name)
+ flags |= KSMBD_PROC_FILE_STREAM;
+ if (fp->is_posix_ctxt)
+ flags |= KSMBD_PROC_FILE_POSIX;
+ if (fp->attrib_only)
+ flags |= KSMBD_PROC_FILE_ATTRIB_ONLY;
+ return flags;
+}
+
static int proc_show_files(struct seq_file *m, void *v)
{
struct ksmbd_file *fp = NULL;
unsigned int id;
struct oplock_info *opinfo;
- seq_printf(m, "#%-10s %-10s %-10s %-10s %-15s %-10s %-10s %s\n",
- "<tree id>", "<pid>", "<vid>", "<refcnt>",
- "<oplock>", "<daccess>", "<saccess>",
- "<name>");
-
read_lock(&global_ft.lock);
idr_for_each_entry(global_ft.idr, fp, id) {
- seq_printf(m, "%#-10x %#-10llx %#-10llx %#-10x",
- fp->tcon->id,
- fp->persistent_id,
- fp->volatile_id,
- atomic_read(&fp->refcount));
+ seq_printf(m, "tree_id:\t0x%x\n", fp->tcon ? fp->tcon->id : 0);
+ seq_printf(m, "persistent_id:\t0x%llx\n", fp->persistent_id);
+ seq_printf(m, "volatile_id:\t0x%llx\n", fp->volatile_id);
+ seq_printf(m, "refcount:\t%d\n", atomic_read(&fp->refcount));
rcu_read_lock();
opinfo = rcu_dereference(fp->f_opinfo);
- rcu_read_unlock();
-
- if (!opinfo) {
- seq_printf(m, " %-15s", " ");
- } else {
+ if (opinfo) {
const struct ksmbd_const_name *const_names;
+ const char *name;
int count;
unsigned int level;
@@ -105,13 +145,33 @@ static int proc_show_files(struct seq_file *m, void *v)
count = ARRAY_SIZE(ksmbd_oplock_const_names);
level = opinfo->level;
}
- ksmbd_proc_show_const_name(m, " %-15s",
- const_names, count, level);
+ rcu_read_unlock();
+ name = ksmbd_proc_const_name(const_names, count, level);
+ if (name)
+ seq_printf(m, "oplock:\t%s\n", name);
+ else
+ seq_printf(m, "oplock:\t0x%x\n", level);
+ } else {
+ rcu_read_unlock();
+ seq_puts(m, "oplock:\tnone\n");
}
- seq_printf(m, " %#010x %#010x %s\n",
- le32_to_cpu(fp->daccess),
- le32_to_cpu(fp->saccess),
+ seq_printf(m, "state:\t%s\n",
+ ksmbd_proc_const_name(ksmbd_file_state_names,
+ ARRAY_SIZE(ksmbd_file_state_names),
+ fp->f_state));
+ seq_printf(m, "durable_timeout:\t%u\n", fp->durable_timeout);
+ seq_printf(m, "create_options:\t0x%08x\n",
+ le32_to_cpu(fp->coption));
+ seq_printf(m, "desired_access:\t0x%08x\n",
+ le32_to_cpu(fp->daccess));
+ seq_printf(m, "share_access:\t0x%08x\n",
+ le32_to_cpu(fp->saccess));
+ seq_puts(m, "flags:\t");
+ ksmbd_proc_show_flag_names(m, ksmbd_file_flag_names,
+ ARRAY_SIZE(ksmbd_file_flag_names),
+ ksmbd_proc_file_flags(fp));
+ seq_printf(m, "\nname:\t%s\n\n",
fp->filp->f_path.dentry->d_name.name);
}
read_unlock(&global_ft.lock);
@@ -120,7 +180,8 @@ static int proc_show_files(struct seq_file *m, void *v)
static int create_proc_files(void)
{
- ksmbd_proc_create("files", proc_show_files, NULL);
+ if (!ksmbd_proc_create("files", proc_show_files, NULL))
+ return -ENOMEM;
return 0;
}
#else
@@ -131,6 +192,16 @@ static bool durable_scavenger_running;
static DEFINE_MUTEX(durable_scavenger_lock);
static wait_queue_head_t dh_wq;
+bool ksmbd_durable_scavenger_active(void)
+{
+ bool active;
+
+ mutex_lock(&durable_scavenger_lock);
+ active = durable_scavenger_running;
+ mutex_unlock(&durable_scavenger_lock);
+ return active;
+}
+
void ksmbd_set_fd_limit(unsigned long limit)
{
limit = min(limit, get_max_files());
@@ -210,13 +281,13 @@ int ksmbd_query_inode_status(struct dentry *dentry)
return ret;
down_read(&ci->m_lock);
- if (ci->m_flags & (S_DEL_PENDING | S_DEL_ON_CLS))
+ if (ci->m_flags & S_DEL_PENDING)
ret = KSMBD_INODE_STATUS_PENDING_DELETE;
else
ret = KSMBD_INODE_STATUS_OK;
up_read(&ci->m_lock);
- atomic_dec(&ci->m_count);
+ ksmbd_inode_put(ci);
return ret;
}
@@ -226,8 +297,14 @@ bool ksmbd_inode_pending_delete(struct ksmbd_file *fp)
int ret;
down_read(&ci->m_lock);
- ret = (ci->m_flags & (S_DEL_PENDING | S_DEL_ON_CLS));
+ ret = (ci->m_flags & S_DEL_PENDING);
up_read(&ci->m_lock);
+ if (ret || !ksmbd_stream_fd(fp))
+ return ret;
+
+ spin_lock(&fp->f_lock);
+ ret = fp->stream_del_pending;
+ spin_unlock(&fp->f_lock);
return ret;
}
@@ -250,6 +327,33 @@ void ksmbd_clear_inode_pending_delete(struct ksmbd_file *fp)
up_write(&ci->m_lock);
}
+bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp)
+{
+ struct ksmbd_file *prev_fp;
+ struct ksmbd_inode *ci = fp->f_ci;
+ bool ret = false;
+
+ if (ksmbd_stream_fd(fp))
+ return false;
+
+ down_read(&ci->m_lock);
+ list_for_each_entry(prev_fp, &ci->m_fp_list, node) {
+ if (prev_fp == fp || !ksmbd_stream_fd(prev_fp))
+ continue;
+
+ if (file_inode(fp->filp) != file_inode(prev_fp->filp))
+ continue;
+
+ if (!(prev_fp->saccess & FILE_SHARE_DELETE_LE)) {
+ ret = true;
+ break;
+ }
+ }
+ up_read(&ci->m_lock);
+
+ return ret;
+}
+
void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp,
int file_info)
{
@@ -263,6 +367,40 @@ void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp,
up_write(&ci->m_lock);
}
+/*
+ * FileDispositionInformation (SET_INFO) on a stream handle must only
+ * mark the stream for deletion, not the whole file -- otherwise
+ * deleting a single alternate data stream (e.g. AFP_AfpInfo) deletes
+ * the entire file's data along with it.
+ *
+ * This is tracked on fp itself (stream_del_pending), not the shared
+ * ksmbd_inode: the inode-wide S_DEL_ON_CLS_STREAM flag used by
+ * ksmbd_fd_set_delete_on_close() can't record *which* stream should be
+ * deleted, so if a different stream handle on the same file closed
+ * first, it would delete the wrong stream.
+ */
+void ksmbd_fd_set_delete_pending(struct ksmbd_file *fp)
+{
+ if (ksmbd_stream_fd(fp)) {
+ spin_lock(&fp->f_lock);
+ fp->stream_del_pending = true;
+ spin_unlock(&fp->f_lock);
+ } else {
+ ksmbd_set_inode_pending_delete(fp);
+ }
+}
+
+void ksmbd_fd_clear_delete_pending(struct ksmbd_file *fp)
+{
+ if (ksmbd_stream_fd(fp)) {
+ spin_lock(&fp->f_lock);
+ fp->stream_del_pending = false;
+ spin_unlock(&fp->f_lock);
+ } else {
+ ksmbd_clear_inode_pending_delete(fp);
+ }
+}
+
static void ksmbd_inode_hash(struct ksmbd_inode *ci)
{
struct hlist_head *b = inode_hashtable +
@@ -383,23 +521,48 @@ static void __ksmbd_inode_close(struct ksmbd_file *fp)
}
up_write(&ci->m_lock);
+ /*
+ * Per-handle delete-pending from ksmbd_fd_set_delete_pending()
+ * (FileDispositionInformation on this stream) -- separate from
+ * the inode-wide flag above, which only ever meant "some
+ * stream on this file" with no way to say which one.
+ */
+ spin_lock(&fp->f_lock);
+ if (fp->stream_del_pending) {
+ fp->stream_del_pending = false;
+ remove_stream_xattr = true;
+ }
+ spin_unlock(&fp->f_lock);
+
if (remove_stream_xattr) {
+ const struct cred *saved_cred;
+
+ saved_cred = override_creds(filp->f_cred);
err = ksmbd_vfs_remove_xattr(file_mnt_idmap(filp),
&filp->f_path,
fp->stream.name,
true);
+ revert_creds(saved_cred);
if (err)
pr_err("remove xattr failed : %s\n",
fp->stream.name);
}
}
+ down_write(&ci->m_lock);
+ /* Promote S_DEL_ON_CLS to S_DEL_PENDING when close */
+ if (ci->m_flags & S_DEL_ON_CLS) {
+ ci->m_flags &= ~S_DEL_ON_CLS;
+ ci->m_flags |= S_DEL_PENDING;
+ }
+ up_write(&ci->m_lock);
+
if (atomic_dec_and_test(&ci->m_count)) {
bool do_unlink = false;
down_write(&ci->m_lock);
- if (ci->m_flags & (S_DEL_ON_CLS | S_DEL_PENDING)) {
- ci->m_flags &= ~(S_DEL_ON_CLS | S_DEL_PENDING);
+ if (ci->m_flags & S_DEL_PENDING) {
+ ci->m_flags &= ~S_DEL_PENDING;
do_unlink = true;
}
up_write(&ci->m_lock);
@@ -417,6 +580,14 @@ static void __ksmbd_remove_durable_fd(struct ksmbd_file *fp)
return;
idr_remove(global_ft.idr, fp->persistent_id);
+ /*
+ * Clear persistent_id so a later __ksmbd_close_fd() that runs from a
+ * delayed putter (e.g. when a concurrent ksmbd_lookup_fd_inode()
+ * walker held the final reference) does not re-issue idr_remove() on
+ * an id that idr_alloc_cyclic() may have already handed out to a new
+ * durable handle.
+ */
+ fp->persistent_id = KSMBD_NO_FID;
}
static void ksmbd_remove_durable_fd(struct ksmbd_file *fp)
@@ -430,13 +601,13 @@ static void ksmbd_remove_durable_fd(struct ksmbd_file *fp)
static void __ksmbd_remove_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
{
- if (!has_file_id(fp->volatile_id))
- return;
-
down_write(&fp->f_ci->m_lock);
list_del_init(&fp->node);
up_write(&fp->f_ci->m_lock);
+ if (!has_file_id(fp->volatile_id))
+ return;
+
write_lock(&ft->lock);
idr_remove(ft->idr, fp->volatile_id);
write_unlock(&ft->lock);
@@ -446,6 +617,7 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
{
struct file *filp;
struct ksmbd_lock *smb_lock, *tmp_lock;
+ struct ksmbd_work *cn_work;
fd_limit_close();
ksmbd_remove_durable_fd(fp);
@@ -459,24 +631,163 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
if (!IS_ERR_OR_NULL(filp))
fput(filp);
- /* because the reference count of fp is 0, it is guaranteed that
- * there are not accesses to fp->lock_list.
+ /*
+ * The zero fp reference count serializes access to fp->lock_list, but
+ * the VFS may still have blocked requests chained below these locks.
*/
list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
- spin_lock(&fp->conn->llist_lock);
- list_del(&smb_lock->clist);
- spin_unlock(&fp->conn->llist_lock);
+ struct ksmbd_conn *conn = smb_lock->conn;
+
+ if (conn) {
+ spin_lock(&conn->llist_lock);
+ list_del_init(&smb_lock->clist);
+ smb_lock->conn = NULL;
+ spin_unlock(&conn->llist_lock);
+ ksmbd_conn_put(conn);
+ }
- list_del(&smb_lock->flist);
+ list_del_init(&smb_lock->flist);
+ ksmbd_vfs_posix_lock_unblock(smb_lock->fl);
locks_free_lock(smb_lock->fl);
kfree(smb_lock);
}
+ /*
+ * Complete any CHANGE_NOTIFY left pending on this handle now that
+ * it is closed. KSMBD never completes CHANGE_NOTIFY spontaneously
+ * (no real change-notification backend), only on close -- matching
+ * genuine SMB2/macOS smbfs semantics and avoiding the Finder
+ * "directory changed, re-enumerate everything" loop.
+ *
+ * smb2_notify() on another connection can be adding to
+ * notify_pendings under fp->f_lock at the same time this handle is
+ * closed, and a client-sent CANCEL can concurrently be racing to
+ * claim the same entry via smb2_notify_cancel_fn() (smb2pdu.c).
+ * Pop one entry at a time under the lock via list_del_init() rather
+ * than a bulk list_splice_init(): list_del_init() leaves the node
+ * self-linked ("empty"), which is what the cancel path checks under
+ * the same lock to tell whether it lost the race -- a bulk splice
+ * would instead relink every entry into a shared local list, so an
+ * entry claimed here would still read as "not empty" to a racing
+ * cancel_fn, and both sides could end up freeing the same work.
+ * ksmbd_conn_write() can sleep (it takes conn's write mutex), so it
+ * must not be called while fp->f_lock is held -- release the lock
+ * before processing each popped entry, then reacquire it for the
+ * next.
+ */
+ for (;;) {
+ spin_lock(&fp->f_lock);
+ if (list_empty(&fp->notify_pendings)) {
+ spin_unlock(&fp->f_lock);
+ break;
+ }
+ cn_work = list_first_entry(&fp->notify_pendings,
+ struct ksmbd_work, notify_entry);
+ list_del_init(&cn_work->notify_entry);
+ spin_unlock(&fp->f_lock);
+
+ ksmbd_conn_write(cn_work);
+ /*
+ * release_async_work() removes cn_work from
+ * conn->async_requests, frees cancel_argv, and releases+zeroes
+ * async_id -- all needed before ksmbd_free_work_struct(), which
+ * only releases async_id itself if still nonzero (i.e. if this
+ * hadn't already been done).
+ */
+ release_async_work(cn_work);
+ ksmbd_free_work_struct(cn_work);
+ }
+
+ /*
+ * Drop fp's strong reference on conn (taken in ksmbd_open_fd() /
+ * ksmbd_reopen_durable_fd()). Durable fps that reached the
+ * scavenger have already had fp->conn cleared by session_fd_check(),
+ * in which case there is nothing to drop here.
+ */
+ if (fp->conn) {
+ ksmbd_conn_put(fp->conn);
+ fp->conn = NULL;
+ }
+
if (ksmbd_stream_fd(fp))
kfree(fp->stream.name);
+ kfree(fp->owner.name);
+
kmem_cache_free(filp_cache, fp);
}
+/**
+ * ksmbd_close_disconnected_durable_delete_on_close() - drop a delete-on-close
+ * file kept present only by disconnected durable handles
+ * @dentry: dentry of the file being opened
+ *
+ * A durable handle opened with delete-on-close is preserved across a
+ * disconnect so it can be reclaimed by a durable reconnect. When a new
+ * (non-reconnect) open arrives for the same name instead, the disconnected
+ * handle has to give way. Close such handles so their delete-on-close is
+ * applied and the file is removed once the last handle is gone, letting the
+ * new open create a fresh file.
+ *
+ * The caller's inode reference is dropped before closing so that the final
+ * close can promote S_DEL_ON_CLS to S_DEL_PENDING and unlink the file.
+ *
+ * Return: true if a disconnected durable handle was closed.
+ */
+bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
+{
+ struct ksmbd_inode *ci;
+ struct ksmbd_file *fp, *tmp;
+ LIST_HEAD(dispose);
+ bool closed = false;
+
+ ci = ksmbd_inode_lookup_lock(dentry);
+ if (!ci)
+ return false;
+
+ down_write(&ci->m_lock);
+ if (ci->m_flags & (S_DEL_ON_CLS | S_DEL_ON_CLS_STREAM | S_DEL_PENDING)) {
+ list_for_each_entry_safe(fp, tmp, &ci->m_fp_list, node) {
+ if (fp->conn || !fp->is_durable ||
+ fp->f_state != FP_INITED)
+ continue;
+
+ /*
+ * 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);
+
+ /*
+ * 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 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);
+ if (atomic_sub_and_test(2, &fp->refcount)) {
+ __ksmbd_close_fd(NULL, fp);
+ closed = true;
+ }
+ }
+
+ return closed;
+}
+
static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp)
{
if (fp->f_state != FP_INITED)
@@ -487,6 +798,11 @@ static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp)
return fp;
}
+struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp)
+{
+ return ksmbd_fp_get(fp);
+}
+
static struct ksmbd_file *__ksmbd_lookup_fd(struct ksmbd_file_table *ft,
u64 id)
{
@@ -505,6 +821,20 @@ static struct ksmbd_file *__ksmbd_lookup_fd(struct ksmbd_file_table *ft,
static void __put_fd_final(struct ksmbd_work *work, struct ksmbd_file *fp)
{
+ /*
+ * Detached durable fp -- session_fd_check() cleared fp->conn at
+ * preserve, so this fp is no longer tracked by any conn's
+ * stats.open_files_count. This happens when
+ * ksmbd_scavenger_dispose_dh() hands the final close off to an
+ * m_fp_list walker (e.g. ksmbd_lookup_fd_inode()) whose work->conn
+ * is unrelated to the conn that originally opened the handle; close
+ * via the NULL-ft path so we do not underflow that unrelated
+ * counter.
+ */
+ if (!fp->conn) {
+ __ksmbd_close_fd(NULL, fp);
+ return;
+ }
__ksmbd_close_fd(&work->sess->file_table, fp);
atomic_dec(&work->conn->stats.open_files_count);
}
@@ -516,16 +846,30 @@ static void set_close_state_blocked_works(struct ksmbd_file *fp)
spin_lock(&fp->f_lock);
list_for_each_entry(cancel_work, &fp->blocked_works,
fp_entry) {
- cancel_work->state = KSMBD_WORK_CLOSED;
- cancel_work->cancel_fn(cancel_work->cancel_argv);
+ if (xchg(&cancel_work->state, KSMBD_WORK_CLOSED) ==
+ KSMBD_WORK_ACTIVE)
+ cancel_work->cancel_fn(cancel_work->cancel_argv);
}
spin_unlock(&fp->f_lock);
}
+void ksmbd_wake_session_blocked_works(struct ksmbd_session *sess)
+{
+ struct ksmbd_file_table *ft = &sess->file_table;
+ struct ksmbd_file *fp;
+ unsigned int id;
+
+ read_lock(&ft->lock);
+ idr_for_each_entry(ft->idr, fp, id)
+ set_close_state_blocked_works(fp);
+ read_unlock(&ft->lock);
+}
+
int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
{
struct ksmbd_file *fp;
struct ksmbd_file_table *ft;
+ bool closed = false;
if (!has_file_id(id))
return 0;
@@ -540,6 +884,9 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
fp = NULL;
else {
fp->f_state = FP_CLOSED;
+ idr_remove(ft->idr, id);
+ fp->volatile_id = KSMBD_NO_FID;
+ closed = true;
if (!atomic_dec_and_test(&fp->refcount))
fp = NULL;
}
@@ -547,7 +894,7 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
write_unlock(&ft->lock);
if (!fp)
- return -EINVAL;
+ return closed ? 0 : -EINVAL;
__put_fd_final(work, fp);
return 0;
@@ -620,7 +967,8 @@ struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id)
struct ksmbd_file *fp;
fp = __ksmbd_lookup_fd(&global_ft, id);
- if (fp && (fp->conn ||
+ if (fp && (fp->durable_reconnect_disabled ||
+ fp->conn ||
(fp->durable_scavenger_timeout &&
(fp->durable_scavenger_timeout <
jiffies_to_msecs(jiffies))))) {
@@ -639,6 +987,126 @@ void ksmbd_put_durable_fd(struct ksmbd_file *fp)
__ksmbd_close_fd(NULL, fp);
}
+bool ksmbd_has_other_active_fd(struct ksmbd_file *fp)
+{
+ struct ksmbd_file *lfp;
+ struct ksmbd_inode *ci = fp->f_ci;
+ bool ret = false;
+
+ down_read(&ci->m_lock);
+ list_for_each_entry(lfp, &ci->m_fp_list, node) {
+ if (lfp == fp)
+ continue;
+
+ if (lfp->f_state == FP_INITED &&
+ (READ_ONCE(lfp->conn) || READ_ONCE(lfp->tcon))) {
+ ret = true;
+ break;
+ }
+ }
+ up_read(&ci->m_lock);
+
+ return ret;
+}
+
+struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id)
+{
+ struct ksmbd_file *fp = NULL;
+ unsigned int id;
+
+ read_lock(&global_ft.lock);
+ idr_for_each_entry(global_ft.idr, fp, id) {
+ if (!fp->has_app_instance_id)
+ continue;
+ if (!memcmp(fp->app_instance_id, app_instance_id,
+ SMB2_CREATE_GUID_SIZE)) {
+ fp = ksmbd_fp_get(fp);
+ break;
+ }
+ }
+ read_unlock(&global_ft.lock);
+
+ return fp;
+}
+
+int ksmbd_close_fd_app_instance_id(char *app_instance_id)
+{
+ struct ksmbd_file_table *ft;
+ struct ksmbd_file *fp;
+ struct oplock_info *opinfo;
+ int n_to_drop = 0;
+
+ fp = ksmbd_lookup_fd_app_instance_id(app_instance_id);
+ if (!fp)
+ return 0;
+
+ opinfo = opinfo_get(fp);
+ 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 && 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;
+
+ if (!n_to_drop)
+ goto out;
+
+ down_write(&fp->f_ci->m_lock);
+ list_del_init(&fp->node);
+ up_write(&fp->f_ci->m_lock);
+
+ if (atomic_sub_and_test(n_to_drop, &fp->refcount)) {
+ if (fp->conn)
+ atomic_dec(&fp->conn->stats.open_files_count);
+ __ksmbd_close_fd(NULL, fp);
+ }
+ return 0;
+
+out:
+ if (opinfo)
+ opinfo_put(opinfo);
+ ksmbd_put_durable_fd(fp);
+ return 0;
+}
+
+int ksmbd_invalidate_durable_fd(unsigned long long id)
+{
+ struct ksmbd_file *fp;
+
+ fp = ksmbd_lookup_global_fd(id);
+ if (!fp)
+ return -ENOENT;
+
+ fp->durable_reconnect_disabled = true;
+
+ if (fp->conn) {
+ ksmbd_put_durable_fd(fp);
+ return -ENOENT;
+ }
+
+ fp->durable_timeout = 1;
+ fp->durable_scavenger_timeout = jiffies_to_msecs(jiffies);
+ ksmbd_put_durable_fd(fp);
+ if (waitqueue_active(&dh_wq))
+ wake_up(&dh_wq);
+
+ return -ENOENT;
+}
+
struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid)
{
struct ksmbd_file *fp = NULL;
@@ -673,17 +1141,71 @@ struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry)
down_read(&ci->m_lock);
list_for_each_entry(lfp, &ci->m_fp_list, node) {
if (inode == file_inode(lfp->filp)) {
- atomic_dec(&ci->m_count);
lfp = ksmbd_fp_get(lfp);
up_read(&ci->m_lock);
+ ksmbd_inode_put(ci);
return lfp;
}
}
- atomic_dec(&ci->m_count);
up_read(&ci->m_lock);
+ ksmbd_inode_put(ci);
return NULL;
}
+bool ksmbd_has_other_nonposix_open(struct dentry *dentry)
+{
+ struct ksmbd_file *fp;
+ struct inode *inode = d_inode(dentry);
+ unsigned int id;
+ bool ret = false;
+
+ if (!inode)
+ return false;
+
+ read_lock(&global_ft.lock);
+ idr_for_each_entry(global_ft.idr, fp, id) {
+ if (READ_ONCE(fp->f_state) != FP_INITED)
+ continue;
+ if (inode != file_inode(fp->filp))
+ continue;
+ if (fp->is_posix_ctxt)
+ continue;
+
+ ret = true;
+ break;
+ }
+ read_unlock(&global_ft.lock);
+
+ return ret;
+}
+
+bool ksmbd_has_nonposix_open_child(struct ksmbd_file *old_fp)
+{
+ struct dentry *dentry = old_fp->filp->f_path.dentry;
+ struct ksmbd_file *fp;
+ unsigned int id;
+ bool ret = false;
+
+ read_lock(&global_ft.lock);
+ idr_for_each_entry(global_ft.idr, fp, id) {
+ struct dentry *fp_dentry = fp->filp->f_path.dentry;
+
+ if (fp->f_state != FP_INITED)
+ continue;
+ if (fp_dentry == dentry)
+ continue;
+ if (old_fp->is_posix_ctxt && fp->is_posix_ctxt)
+ continue;
+ if (is_subdir(fp_dentry, dentry)) {
+ ret = true;
+ break;
+ }
+ }
+ read_unlock(&global_ft.lock);
+
+ return ret;
+}
+
#define OPEN_ID_TYPE_VOLATILE_ID (0)
#define OPEN_ID_TYPE_PERSISTENT_ID (1)
@@ -708,7 +1230,8 @@ static int __open_id(struct ksmbd_file_table *ft, struct ksmbd_file *fp,
idr_preload(KSMBD_DEFAULT_GFP);
write_lock(&ft->lock);
- ret = idr_alloc_cyclic(ft->idr, fp, 0, INT_MAX - 1, GFP_NOWAIT);
+ ret = idr_alloc_cyclic(ft->idr, fp, KSMBD_START_FID, INT_MAX - 1,
+ GFP_NOWAIT);
if (ret >= 0) {
id = ret;
ret = 0;
@@ -743,11 +1266,20 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp)
INIT_LIST_HEAD(&fp->blocked_works);
INIT_LIST_HEAD(&fp->node);
INIT_LIST_HEAD(&fp->lock_list);
+ INIT_LIST_HEAD(&fp->notify_pendings);
spin_lock_init(&fp->f_lock);
+ mutex_init(&fp->readdir_lock);
atomic_set(&fp->refcount, 1);
fp->filp = filp;
- fp->conn = work->conn;
+ /*
+ * fp owns a strong reference on fp->conn for as long as fp->conn is
+ * non-NULL, so session_fd_check() and __ksmbd_close_fd() never
+ * dereference a dangling pointer. Paired with ksmbd_conn_put() in
+ * session_fd_check() (durable preserve), in __ksmbd_close_fd()
+ * (final close), and on the error paths below.
+ */
+ fp->conn = ksmbd_conn_get(work->conn);
fp->tcon = work->tcon;
fp->volatile_id = KSMBD_NO_FID;
fp->persistent_id = KSMBD_NO_FID;
@@ -769,58 +1301,195 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp)
return fp;
err_out:
+ /* fp->conn was set and refcounted before every branch here. */
+ ksmbd_conn_put(fp->conn);
kmem_cache_free(filp_cache, fp);
return ERR_PTR(ret);
}
-void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,
- unsigned int state)
+/**
+ * ksmbd_update_fstate() - update an fp state under the file-table lock
+ * @ft: file table that publishes @fp's volatile id
+ * @fp: file pointer to update
+ * @state: new state
+ *
+ * Return: 0 on success. The FP_NEW -> FP_INITED transition is special:
+ * -ENOENT if teardown already unpublished @fp by advancing the state or
+ * clearing the volatile id. Other state updates preserve the historical
+ * fire-and-forget behavior.
+ */
+int ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,
+ unsigned int state)
{
+ int ret;
+
if (!fp)
- return;
+ return -ENOENT;
write_lock(&ft->lock);
- fp->f_state = state;
+ if (state == FP_INITED &&
+ (fp->f_state != FP_NEW || !has_file_id(fp->volatile_id))) {
+ ret = -ENOENT;
+ } else {
+ fp->f_state = state;
+ ret = 0;
+ }
write_unlock(&ft->lock);
+
+ return ret;
+}
+
+/*
+ * ksmbd_mark_fp_closed() - mark fp closed under ft->lock and return how many
+ * refs the teardown path owns.
+ *
+ * FP_INITED has a normal idr-owned reference, so teardown owns both that
+ * reference and the transient lookup reference. FP_NEW is still owned by the
+ * in-flight opener/reopener, which will drop the original reference after
+ * ksmbd_update_fstate(..., FP_INITED) observes the cleared volatile id.
+ * FP_CLOSED on entry means an earlier ksmbd_close_fd() already consumed the
+ * idr-owned ref.
+ */
+static int ksmbd_mark_fp_closed(struct ksmbd_file *fp)
+{
+ if (fp->f_state == FP_INITED) {
+ set_close_state_blocked_works(fp);
+ fp->f_state = FP_CLOSED;
+ return 2;
+ }
+
+ return 1;
}
static int
-__close_file_table_ids(struct ksmbd_file_table *ft,
+__close_file_table_ids(struct ksmbd_session *sess,
struct ksmbd_tree_connect *tcon,
bool (*skip)(struct ksmbd_tree_connect *tcon,
- struct ksmbd_file *fp))
+ struct ksmbd_file *fp,
+ struct ksmbd_user *user),
+ bool skip_preserves_fp)
{
+ struct ksmbd_file_table *ft = &sess->file_table;
struct ksmbd_file *fp;
unsigned int id = 0;
int num = 0;
while (1) {
+ int n_to_drop;
+
write_lock(&ft->lock);
fp = idr_get_next(ft->idr, &id);
if (!fp) {
write_unlock(&ft->lock);
break;
}
-
- if (skip(tcon, fp) ||
- !atomic_dec_and_test(&fp->refcount)) {
+ if (!atomic_inc_not_zero(&fp->refcount)) {
id++;
write_unlock(&ft->lock);
continue;
}
- set_close_state_blocked_works(fp);
- idr_remove(ft->idr, fp->volatile_id);
- fp->volatile_id = KSMBD_NO_FID;
- write_unlock(&ft->lock);
+ if (skip_preserves_fp) {
+ /*
+ * Session teardown: skip() is session_fd_check(),
+ * which may sleep and mutates fp->conn / fp->tcon /
+ * fp->volatile_id when it chooses to preserve fp
+ * for durable reconnect. Unpublish fp from the
+ * session idr here, under ft->lock, so that
+ * __ksmbd_lookup_fd() through this session cannot
+ * grant a new ksmbd_fp_get() reference to an fp
+ * whose fields are about to be rewritten outside
+ * the lock. Durable reconnect still reaches fp via
+ * global_ft.
+ */
+ idr_remove(ft->idr, id);
+ fp->durable_volatile_id = fp->volatile_id;
+ fp->volatile_id = KSMBD_NO_FID;
+ write_unlock(&ft->lock);
+
+ if (skip(tcon, fp, sess->user)) {
+ /*
+ * session_fd_check() has converted fp to
+ * durable-preserve state and cleared its
+ * per-conn fields. fp is already unpublished
+ * above; the original idr-owned ref keeps it
+ * alive for the durable scavenger. Drop only
+ * the transient ref. atomic_dec() is safe --
+ * atomic_inc_not_zero() succeeded on a
+ * positive value and we added one more, so
+ * refcount cannot be zero here.
+ */
+ atomic_dec(&fp->refcount);
+ id++;
+ continue;
+ }
+ /*
+ * Keep the close-state decision under the same lock
+ * observed by ksmbd_update_fstate(), which is how an
+ * in-flight FP_NEW opener learns that teardown has
+ * cleared its volatile id.
+ */
+ write_lock(&ft->lock);
+ n_to_drop = ksmbd_mark_fp_closed(fp);
+ write_unlock(&ft->lock);
+ } else {
+ /*
+ * Tree teardown: skip() is tree_conn_fd_check(), a
+ * cheap pointer compare that doesn't sleep and has
+ * no side effects, so keep the skip decision plus
+ * the unpublish-and-mark-closed sequence atomic
+ * under ft->lock. fps belonging to other tree
+ * connects (skip() == true) stay fully published in
+ * the session idr with no lock window.
+ */
+ if (skip(tcon, fp, sess->user)) {
+ atomic_dec(&fp->refcount);
+ write_unlock(&ft->lock);
+ id++;
+ continue;
+ }
+ idr_remove(ft->idr, id);
+ fp->volatile_id = KSMBD_NO_FID;
+ n_to_drop = ksmbd_mark_fp_closed(fp);
+ write_unlock(&ft->lock);
+ }
+
+ /*
+ * fp->volatile_id is already cleared to prevent stale idr
+ * removal from a deferred final close. Remove fp from
+ * m_fp_list here because __ksmbd_remove_fd() will skip the
+ * list unlink when volatile_id is KSMBD_NO_FID.
+ */
down_write(&fp->f_ci->m_lock);
list_del_init(&fp->node);
up_write(&fp->f_ci->m_lock);
- __ksmbd_close_fd(ft, fp);
-
- num++;
+ /*
+ * Drop the references this iteration owns:
+ *
+ * n_to_drop == 2: we observed FP_INITED and committed
+ * the FP_CLOSED transition ourselves, so we own the
+ * transient (+1) and the still-intact idr-owned ref.
+ *
+ * n_to_drop == 1: either a prior ksmbd_close_fd()
+ * already consumed the idr-owned ref, or fp was still
+ * FP_NEW and the in-flight opener/reopener must keep
+ * the original reference until ksmbd_update_fstate()
+ * observes the cleared volatile id.
+ *
+ * If we end up as the final putter, finalize fp and
+ * account the open_files_count decrement via the caller's
+ * atomic_sub(num, ...). Otherwise the remaining user's
+ * ksmbd_fd_put() reaches __put_fd_final(), which does its
+ * own atomic_dec(&open_files_count), so we must not count
+ * this fp here -- doing so would double-decrement the
+ * connection-wide counter.
+ */
+ if (atomic_sub_and_test(n_to_drop, &fp->refcount)) {
+ __ksmbd_close_fd(NULL, fp);
+ num++;
+ }
id++;
}
@@ -854,7 +1523,8 @@ static inline bool is_reconnectable(struct ksmbd_file *fp)
}
static bool tree_conn_fd_check(struct ksmbd_tree_connect *tcon,
- struct ksmbd_file *fp)
+ struct ksmbd_file *fp,
+ struct ksmbd_user *user)
{
return fp->tcon != tcon;
}
@@ -873,24 +1543,37 @@ static bool ksmbd_durable_scavenger_alive(void)
return true;
}
-static void ksmbd_scavenger_dispose_dh(struct list_head *head)
+static void ksmbd_scavenger_dispose_dh(struct ksmbd_file *fp)
{
- while (!list_empty(head)) {
- struct ksmbd_file *fp;
+ /*
+ * Durable-preserved fp can remain linked on f_ci->m_fp_list for
+ * share-mode checks. Unlink it before final close; fp->node is not
+ * available as a scavenger-private list node because re-adding it to
+ * another list corrupts m_fp_list.
+ */
+ down_write(&fp->f_ci->m_lock);
+ list_del_init(&fp->node);
+ up_write(&fp->f_ci->m_lock);
- fp = list_first_entry(head, struct ksmbd_file, node);
- list_del_init(&fp->node);
+ /*
+ * Drop both the durable lifetime reference and the transient reference
+ * taken by the scavenger under global_ft.lock. If a concurrent
+ * ksmbd_lookup_fd_inode() (or any other m_fp_list walker) snatched fp
+ * before the unlink above, that holder owns the final close via
+ * ksmbd_fd_put() -> __ksmbd_close_fd(). Otherwise the scavenger is
+ * the last putter and finalises fp here.
+ */
+ if (atomic_sub_and_test(2, &fp->refcount))
__ksmbd_close_fd(NULL, fp);
- }
}
static int ksmbd_durable_scavenger(void *dummy)
{
struct ksmbd_file *fp = NULL;
+ struct ksmbd_file *expired_fp;
unsigned int id;
unsigned int min_timeout = 1;
bool found_fp_timeout;
- LIST_HEAD(scavenger_list);
unsigned long remaining_jiffies;
__module_get(THIS_MODULE);
@@ -900,33 +1583,47 @@ static int ksmbd_durable_scavenger(void *dummy)
if (try_to_freeze())
continue;
- found_fp_timeout = false;
-
- remaining_jiffies = wait_event_timeout(dh_wq,
+ remaining_jiffies = wait_event_interruptible_timeout(dh_wq,
ksmbd_durable_scavenger_alive() == false,
__msecs_to_jiffies(min_timeout));
- if (remaining_jiffies)
+ if ((long)remaining_jiffies > 0)
min_timeout = jiffies_to_msecs(remaining_jiffies);
else
min_timeout = DURABLE_HANDLE_MAX_TIMEOUT;
- write_lock(&global_ft.lock);
- idr_for_each_entry(global_ft.idr, fp, id) {
- if (!fp->durable_timeout)
- continue;
-
- if (atomic_read(&fp->refcount) > 1 ||
- fp->conn)
- continue;
+ do {
+ expired_fp = NULL;
+ found_fp_timeout = false;
- found_fp_timeout = true;
- if (fp->durable_scavenger_timeout <=
- jiffies_to_msecs(jiffies)) {
- __ksmbd_remove_durable_fd(fp);
- list_add(&fp->node, &scavenger_list);
- } else {
+ write_lock(&global_ft.lock);
+ idr_for_each_entry(global_ft.idr, fp, id) {
unsigned long durable_timeout;
+ if (!fp->durable_timeout)
+ continue;
+
+ if (atomic_read(&fp->refcount) > 1 ||
+ fp->conn)
+ continue;
+
+ found_fp_timeout = true;
+ if (fp->durable_scavenger_timeout <=
+ jiffies_to_msecs(jiffies)) {
+ __ksmbd_remove_durable_fd(fp);
+ /*
+ * Take a transient reference so fp
+ * cannot be freed by an in-flight
+ * ksmbd_lookup_fd_inode() that found
+ * it through f_ci->m_fp_list while we
+ * drop global_ft.lock and reach the
+ * m_fp_list unlink in
+ * ksmbd_scavenger_dispose_dh().
+ */
+ atomic_inc(&fp->refcount);
+ expired_fp = fp;
+ break;
+ }
+
durable_timeout =
fp->durable_scavenger_timeout -
jiffies_to_msecs(jiffies);
@@ -934,10 +1631,11 @@ static int ksmbd_durable_scavenger(void *dummy)
if (min_timeout > durable_timeout)
min_timeout = durable_timeout;
}
- }
- write_unlock(&global_ft.lock);
+ write_unlock(&global_ft.lock);
- ksmbd_scavenger_dispose_dh(&scavenger_list);
+ if (expired_fp)
+ ksmbd_scavenger_dispose_dh(expired_fp);
+ } while (expired_fp);
if (found_fp_timeout == false)
break;
@@ -965,9 +1663,12 @@ void ksmbd_launch_ksmbd_durable_scavenger(void)
server_conf.dh_task = kthread_run(ksmbd_durable_scavenger,
(void *)NULL, "ksmbd-durable-scavenger");
- if (IS_ERR(server_conf.dh_task))
+ if (IS_ERR(server_conf.dh_task)) {
pr_err("cannot start conn thread, err : %ld\n",
PTR_ERR(server_conf.dh_task));
+ server_conf.dh_task = NULL;
+ durable_scavenger_running = false;
+ }
mutex_unlock(&durable_scavenger_lock);
}
@@ -989,28 +1690,123 @@ void ksmbd_stop_durable_scavenger(void)
kthread_stop(server_conf.dh_task);
}
+/*
+ * ksmbd_vfs_set_durable_owner - Store owner info for durable replay/reconnect
+ * @fp: ksmbd file pointer to store owner info
+ * @user: user pointer to copy from
+ *
+ * This function binds the current user's identity to the file handle
+ * to satisfy MS-SMB2 Step 8 (SecurityContext matching) during reconnect.
+ *
+ * Return: 0 on success, or negative error code on failure
+ */
+int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
+ struct ksmbd_user *user)
+{
+ char *name, *old_name;
+
+ if (!user)
+ return -EINVAL;
+
+ /* Duplicate the user name to ensure identity persistence */
+ name = kstrdup(user->name, GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+
+ spin_lock(&fp->f_lock);
+ old_name = fp->owner.name;
+ fp->owner.uid = user->uid;
+ fp->owner.gid = user->gid;
+ fp->owner.name = name;
+ spin_unlock(&fp->f_lock);
+ kfree(old_name);
+
+ return 0;
+}
+
+/**
+ * ksmbd_vfs_compare_durable_owner - Verify if the requester is original owner
+ * @fp: existing ksmbd file pointer
+ * @user: user pointer of the reconnect requester
+ *
+ * Compares the UID, GID, and name of the current requester against the
+ * original owner stored in the file handle.
+ *
+ * Return: true if the user matches, false otherwise
+ */
+bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp,
+ struct ksmbd_user *user)
+{
+ 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)
+ goto out;
+
+ /* Validate the account name to ensure the same SecurityContext */
+ 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,
- struct ksmbd_file *fp)
+ struct ksmbd_file *fp, struct ksmbd_user *user)
{
struct ksmbd_inode *ci;
struct oplock_info *op;
struct ksmbd_conn *conn;
+ struct ksmbd_lock *smb_lock, *tmp_lock;
if (!is_reconnectable(fp))
return false;
+ if (fp->f_state != FP_INITED)
+ return false;
+
+ if (WARN_ON_ONCE(!fp->conn))
+ return false;
+
+ if (ksmbd_vfs_set_durable_owner(fp, user))
+ return false;
+
+ /*
+ * fp owns a strong reference on fp->conn (taken in ksmbd_open_fd()
+ * / ksmbd_reopen_durable_fd()), so conn stays valid for the whole
+ * body of this function regardless of any op->conn puts below.
+ */
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;
- if (op->conn && atomic_dec_and_test(&op->conn->refcnt))
- kfree(op->conn);
+ ksmbd_conn_put(op->conn);
op->conn = NULL;
+ op->sess = NULL;
}
up_write(&ci->m_lock);
+ list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
+ struct ksmbd_conn *lock_conn = smb_lock->conn;
+
+ if (!lock_conn)
+ continue;
+ spin_lock(&lock_conn->llist_lock);
+ list_del_init(&smb_lock->clist);
+ smb_lock->conn = NULL;
+ spin_unlock(&lock_conn->llist_lock);
+ ksmbd_conn_put(lock_conn);
+ }
+
fp->conn = NULL;
fp->tcon = NULL;
fp->volatile_id = KSMBD_NO_FID;
@@ -1019,30 +1815,35 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
fp->durable_scavenger_timeout =
jiffies_to_msecs(jiffies) + fp->durable_timeout;
+ /* Drop fp's own reference on conn. */
+ ksmbd_conn_put(conn);
return true;
}
void ksmbd_close_tree_conn_fds(struct ksmbd_work *work)
{
- int num = __close_file_table_ids(&work->sess->file_table,
+ int num = __close_file_table_ids(work->sess,
work->tcon,
- tree_conn_fd_check);
+ tree_conn_fd_check,
+ false);
atomic_sub(num, &work->conn->stats.open_files_count);
}
void ksmbd_close_session_fds(struct ksmbd_work *work)
{
- int num = __close_file_table_ids(&work->sess->file_table,
+ int num = __close_file_table_ids(work->sess,
work->tcon,
- session_fd_check);
+ session_fd_check,
+ true);
atomic_sub(num, &work->conn->stats.open_files_count);
}
int ksmbd_init_global_file_table(void)
{
- create_proc_files();
+ if (create_proc_files())
+ pr_warn("Unable to create files procfs entry\n");
return ksmbd_init_file_table(&global_ft);
}
@@ -1090,37 +1891,73 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
{
struct ksmbd_inode *ci;
struct oplock_info *op;
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_lock *smb_lock;
+ unsigned int old_f_state;
- if (!fp->is_durable || fp->conn || fp->tcon) {
+ write_lock(&global_ft.lock);
+ if ((!fp->is_durable && !fp->is_persistent) || fp->conn || fp->tcon) {
+ write_unlock(&global_ft.lock);
pr_err("Invalid durable fd [%p:%p]\n", fp->conn, fp->tcon);
return -EBADF;
}
if (has_file_id(fp->volatile_id)) {
+ write_unlock(&global_ft.lock);
pr_err("Still in use durable fd: %llu\n", fp->volatile_id);
return -EBADF;
}
- fp->conn = work->conn;
+ /*
+ * Initialize fp's connection binding before publishing fp into the
+ * session's file table. If __open_id() is ordered first, a
+ * concurrent teardown that iterates the table can observe a valid
+ * volatile_id with fp->conn == NULL and preserve a
+ * partially-initialized fp. fp owns a strong reference on the new
+ * conn (see ksmbd_open_fd()); undo it on __open_id() failure.
+ */
+ fp->conn = ksmbd_conn_get(conn);
fp->tcon = work->tcon;
+ write_unlock(&global_ft.lock);
- ci = fp->f_ci;
- down_write(&ci->m_lock);
- list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
- if (op->conn)
- continue;
- op->conn = fp->conn;
- atomic_inc(&op->conn->refcnt);
- }
- up_write(&ci->m_lock);
-
+ old_f_state = fp->f_state;
fp->f_state = FP_NEW;
+
__open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID);
if (!has_file_id(fp->volatile_id)) {
+ write_lock(&global_ft.lock);
fp->conn = NULL;
fp->tcon = NULL;
+ write_unlock(&global_ft.lock);
+ ksmbd_conn_put(conn);
+ fp->f_state = old_f_state;
return -EBADF;
}
+
+ list_for_each_entry(smb_lock, &fp->lock_list, flist) {
+ smb_lock->conn = ksmbd_conn_get(conn);
+ spin_lock(&conn->llist_lock);
+ list_add_tail(&smb_lock->clist, &conn->lock_list);
+ spin_unlock(&conn->llist_lock);
+ }
+
+ ci = fp->f_ci;
+ down_write(&ci->m_lock);
+ list_for_each_entry_rcu(op, &ci->m_op_list, op_entry,
+ lockdep_is_held(&ci->m_lock)) {
+ if (op->conn || op->o_fp != fp)
+ 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;
}
@@ -1135,12 +1972,14 @@ int ksmbd_init_file_table(struct ksmbd_file_table *ft)
return 0;
}
-void ksmbd_destroy_file_table(struct ksmbd_file_table *ft)
+void ksmbd_destroy_file_table(struct ksmbd_session *sess)
{
+ struct ksmbd_file_table *ft = &sess->file_table;
+
if (!ft->idr)
return;
- __close_file_table_ids(ft, NULL, session_fd_check);
+ __close_file_table_ids(sess, NULL, session_fd_check, true);
idr_destroy(ft->idr);
kfree(ft->idr);
ft->idr = NULL;
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 78b506c5ef03..1884f6deb9d0 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -8,6 +8,7 @@
#include <linux/file.h>
#include <linux/fs.h>
+#include <linux/mutex.h>
#include <linux/rwsem.h>
#include <linux/spinlock.h>
#include <linux/idr.h>
@@ -22,7 +23,12 @@
#define FILE_GENERIC_WRITE 0x120116
#define FILE_GENERIC_EXECUTE 0X1200a0
-#define KSMBD_START_FID 0
+/*
+ * Start volatile/persistent file id allocation at 1. A file id of 0 yields an
+ * SMB2 FileId of {0, 0}, which clients (e.g. Windows, Samba) treat as a null
+ * handle and never close, leaking the open on the server.
+ */
+#define KSMBD_START_FID 1
#define KSMBD_NO_FID (INT_MAX)
#define SMB2_NO_FID (0xFFFFFFFFFFFFFFFFULL)
@@ -31,6 +37,7 @@ struct ksmbd_session;
struct ksmbd_lock {
struct file_lock *fl;
+ struct ksmbd_conn *conn;
struct list_head clist;
struct list_head flist;
struct list_head llist;
@@ -68,10 +75,25 @@ enum {
FP_CLOSED
};
+/* Owner information for durable handle reconnect */
+struct durable_owner {
+ unsigned int uid;
+ unsigned int gid;
+ char *name;
+};
+
+#define KSMBD_LOCK_SEQ_ARRAY_SIZE 64
+
+struct ksmbd_lock_sequence {
+ bool valid;
+ u8 sequence;
+};
+
struct ksmbd_file {
struct file *filp;
u64 persistent_id;
u64 volatile_id;
+ u64 durable_volatile_id;
spinlock_t f_lock;
@@ -86,11 +108,16 @@ struct ksmbd_file {
__le32 saccess;
__le32 coption;
__le32 cdoption;
+ __le32 create_file_attributes;
__u64 create_time;
+ __u64 change_time;
+ __u64 allocation_size;
__u64 itime;
+ __u64 open_mtime;
bool is_nt_open;
bool attrib_only;
+ bool allocation_size_set;
char client_guid[16];
char create_guid[16];
@@ -100,20 +127,47 @@ struct ksmbd_file {
struct list_head node;
struct list_head blocked_works;
struct list_head lock_list;
+ /*
+ * Per-handle FileDispositionInformation delete-pending state for a
+ * stream handle -- separate from ksmbd_inode's inode-wide m_flags,
+ * which have no way to record which stream on a multi-stream file
+ * was actually marked for deletion. See ksmbd_fd_set_delete_pending().
+ */
+ bool stream_del_pending;
unsigned int durable_timeout;
unsigned int durable_scavenger_timeout;
+ /* CREATE action returned when this durable handle was established. */
+ __le32 create_action;
/* if ls is happening on directory, below is valid*/
struct ksmbd_readdir_data readdir_data;
+ struct mutex readdir_lock;
int dot_dotdot[2];
unsigned int f_state;
bool reserve_lease_break;
bool is_durable;
bool is_persistent;
bool is_resilient;
+ bool has_app_instance_id;
+ bool app_instance_version_valid;
+ u64 app_instance_version_high;
+ u64 app_instance_version_low;
+ bool durable_reconnect_disabled;
+ bool durable_replay_consumed;
bool is_posix_ctxt;
+ struct durable_owner owner;
+ __le16 channel_sequence;
+ unsigned int outstanding_requests;
+ unsigned int outstanding_pre_requests;
+ struct ksmbd_lock_sequence lock_seq[KSMBD_LOCK_SEQ_ARRAY_SIZE];
+
+ /*
+ * Pending CHANGE_NOTIFY completions for this handle, sent with
+ * STATUS_NOTIFY_CLEANUP when the handle is closed.
+ */
+ struct list_head notify_pendings;
};
static inline void set_ctx_actor(struct dir_context *ctx,
@@ -140,32 +194,47 @@ static inline bool ksmbd_stream_fd(struct ksmbd_file *fp)
}
int ksmbd_init_file_table(struct ksmbd_file_table *ft);
-void ksmbd_destroy_file_table(struct ksmbd_file_table *ft);
+void ksmbd_destroy_file_table(struct ksmbd_session *sess);
int ksmbd_close_fd(struct ksmbd_work *work, u64 id);
struct ksmbd_file *ksmbd_lookup_fd_fast(struct ksmbd_work *work, u64 id);
struct ksmbd_file *ksmbd_lookup_foreign_fd(struct ksmbd_work *work, u64 id);
struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id,
u64 pid);
+int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
+ struct ksmbd_user *user);
+struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp);
void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp);
struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d);
void ksmbd_inode_put(struct ksmbd_inode *ci);
+bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry);
struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id);
struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
void ksmbd_put_durable_fd(struct ksmbd_file *fp);
+int ksmbd_invalidate_durable_fd(unsigned long long id);
+bool ksmbd_has_other_active_fd(struct ksmbd_file *fp);
+bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp);
+struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id);
+int ksmbd_close_fd_app_instance_id(char *app_instance_id);
struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
+bool ksmbd_has_other_nonposix_open(struct dentry *dentry);
+bool ksmbd_has_nonposix_open_child(struct ksmbd_file *old_fp);
unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp);
struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp);
void ksmbd_launch_ksmbd_durable_scavenger(void);
void ksmbd_stop_durable_scavenger(void);
+bool ksmbd_durable_scavenger_active(void);
void ksmbd_close_tree_conn_fds(struct ksmbd_work *work);
void ksmbd_close_session_fds(struct ksmbd_work *work);
+void ksmbd_wake_session_blocked_works(struct ksmbd_session *sess);
int ksmbd_close_inode_fds(struct ksmbd_work *work, struct inode *inode);
int ksmbd_init_global_file_table(void);
void ksmbd_free_global_file_table(void);
void ksmbd_set_fd_limit(unsigned long limit);
-void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,
- unsigned int state);
+int ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,
+ unsigned int state);
+bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp,
+ struct ksmbd_user *user);
/*
* INODE hash
@@ -185,6 +254,8 @@ void ksmbd_set_inode_pending_delete(struct ksmbd_file *fp);
void ksmbd_clear_inode_pending_delete(struct ksmbd_file *fp);
void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp,
int file_info);
+void ksmbd_fd_set_delete_pending(struct ksmbd_file *fp);
+void ksmbd_fd_clear_delete_pending(struct ksmbd_file *fp);
int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp);
int ksmbd_validate_name_reconnect(struct ksmbd_share_config *share,
struct ksmbd_file *fp, char *name);
diff --git a/fs/smb/smbdirect/Kconfig b/fs/smb/smbdirect/Kconfig
new file mode 100644
index 000000000000..2bf1b3350f7d
--- /dev/null
+++ b/fs/smb/smbdirect/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# smbdirect configuration
+
+config SMBDIRECT
+ def_tristate n
+ depends on INFINIBAND && INFINIBAND_ADDR_TRANS
+ depends on m || INFINIBAND=y
+ select SG_POOL
diff --git a/fs/smb/smbdirect/Makefile b/fs/smb/smbdirect/Makefile
new file mode 100644
index 000000000000..80d6025984d1
--- /dev/null
+++ b/fs/smb/smbdirect/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for smbdirect support
+#
+
+obj-$(CONFIG_SMBDIRECT) += smbdirect.o
+
+smbdirect-y := \
+ socket.o \
+ connection.o \
+ mr.o \
+ rw.o \
+ debug.o \
+ connect.o \
+ listen.o \
+ accept.o \
+ devices.o \
+ main.o
diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c
new file mode 100644
index 000000000000..1b30ca8476c1
--- /dev/null
+++ b/fs/smb/smbdirect/accept.c
@@ -0,0 +1,868 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (C) 2018, LG Electronics.
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+#include <net/sock.h>
+#include "../common/smb2status.h"
+
+static int smbdirect_accept_rdma_event_handler(struct rdma_cm_id *id,
+ struct rdma_cm_event *event);
+static int smbdirect_accept_init_params(struct smbdirect_socket *sc);
+static void smbdirect_accept_negotiate_recv_done(struct ib_cq *cq, struct ib_wc *wc);
+static void smbdirect_accept_negotiate_send_done(struct ib_cq *cq, struct ib_wc *wc);
+
+int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
+ const struct rdma_conn_param *param)
+{
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_recv_io *recv_io;
+ u8 peer_initiator_depth;
+ u8 peer_responder_resources;
+ struct rdma_conn_param conn_param;
+ __be32 ird_ord_hdr[2];
+ int ret;
+
+ if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_CREATED))
+ return -EINVAL;
+
+ /*
+ * First set what the we as server are able to support
+ */
+ sp->initiator_depth = min3(sp->initiator_depth,
+ sc->ib.dev->attrs.max_qp_rd_atom,
+ U8_MAX);
+
+ peer_initiator_depth = param->initiator_depth;
+ peer_responder_resources = param->responder_resources;
+ smbdirect_connection_negotiate_rdma_resources(sc,
+ peer_initiator_depth,
+ peer_responder_resources,
+ param);
+
+ ret = smbdirect_accept_init_params(sc);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_accept_init_params() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto init_params_failed;
+ }
+
+ ret = smbdirect_connection_create_qp(sc);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_create_qp() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto create_qp_failed;
+ }
+
+ ret = smbdirect_connection_create_mem_pools(sc);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_create_mem_pools() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto create_mem_failed;
+ }
+
+ recv_io = smbdirect_connection_get_recv_io(sc);
+ if (WARN_ON_ONCE(!recv_io)) {
+ ret = -EINVAL;
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_get_recv_io() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto get_recv_io_failed;
+ }
+ recv_io->cqe.done = smbdirect_accept_negotiate_recv_done;
+
+ /*
+ * Now post the recv_io buffer in order to get
+ * the negotiate request
+ */
+ sc->recv_io.expected = SMBDIRECT_EXPECT_NEGOTIATE_REQ;
+ ret = smbdirect_connection_post_recv_io(recv_io);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_post_recv_io() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto post_recv_io_failed;
+ }
+ /*
+ * From here recv_io is known to the RDMA QP and needs ib_drain_qp and
+ * smbdirect_accept_negotiate_recv_done to cleanup...
+ */
+ recv_io = NULL;
+
+ /* already checked with SMBDIRECT_CHECK_STATUS_WARN above */
+ WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED);
+ sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED;
+
+ /*
+ * We already negotiated sp->initiator_depth
+ * and sp->responder_resources above.
+ */
+ memset(&conn_param, 0, sizeof(conn_param));
+ conn_param.initiator_depth = sp->initiator_depth;
+ conn_param.responder_resources = sp->responder_resources;
+
+ if (sc->rdma.legacy_iwarp) {
+ ird_ord_hdr[0] = cpu_to_be32(conn_param.responder_resources);
+ ird_ord_hdr[1] = cpu_to_be32(conn_param.initiator_depth);
+ conn_param.private_data = ird_ord_hdr;
+ conn_param.private_data_len = sizeof(ird_ord_hdr);
+ } else {
+ conn_param.private_data = NULL;
+ conn_param.private_data_len = 0;
+ }
+ conn_param.retry_count = SMBDIRECT_RDMA_CM_RETRY;
+ conn_param.rnr_retry_count = SMBDIRECT_RDMA_CM_RNR_RETRY;
+ conn_param.flow_control = 0;
+
+ /* explicitly set above */
+ WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED);
+ sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING;
+ sc->rdma.expected_event = RDMA_CM_EVENT_ESTABLISHED;
+ sc->rdma.cm_id->event_handler = smbdirect_accept_rdma_event_handler;
+ ret = rdma_accept(sc->rdma.cm_id, &conn_param);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "rdma_accept() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto rdma_accept_failed;
+ }
+
+ /*
+ * start with the negotiate timeout and SMBDIRECT_KEEPALIVE_PENDING
+ * so that the timer will cause a disconnect.
+ */
+ INIT_DELAYED_WORK(&sc->idle.timer_work, smbdirect_connection_idle_timer_work);
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_PENDING;
+ mod_delayed_work(sc->workqueues.idle, &sc->idle.timer_work,
+ msecs_to_jiffies(sp->negotiate_timeout_msec));
+
+ return 0;
+
+rdma_accept_failed:
+ /*
+ * The recv_io posted above is now owned by the QP (recv_io was set to
+ * NULL after a successful post). smbdirect_connection_destroy_qp()
+ * calls ib_drain_qp(), whose completion
+ * (smbdirect_accept_negotiate_recv_done) returns the recv_io to the
+ * free list via smbdirect_connection_put_recv_io(). It therefore MUST
+ * run BEFORE smbdirect_connection_destroy_mem_pools(): otherwise the
+ * posted recv_io is still outstanding when kmem_cache_destroy() runs
+ * ("Slab cache still has objects") and is later freed into an
+ * already-destroyed mempool (mempool_free_bulk NULL-ptr-deref).
+ */
+ smbdirect_connection_destroy_qp(sc);
+ smbdirect_connection_destroy_mem_pools(sc);
+ return ret;
+post_recv_io_failed:
+ /* post failed: recv_io was not accepted by the QP, still in hand */
+ if (recv_io)
+ smbdirect_connection_put_recv_io(recv_io);
+get_recv_io_failed:
+ smbdirect_connection_destroy_mem_pools(sc);
+create_mem_failed:
+ smbdirect_connection_destroy_qp(sc);
+create_qp_failed:
+init_params_failed:
+ return ret;
+}
+
+static int smbdirect_accept_init_params(struct smbdirect_socket *sc)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ int max_send_sges;
+ unsigned int maxpages;
+
+ /* need 3 more sge. because a SMB_DIRECT header, SMB2 header,
+ * SMB2 response could be mapped.
+ */
+ max_send_sges = DIV_ROUND_UP(sp->max_send_size, PAGE_SIZE) + 3;
+ if (max_send_sges > SMBDIRECT_SEND_IO_MAX_SGE) {
+ pr_err("max_send_size %d is too large\n", sp->max_send_size);
+ return -EINVAL;
+ }
+
+ /*
+ * There is only a single batch credit
+ */
+ atomic_set(&sc->send_io.bcredits.count, 1);
+
+ /*
+ * Initialize the local credits to post
+ * IB_WR_SEND[_WITH_INV].
+ */
+ atomic_set(&sc->send_io.lcredits.count, sp->send_credit_target);
+
+ if (sp->max_read_write_size) {
+ maxpages = DIV_ROUND_UP(sp->max_read_write_size, PAGE_SIZE);
+ sc->rw_io.credits.max = rdma_rw_mr_factor(sc->ib.dev,
+ sc->rdma.cm_id->port_num,
+ maxpages);
+ sc->rw_io.credits.num_pages = DIV_ROUND_UP(maxpages, sc->rw_io.credits.max);
+ /* add one extra in order to handle unaligned pages */
+ sc->rw_io.credits.max += 1;
+ }
+
+ sc->recv_io.credits.target = 1;
+
+ atomic_set(&sc->rw_io.credits.count, sc->rw_io.credits.max);
+
+ return 0;
+}
+
+static void smbdirect_accept_negotiate_recv_work(struct work_struct *work);
+
+static void smbdirect_accept_negotiate_recv_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ struct smbdirect_recv_io *recv_io =
+ container_of(wc->wr_cqe, struct smbdirect_recv_io, cqe);
+ struct smbdirect_socket *sc = recv_io->socket;
+ unsigned long flags;
+
+ if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) {
+ if (wc->status != IB_WC_WR_FLUSH_ERR)
+ smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR,
+ "wc->status=%s (%d) wc->opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+ goto error;
+ }
+
+ smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_INFO,
+ "smbdirect_recv_io completed. status='%s (%d)', opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+
+ /*
+ * This is an internal error!
+ */
+ if (WARN_ON_ONCE(sc->recv_io.expected != SMBDIRECT_EXPECT_NEGOTIATE_REQ))
+ goto error;
+
+ /*
+ * Don't reset timer to the keepalive interval in
+ * this will be done in smbdirect_accept_direct_negotiate_recv_work.
+ */
+
+ ib_dma_sync_single_for_cpu(sc->ib.dev,
+ recv_io->sge.addr,
+ recv_io->sge.length,
+ DMA_FROM_DEVICE);
+
+ /*
+ * Only remember recv_io if it has enough bytes,
+ * this gives smbdirect_accept_negotiate_recv_work enough
+ * information in order to disconnect if it was not
+ * valid.
+ */
+ sc->recv_io.reassembly.full_packet_received = true;
+ if (wc->byte_len >= sizeof(struct smbdirect_negotiate_req))
+ smbdirect_connection_reassembly_append_recv_io(sc, recv_io, 0);
+ else
+ smbdirect_connection_put_recv_io(recv_io);
+
+ /*
+ * Some drivers (at least mlx5_ib and irdma) might post a
+ * recv completion before RDMA_CM_EVENT_ESTABLISHED,
+ * we need to adjust our expectation in that case.
+ *
+ * So we defer further processing of the negotiation
+ * to smbdirect_accept_negotiate_recv_work().
+ *
+ * If we are already in SMBDIRECT_SOCKET_NEGOTIATE_NEEDED
+ * we queue the work directly otherwise
+ * smbdirect_accept_rdma_event_handler() will do it, when
+ * RDMA_CM_EVENT_ESTABLISHED arrived.
+ */
+ spin_lock_irqsave(&sc->connect.lock, flags);
+ if (!sc->first_error) {
+ INIT_WORK(&sc->connect.work, smbdirect_accept_negotiate_recv_work);
+ if (sc->status == SMBDIRECT_SOCKET_NEGOTIATE_NEEDED)
+ queue_work(sc->workqueues.accept, &sc->connect.work);
+ }
+ spin_unlock_irqrestore(&sc->connect.lock, flags);
+
+ return;
+
+error:
+ /*
+ * recv_io.posted.refill_work is still disabled,
+ * so smbdirect_connection_put_recv_io() won't
+ * start it.
+ */
+ smbdirect_connection_put_recv_io(recv_io);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+}
+
+static void smbdirect_accept_negotiate_recv_work(struct work_struct *work)
+{
+ struct smbdirect_socket *sc =
+ container_of(work, struct smbdirect_socket, connect.work);
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_recv_io *recv_io;
+ struct smbdirect_negotiate_req *nreq;
+ unsigned long flags;
+ u16 min_version;
+ u16 max_version;
+ u16 credits_requested;
+ u32 preferred_send_size;
+ u32 max_receive_size;
+ u32 max_fragmented_size;
+ u32 ntstatus;
+
+ if (sc->first_error)
+ return;
+
+ /*
+ * make sure we won't start again...
+ */
+ disable_work(work);
+
+ /*
+ * Reset timer to the keepalive interval in
+ * order to trigger our next keepalive message.
+ */
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
+ mod_delayed_work(sc->workqueues.idle, &sc->idle.timer_work,
+ msecs_to_jiffies(sp->keepalive_interval_msec));
+
+ /*
+ * If smbdirect_accept_negotiate_recv_done() detected an
+ * invalid request we want to disconnect.
+ */
+ recv_io = smbdirect_connection_reassembly_first_recv_io(sc);
+ if (!recv_io) {
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+ spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
+ sc->recv_io.reassembly.queue_length--;
+ list_del(&recv_io->list);
+ spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
+ smbdirect_connection_put_recv_io(recv_io);
+
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_NEGOTIATE_NEEDED))
+ return;
+ sc->status = SMBDIRECT_SOCKET_NEGOTIATE_RUNNING;
+
+ /*
+ * Note recv_io is already part of the free list,
+ * as we just called smbdirect_connection_put_recv_io(),
+ * but it won't be reused before we call
+ * smbdirect_connection_recv_io_refill() below.
+ */
+
+ nreq = (struct smbdirect_negotiate_req *)recv_io->packet;
+ min_version = le16_to_cpu(nreq->min_version);
+ max_version = le16_to_cpu(nreq->max_version);
+ credits_requested = le16_to_cpu(nreq->credits_requested);
+ preferred_send_size = le32_to_cpu(nreq->preferred_send_size);
+ max_receive_size = le32_to_cpu(nreq->max_receive_size);
+ max_fragmented_size = le32_to_cpu(nreq->max_fragmented_size);
+
+ smbdirect_log_negotiate(sc, SMBDIRECT_LOG_INFO,
+ "ReqIn: %s%x, %s%x, %s%u, %s%u, %s%u, %s%u\n",
+ "MinVersion=0x",
+ le16_to_cpu(nreq->min_version),
+ "MaxVersion=0x",
+ le16_to_cpu(nreq->max_version),
+ "CreditsRequested=",
+ le16_to_cpu(nreq->credits_requested),
+ "PreferredSendSize=",
+ le32_to_cpu(nreq->preferred_send_size),
+ "MaxRecvSize=",
+ le32_to_cpu(nreq->max_receive_size),
+ "MaxFragmentedSize=",
+ le32_to_cpu(nreq->max_fragmented_size));
+
+ if (!(min_version <= SMBDIRECT_V1 && max_version >= SMBDIRECT_V1)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: min_version=0x%x max_version=0x%x\n",
+ min_version, max_version);
+ ntstatus = le32_to_cpu(STATUS_NOT_SUPPORTED);
+ goto not_supported;
+ }
+
+ if (credits_requested == 0) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: credits_requested == 0\n");
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ if (max_receive_size < SMBDIRECT_MIN_RECEIVE_SIZE) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: max_receive_size=%u < %u\n",
+ max_receive_size,
+ SMBDIRECT_MIN_RECEIVE_SIZE);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ if (max_fragmented_size < SMBDIRECT_MIN_FRAGMENTED_SIZE) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: max_fragmented_size=%u < %u\n",
+ max_fragmented_size,
+ SMBDIRECT_MIN_FRAGMENTED_SIZE);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ /*
+ * At least the value of SMBDIRECT_MIN_RECEIVE_SIZE is used.
+ */
+ sp->max_recv_size = min_t(u32, sp->max_recv_size, preferred_send_size);
+ sp->max_recv_size = max_t(u32, sp->max_recv_size, SMBDIRECT_MIN_RECEIVE_SIZE);
+
+ /*
+ * The maximum fragmented upper-layer payload receive size supported
+ *
+ * Assume max_payload_per_credit is
+ * smb_direct_receive_credit_max - 24 = 1340
+ *
+ * The maximum number would be
+ * smb_direct_receive_credit_max * max_payload_per_credit
+ *
+ * 1340 * 255 = 341700 (0x536C4)
+ *
+ * The minimum value from the spec is 131072 (0x20000)
+ *
+ * For now we use the logic we used in ksmbd before:
+ * (1364 * 255) / 2 = 173910 (0x2A756)
+ *
+ * We need to adjust this here in case the peer
+ * lowered sp->max_recv_size.
+ *
+ * TODO: instead of adjusting max_fragmented_recv_size
+ * we should adjust the number of available buffers,
+ * but for now we keep the logic as it was used
+ * in ksmbd before.
+ */
+ sp->max_fragmented_recv_size = (sp->recv_credit_max * sp->max_recv_size) / 2;
+
+ /*
+ * We take the value from the peer, which is checked to be higher than 0,
+ * but we limit it to the max value we support in order to have
+ * the main logic simpler.
+ */
+ sc->recv_io.credits.target = credits_requested;
+ sc->recv_io.credits.target = min_t(u16, sc->recv_io.credits.target,
+ sp->recv_credit_max);
+
+ /*
+ * Note nreq->max_receive_size was already checked against
+ * SMBDIRECT_MIN_RECEIVE_SIZE above.
+ */
+ sp->max_send_size = min_t(u32, sp->max_send_size, max_receive_size);
+
+ /*
+ * Note nreq->max_fragmented_size was already checked against
+ * SMBDIRECT_MIN_FRAGMENTED_SIZE above.
+ */
+ sp->max_fragmented_send_size = max_fragmented_size;
+
+ if (sc->accept.listener) {
+ struct smbdirect_socket *lsc = sc->accept.listener;
+ unsigned long flags;
+
+ spin_lock_irqsave(&lsc->listen.lock, flags);
+ list_del(&sc->accept.list);
+ list_add_tail(&sc->accept.list, &lsc->listen.ready);
+ wake_up(&lsc->listen.wait_queue);
+ spin_unlock_irqrestore(&lsc->listen.lock, flags);
+
+ /*
+ * smbdirect_socket_accept() will call
+ * smbdirect_accept_negotiate_finish(nsc, 0);
+ *
+ * So that we don't send the negotiation
+ * response that grants credits to the peer
+ * before the socket is accepted by the
+ * application.
+ */
+ return;
+ }
+
+ ntstatus = le32_to_cpu(STATUS_SUCCESS);
+
+not_supported:
+ smbdirect_accept_negotiate_finish(sc, ntstatus);
+}
+
+void smbdirect_accept_negotiate_finish(struct smbdirect_socket *sc, u32 ntstatus)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_recv_io *recv_io;
+ struct smbdirect_send_io *send_io;
+ struct smbdirect_negotiate_resp *nrep;
+ int posted;
+ u16 new_credits;
+ int ret;
+
+ if (ntstatus)
+ goto not_supported;
+
+ /*
+ * Prepare for receiving data_transfer messages
+ */
+ sc->recv_io.reassembly.full_packet_received = true;
+ sc->recv_io.expected = SMBDIRECT_EXPECT_DATA_TRANSFER;
+ list_for_each_entry(recv_io, &sc->recv_io.free.list, list)
+ recv_io->cqe.done = smbdirect_connection_recv_io_done;
+ recv_io = NULL;
+
+ /*
+ * We should at least post 1 smbdirect_recv_io!
+ */
+ posted = smbdirect_connection_recv_io_refill(sc);
+ if (posted < 1) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_recv_io_refill() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(posted));
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ /*
+ * The response will grant credits for all posted
+ * smbdirect_recv_io messages.
+ */
+ new_credits = smbdirect_connection_grant_recv_credits(sc);
+
+not_supported:
+ send_io = smbdirect_connection_alloc_send_io(sc);
+ if (IS_ERR(send_io)) {
+ ret = PTR_ERR(send_io);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_alloc_send_io() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ return;
+ }
+ send_io->cqe.done = smbdirect_accept_negotiate_send_done;
+
+ nrep = (struct smbdirect_negotiate_resp *)send_io->packet;
+ nrep->min_version = cpu_to_le16(SMBDIRECT_V1);
+ nrep->max_version = cpu_to_le16(SMBDIRECT_V1);
+ if (ntstatus == 0) {
+ nrep->negotiated_version = cpu_to_le16(SMBDIRECT_V1);
+ nrep->reserved = 0;
+ nrep->credits_requested = cpu_to_le16(sp->send_credit_target);
+ nrep->credits_granted = cpu_to_le16(new_credits);
+ nrep->status = cpu_to_le32(ntstatus);
+ nrep->max_readwrite_size = cpu_to_le32(sp->max_read_write_size);
+ nrep->preferred_send_size = cpu_to_le32(sp->max_send_size);
+ nrep->max_receive_size = cpu_to_le32(sp->max_recv_size);
+ nrep->max_fragmented_size = cpu_to_le32(sp->max_fragmented_recv_size);
+ } else {
+ nrep->negotiated_version = 0;
+ nrep->reserved = 0;
+ nrep->credits_requested = 0;
+ nrep->credits_granted = 0;
+ nrep->status = cpu_to_le32(ntstatus);
+ nrep->max_readwrite_size = 0;
+ nrep->preferred_send_size = 0;
+ nrep->max_receive_size = 0;
+ nrep->max_fragmented_size = 0;
+ }
+
+ smbdirect_log_negotiate(sc, SMBDIRECT_LOG_INFO,
+ "RepOut: %s%x, %s%x, %s%x, %s%u, %s%u, %s%x, %s%u, %s%u, %s%u, %s%u\n",
+ "MinVersion=0x",
+ le16_to_cpu(nrep->min_version),
+ "MaxVersion=0x",
+ le16_to_cpu(nrep->max_version),
+ "NegotiatedVersion=0x",
+ le16_to_cpu(nrep->negotiated_version),
+ "CreditsRequested=",
+ le16_to_cpu(nrep->credits_requested),
+ "CreditsGranted=",
+ le16_to_cpu(nrep->credits_granted),
+ "Status=0x",
+ le32_to_cpu(nrep->status),
+ "MaxReadWriteSize=",
+ le32_to_cpu(nrep->max_readwrite_size),
+ "PreferredSendSize=",
+ le32_to_cpu(nrep->preferred_send_size),
+ "MaxRecvSize=",
+ le32_to_cpu(nrep->max_receive_size),
+ "MaxFragmentedSize=",
+ le32_to_cpu(nrep->max_fragmented_size));
+
+ send_io->sge[0].addr = ib_dma_map_single(sc->ib.dev,
+ nrep,
+ sizeof(*nrep),
+ DMA_TO_DEVICE);
+ ret = ib_dma_mapping_error(sc->ib.dev, send_io->sge[0].addr);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "ib_dma_mapping_error() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ smbdirect_connection_free_send_io(send_io);
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ return;
+ }
+
+ send_io->sge[0].length = sizeof(*nrep);
+ send_io->sge[0].lkey = sc->ib.pd->local_dma_lkey;
+ send_io->num_sge = 1;
+
+ ib_dma_sync_single_for_device(sc->ib.dev,
+ send_io->sge[0].addr,
+ send_io->sge[0].length,
+ DMA_TO_DEVICE);
+
+ send_io->wr.next = NULL;
+ send_io->wr.wr_cqe = &send_io->cqe;
+ send_io->wr.sg_list = send_io->sge;
+ send_io->wr.num_sge = send_io->num_sge;
+ send_io->wr.opcode = IB_WR_SEND;
+ send_io->wr.send_flags = IB_SEND_SIGNALED;
+
+ ret = smbdirect_connection_post_send_wr(sc, &send_io->wr);
+ if (ret) {
+ /* if we reach here, post send failed */
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_post_send_wr() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ /*
+ * Note smbdirect_connection_free_send_io()
+ * does ib_dma_unmap_page()
+ */
+ smbdirect_connection_free_send_io(send_io);
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ return;
+ }
+
+ /*
+ * smbdirect_accept_negotiate_send_done
+ * will do all remaining work...
+ */
+}
+
+static void smbdirect_accept_negotiate_send_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ struct smbdirect_send_io *send_io =
+ container_of(wc->wr_cqe, struct smbdirect_send_io, cqe);
+ struct smbdirect_socket *sc = send_io->socket;
+ struct smbdirect_negotiate_resp *nrep;
+ u32 ntstatus;
+
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_INFO,
+ "smbdirect_send_io completed. status='%s (%d)', opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+
+ nrep = (struct smbdirect_negotiate_resp *)send_io->packet;
+ ntstatus = le32_to_cpu(nrep->status);
+
+ /* Note this frees wc->wr_cqe, but not wc */
+ smbdirect_connection_free_send_io(send_io);
+ atomic_dec(&sc->send_io.pending.count);
+
+ if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_SEND))) {
+ if (wc->status != IB_WC_WR_FLUSH_ERR)
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_ERR,
+ "wc->status=%s (%d) wc->opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ /*
+ * If we send a smbdirect_negotiate_resp without NT_STATUS_OK (0)
+ * we need to disconnect now.
+ *
+ * Otherwise smbdirect_connection_negotiation_done()
+ * will setup all required things and wake up
+ * the waiter.
+ */
+ if (ntstatus)
+ smbdirect_socket_schedule_cleanup(sc, -EOPNOTSUPP);
+ else
+ smbdirect_connection_negotiation_done(sc);
+}
+
+static int smbdirect_accept_rdma_event_handler(struct rdma_cm_id *id,
+ struct rdma_cm_event *event)
+{
+ struct smbdirect_socket *sc = id->context;
+ unsigned long flags;
+
+ /*
+ * cma_cm_event_handler() has
+ * lockdep_assert_held(&id_priv->handler_mutex);
+ *
+ * Mutexes are not allowed in interrupts,
+ * and we rely on not being in an interrupt here,
+ * as we might sleep.
+ *
+ * We didn't timeout so we cancel our idle timer,
+ * it will be scheduled again if needed.
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ if (event->status || event->event != sc->rdma.expected_event) {
+ int ret = -ECONNABORTED;
+
+ if (event->event == RDMA_CM_EVENT_REJECTED)
+ ret = -ECONNREFUSED;
+ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+ ret = -ENETDOWN;
+ if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
+ ret = event->status;
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "%s (first_error=%1pe, expected=%s) => event=%s status=%d => ret=%1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ rdma_event_msg(sc->rdma.expected_event),
+ rdma_event_msg(event->event),
+ event->status,
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ return 0;
+ }
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "%s (first_error=%1pe) event=%s\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ rdma_event_msg(event->event));
+
+ if (sc->first_error)
+ return 0;
+
+ switch (event->event) {
+ case RDMA_CM_EVENT_ESTABLISHED:
+ smbdirect_connection_rdma_established(sc);
+
+ /*
+ * Some drivers (at least mlx5_ib and irdma) might post a
+ * recv completion before RDMA_CM_EVENT_ESTABLISHED,
+ * we need to adjust our expectation in that case.
+ *
+ * If smbdirect_accept_negotiate_recv_done was called first
+ * it initialized sc->connect.work only for us to
+ * start, so that we turned into
+ * SMBDIRECT_SOCKET_NEGOTIATE_NEEDED, before
+ * smbdirect_accept_negotiate_recv_work() runs.
+ *
+ * If smbdirect_accept_negotiate_recv_done didn't happen
+ * yet. sc->connect.work is still be disabled and
+ * queue_work() is a no-op.
+ */
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING))
+ return 0;
+ sc->status = SMBDIRECT_SOCKET_NEGOTIATE_NEEDED;
+ spin_lock_irqsave(&sc->connect.lock, flags);
+ if (!sc->first_error)
+ queue_work(sc->workqueues.accept, &sc->connect.work);
+ spin_unlock_irqrestore(&sc->connect.lock, flags);
+
+ /*
+ * wait for smbdirect_accept_negotiate_recv_done()
+ * to get the negotiate request.
+ */
+ return 0;
+
+ default:
+ break;
+ }
+
+ /*
+ * This is an internal error
+ */
+ WARN_ON_ONCE(sc->rdma.expected_event != RDMA_CM_EVENT_ESTABLISHED);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return 0;
+}
+
+static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long timeo)
+{
+ long ret;
+
+ ret = wait_event_interruptible_timeout(lsc->listen.wait_queue,
+ !list_empty_careful(&lsc->listen.ready) ||
+ lsc->status != SMBDIRECT_SOCKET_LISTENING ||
+ lsc->first_error,
+ timeo);
+ if (lsc->status != SMBDIRECT_SOCKET_LISTENING)
+ return -EINVAL;
+ if (lsc->first_error)
+ return lsc->first_error;
+ if (!ret)
+ ret = -ETIMEDOUT;
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
+ long timeo,
+ struct proto_accept_arg *arg)
+{
+ struct smbdirect_socket *nsc;
+ unsigned long flags;
+
+ if (lsc->status != SMBDIRECT_SOCKET_LISTENING) {
+ arg->err = -EINVAL;
+ return NULL;
+ }
+
+ if (lsc->first_error) {
+ arg->err = lsc->first_error;
+ return NULL;
+ }
+
+ if (list_empty_careful(&lsc->listen.ready)) {
+ int ret;
+
+ if (timeo == 0) {
+ arg->err = -EAGAIN;
+ return NULL;
+ }
+
+ ret = smbdirect_socket_wait_for_accept(lsc, timeo);
+ if (ret) {
+ arg->err = ret;
+ return NULL;
+ }
+ }
+
+ spin_lock_irqsave(&lsc->listen.lock, flags);
+ nsc = list_first_entry_or_null(&lsc->listen.ready,
+ struct smbdirect_socket,
+ accept.list);
+ if (nsc) {
+ nsc->accept.listener = NULL;
+ list_del_init_careful(&nsc->accept.list);
+ arg->is_empty = list_empty_careful(&lsc->listen.ready);
+ }
+ spin_unlock_irqrestore(&lsc->listen.lock, flags);
+ if (!nsc) {
+ arg->err = -EAGAIN;
+ return NULL;
+ }
+
+ /*
+ * We did not send the negotiation response
+ * yet, so we did not grant any credits to the client,
+ * so it didn't grant any credits to us.
+ *
+ * The caller expects a connected socket
+ * now as there are no credits anyway.
+ *
+ * Then we send the negotiation response in
+ * order to grant credits to the peer.
+ */
+ nsc->status = SMBDIRECT_SOCKET_CONNECTED;
+ smbdirect_accept_negotiate_finish(nsc, 0);
+
+ return nsc;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_accept);
diff --git a/fs/smb/smbdirect/connect.c b/fs/smb/smbdirect/connect.c
new file mode 100644
index 000000000000..34a3e72c38fb
--- /dev/null
+++ b/fs/smb/smbdirect/connect.c
@@ -0,0 +1,926 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2012,2016,2017,2025 Stefan Metzmacher
+ */
+
+#include "internal.h"
+#include "../common/smb2status.h"
+
+static int smbdirect_connect_setup_connection(struct smbdirect_socket *sc);
+static int smbdirect_connect_resolve_addr(struct smbdirect_socket *sc,
+ const struct sockaddr *src,
+ const struct sockaddr *dst);
+static int smbdirect_connect_rdma_event_handler(struct rdma_cm_id *id,
+ struct rdma_cm_event *event);
+static int smbdirect_connect_negotiate_start(struct smbdirect_socket *sc);
+static void smbdirect_connect_negotiate_send_done(struct ib_cq *cq, struct ib_wc *wc);
+static void smbdirect_connect_negotiate_recv_done(struct ib_cq *cq, struct ib_wc *wc);
+
+int smbdirect_connect(struct smbdirect_socket *sc, const struct sockaddr *dst)
+{
+ const struct sockaddr *src = NULL;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_storage ss;
+ } src_addr = {
+ .sa = {
+ .sa_family = AF_UNSPEC,
+ },
+ };
+ int ret;
+
+ if (sc->first_error)
+ return -ENOTCONN;
+
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EALREADY;
+
+ if (WARN_ON_ONCE(!sc->rdma.cm_id))
+ return -EINVAL;
+
+ src_addr.ss = sc->rdma.cm_id->route.addr.src_addr;
+ if (src_addr.sa.sa_family != AF_UNSPEC)
+ src = &src_addr.sa;
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "connect: src: %pISpsfc dst: %pISpsfc\n",
+ src, dst);
+
+ ret = smbdirect_connect_setup_connection(sc);
+ if (ret)
+ return ret;
+
+ ret = smbdirect_connect_resolve_addr(sc, src, dst);
+ if (ret)
+ return ret;
+
+ /*
+ * The rest happens async via smbdirect_connect_rdma_event_handler()
+ * the caller will decide to wait or not.
+ */
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connect);
+
+static int smbdirect_connect_setup_connection(struct smbdirect_socket *sc)
+{
+ rdma_lock_handler(sc->rdma.cm_id);
+ sc->rdma.cm_id->event_handler = smbdirect_connect_rdma_event_handler;
+ rdma_unlock_handler(sc->rdma.cm_id);
+
+ if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_CREATED))
+ return -EINVAL;
+ sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED;
+
+ return 0;
+}
+
+static int smbdirect_connect_resolve_addr(struct smbdirect_socket *sc,
+ const struct sockaddr *src,
+ const struct sockaddr *dst)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct sockaddr *src_addr = NULL;
+ struct sockaddr *dst_addr = NULL;
+ int ret;
+
+ src_addr = (struct sockaddr *)src;
+ if (src_addr && src_addr->sa_family == AF_UNSPEC)
+ src_addr = NULL;
+ dst_addr = (struct sockaddr *)dst;
+
+ if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED))
+ return -EINVAL;
+ sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING;
+ sc->rdma.expected_event = RDMA_CM_EVENT_ADDR_RESOLVED;
+ ret = rdma_resolve_addr(sc->rdma.cm_id, src_addr, dst_addr,
+ sp->resolve_addr_timeout_msec);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "rdma_resolve_addr() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int smbdirect_connect_resolve_route(struct smbdirect_socket *sc)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ int ret;
+
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED))
+ return sc->first_error;
+ sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING;
+ sc->rdma.expected_event = RDMA_CM_EVENT_ROUTE_RESOLVED;
+ ret = rdma_resolve_route(sc->rdma.cm_id, sp->resolve_route_timeout_msec);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "rdma_resolve_route() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int smbdirect_connect_rdma_connect(struct smbdirect_socket *sc)
+{
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct rdma_conn_param conn_param;
+ __be32 ird_ord_hdr[2];
+ int ret;
+
+ sc->ib.dev = sc->rdma.cm_id->device;
+
+ if (!smbdirect_frwr_is_supported(&sc->ib.dev->attrs)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "Fast Registration Work Requests (FRWR) is not supported device %.*s\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "Device capability flags = %llx max_fast_reg_page_list_len = %u\n",
+ sc->ib.dev->attrs.device_cap_flags,
+ sc->ib.dev->attrs.max_fast_reg_page_list_len);
+ return -EPROTONOSUPPORT;
+ }
+
+ if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB &&
+ !rdma_ib_or_roce(sc->ib.dev, sc->rdma.cm_id->port_num)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "Not IB: device: %.*s IW:%u local: %pISpsfc remote: %pISpsfc\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name,
+ rdma_protocol_iwarp(sc->ib.dev, sc->rdma.cm_id->port_num),
+ &sc->rdma.cm_id->route.addr.src_addr,
+ &sc->rdma.cm_id->route.addr.dst_addr);
+ return -EPROTONOSUPPORT;
+ }
+ if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW &&
+ !rdma_protocol_iwarp(sc->ib.dev, sc->rdma.cm_id->port_num)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "Not IW: device: %.*s IB:%u local: %pISpsfc remote: %pISpsfc\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name,
+ rdma_ib_or_roce(sc->ib.dev, sc->rdma.cm_id->port_num),
+ &sc->rdma.cm_id->route.addr.src_addr,
+ &sc->rdma.cm_id->route.addr.dst_addr);
+ return -EPROTONOSUPPORT;
+ }
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "rdma connect: device: %.*s local: %pISpsfc remote: %pISpsfc\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name,
+ &sc->rdma.cm_id->route.addr.src_addr,
+ &sc->rdma.cm_id->route.addr.dst_addr);
+
+ sp->max_frmr_depth = min_t(u32, sp->max_frmr_depth,
+ sc->ib.dev->attrs.max_fast_reg_page_list_len);
+ sc->mr_io.type = IB_MR_TYPE_MEM_REG;
+ if (sc->ib.dev->attrs.kernel_cap_flags & IBK_SG_GAPS_REG)
+ sc->mr_io.type = IB_MR_TYPE_SG_GAPS;
+
+ sp->responder_resources = min3(sp->responder_resources,
+ sc->ib.dev->attrs.max_qp_rd_atom,
+ U8_MAX);
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_INFO,
+ "responder_resources=%d\n",
+ sp->responder_resources);
+
+ ret = smbdirect_connection_create_qp(sc);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_create_qp() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ memset(&conn_param, 0, sizeof(conn_param));
+ conn_param.initiator_depth = sp->initiator_depth;
+ conn_param.responder_resources = sp->responder_resources;
+
+ /* Need to send IRD/ORD in private data for iWARP */
+ if (rdma_protocol_iwarp(sc->ib.dev, sc->rdma.cm_id->port_num)) {
+ ird_ord_hdr[0] = cpu_to_be32(conn_param.responder_resources);
+ ird_ord_hdr[1] = cpu_to_be32(conn_param.initiator_depth);
+ conn_param.private_data = ird_ord_hdr;
+ conn_param.private_data_len = sizeof(ird_ord_hdr);
+ } else {
+ conn_param.private_data = NULL;
+ conn_param.private_data_len = 0;
+ }
+
+ conn_param.retry_count = SMBDIRECT_RDMA_CM_RETRY;
+ conn_param.rnr_retry_count = SMBDIRECT_RDMA_CM_RNR_RETRY;
+ conn_param.flow_control = 0;
+
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED))
+ return sc->first_error;
+ sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING;
+ sc->rdma.expected_event = RDMA_CM_EVENT_ESTABLISHED;
+ ret = rdma_connect_locked(sc->rdma.cm_id, &conn_param);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "rdma_connect_locked() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ /*
+ * start with the rdma connect timeout and SMBDIRECT_KEEPALIVE_PENDING
+ * so that the timer will cause a disconnect.
+ */
+ INIT_DELAYED_WORK(&sc->idle.timer_work, smbdirect_connection_idle_timer_work);
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_PENDING;
+ mod_delayed_work(sc->workqueues.idle, &sc->idle.timer_work,
+ msecs_to_jiffies(sp->rdma_connect_timeout_msec));
+
+ return 0;
+}
+
+static int smbdirect_connect_rdma_event_handler(struct rdma_cm_id *id,
+ struct rdma_cm_event *event)
+{
+ struct smbdirect_socket *sc = id->context;
+ u8 peer_initiator_depth;
+ u8 peer_responder_resources;
+ int ret;
+
+ /*
+ * cma_cm_event_handler() has
+ * lockdep_assert_held(&id_priv->handler_mutex);
+ *
+ * Mutexes are not allowed in interrupts,
+ * and we rely on not being in an interrupt here,
+ * as we might sleep.
+ *
+ * We didn't timeout so we cancel our idle timer,
+ * it will be scheduled again if needed.
+ */
+ WARN_ON_ONCE(in_interrupt());
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
+ cancel_delayed_work_sync(&sc->idle.timer_work);
+
+ if (event->status || event->event != sc->rdma.expected_event) {
+ int lvl = SMBDIRECT_LOG_ERR;
+
+ ret = -ECONNABORTED;
+
+ if (event->event == RDMA_CM_EVENT_REJECTED)
+ ret = -ECONNREFUSED;
+ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+ ret = -ENETDOWN;
+ if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
+ ret = event->status;
+
+ if (ret == -ENODEV)
+ lvl = SMBDIRECT_LOG_INFO;
+
+ smbdirect_log_rdma_event(sc, lvl,
+ "%s (first_error=%1pe, expected=%s) => event=%s status=%d => ret=%1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ rdma_event_msg(sc->rdma.expected_event),
+ rdma_event_msg(event->event),
+ event->status,
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+
+ smbdirect_socket_schedule_cleanup_lvl(sc,
+ lvl,
+ ret);
+ return 0;
+ }
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "%s (first_error=%1pe) event=%s\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ rdma_event_msg(event->event));
+
+ if (sc->first_error)
+ return 0;
+
+ switch (event->event) {
+ case RDMA_CM_EVENT_ADDR_RESOLVED:
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING))
+ return 0;
+ sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED;
+
+ ret = smbdirect_connect_resolve_route(sc);
+ if (ret)
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ return 0;
+
+ case RDMA_CM_EVENT_ROUTE_RESOLVED:
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING))
+ return 0;
+ sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED;
+
+ ret = smbdirect_connect_rdma_connect(sc);
+ if (ret)
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ return 0;
+
+ case RDMA_CM_EVENT_ESTABLISHED:
+ smbdirect_connection_rdma_established(sc);
+
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING))
+ return 0;
+ sc->status = SMBDIRECT_SOCKET_NEGOTIATE_NEEDED;
+
+ /*
+ * Here we work around an inconsistency between
+ * iWarp and other devices (at least rxe and irdma using RoCEv2)
+ */
+ if (rdma_protocol_iwarp(id->device, id->port_num)) {
+ /*
+ * iWarp devices report the peer's values
+ * with the perspective of the peer here.
+ * Tested with siw and irdma (in iwarp mode)
+ * We need to change to our perspective here,
+ * so we need to switch the values.
+ */
+ peer_initiator_depth = event->param.conn.responder_resources;
+ peer_responder_resources = event->param.conn.initiator_depth;
+ } else {
+ /*
+ * Non iWarp devices report the peer's values
+ * already changed to our perspective here.
+ * Tested with rxe and irdma (in roce mode).
+ */
+ peer_initiator_depth = event->param.conn.initiator_depth;
+ peer_responder_resources = event->param.conn.responder_resources;
+ }
+ smbdirect_connection_negotiate_rdma_resources(sc,
+ peer_initiator_depth,
+ peer_responder_resources,
+ &event->param.conn);
+
+ ret = smbdirect_connect_negotiate_start(sc);
+ if (ret)
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ return 0;
+
+ default:
+ break;
+ }
+
+ /*
+ * This is an internal error
+ */
+ WARN_ON_ONCE(sc->rdma.expected_event != RDMA_CM_EVENT_ESTABLISHED);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return 0;
+}
+
+static int smbdirect_connect_negotiate_start(struct smbdirect_socket *sc)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_recv_io *recv_io = NULL;
+ struct smbdirect_send_io *send_io = NULL;
+ struct smbdirect_negotiate_req *nreq = NULL;
+ int ret;
+
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_NEGOTIATE_NEEDED))
+ return sc->first_error;
+ sc->status = SMBDIRECT_SOCKET_NEGOTIATE_RUNNING;
+
+ ret = smbdirect_connection_create_mem_pools(sc);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_create_mem_pools() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto create_mem_pools_failed;
+ }
+
+ /*
+ * There is only a single batch credit
+ */
+ atomic_set(&sc->send_io.bcredits.count, 1);
+
+ /*
+ * Initialize the local credits to post
+ * IB_WR_SEND[_WITH_INV].
+ */
+ atomic_set(&sc->send_io.lcredits.count, sp->send_credit_target);
+
+ recv_io = smbdirect_connection_get_recv_io(sc);
+ if (WARN_ON_ONCE(!recv_io)) {
+ ret = -EINVAL;
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_get_recv_io() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto get_recv_io_failed;
+ }
+ recv_io->cqe.done = smbdirect_connect_negotiate_recv_done;
+
+ send_io = smbdirect_connection_alloc_send_io(sc);
+ if (IS_ERR(send_io)) {
+ ret = PTR_ERR(send_io);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_alloc_send_io() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto alloc_send_io_failed;
+ }
+ send_io->cqe.done = smbdirect_connect_negotiate_send_done;
+
+ nreq = (struct smbdirect_negotiate_req *)send_io->packet;
+ nreq->min_version = cpu_to_le16(SMBDIRECT_V1);
+ nreq->max_version = cpu_to_le16(SMBDIRECT_V1);
+ nreq->reserved = 0;
+ nreq->credits_requested = cpu_to_le16(sp->send_credit_target);
+ nreq->preferred_send_size = cpu_to_le32(sp->max_send_size);
+ nreq->max_receive_size = cpu_to_le32(sp->max_recv_size);
+ nreq->max_fragmented_size = cpu_to_le32(sp->max_fragmented_recv_size);
+
+ smbdirect_log_negotiate(sc, SMBDIRECT_LOG_INFO,
+ "ReqOut: %s%x, %s%x, %s%u, %s%u, %s%u, %s%u\n",
+ "MinVersion=0x",
+ le16_to_cpu(nreq->min_version),
+ "MaxVersion=0x",
+ le16_to_cpu(nreq->max_version),
+ "CreditsRequested=",
+ le16_to_cpu(nreq->credits_requested),
+ "PreferredSendSize=",
+ le32_to_cpu(nreq->preferred_send_size),
+ "MaxRecvSize=",
+ le32_to_cpu(nreq->max_receive_size),
+ "MaxFragmentedSize=",
+ le32_to_cpu(nreq->max_fragmented_size));
+
+ send_io->sge[0].addr = ib_dma_map_single(sc->ib.dev,
+ nreq,
+ sizeof(*nreq),
+ DMA_TO_DEVICE);
+ ret = ib_dma_mapping_error(sc->ib.dev, send_io->sge[0].addr);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "ib_dma_mapping_error() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto dma_mapping_failed;
+ }
+
+ send_io->sge[0].length = sizeof(*nreq);
+ send_io->sge[0].lkey = sc->ib.pd->local_dma_lkey;
+ send_io->num_sge = 1;
+
+ ib_dma_sync_single_for_device(sc->ib.dev,
+ send_io->sge[0].addr,
+ send_io->sge[0].length,
+ DMA_TO_DEVICE);
+
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_INFO,
+ "sge addr=0x%llx length=%u lkey=0x%x\n",
+ send_io->sge[0].addr,
+ send_io->sge[0].length,
+ send_io->sge[0].lkey);
+
+ /*
+ * Now post the recv_io buffer in order to get
+ * the negotiate response
+ */
+ sc->recv_io.expected = SMBDIRECT_EXPECT_NEGOTIATE_REP;
+ ret = smbdirect_connection_post_recv_io(recv_io);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_post_recv_io() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto post_recv_io_failed;
+ }
+
+ send_io->wr.next = NULL;
+ send_io->wr.wr_cqe = &send_io->cqe;
+ send_io->wr.sg_list = send_io->sge;
+ send_io->wr.num_sge = send_io->num_sge;
+ send_io->wr.opcode = IB_WR_SEND;
+ send_io->wr.send_flags = IB_SEND_SIGNALED;
+
+ ret = smbdirect_connection_post_send_wr(sc, &send_io->wr);
+ if (ret) {
+ /* if we reach here, post send failed */
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_post_send_wr() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto post_send_wr_failed;
+ }
+
+ /*
+ * start with the negotiate timeout and SMBDIRECT_KEEPALIVE_PENDING
+ * so that the timer will cause a disconnect.
+ */
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_PENDING;
+ mod_delayed_work(sc->workqueues.idle, &sc->idle.timer_work,
+ msecs_to_jiffies(sp->negotiate_timeout_msec));
+
+ return 0;
+
+post_send_wr_failed:
+ /*
+ * ib_dma_unmap_single is called in
+ * smbdirect_connection_free_send_io()
+ */
+ smbdirect_connection_free_send_io(send_io);
+ /*
+ * recv_io is given to the rdma layer,
+ * we should not put it even on error
+ * nor call smbdirect_connection_destroy_mem_pools()
+ * it will be cleaned up during disconnect.
+ */
+ return ret;
+
+post_recv_io_failed:
+ /*
+ * ib_dma_unmap_single is called in
+ * smbdirect_connection_free_send_io()
+ */
+dma_mapping_failed:
+ smbdirect_connection_free_send_io(send_io);
+
+alloc_send_io_failed:
+ smbdirect_connection_put_recv_io(recv_io);
+
+get_recv_io_failed:
+ smbdirect_connection_destroy_mem_pools(sc);
+
+create_mem_pools_failed:
+ return ret;
+}
+
+static void smbdirect_connect_negotiate_send_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ struct smbdirect_send_io *send_io =
+ container_of(wc->wr_cqe, struct smbdirect_send_io, cqe);
+ struct smbdirect_socket *sc = send_io->socket;
+
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_INFO,
+ "smbdirect_send_io completed. status='%s (%d)', opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+
+ /* Note this frees wc->wr_cqe, but not wc */
+ smbdirect_connection_free_send_io(send_io);
+ atomic_dec(&sc->send_io.pending.count);
+
+ if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_SEND))) {
+ if (wc->status != IB_WC_WR_FLUSH_ERR)
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_ERR,
+ "wc->status=%s (%d) wc->opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+}
+
+static void smbdirect_connect_negotiate_recv_work(struct work_struct *work);
+
+static void smbdirect_connect_negotiate_recv_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ struct smbdirect_recv_io *recv_io =
+ container_of(wc->wr_cqe, struct smbdirect_recv_io, cqe);
+ struct smbdirect_socket *sc = recv_io->socket;
+ unsigned long flags;
+
+ if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) {
+ if (wc->status != IB_WC_WR_FLUSH_ERR)
+ smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR,
+ "wc->status=%s (%d) wc->opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+ goto error;
+ }
+
+ smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_INFO,
+ "smbdirect_recv_io completed. status='%s (%d)', opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+
+ /*
+ * This is an internal error!
+ */
+ if (WARN_ON_ONCE(sc->recv_io.expected != SMBDIRECT_EXPECT_NEGOTIATE_REP))
+ goto error;
+
+ /*
+ * Don't reset timer to the keepalive interval in
+ * this will be done in smbdirect_accept_direct_negotiate_recv_work.
+ */
+
+ ib_dma_sync_single_for_cpu(sc->ib.dev,
+ recv_io->sge.addr,
+ recv_io->sge.length,
+ DMA_FROM_DEVICE);
+
+ /*
+ * Only remember recv_io if it has enough bytes,
+ * this gives smbdirect_accept_negotiate_recv_work enough
+ * information in order to disconnect if it was not
+ * valid.
+ */
+ sc->recv_io.reassembly.full_packet_received = true;
+ if (wc->byte_len >= sizeof(struct smbdirect_negotiate_resp))
+ smbdirect_connection_reassembly_append_recv_io(sc, recv_io, 0);
+ else
+ smbdirect_connection_put_recv_io(recv_io);
+
+ /*
+ * We continue via the workqueue as we may have
+ * complex work that might sleep.
+ *
+ * So we defer further processing of the negotiation
+ * to smbdirect_connect_negotiate_recv_work().
+ */
+ spin_lock_irqsave(&sc->connect.lock, flags);
+ if (!sc->first_error) {
+ INIT_WORK(&sc->connect.work, smbdirect_connect_negotiate_recv_work);
+ if (sc->status == SMBDIRECT_SOCKET_NEGOTIATE_RUNNING)
+ queue_work(sc->workqueues.connect, &sc->connect.work);
+ }
+ spin_unlock_irqrestore(&sc->connect.lock, flags);
+
+ return;
+
+error:
+ /*
+ * recv_io.posted.refill_work is still disabled,
+ * so smbdirect_connection_put_recv_io() won't
+ * start it.
+ */
+ smbdirect_connection_put_recv_io(recv_io);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+}
+
+static void smbdirect_connect_negotiate_recv_work(struct work_struct *work)
+{
+ struct smbdirect_socket *sc =
+ container_of(work, struct smbdirect_socket, connect.work);
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_recv_io *recv_io;
+ struct smbdirect_negotiate_resp *nrep;
+ unsigned long flags;
+ u16 negotiated_version;
+ u16 credits_requested;
+ u16 credits_granted;
+ u32 status;
+ u32 max_readwrite_size;
+ u32 preferred_send_size;
+ u32 max_receive_size;
+ u32 max_fragmented_size;
+ int posted;
+ int ret;
+
+ if (sc->first_error)
+ return;
+
+ /*
+ * make sure we won't start again...
+ */
+ disable_work(work);
+
+ /*
+ * Reset timer to the keepalive interval in
+ * order to trigger our next keepalive message.
+ */
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
+ mod_delayed_work(sc->workqueues.idle, &sc->idle.timer_work,
+ msecs_to_jiffies(sp->keepalive_interval_msec));
+
+ /*
+ * If smbdirect_connect_negotiate_recv_done() detected an
+ * invalid request we want to disconnect.
+ */
+ recv_io = smbdirect_connection_reassembly_first_recv_io(sc);
+ if (!recv_io) {
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+ spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
+ sc->recv_io.reassembly.queue_length--;
+ list_del(&recv_io->list);
+ spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
+ smbdirect_connection_put_recv_io(recv_io);
+
+ if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_NEGOTIATE_RUNNING))
+ return;
+
+ /*
+ * Note recv_io is already part of the free list,
+ * as we just called smbdirect_connection_put_recv_io(),
+ * but it won't be reused before we call
+ * smbdirect_connection_recv_io_refill() below.
+ */
+
+ nrep = (struct smbdirect_negotiate_resp *)recv_io->packet;
+ negotiated_version = le16_to_cpu(nrep->negotiated_version);
+ credits_requested = le16_to_cpu(nrep->credits_requested);
+ credits_granted = le16_to_cpu(nrep->credits_granted);
+ status = le32_to_cpu(nrep->status);
+ max_readwrite_size = le32_to_cpu(nrep->max_readwrite_size);
+ preferred_send_size = le32_to_cpu(nrep->preferred_send_size);
+ max_receive_size = le32_to_cpu(nrep->max_receive_size);
+ max_fragmented_size = le32_to_cpu(nrep->max_fragmented_size);
+
+ smbdirect_log_negotiate(sc, SMBDIRECT_LOG_INFO,
+ "RepIn: %s%x, %s%x, %s%x, %s%u, %s%u, %s%x, %s%u, %s%u, %s%u, %s%u\n",
+ "MinVersion=0x",
+ le16_to_cpu(nrep->min_version),
+ "MaxVersion=0x",
+ le16_to_cpu(nrep->max_version),
+ "NegotiatedVersion=0x",
+ le16_to_cpu(nrep->negotiated_version),
+ "CreditsRequested=",
+ le16_to_cpu(nrep->credits_requested),
+ "CreditsGranted=",
+ le16_to_cpu(nrep->credits_granted),
+ "Status=0x",
+ le32_to_cpu(nrep->status),
+ "MaxReadWriteSize=",
+ le32_to_cpu(nrep->max_readwrite_size),
+ "PreferredSendSize=",
+ le32_to_cpu(nrep->preferred_send_size),
+ "MaxRecvSize=",
+ le32_to_cpu(nrep->max_receive_size),
+ "MaxFragmentedSize=",
+ le32_to_cpu(nrep->max_fragmented_size));
+
+ if (negotiated_version != SMBDIRECT_V1) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: negotiated_version=0x%x\n",
+ negotiated_version);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNREFUSED);
+ return;
+ }
+
+ if (status != le32_to_cpu(STATUS_SUCCESS)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: status=0x%x != 0x0\n",
+ status);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNREFUSED);
+ return;
+ }
+
+ if (max_receive_size < SMBDIRECT_MIN_RECEIVE_SIZE) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: max_receive_size=%u < %u\n",
+ max_receive_size,
+ SMBDIRECT_MIN_RECEIVE_SIZE);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ if (max_fragmented_size < SMBDIRECT_MIN_FRAGMENTED_SIZE) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: max_fragmented_size=%u < %u\n",
+ max_fragmented_size,
+ SMBDIRECT_MIN_FRAGMENTED_SIZE);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ if (credits_granted == 0) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: credits_granted == 0\n");
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ if (credits_requested == 0) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: credits_requested == 0\n");
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ if (preferred_send_size > sp->max_recv_size) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: preferred_send_size=%u > max_recv_size=%u\n",
+ preferred_send_size,
+ sp->max_recv_size);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ /*
+ * We take the value from the peer, which is checked to be higher than 0,
+ * but we limit it to the max value we support in order to have
+ * the main logic simpler.
+ */
+ sc->recv_io.credits.target = credits_requested;
+ sc->recv_io.credits.target = min_t(u16, sc->recv_io.credits.target,
+ sp->recv_credit_max);
+
+ /*
+ * At least the value of SMBDIRECT_MIN_RECEIVE_SIZE is used.
+ */
+ sp->max_recv_size = min_t(u32, sp->max_recv_size, preferred_send_size);
+ sp->max_recv_size = max_t(u32, sp->max_recv_size, SMBDIRECT_MIN_RECEIVE_SIZE);
+
+ /*
+ * We already sent our sp->max_fragmented_recv_size
+ * to the peer, so we can't lower it here any more.
+ *
+ * TODO: but if the peer lowered sp->max_recv_size
+ * we will have to adjust our number of buffers.
+ *
+ * But for now we keep it as the cifs.ko code
+ * worked before.
+ */
+
+ /*
+ * Note nrep->max_receive_size was already checked against
+ * SMBDIRECT_MIN_RECEIVE_SIZE above.
+ */
+ sp->max_send_size = min_t(u32, sp->max_send_size, max_receive_size);
+
+ /*
+ * Make sure the resulting max_frmr_depth is at least 1,
+ * which means max_read_write_size needs to be at least PAGE_SIZE.
+ */
+ sp->max_read_write_size = min_t(u32, sp->max_frmr_depth * PAGE_SIZE,
+ max_readwrite_size);
+ if (sp->max_read_write_size < PAGE_SIZE) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: max_readwrite_size=%u < PAGE_SIZE(%lu)\n",
+ max_readwrite_size,
+ PAGE_SIZE);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+ sp->max_frmr_depth = sp->max_read_write_size / PAGE_SIZE;
+
+ /*
+ * Note nrep->credits_granted was already checked against 0 above.
+ */
+ atomic_set(&sc->send_io.credits.count, credits_granted);
+
+ /*
+ * Note nrep->max_fragmented_size was already checked against
+ * SMBDIRECT_MIN_FRAGMENTED_SIZE above.
+ */
+ sp->max_fragmented_send_size = max_fragmented_size;
+
+ ret = smbdirect_connection_create_mr_list(sc);
+ if (ret) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_create_mr_list() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ return;
+ }
+
+ /*
+ * Prepare for receiving data_transfer messages
+ */
+ sc->recv_io.reassembly.full_packet_received = true;
+ sc->recv_io.expected = SMBDIRECT_EXPECT_DATA_TRANSFER;
+ list_for_each_entry(recv_io, &sc->recv_io.free.list, list)
+ recv_io->cqe.done = smbdirect_connection_recv_io_done;
+ recv_io = NULL;
+
+ /*
+ * We should at least post 1 smbdirect_recv_io!
+ */
+ posted = smbdirect_connection_recv_io_refill(sc);
+ if (posted < 1) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_recv_io_refill() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ /*
+ * smbdirect_connection_negotiation_done()
+ * will setup all required things and wake up
+ * the waiter.
+ */
+ smbdirect_connection_negotiation_done(sc);
+}
+
+int smbdirect_connect_sync(struct smbdirect_socket *sc,
+ const struct sockaddr *dst)
+{
+ int ret;
+
+ ret = smbdirect_connect(sc, dst);
+ if (ret) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connect(%pISpsfc) failed %1pe\n",
+ dst, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ ret = smbdirect_connection_wait_for_connected(sc);
+ if (ret) {
+ int lvl = SMBDIRECT_LOG_ERR;
+
+ if (ret == -ENODEV)
+ lvl = SMBDIRECT_LOG_INFO;
+
+ smbdirect_log_rdma_event(sc, lvl,
+ "wait for smbdirect_connect(%pISpsfc) failed %1pe\n",
+ dst, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connect_sync);
diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c
new file mode 100644
index 000000000000..afd31fa12a36
--- /dev/null
+++ b/fs/smb/smbdirect/connection.c
@@ -0,0 +1,2190 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+#include <linux/folio_queue.h>
+
+struct smbdirect_map_sges {
+ struct ib_sge *sge;
+ size_t num_sge;
+ size_t max_sge;
+ struct ib_device *device;
+ u32 local_dma_lkey;
+ enum dma_data_direction direction;
+};
+
+static ssize_t smbdirect_map_sges_from_iter(struct iov_iter *iter, size_t len,
+ struct smbdirect_map_sges *state);
+
+static void smbdirect_connection_recv_io_refill_work(struct work_struct *work);
+static void smbdirect_connection_send_immediate_work(struct work_struct *work);
+
+static void smbdirect_connection_qp_event_handler(struct ib_event *event, void *context)
+{
+ struct smbdirect_socket *sc = context;
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "%s on device %.*s socket %p (cm_id=%p) status %s first_error %1pe\n",
+ ib_event_msg(event->event),
+ IB_DEVICE_NAME_MAX,
+ event->device->name,
+ sc, sc->rdma.cm_id,
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ switch (event->event) {
+ case IB_EVENT_CQ_ERR:
+ case IB_EVENT_QP_FATAL:
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id,
+ struct rdma_cm_event *event)
+{
+ struct smbdirect_socket *sc = id->context;
+ int ret = -ECONNRESET;
+
+ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+ ret = -ENETDOWN;
+ if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
+ ret = event->status;
+
+ /*
+ * cma_cm_event_handler() has
+ * lockdep_assert_held(&id_priv->handler_mutex);
+ *
+ * Mutexes are not allowed in interrupts,
+ * and we rely on not being in an interrupt here.
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ if (event->event != sc->rdma.expected_event) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "%s (first_error=%1pe, expected=%s) => event=%s status=%d => ret=%1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ rdma_event_msg(sc->rdma.expected_event),
+ rdma_event_msg(event->event),
+ event->status,
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+
+ /*
+ * If we get RDMA_CM_EVENT_DEVICE_REMOVAL,
+ * we should change to SMBDIRECT_SOCKET_DISCONNECTED,
+ * so that rdma_disconnect() is avoided later via
+ * smbdirect_socket_schedule_cleanup[_status]() =>
+ * smbdirect_socket_cleanup_work().
+ *
+ * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING,
+ * but never ever get RDMA_CM_EVENT_DISCONNECTED and
+ * never reach SMBDIRECT_SOCKET_DISCONNECTED.
+ */
+ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+ smbdirect_socket_schedule_cleanup_status(sc,
+ SMBDIRECT_LOG_ERR,
+ ret,
+ SMBDIRECT_SOCKET_DISCONNECTED);
+ else
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ if (sc->ib.qp)
+ ib_drain_qp(sc->ib.qp);
+ return 0;
+ }
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "%s (first_error=%1pe) event=%s\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ rdma_event_msg(event->event));
+
+ switch (event->event) {
+ case RDMA_CM_EVENT_DISCONNECTED:
+ /*
+ * We need to change to SMBDIRECT_SOCKET_DISCONNECTED,
+ * so that rdma_disconnect() is avoided later via
+ * smbdirect_socket_schedule_cleanup_status() =>
+ * smbdirect_socket_cleanup_work().
+ *
+ * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING,
+ * but never ever get RDMA_CM_EVENT_DISCONNECTED and
+ * never reach SMBDIRECT_SOCKET_DISCONNECTED.
+ *
+ * This is also a normal disconnect so
+ * SMBDIRECT_LOG_INFO should be good enough
+ * and avoids spamming the default logs.
+ */
+ smbdirect_socket_schedule_cleanup_status(sc,
+ SMBDIRECT_LOG_INFO,
+ ret,
+ SMBDIRECT_SOCKET_DISCONNECTED);
+ if (sc->ib.qp)
+ ib_drain_qp(sc->ib.qp);
+ return 0;
+
+ default:
+ break;
+ }
+
+ /*
+ * This is an internal error, should be handled above via
+ * event->event != sc->rdma.expected_event already.
+ */
+ WARN_ON_ONCE(sc->rdma.expected_event != RDMA_CM_EVENT_DISCONNECTED);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return 0;
+}
+
+void smbdirect_connection_rdma_established(struct smbdirect_socket *sc)
+{
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "rdma established: device: %.*s local: %pISpsfc remote: %pISpsfc\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name,
+ &sc->rdma.cm_id->route.addr.src_addr,
+ &sc->rdma.cm_id->route.addr.dst_addr);
+
+ sc->rdma.cm_id->event_handler = smbdirect_connection_rdma_event_handler;
+ sc->rdma.expected_event = RDMA_CM_EVENT_DISCONNECTED;
+}
+
+void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
+{
+ if (unlikely(sc->first_error))
+ return;
+
+ if (sc->status == SMBDIRECT_SOCKET_CONNECTED)
+ /*
+ * This is the accept case where
+ * smbdirect_socket_accept() already sets
+ * SMBDIRECT_SOCKET_CONNECTED
+ */
+ goto done;
+
+ if (sc->status != SMBDIRECT_SOCKET_NEGOTIATE_RUNNING) {
+ /*
+ * Something went wrong...
+ */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "status=%s first_error=%1pe local: %pISpsfc remote: %pISpsfc\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ &sc->rdma.cm_id->route.addr.src_addr,
+ &sc->rdma.cm_id->route.addr.dst_addr);
+ return;
+ }
+
+ /*
+ * We are done, so we can wake up the waiter.
+ */
+ WARN_ONCE(sc->status == SMBDIRECT_SOCKET_CONNECTED,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+ sc->status = SMBDIRECT_SOCKET_CONNECTED;
+
+ /*
+ * We need to setup the refill and send immediate work
+ * in order to get a working connection.
+ */
+done:
+ INIT_WORK(&sc->recv_io.posted.refill_work, smbdirect_connection_recv_io_refill_work);
+ INIT_WORK(&sc->idle.immediate_work, smbdirect_connection_send_immediate_work);
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "negotiated: local: %pISpsfc remote: %pISpsfc\n",
+ &sc->rdma.cm_id->route.addr.src_addr,
+ &sc->rdma.cm_id->route.addr.dst_addr);
+
+ wake_up(&sc->status_wait);
+}
+
+static u32 smbdirect_rdma_rw_send_wrs(struct ib_device *dev,
+ const struct ib_qp_init_attr *attr)
+{
+ /*
+ * This could be split out of rdma_rw_init_qp()
+ * and be a helper function next to rdma_rw_mr_factor()
+ *
+ * We can't check unlikely(rdma_rw_force_mr) here,
+ * but that is most likely 0 anyway.
+ */
+ u32 factor;
+
+ WARN_ON_ONCE(attr->port_num == 0);
+
+ /*
+ * Each context needs at least one RDMA READ or WRITE WR.
+ *
+ * For some hardware we might need more, eventually we should ask the
+ * HCA driver for a multiplier here.
+ */
+ factor = 1;
+
+ /*
+ * If the device needs MRs to perform RDMA READ or WRITE operations,
+ * we'll need two additional MRs for the registrations and the
+ * invalidation.
+ */
+ if (rdma_protocol_iwarp(dev, attr->port_num) || dev->attrs.max_sgl_rd)
+ factor += 2; /* inv + reg */
+
+ return factor * attr->cap.max_rdma_ctxs;
+}
+
+int smbdirect_connection_create_qp(struct smbdirect_socket *sc)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct ib_qp_init_attr qp_attr;
+ struct ib_qp_cap qp_cap;
+ u32 rdma_send_wr;
+ u32 max_send_wr;
+ int ret;
+
+ /*
+ * Note that {rdma,ib}_create_qp() will call
+ * rdma_rw_init_qp() if max_rdma_ctxs is not 0.
+ * It will adjust max_send_wr to the required
+ * number of additional WRs for the RDMA RW operations.
+ * It will cap max_send_wr to the device limit.
+ *
+ * We use allocate sp->responder_resources * 2 MRs
+ * and each MR needs WRs for REG and INV, so
+ * we use '* 4'.
+ *
+ * +1 for ib_drain_qp()
+ */
+ memset(&qp_cap, 0, sizeof(qp_cap));
+ qp_cap.max_send_wr = sp->send_credit_target + sp->responder_resources * 4 + 1;
+ qp_cap.max_recv_wr = sp->recv_credit_max + 1;
+ qp_cap.max_send_sge = SMBDIRECT_SEND_IO_MAX_SGE;
+ qp_cap.max_recv_sge = SMBDIRECT_RECV_IO_MAX_SGE;
+ qp_cap.max_inline_data = 0;
+ qp_cap.max_rdma_ctxs = sc->rw_io.credits.max;
+
+ /*
+ * Find out the number of max_send_wr
+ * after rdma_rw_init_qp() adjusted it.
+ *
+ * We only do it on a temporary variable,
+ * as rdma_create_qp() will trigger
+ * rdma_rw_init_qp() again.
+ */
+ memset(&qp_attr, 0, sizeof(qp_attr));
+ qp_attr.cap = qp_cap;
+ qp_attr.port_num = sc->rdma.cm_id->port_num;
+ rdma_send_wr = smbdirect_rdma_rw_send_wrs(sc->ib.dev, &qp_attr);
+ max_send_wr = qp_cap.max_send_wr + rdma_send_wr;
+
+ if (qp_cap.max_send_wr > sc->ib.dev->attrs.max_cqe ||
+ qp_cap.max_send_wr > sc->ib.dev->attrs.max_qp_wr) {
+ pr_err("Possible CQE overrun: max_send_wr %d\n",
+ qp_cap.max_send_wr);
+ pr_err("device %.*s reporting max_cqe %u max_qp_wr %u\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name,
+ sc->ib.dev->attrs.max_cqe,
+ sc->ib.dev->attrs.max_qp_wr);
+ pr_err("consider lowering send_credit_target = %d\n",
+ sp->send_credit_target);
+ return -EINVAL;
+ }
+
+ if (qp_cap.max_rdma_ctxs &&
+ (max_send_wr >= sc->ib.dev->attrs.max_cqe ||
+ max_send_wr >= sc->ib.dev->attrs.max_qp_wr)) {
+ pr_err("Possible CQE overrun: rdma_send_wr %d + max_send_wr %d = %d\n",
+ rdma_send_wr, qp_cap.max_send_wr, max_send_wr);
+ pr_err("device %.*s reporting max_cqe %u max_qp_wr %u\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name,
+ sc->ib.dev->attrs.max_cqe,
+ sc->ib.dev->attrs.max_qp_wr);
+ pr_err("consider lowering send_credit_target = %d, max_rdma_ctxs = %d\n",
+ sp->send_credit_target, qp_cap.max_rdma_ctxs);
+ return -EINVAL;
+ }
+
+ if (qp_cap.max_recv_wr > sc->ib.dev->attrs.max_cqe ||
+ qp_cap.max_recv_wr > sc->ib.dev->attrs.max_qp_wr) {
+ pr_err("Possible CQE overrun: max_recv_wr %d\n",
+ qp_cap.max_recv_wr);
+ pr_err("device %.*s reporting max_cqe %u max_qp_wr %u\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name,
+ sc->ib.dev->attrs.max_cqe,
+ sc->ib.dev->attrs.max_qp_wr);
+ pr_err("consider lowering receive_credit_max = %d\n",
+ sp->recv_credit_max);
+ return -EINVAL;
+ }
+
+ if (qp_cap.max_send_sge > sc->ib.dev->attrs.max_send_sge ||
+ qp_cap.max_recv_sge > sc->ib.dev->attrs.max_recv_sge) {
+ pr_err("device %.*s max_send_sge/max_recv_sge = %u/%u too small\n",
+ IB_DEVICE_NAME_MAX,
+ sc->ib.dev->name,
+ sc->ib.dev->attrs.max_send_sge,
+ sc->ib.dev->attrs.max_recv_sge);
+ return -EINVAL;
+ }
+
+ sc->ib.pd = ib_alloc_pd(sc->ib.dev, 0);
+ if (IS_ERR(sc->ib.pd)) {
+ pr_err("Can't create RDMA PD: %1pe\n", sc->ib.pd);
+ ret = PTR_ERR(sc->ib.pd);
+ sc->ib.pd = NULL;
+ return ret;
+ }
+
+ sc->ib.send_cq = ib_alloc_cq_any(sc->ib.dev, sc,
+ max_send_wr,
+ sc->ib.poll_ctx);
+ if (IS_ERR(sc->ib.send_cq)) {
+ pr_err("Can't create RDMA send CQ: %1pe\n", sc->ib.send_cq);
+ ret = PTR_ERR(sc->ib.send_cq);
+ sc->ib.send_cq = NULL;
+ goto err;
+ }
+
+ sc->ib.recv_cq = ib_alloc_cq_any(sc->ib.dev, sc,
+ qp_cap.max_recv_wr,
+ sc->ib.poll_ctx);
+ if (IS_ERR(sc->ib.recv_cq)) {
+ pr_err("Can't create RDMA recv CQ: %1pe\n", sc->ib.recv_cq);
+ ret = PTR_ERR(sc->ib.recv_cq);
+ sc->ib.recv_cq = NULL;
+ goto err;
+ }
+
+ /*
+ * We reset completely here!
+ * As the above use was just temporary
+ * to calc max_send_wr and rdma_send_wr.
+ *
+ * rdma_create_qp() will trigger rdma_rw_init_qp()
+ * again if max_rdma_ctxs is not 0.
+ */
+ memset(&qp_attr, 0, sizeof(qp_attr));
+ qp_attr.event_handler = smbdirect_connection_qp_event_handler;
+ qp_attr.qp_context = sc;
+ qp_attr.cap = qp_cap;
+ qp_attr.sq_sig_type = IB_SIGNAL_REQ_WR;
+ qp_attr.qp_type = IB_QPT_RC;
+ qp_attr.send_cq = sc->ib.send_cq;
+ qp_attr.recv_cq = sc->ib.recv_cq;
+ qp_attr.port_num = ~0;
+
+ ret = rdma_create_qp(sc->rdma.cm_id, sc->ib.pd, &qp_attr);
+ if (ret) {
+ pr_err("Can't create RDMA QP: %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ goto err;
+ }
+ sc->ib.qp = sc->rdma.cm_id->qp;
+
+ return 0;
+err:
+ smbdirect_connection_destroy_qp(sc);
+ return ret;
+}
+
+void smbdirect_connection_destroy_qp(struct smbdirect_socket *sc)
+{
+ if (sc->ib.qp) {
+ ib_drain_qp(sc->ib.qp);
+ sc->ib.qp = NULL;
+ rdma_destroy_qp(sc->rdma.cm_id);
+ }
+ /*
+ * These CQs were created with ib_alloc_cq_any(), which arms an internal
+ * completion handler (ib_cq_poll_work for IB_POLL_WORKQUEUE). They MUST be
+ * torn down with ib_free_cq(), which cancel_work_sync()es that poll work
+ * before freeing the CQ. ib_destroy_cq() skips that step, so a completion
+ * posted late by the (software) provider — e.g. rxe posting an RNR error
+ * from rxe_receiver after rdma_destroy_qp() — re-queues ib_cq_poll_work on
+ * an already-freed CQ (KASAN slab-use-after-free in ib_cq_poll_work).
+ */
+ if (sc->ib.recv_cq) {
+ ib_free_cq(sc->ib.recv_cq);
+ sc->ib.recv_cq = NULL;
+ }
+ if (sc->ib.send_cq) {
+ ib_free_cq(sc->ib.send_cq);
+ sc->ib.send_cq = NULL;
+ }
+ if (sc->ib.pd) {
+ ib_dealloc_pd(sc->ib.pd);
+ sc->ib.pd = NULL;
+ }
+}
+
+int smbdirect_connection_create_mem_pools(struct smbdirect_socket *sc)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ char name[80];
+ size_t i;
+
+ /*
+ * We use sizeof(struct smbdirect_negotiate_resp) for the
+ * payload size as it is larger as
+ * sizeof(struct smbdirect_data_transfer).
+ *
+ * This will fit client and server usage for now.
+ */
+ snprintf(name, sizeof(name), "smbdirect_send_io_cache_%p", sc);
+ struct kmem_cache_args send_io_args = {
+ .align = __alignof__(struct smbdirect_send_io),
+ };
+ sc->send_io.mem.cache = kmem_cache_create(name,
+ sizeof(struct smbdirect_send_io) +
+ sizeof(struct smbdirect_negotiate_resp),
+ &send_io_args,
+ SLAB_HWCACHE_ALIGN);
+ if (!sc->send_io.mem.cache)
+ goto err;
+
+ sc->send_io.mem.pool = mempool_create_slab_pool(sp->send_credit_target,
+ sc->send_io.mem.cache);
+ if (!sc->send_io.mem.pool)
+ goto err;
+
+ /*
+ * A payload size of sp->max_recv_size should fit
+ * any message.
+ *
+ * For smbdirect_data_transfer messages the whole
+ * buffer might be exposed to userspace
+ * (currently on the client side...)
+ * The documentation says data_offset = 0 would be
+ * strange but valid.
+ */
+ snprintf(name, sizeof(name), "smbdirect_recv_io_cache_%p", sc);
+ struct kmem_cache_args recv_io_args = {
+ .align = __alignof__(struct smbdirect_recv_io),
+ .useroffset = sizeof(struct smbdirect_recv_io),
+ .usersize = sp->max_recv_size,
+ };
+ sc->recv_io.mem.cache = kmem_cache_create(name,
+ sizeof(struct smbdirect_recv_io) +
+ sp->max_recv_size,
+ &recv_io_args,
+ SLAB_HWCACHE_ALIGN);
+ if (!sc->recv_io.mem.cache)
+ goto err;
+
+ sc->recv_io.mem.pool = mempool_create_slab_pool(sp->recv_credit_max,
+ sc->recv_io.mem.cache);
+ if (!sc->recv_io.mem.pool)
+ goto err;
+
+ for (i = 0; i < sp->recv_credit_max; i++) {
+ struct smbdirect_recv_io *recv_io;
+
+ recv_io = mempool_alloc(sc->recv_io.mem.pool,
+ sc->recv_io.mem.gfp_mask);
+ if (!recv_io)
+ goto err;
+ recv_io->socket = sc;
+ recv_io->sge.length = 0;
+ list_add_tail(&recv_io->list, &sc->recv_io.free.list);
+ }
+
+ return 0;
+err:
+ smbdirect_connection_destroy_mem_pools(sc);
+ return -ENOMEM;
+}
+
+void smbdirect_connection_destroy_mem_pools(struct smbdirect_socket *sc)
+{
+ struct smbdirect_recv_io *recv_io, *next_io;
+
+ list_for_each_entry_safe(recv_io, next_io, &sc->recv_io.free.list, list) {
+ list_del(&recv_io->list);
+ mempool_free(recv_io, sc->recv_io.mem.pool);
+ }
+
+ /*
+ * Note mempool_destroy() and kmem_cache_destroy()
+ * work fine with a NULL pointer
+ */
+
+ mempool_destroy(sc->recv_io.mem.pool);
+ sc->recv_io.mem.pool = NULL;
+
+ kmem_cache_destroy(sc->recv_io.mem.cache);
+ sc->recv_io.mem.cache = NULL;
+
+ mempool_destroy(sc->send_io.mem.pool);
+ sc->send_io.mem.pool = NULL;
+
+ kmem_cache_destroy(sc->send_io.mem.cache);
+ sc->send_io.mem.cache = NULL;
+}
+
+struct smbdirect_send_io *smbdirect_connection_alloc_send_io(struct smbdirect_socket *sc)
+{
+ struct smbdirect_send_io *msg;
+
+ msg = mempool_alloc(sc->send_io.mem.pool, sc->send_io.mem.gfp_mask);
+ if (!msg)
+ return ERR_PTR(-ENOMEM);
+ msg->socket = sc;
+ INIT_LIST_HEAD(&msg->sibling_list);
+ msg->num_sge = 0;
+
+ return msg;
+}
+
+void smbdirect_connection_free_send_io(struct smbdirect_send_io *msg)
+{
+ struct smbdirect_socket *sc = msg->socket;
+ size_t i;
+
+ /*
+ * The list needs to be empty!
+ * The caller should take care of it.
+ */
+ WARN_ON_ONCE(!list_empty(&msg->sibling_list));
+
+ /*
+ * Note we call ib_dma_unmap_page(), even if some sges are mapped using
+ * ib_dma_map_single().
+ *
+ * The difference between _single() and _page() only matters for the
+ * ib_dma_map_*() case.
+ *
+ * For the ib_dma_unmap_*() case it does not matter as both take the
+ * dma_addr_t and dma_unmap_single_attrs() is just an alias to
+ * dma_unmap_page_attrs().
+ */
+ for (i = 0; i < msg->num_sge; i++)
+ ib_dma_unmap_page(sc->ib.dev,
+ msg->sge[i].addr,
+ msg->sge[i].length,
+ DMA_TO_DEVICE);
+
+ mempool_free(msg, sc->send_io.mem.pool);
+}
+
+struct smbdirect_recv_io *smbdirect_connection_get_recv_io(struct smbdirect_socket *sc)
+{
+ struct smbdirect_recv_io *msg = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sc->recv_io.free.lock, flags);
+ if (likely(!sc->first_error))
+ msg = list_first_entry_or_null(&sc->recv_io.free.list,
+ struct smbdirect_recv_io,
+ list);
+ if (likely(msg)) {
+ list_del(&msg->list);
+ sc->statistics.get_receive_buffer++;
+ }
+ spin_unlock_irqrestore(&sc->recv_io.free.lock, flags);
+
+ return msg;
+}
+
+void smbdirect_connection_put_recv_io(struct smbdirect_recv_io *msg)
+{
+ struct smbdirect_socket *sc = msg->socket;
+ unsigned long flags;
+
+ if (likely(msg->sge.length != 0)) {
+ ib_dma_unmap_single(sc->ib.dev,
+ msg->sge.addr,
+ msg->sge.length,
+ DMA_FROM_DEVICE);
+ msg->sge.length = 0;
+ }
+
+ spin_lock_irqsave(&sc->recv_io.free.lock, flags);
+ list_add_tail(&msg->list, &sc->recv_io.free.list);
+ sc->statistics.put_receive_buffer++;
+ spin_unlock_irqrestore(&sc->recv_io.free.lock, flags);
+
+ queue_work(sc->workqueues.refill, &sc->recv_io.posted.refill_work);
+}
+
+void smbdirect_connection_reassembly_append_recv_io(struct smbdirect_socket *sc,
+ struct smbdirect_recv_io *msg,
+ u32 data_length)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
+ list_add_tail(&msg->list, &sc->recv_io.reassembly.list);
+ sc->recv_io.reassembly.queue_length++;
+ /*
+ * Make sure reassembly_data_length is updated after list and
+ * reassembly_queue_length are updated. On the dequeue side
+ * reassembly_data_length is checked without a lock to determine
+ * if reassembly_queue_length and list is up to date
+ */
+ virt_wmb();
+ sc->recv_io.reassembly.data_length += data_length;
+ spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
+ sc->statistics.enqueue_reassembly_queue++;
+}
+
+struct smbdirect_recv_io *
+smbdirect_connection_reassembly_first_recv_io(struct smbdirect_socket *sc)
+{
+ struct smbdirect_recv_io *msg;
+
+ msg = list_first_entry_or_null(&sc->recv_io.reassembly.list,
+ struct smbdirect_recv_io,
+ list);
+
+ return msg;
+}
+
+void smbdirect_connection_negotiate_rdma_resources(struct smbdirect_socket *sc,
+ u8 peer_initiator_depth,
+ u8 peer_responder_resources,
+ const struct rdma_conn_param *param)
+{
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+
+ if (rdma_protocol_iwarp(sc->ib.dev, sc->rdma.cm_id->port_num) &&
+ param->private_data_len == 8) {
+ /*
+ * Legacy clients with only iWarp MPA v1 support
+ * need a private blob in order to negotiate
+ * the IRD/ORD values.
+ */
+ const __be32 *ird_ord_hdr = param->private_data;
+ u32 ird32 = be32_to_cpu(ird_ord_hdr[0]);
+ u32 ord32 = be32_to_cpu(ird_ord_hdr[1]);
+
+ /*
+ * cifs.ko sends the legacy IRD/ORD negotiation
+ * event if iWarp MPA v2 was used.
+ *
+ * Here we check that the values match and only
+ * mark the client as legacy if they don't match.
+ */
+ if ((u32)param->initiator_depth != ird32 ||
+ (u32)param->responder_resources != ord32) {
+ /*
+ * There are broken clients (old cifs.ko)
+ * using little endian and also
+ * struct rdma_conn_param only uses u8
+ * for initiator_depth and responder_resources,
+ * so we truncate the value to U8_MAX.
+ *
+ * smb_direct_accept_client() will then
+ * do the real negotiation in order to
+ * select the minimum between client and
+ * server.
+ */
+ ird32 = min_t(u32, ird32, U8_MAX);
+ ord32 = min_t(u32, ord32, U8_MAX);
+
+ sc->rdma.legacy_iwarp = true;
+ peer_initiator_depth = (u8)ird32;
+ peer_responder_resources = (u8)ord32;
+ }
+ }
+
+ /*
+ * negotiate the value by using the minimum
+ * between client and server if the client provided
+ * non 0 values.
+ */
+ if (peer_initiator_depth != 0)
+ sp->initiator_depth = min_t(u8, sp->initiator_depth,
+ peer_initiator_depth);
+ if (peer_responder_resources != 0)
+ sp->responder_resources = min_t(u8, sp->responder_resources,
+ peer_responder_resources);
+}
+
+bool smbdirect_connection_is_connected(struct smbdirect_socket *sc)
+{
+ if (unlikely(!sc || sc->first_error || sc->status != SMBDIRECT_SOCKET_CONNECTED))
+ return false;
+ return true;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_is_connected);
+
+int smbdirect_connection_wait_for_connected(struct smbdirect_socket *sc)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_storage ss;
+ } src_addr, dst_addr;
+ const struct sockaddr *src = NULL;
+ const struct sockaddr *dst = NULL;
+ char _devname[IB_DEVICE_NAME_MAX] = { 0, };
+ const char *devname = NULL;
+ int ret;
+
+ if (sc->rdma.cm_id) {
+ src_addr.ss = sc->rdma.cm_id->route.addr.src_addr;
+ if (src_addr.sa.sa_family != AF_UNSPEC)
+ src = &src_addr.sa;
+ dst_addr.ss = sc->rdma.cm_id->route.addr.dst_addr;
+ if (dst_addr.sa.sa_family != AF_UNSPEC)
+ dst = &dst_addr.sa;
+
+ if (sc->ib.dev) {
+ memcpy(_devname, sc->ib.dev->name, IB_DEVICE_NAME_MAX);
+ devname = _devname;
+ }
+ }
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "waiting for connection: device: %.*s local: %pISpsfc remote: %pISpsfc\n",
+ IB_DEVICE_NAME_MAX, devname, src, dst);
+
+ ret = wait_event_interruptible_timeout(sc->status_wait,
+ sc->status == SMBDIRECT_SOCKET_CONNECTED ||
+ sc->first_error,
+ msecs_to_jiffies(sp->negotiate_timeout_msec));
+ if (sc->rdma.cm_id) {
+ /*
+ * Maybe src and dev are updated in the meantime.
+ */
+ src_addr.ss = sc->rdma.cm_id->route.addr.src_addr;
+ if (src_addr.sa.sa_family != AF_UNSPEC)
+ src = &src_addr.sa;
+ dst_addr.ss = sc->rdma.cm_id->route.addr.dst_addr;
+ if (dst_addr.sa.sa_family != AF_UNSPEC)
+ dst = &dst_addr.sa;
+
+ if (sc->ib.dev) {
+ memcpy(_devname, sc->ib.dev->name, IB_DEVICE_NAME_MAX);
+ devname = _devname;
+ }
+ }
+ if (ret == 0)
+ ret = -ETIMEDOUT;
+ if (ret < 0)
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ if (sc->first_error) {
+ int lvl = SMBDIRECT_LOG_ERR;
+
+ ret = sc->first_error;
+ if (ret == -ENODEV)
+ lvl = SMBDIRECT_LOG_INFO;
+
+ smbdirect_log_rdma_event(sc, lvl,
+ "connection failed %1pe device: %.*s local: %pISpsfc remote: %pISpsfc\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret),
+ IB_DEVICE_NAME_MAX, devname, src, dst);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_wait_for_connected);
+
+void smbdirect_connection_idle_timer_work(struct work_struct *work)
+{
+ struct smbdirect_socket *sc =
+ container_of(work, struct smbdirect_socket, idle.timer_work.work);
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+
+ if (sc->idle.keepalive != SMBDIRECT_KEEPALIVE_NONE) {
+ smbdirect_log_keep_alive(sc, SMBDIRECT_LOG_ERR,
+ "%s => timeout sc->idle.keepalive=%s\n",
+ smbdirect_socket_status_string(sc->status),
+ sc->idle.keepalive == SMBDIRECT_KEEPALIVE_SENT ?
+ "SENT" : "PENDING");
+ smbdirect_socket_schedule_cleanup(sc, -ETIMEDOUT);
+ return;
+ }
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
+ return;
+
+ /*
+ * Now use the keepalive timeout (instead of keepalive interval)
+ * in order to wait for a response
+ */
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_PENDING;
+ mod_delayed_work(sc->workqueues.idle, &sc->idle.timer_work,
+ msecs_to_jiffies(sp->keepalive_timeout_msec));
+ smbdirect_log_keep_alive(sc, SMBDIRECT_LOG_INFO,
+ "schedule send of empty idle message\n");
+ queue_work(sc->workqueues.immediate, &sc->idle.immediate_work);
+}
+
+u16 smbdirect_connection_grant_recv_credits(struct smbdirect_socket *sc)
+{
+ int missing;
+ int available;
+ int new_credits;
+
+ if (atomic_read(&sc->recv_io.credits.count) >= sc->recv_io.credits.target)
+ return 0;
+
+ missing = (int)sc->recv_io.credits.target - atomic_read(&sc->recv_io.credits.count);
+ available = atomic_xchg(&sc->recv_io.credits.available, 0);
+ new_credits = min3((int)U16_MAX, missing, available);
+ if (new_credits <= 0) {
+ /*
+ * If credits are available, but not granted
+ * we need to re-add them again.
+ */
+ if (available)
+ atomic_add(available, &sc->recv_io.credits.available);
+ return 0;
+ }
+
+ if (new_credits < available) {
+ /*
+ * Readd the remaining available again.
+ */
+ available -= new_credits;
+ atomic_add(available, &sc->recv_io.credits.available);
+ }
+
+ /*
+ * Remember we granted the credits
+ */
+ atomic_add(new_credits, &sc->recv_io.credits.count);
+ return new_credits;
+}
+
+static bool smbdirect_connection_request_keep_alive(struct smbdirect_socket *sc)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+
+ if (sc->idle.keepalive == SMBDIRECT_KEEPALIVE_PENDING) {
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_SENT;
+ /*
+ * Now use the keepalive timeout (instead of keepalive interval)
+ * in order to wait for a response
+ */
+ mod_delayed_work(sc->workqueues.idle, &sc->idle.timer_work,
+ msecs_to_jiffies(sp->keepalive_timeout_msec));
+ return true;
+ }
+
+ return false;
+}
+
+int smbdirect_connection_post_send_wr(struct smbdirect_socket *sc,
+ struct ib_send_wr *wr)
+{
+ int ret;
+
+ if (unlikely(sc->first_error))
+ return sc->first_error;
+
+ atomic_inc(&sc->send_io.pending.count);
+ ret = ib_post_send(sc->ib.qp, wr, NULL);
+ if (ret) {
+ atomic_dec(&sc->send_io.pending.count);
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_ERR,
+ "ib_post_send() failed %1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ }
+
+ return ret;
+}
+
+static void smbdirect_connection_send_batch_init(struct smbdirect_send_batch *batch,
+ bool need_invalidate_rkey,
+ unsigned int remote_key)
+{
+ INIT_LIST_HEAD(&batch->msg_list);
+ batch->wr_cnt = 0;
+ batch->need_invalidate_rkey = need_invalidate_rkey;
+ batch->remote_key = remote_key;
+ batch->credit = 0;
+}
+
+int smbdirect_connection_send_batch_flush(struct smbdirect_socket *sc,
+ struct smbdirect_send_batch *batch,
+ bool is_last)
+{
+ struct smbdirect_send_io *first, *last;
+ int ret = 0;
+
+ if (list_empty(&batch->msg_list))
+ goto release_credit;
+
+ first = list_first_entry(&batch->msg_list,
+ struct smbdirect_send_io,
+ sibling_list);
+ last = list_last_entry(&batch->msg_list,
+ struct smbdirect_send_io,
+ sibling_list);
+
+ if (batch->need_invalidate_rkey) {
+ first->wr.opcode = IB_WR_SEND_WITH_INV;
+ first->wr.ex.invalidate_rkey = batch->remote_key;
+ batch->need_invalidate_rkey = false;
+ batch->remote_key = 0;
+ }
+
+ last->wr.send_flags = IB_SEND_SIGNALED;
+ last->wr.wr_cqe = &last->cqe;
+
+ /*
+ * Remove last from send_ctx->msg_list
+ * and splice the rest of send_ctx->msg_list
+ * to last->sibling_list.
+ *
+ * send_ctx->msg_list is a valid empty list
+ * at the end.
+ */
+ list_del_init(&last->sibling_list);
+ list_splice_tail_init(&batch->msg_list, &last->sibling_list);
+ batch->wr_cnt = 0;
+
+ ret = smbdirect_connection_post_send_wr(sc, &first->wr);
+ if (ret) {
+ struct smbdirect_send_io *sibling, *next;
+
+ list_for_each_entry_safe(sibling, next, &last->sibling_list, sibling_list) {
+ list_del_init(&sibling->sibling_list);
+ smbdirect_connection_free_send_io(sibling);
+ }
+ smbdirect_connection_free_send_io(last);
+ }
+
+release_credit:
+ if (is_last && !ret && batch->credit) {
+ atomic_add(batch->credit, &sc->send_io.bcredits.count);
+ batch->credit = 0;
+ wake_up(&sc->send_io.bcredits.wait_queue);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_send_batch_flush);
+
+struct smbdirect_send_batch *
+smbdirect_init_send_batch_storage(struct smbdirect_send_batch_storage *storage,
+ bool need_invalidate_rkey,
+ unsigned int remote_key)
+{
+ struct smbdirect_send_batch *batch = (struct smbdirect_send_batch *)storage;
+
+ memset(storage, 0, sizeof(*storage));
+ BUILD_BUG_ON(sizeof(*batch) > sizeof(*storage));
+
+ smbdirect_connection_send_batch_init(batch,
+ need_invalidate_rkey,
+ remote_key);
+
+ return batch;
+}
+EXPORT_SYMBOL_GPL(smbdirect_init_send_batch_storage);
+
+static int smbdirect_connection_wait_for_send_bcredit(struct smbdirect_socket *sc,
+ struct smbdirect_send_batch *batch)
+{
+ int ret;
+
+ if (batch->credit)
+ return 0;
+
+ ret = smbdirect_socket_wait_for_credits(sc,
+ SMBDIRECT_SOCKET_CONNECTED,
+ -ENOTCONN,
+ &sc->send_io.bcredits.wait_queue,
+ &sc->send_io.bcredits.count,
+ 1);
+ if (ret)
+ return ret;
+
+ batch->credit = 1;
+ return 0;
+}
+
+static int smbdirect_connection_wait_for_send_lcredit(struct smbdirect_socket *sc,
+ struct smbdirect_send_batch *batch)
+{
+ if (batch && atomic_read(&sc->send_io.lcredits.count) <= 1) {
+ int ret;
+
+ ret = smbdirect_connection_send_batch_flush(sc, batch, false);
+ if (ret)
+ return ret;
+ }
+
+ return smbdirect_socket_wait_for_credits(sc,
+ SMBDIRECT_SOCKET_CONNECTED,
+ -ENOTCONN,
+ &sc->send_io.lcredits.wait_queue,
+ &sc->send_io.lcredits.count,
+ 1);
+}
+
+static int smbdirect_connection_wait_for_send_credits(struct smbdirect_socket *sc,
+ struct smbdirect_send_batch *batch)
+{
+ if (batch && (batch->wr_cnt >= 16 || atomic_read(&sc->send_io.credits.count) <= 1)) {
+ int ret;
+
+ ret = smbdirect_connection_send_batch_flush(sc, batch, false);
+ if (ret)
+ return ret;
+ }
+
+ return smbdirect_socket_wait_for_credits(sc,
+ SMBDIRECT_SOCKET_CONNECTED,
+ -ENOTCONN,
+ &sc->send_io.credits.wait_queue,
+ &sc->send_io.credits.count,
+ 1);
+}
+
+static void smbdirect_connection_send_io_done(struct ib_cq *cq, struct ib_wc *wc);
+
+static int smbdirect_connection_post_send_io(struct smbdirect_socket *sc,
+ struct smbdirect_send_batch *batch,
+ struct smbdirect_send_io *msg)
+{
+ int i;
+
+ for (i = 0; i < msg->num_sge; i++)
+ ib_dma_sync_single_for_device(sc->ib.dev,
+ msg->sge[i].addr, msg->sge[i].length,
+ DMA_TO_DEVICE);
+
+ msg->cqe.done = smbdirect_connection_send_io_done;
+ msg->wr.wr_cqe = &msg->cqe;
+ msg->wr.opcode = IB_WR_SEND;
+ msg->wr.sg_list = &msg->sge[0];
+ msg->wr.num_sge = msg->num_sge;
+ msg->wr.next = NULL;
+
+ if (batch) {
+ msg->wr.send_flags = 0;
+ if (!list_empty(&batch->msg_list)) {
+ struct smbdirect_send_io *last;
+
+ last = list_last_entry(&batch->msg_list,
+ struct smbdirect_send_io,
+ sibling_list);
+ last->wr.next = &msg->wr;
+ }
+ list_add_tail(&msg->sibling_list, &batch->msg_list);
+ batch->wr_cnt++;
+ return 0;
+ }
+
+ msg->wr.send_flags = IB_SEND_SIGNALED;
+ return smbdirect_connection_post_send_wr(sc, &msg->wr);
+}
+
+int smbdirect_connection_send_single_iter(struct smbdirect_socket *sc,
+ struct smbdirect_send_batch *batch,
+ struct iov_iter *iter,
+ unsigned int flags,
+ u32 remaining_data_length)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_send_batch _batch;
+ struct smbdirect_send_io *msg;
+ struct smbdirect_data_transfer *packet;
+ size_t header_length;
+ u16 new_credits = 0;
+ u32 data_length = 0;
+ int ret;
+
+ if (WARN_ON_ONCE(flags))
+ return -EINVAL; /* no flags support for now */
+
+ if (iter) {
+ if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_SOURCE))
+ return -EINVAL; /* It's a bug in upper layer to get there */
+
+ header_length = sizeof(struct smbdirect_data_transfer);
+ if (WARN_ON_ONCE(remaining_data_length == 0 ||
+ iov_iter_count(iter) > remaining_data_length))
+ return -EINVAL;
+ } else {
+ /* If this is a packet without payload, don't send padding */
+ header_length = offsetof(struct smbdirect_data_transfer, padding);
+ if (WARN_ON_ONCE(remaining_data_length))
+ return -EINVAL;
+ }
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+ smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
+ "status=%s first_error=%1pe => %1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ SMBDIRECT_DEBUG_ERR_PTR(-ENOTCONN));
+ return -ENOTCONN;
+ }
+
+ if (!batch) {
+ smbdirect_connection_send_batch_init(&_batch, false, 0);
+ batch = &_batch;
+ }
+
+ ret = smbdirect_connection_wait_for_send_bcredit(sc, batch);
+ if (ret)
+ goto bcredit_failed;
+
+ ret = smbdirect_connection_wait_for_send_lcredit(sc, batch);
+ if (ret)
+ goto lcredit_failed;
+
+ ret = smbdirect_connection_wait_for_send_credits(sc, batch);
+ if (ret)
+ goto credit_failed;
+
+ new_credits = smbdirect_connection_grant_recv_credits(sc);
+ if (new_credits == 0 &&
+ atomic_read(&sc->send_io.credits.count) == 0 &&
+ atomic_read(&sc->recv_io.credits.count) == 0) {
+ /*
+ * queue the refill work in order to
+ * get some new recv credits we can grant to
+ * the peer.
+ */
+ queue_work(sc->workqueues.refill, &sc->recv_io.posted.refill_work);
+
+ /*
+ * wait until either the refill work or the peer
+ * granted new credits
+ */
+ ret = wait_event_interruptible(sc->send_io.credits.wait_queue,
+ atomic_read(&sc->send_io.credits.count) >= 1 ||
+ atomic_read(&sc->recv_io.credits.available) >= 1 ||
+ sc->status != SMBDIRECT_SOCKET_CONNECTED);
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
+ ret = -ENOTCONN;
+ if (ret < 0)
+ goto credit_failed;
+
+ new_credits = smbdirect_connection_grant_recv_credits(sc);
+ }
+
+ msg = smbdirect_connection_alloc_send_io(sc);
+ if (IS_ERR(msg)) {
+ ret = PTR_ERR(msg);
+ goto alloc_failed;
+ }
+
+ /* Map the packet to DMA */
+ msg->sge[0].addr = ib_dma_map_single(sc->ib.dev,
+ msg->packet,
+ header_length,
+ DMA_TO_DEVICE);
+ ret = ib_dma_mapping_error(sc->ib.dev, msg->sge[0].addr);
+ if (ret)
+ goto err;
+
+ msg->sge[0].length = header_length;
+ msg->sge[0].lkey = sc->ib.pd->local_dma_lkey;
+ msg->num_sge = 1;
+
+ if (iter) {
+ struct smbdirect_map_sges extract = {
+ .num_sge = msg->num_sge,
+ .max_sge = ARRAY_SIZE(msg->sge),
+ .sge = msg->sge,
+ .device = sc->ib.dev,
+ .local_dma_lkey = sc->ib.pd->local_dma_lkey,
+ .direction = DMA_TO_DEVICE,
+ };
+ size_t payload_len = umin(iov_iter_count(iter),
+ sp->max_send_size - sizeof(*packet));
+
+ ret = smbdirect_map_sges_from_iter(iter, payload_len, &extract);
+ if (ret < 0)
+ goto err;
+ data_length = ret;
+ remaining_data_length -= data_length;
+ msg->num_sge = extract.num_sge;
+ }
+
+ /* Fill in the packet header */
+ packet = (struct smbdirect_data_transfer *)msg->packet;
+ packet->credits_requested = cpu_to_le16(sp->send_credit_target);
+ packet->credits_granted = cpu_to_le16(new_credits);
+
+ packet->flags = 0;
+ if (smbdirect_connection_request_keep_alive(sc))
+ packet->flags |= cpu_to_le16(SMBDIRECT_FLAG_RESPONSE_REQUESTED);
+
+ packet->reserved = 0;
+ if (!data_length)
+ packet->data_offset = 0;
+ else
+ packet->data_offset = cpu_to_le32(24);
+ packet->data_length = cpu_to_le32(data_length);
+ packet->remaining_data_length = cpu_to_le32(remaining_data_length);
+ packet->padding = 0;
+
+ smbdirect_log_outgoing(sc, SMBDIRECT_LOG_INFO,
+ "DataOut: %s=%u, %s=%u, %s=0x%x, %s=%u, %s=%u, %s=%u\n",
+ "CreditsRequested",
+ le16_to_cpu(packet->credits_requested),
+ "CreditsGranted",
+ le16_to_cpu(packet->credits_granted),
+ "Flags",
+ le16_to_cpu(packet->flags),
+ "RemainingDataLength",
+ le32_to_cpu(packet->remaining_data_length),
+ "DataOffset",
+ le32_to_cpu(packet->data_offset),
+ "DataLength",
+ le32_to_cpu(packet->data_length));
+
+ ret = smbdirect_connection_post_send_io(sc, batch, msg);
+ if (ret)
+ goto err;
+
+ /*
+ * From here msg is moved to send_ctx
+ * and we should not free it explicitly.
+ */
+
+ if (batch == &_batch) {
+ ret = smbdirect_connection_send_batch_flush(sc, batch, true);
+ if (ret)
+ goto flush_failed;
+ }
+
+ return data_length;
+err:
+ smbdirect_connection_free_send_io(msg);
+flush_failed:
+alloc_failed:
+ atomic_inc(&sc->send_io.credits.count);
+credit_failed:
+ atomic_inc(&sc->send_io.lcredits.count);
+lcredit_failed:
+ atomic_add(batch->credit, &sc->send_io.bcredits.count);
+ batch->credit = 0;
+bcredit_failed:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_send_single_iter);
+
+int smbdirect_connection_send_wait_zero_pending(struct smbdirect_socket *sc)
+{
+ /*
+ * As an optimization, we don't wait for individual I/O to finish
+ * before sending the next one.
+ * Send them all and wait for pending send count to get to 0
+ * that means all the I/Os have been out and we are good to return
+ */
+
+ wait_event(sc->send_io.pending.zero_wait_queue,
+ atomic_read(&sc->send_io.pending.count) == 0 ||
+ sc->status != SMBDIRECT_SOCKET_CONNECTED);
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+ smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
+ "status=%s first_error=%1pe => %1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ SMBDIRECT_DEBUG_ERR_PTR(-ENOTCONN));
+ return -ENOTCONN;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_send_wait_zero_pending);
+
+int smbdirect_connection_send_iter(struct smbdirect_socket *sc,
+ struct iov_iter *iter,
+ unsigned int flags,
+ bool need_invalidate,
+ unsigned int remote_key)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_send_batch batch;
+ int total_count = iov_iter_count(iter);
+ int ret;
+ int error = 0;
+ __be32 hdr;
+
+ if (WARN_ONCE(flags, "unexpected flags=0x%x\n", flags))
+ return -EINVAL; /* no flags support for now */
+
+ if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_SOURCE))
+ return -EINVAL; /* It's a bug in upper layer to get there */
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+ smbdirect_log_write(sc, SMBDIRECT_LOG_INFO,
+ "status=%s first_error=%1pe => %1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ SMBDIRECT_DEBUG_ERR_PTR(-ENOTCONN));
+ return -ENOTCONN;
+ }
+
+ /*
+ * For now we expect the iter to have the full
+ * message, including a 4 byte length header.
+ */
+ if (iov_iter_count(iter) <= 4)
+ return -EINVAL;
+ if (!copy_from_iter_full(&hdr, sizeof(hdr), iter))
+ return -EFAULT;
+ if (iov_iter_count(iter) != be32_to_cpu(hdr))
+ return -EINVAL;
+
+ /*
+ * The size must fit into the negotiated
+ * fragmented send size.
+ */
+ if (iov_iter_count(iter) > sp->max_fragmented_send_size)
+ return -EMSGSIZE;
+
+ smbdirect_log_write(sc, SMBDIRECT_LOG_INFO,
+ "Sending (RDMA): length=%zu\n",
+ iov_iter_count(iter));
+
+ smbdirect_connection_send_batch_init(&batch, need_invalidate, remote_key);
+ while (iov_iter_count(iter)) {
+ ret = smbdirect_connection_send_single_iter(sc,
+ &batch,
+ iter,
+ flags,
+ iov_iter_count(iter));
+ if (unlikely(ret < 0)) {
+ error = ret;
+ break;
+ }
+ }
+
+ ret = smbdirect_connection_send_batch_flush(sc, &batch, true);
+ if (unlikely(ret && !error))
+ error = ret;
+
+ /*
+ * As an optimization, we don't wait for individual I/O to finish
+ * before sending the next one.
+ * Send them all and wait for pending send count to get to 0
+ * that means all the I/Os have been out and we are good to return
+ */
+
+ ret = smbdirect_connection_send_wait_zero_pending(sc);
+ if (unlikely(ret && !error))
+ error = ret;
+
+ if (unlikely(error))
+ return error;
+
+ return total_count;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_send_iter);
+
+static void smbdirect_connection_send_io_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ struct smbdirect_send_io *msg =
+ container_of(wc->wr_cqe, struct smbdirect_send_io, cqe);
+ struct smbdirect_socket *sc = msg->socket;
+ struct smbdirect_send_io *sibling, *next;
+ int lcredits = 0;
+
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_INFO,
+ "smbdirect_send_io completed. status='%s (%d)', opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+
+ if (unlikely(!(msg->wr.send_flags & IB_SEND_SIGNALED))) {
+ /*
+ * This happens when smbdirect_send_io is a sibling
+ * before the final message, it is signaled on
+ * error anyway, so we need to skip
+ * smbdirect_connection_free_send_io here,
+ * otherwise is will destroy the memory
+ * of the siblings too, which will cause
+ * use after free problems for the others
+ * triggered from ib_drain_qp().
+ */
+ if (wc->status != IB_WC_SUCCESS)
+ goto skip_free;
+
+ /*
+ * This should not happen!
+ * But we better just close the
+ * connection...
+ */
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_ERR,
+ "unexpected send completion wc->status=%s (%d) wc->opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ /*
+ * Free possible siblings and then the main send_io
+ */
+ list_for_each_entry_safe(sibling, next, &msg->sibling_list, sibling_list) {
+ list_del_init(&sibling->sibling_list);
+ smbdirect_connection_free_send_io(sibling);
+ lcredits += 1;
+ }
+ /* Note this frees wc->wr_cqe, but not wc */
+ smbdirect_connection_free_send_io(msg);
+ lcredits += 1;
+
+ if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_SEND))) {
+skip_free:
+ if (wc->status != IB_WC_WR_FLUSH_ERR)
+ smbdirect_log_rdma_send(sc, SMBDIRECT_LOG_ERR,
+ "wc->status=%s (%d) wc->opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ return;
+ }
+
+ atomic_add(lcredits, &sc->send_io.lcredits.count);
+ wake_up(&sc->send_io.lcredits.wait_queue);
+
+ if (atomic_dec_and_test(&sc->send_io.pending.count))
+ wake_up(&sc->send_io.pending.zero_wait_queue);
+}
+
+static void smbdirect_connection_send_immediate_work(struct work_struct *work)
+{
+ struct smbdirect_socket *sc =
+ container_of(work, struct smbdirect_socket, idle.immediate_work);
+ int ret;
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
+ return;
+
+ smbdirect_log_keep_alive(sc, SMBDIRECT_LOG_INFO,
+ "send an empty message\n");
+ sc->statistics.send_empty++;
+ ret = smbdirect_connection_send_single_iter(sc, NULL, NULL, 0, 0);
+ if (ret < 0) {
+ smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_send_single_iter ret=%1pe\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ }
+}
+
+int smbdirect_connection_post_recv_io(struct smbdirect_recv_io *msg)
+{
+ struct smbdirect_socket *sc = msg->socket;
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct ib_recv_wr recv_wr = {
+ .wr_cqe = &msg->cqe,
+ .sg_list = &msg->sge,
+ .num_sge = 1,
+ };
+ int ret;
+
+ if (unlikely(sc->first_error))
+ return sc->first_error;
+
+ msg->sge.addr = ib_dma_map_single(sc->ib.dev,
+ msg->packet,
+ sp->max_recv_size,
+ DMA_FROM_DEVICE);
+ ret = ib_dma_mapping_error(sc->ib.dev, msg->sge.addr);
+ if (ret)
+ return ret;
+
+ msg->sge.length = sp->max_recv_size;
+ msg->sge.lkey = sc->ib.pd->local_dma_lkey;
+
+ ret = ib_post_recv(sc->ib.qp, &recv_wr, NULL);
+ if (ret) {
+ smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR,
+ "ib_post_recv failed ret=%d (%1pe)\n",
+ ret, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ ib_dma_unmap_single(sc->ib.dev,
+ msg->sge.addr,
+ msg->sge.length,
+ DMA_FROM_DEVICE);
+ msg->sge.length = 0;
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ }
+
+ return ret;
+}
+
+void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ struct smbdirect_recv_io *recv_io =
+ container_of(wc->wr_cqe, struct smbdirect_recv_io, cqe);
+ struct smbdirect_socket *sc = recv_io->socket;
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_data_transfer *data_transfer;
+ int current_recv_credits;
+ u16 old_recv_credit_target;
+ u16 credits_requested;
+ u16 credits_granted;
+ u16 flags;
+ u32 data_offset;
+ u32 data_length;
+ u32 remaining_data_length;
+
+ if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) {
+ if (wc->status != IB_WC_WR_FLUSH_ERR)
+ smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR,
+ "wc->status=%s (%d) wc->opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
+ goto error;
+ }
+
+ smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_INFO,
+ "recv_io=0x%p type=%d wc status=%s wc opcode %d byte_len=%d pkey_index=%u\n",
+ recv_io, sc->recv_io.expected,
+ ib_wc_status_msg(wc->status), wc->opcode,
+ wc->byte_len, wc->pkey_index);
+
+ /*
+ * Reset timer to the keepalive interval in
+ * order to trigger our next keepalive message.
+ */
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
+ mod_delayed_work(sc->workqueues.idle, &sc->idle.timer_work,
+ msecs_to_jiffies(sp->keepalive_interval_msec));
+
+ ib_dma_sync_single_for_cpu(sc->ib.dev,
+ recv_io->sge.addr,
+ recv_io->sge.length,
+ DMA_FROM_DEVICE);
+
+ if (unlikely(wc->byte_len <
+ offsetof(struct smbdirect_data_transfer, padding))) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "wc->byte_len=%u < %zu\n",
+ wc->byte_len,
+ offsetof(struct smbdirect_data_transfer, padding));
+ goto error;
+ }
+
+ data_transfer = (struct smbdirect_data_transfer *)recv_io->packet;
+ credits_requested = le16_to_cpu(data_transfer->credits_requested);
+ credits_granted = le16_to_cpu(data_transfer->credits_granted);
+ flags = le16_to_cpu(data_transfer->flags);
+ remaining_data_length = le32_to_cpu(data_transfer->remaining_data_length);
+ data_offset = le32_to_cpu(data_transfer->data_offset);
+ data_length = le32_to_cpu(data_transfer->data_length);
+
+ smbdirect_log_incoming(sc, SMBDIRECT_LOG_INFO,
+ "DataIn: %s=%u, %s=%u, %s=0x%x, %s=%u, %s=%u, %s=%u\n",
+ "CreditsRequested",
+ credits_requested,
+ "CreditsGranted",
+ credits_granted,
+ "Flags",
+ flags,
+ "RemainingDataLength",
+ remaining_data_length,
+ "DataOffset",
+ data_offset,
+ "DataLength",
+ data_length);
+
+ if (unlikely(credits_requested == 0)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: credits_requested == 0\n");
+ goto error;
+ }
+
+ if (unlikely(data_offset % 8 != 0)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "invalid: data_offset=%u (0x%x) not aligned to 8\n",
+ data_offset, data_offset);
+ goto error;
+ }
+
+ if (unlikely(wc->byte_len < data_offset ||
+ (u64)wc->byte_len < (u64)data_offset + data_length)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "wc->byte_len=%u < date_offset=%u + data_length=%u\n",
+ wc->byte_len, data_offset, data_length);
+ goto error;
+ }
+
+ if (unlikely(remaining_data_length > sp->max_fragmented_recv_size ||
+ data_length > sp->max_fragmented_recv_size ||
+ (u64)remaining_data_length + (u64)data_length > (u64)sp->max_fragmented_recv_size)) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "remaining_data_length=%u + data_length=%u > max_fragmented=%u\n",
+ remaining_data_length, data_length, sp->max_fragmented_recv_size);
+ goto error;
+ }
+
+ if (data_length) {
+ if (sc->recv_io.reassembly.full_packet_received)
+ recv_io->first_segment = true;
+
+ if (remaining_data_length)
+ sc->recv_io.reassembly.full_packet_received = false;
+ else
+ sc->recv_io.reassembly.full_packet_received = true;
+ }
+
+ atomic_dec(&sc->recv_io.posted.count);
+ current_recv_credits = atomic_dec_return(&sc->recv_io.credits.count);
+
+ /*
+ * We take the value from the peer, which is checked to be higher than 0,
+ * but we limit it to the max value we support in order to have
+ * the main logic simpler.
+ */
+ old_recv_credit_target = sc->recv_io.credits.target;
+ sc->recv_io.credits.target = credits_requested;
+ sc->recv_io.credits.target = min_t(u16, sc->recv_io.credits.target,
+ sp->recv_credit_max);
+ if (credits_granted) {
+ atomic_add(credits_granted, &sc->send_io.credits.count);
+ /*
+ * We have new send credits granted from remote peer
+ * If any sender is waiting for credits, unblock it
+ */
+ wake_up(&sc->send_io.credits.wait_queue);
+ }
+
+ /* Send an immediate response right away if requested */
+ if (flags & SMBDIRECT_FLAG_RESPONSE_REQUESTED) {
+ smbdirect_log_keep_alive(sc, SMBDIRECT_LOG_INFO,
+ "schedule send of immediate response\n");
+ queue_work(sc->workqueues.immediate, &sc->idle.immediate_work);
+ }
+
+ /*
+ * If this is a packet with data playload place the data in
+ * reassembly queue and wake up the reading thread
+ */
+ if (data_length) {
+ if (current_recv_credits <= (sc->recv_io.credits.target / 4) ||
+ sc->recv_io.credits.target > old_recv_credit_target)
+ queue_work(sc->workqueues.refill, &sc->recv_io.posted.refill_work);
+
+ smbdirect_connection_reassembly_append_recv_io(sc, recv_io, data_length);
+ wake_up(&sc->recv_io.reassembly.wait_queue);
+ } else
+ smbdirect_connection_put_recv_io(recv_io);
+
+ return;
+
+error:
+ /*
+ * Make sure smbdirect_connection_put_recv_io() does not
+ * start recv_io.posted.refill_work.
+ */
+ disable_work(&sc->recv_io.posted.refill_work);
+ smbdirect_connection_put_recv_io(recv_io);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+}
+
+int smbdirect_connection_recv_io_refill(struct smbdirect_socket *sc)
+{
+ int missing;
+ int posted = 0;
+
+ if (unlikely(sc->first_error))
+ return sc->first_error;
+
+ /*
+ * Find out how much smbdirect_recv_io buffers we should post.
+ *
+ * Note that sc->recv_io.credits.target is the value
+ * from the peer and it can in theory change over time,
+ * but it is forced to be at least 1 and at max
+ * sp->recv_credit_max.
+ *
+ * So it can happen that missing will be lower than 0,
+ * which means the peer has recently lowered its desired
+ * target, while be already granted a higher number of credits.
+ *
+ * Note 'posted' is the number of smbdirect_recv_io buffers
+ * posted within this function, while sc->recv_io.posted.count
+ * is the overall value of posted smbdirect_recv_io buffers.
+ *
+ * We try to post as much buffers as missing, but
+ * this is limited if a lot of smbdirect_recv_io buffers
+ * are still in the sc->recv_io.reassembly.list instead of
+ * the sc->recv_io.free.list.
+ *
+ */
+ missing = (int)sc->recv_io.credits.target - atomic_read(&sc->recv_io.posted.count);
+ while (posted < missing) {
+ struct smbdirect_recv_io *recv_io;
+ int ret;
+
+ /*
+ * It's ok if smbdirect_connection_get_recv_io()
+ * returns NULL, it means smbdirect_recv_io structures
+ * are still be in the reassembly.list.
+ */
+ recv_io = smbdirect_connection_get_recv_io(sc);
+ if (!recv_io)
+ break;
+
+ recv_io->first_segment = false;
+
+ ret = smbdirect_connection_post_recv_io(recv_io);
+ if (ret) {
+ smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_post_recv_io failed rc=%d (%1pe)\n",
+ ret, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ smbdirect_connection_put_recv_io(recv_io);
+ return ret;
+ }
+
+ atomic_inc(&sc->recv_io.posted.count);
+ posted += 1;
+ }
+
+ /* If nothing was posted we're done */
+ if (posted == 0)
+ return 0;
+
+ atomic_add(posted, &sc->recv_io.credits.available);
+
+ /*
+ * If the last send credit is waiting for credits
+ * it can grant we need to wake it up
+ */
+ if (atomic_read(&sc->send_io.bcredits.count) == 0 &&
+ atomic_read(&sc->send_io.credits.count) == 0)
+ wake_up(&sc->send_io.credits.wait_queue);
+
+ /*
+ * If we posted at least one smbdirect_recv_io buffer,
+ * we need to inform the peer about it and grant
+ * additional credits.
+ *
+ * However there is one case where we don't want to
+ * do that.
+ *
+ * If only a single credit was missing before
+ * reaching the requested target, we should not
+ * post an immediate send, as that would cause
+ * endless ping pong once a keep alive exchange
+ * is started.
+ *
+ * However if sc->recv_io.credits.target is only 1,
+ * the peer has no credit left and we need to
+ * grant the credit anyway.
+ */
+ if (missing == 1 && sc->recv_io.credits.target != 1)
+ return 0;
+
+ return posted;
+}
+
+static void smbdirect_connection_recv_io_refill_work(struct work_struct *work)
+{
+ struct smbdirect_socket *sc =
+ container_of(work, struct smbdirect_socket, recv_io.posted.refill_work);
+ int posted;
+
+ posted = smbdirect_connection_recv_io_refill(sc);
+ if (unlikely(posted < 0)) {
+ smbdirect_socket_schedule_cleanup(sc, posted);
+ return;
+ }
+ if (posted > 0) {
+ smbdirect_log_keep_alive(sc, SMBDIRECT_LOG_INFO,
+ "schedule send of an empty message\n");
+ queue_work(sc->workqueues.immediate, &sc->idle.immediate_work);
+ }
+}
+
+int smbdirect_connection_recvmsg(struct smbdirect_socket *sc,
+ struct msghdr *msg,
+ unsigned int flags)
+{
+ struct smbdirect_recv_io *response;
+ struct smbdirect_data_transfer *data_transfer;
+ size_t size = iov_iter_count(&msg->msg_iter);
+ int to_copy, to_read, data_read, offset;
+ u32 data_length, remaining_data_length, data_offset;
+ int ret;
+
+ if (WARN_ONCE(flags, "unexpected flags=0x%x\n", flags))
+ return -EINVAL; /* no flags support for now */
+
+ if (WARN_ON_ONCE(iov_iter_rw(&msg->msg_iter) != ITER_DEST))
+ return -EINVAL; /* It's a bug in upper layer to get there */
+
+again:
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+ smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
+ "status=%s first_error=%1pe => %1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ SMBDIRECT_DEBUG_ERR_PTR(-ENOTCONN));
+ return -ENOTCONN;
+ }
+
+ /*
+ * No need to hold the reassembly queue lock all the time as we are
+ * the only one reading from the front of the queue. The transport
+ * may add more entries to the back of the queue at the same time
+ */
+ smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
+ "size=%zd sc->recv_io.reassembly.data_length=%d\n",
+ size, sc->recv_io.reassembly.data_length);
+ if (sc->recv_io.reassembly.data_length >= size) {
+ int queue_length;
+ int queue_removed = 0;
+ unsigned long flags;
+
+ /*
+ * Need to make sure reassembly_data_length is read before
+ * reading reassembly_queue_length and calling
+ * smbdirect_connection_reassembly_first_recv_io. This call is lock free
+ * as we never read at the end of the queue which are being
+ * updated in SOFTIRQ as more data is received
+ */
+ virt_rmb();
+ queue_length = sc->recv_io.reassembly.queue_length;
+ data_read = 0;
+ to_read = size;
+ offset = sc->recv_io.reassembly.first_entry_offset;
+ while (data_read < size) {
+ response = smbdirect_connection_reassembly_first_recv_io(sc);
+ data_transfer = (void *)response->packet;
+ data_length = le32_to_cpu(data_transfer->data_length);
+ remaining_data_length =
+ le32_to_cpu(
+ data_transfer->remaining_data_length);
+ data_offset = le32_to_cpu(data_transfer->data_offset);
+
+ /*
+ * The upper layer expects RFC1002 length at the
+ * beginning of the payload. Return it to indicate
+ * the total length of the packet. This minimize the
+ * change to upper layer packet processing logic. This
+ * will be eventually remove when an intermediate
+ * transport layer is added
+ */
+ if (response->first_segment && size == 4) {
+ unsigned int rfc1002_len =
+ data_length + remaining_data_length;
+ __be32 rfc1002_hdr = cpu_to_be32(rfc1002_len);
+
+ if (copy_to_iter(&rfc1002_hdr, sizeof(rfc1002_hdr),
+ &msg->msg_iter) != sizeof(rfc1002_hdr))
+ return -EFAULT;
+ data_read = 4;
+ response->first_segment = false;
+ smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
+ "returning rfc1002 length %d\n",
+ rfc1002_len);
+ goto read_rfc1002_done;
+ }
+
+ to_copy = min_t(int, data_length - offset, to_read);
+ if (copy_to_iter((u8 *)data_transfer + data_offset + offset,
+ to_copy, &msg->msg_iter) != to_copy)
+ return -EFAULT;
+
+ /* move on to the next buffer? */
+ if (to_copy == data_length - offset) {
+ queue_length--;
+ /*
+ * No need to lock if we are not at the
+ * end of the queue
+ */
+ if (queue_length)
+ list_del(&response->list);
+ else {
+ spin_lock_irqsave(
+ &sc->recv_io.reassembly.lock, flags);
+ list_del(&response->list);
+ spin_unlock_irqrestore(
+ &sc->recv_io.reassembly.lock, flags);
+ }
+ queue_removed++;
+ sc->statistics.dequeue_reassembly_queue++;
+ smbdirect_connection_put_recv_io(response);
+ offset = 0;
+ smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
+ "smbdirect_connection_put_recv_io offset=0\n");
+ } else
+ offset += to_copy;
+
+ to_read -= to_copy;
+ data_read += to_copy;
+
+ smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
+ "memcpy %d bytes len-ofs=%u => todo=%u done=%u ofs=%u\n",
+ to_copy, data_length - offset,
+ to_read, data_read, offset);
+ }
+
+ spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
+ sc->recv_io.reassembly.data_length -= data_read;
+ sc->recv_io.reassembly.queue_length -= queue_removed;
+ spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
+
+ sc->recv_io.reassembly.first_entry_offset = offset;
+ smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
+ "returning data_read=%d reassembly_length=%d first_ofs=%u\n",
+ data_read, sc->recv_io.reassembly.data_length,
+ sc->recv_io.reassembly.first_entry_offset);
+read_rfc1002_done:
+ return data_read;
+ }
+
+ smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
+ "wait_event on more data\n");
+ ret = wait_event_interruptible(sc->recv_io.reassembly.wait_queue,
+ sc->recv_io.reassembly.data_length >= size ||
+ sc->status != SMBDIRECT_SOCKET_CONNECTED);
+ /* Don't return any data if interrupted */
+ if (ret)
+ return ret;
+
+ goto again;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_recvmsg);
+
+static bool smbdirect_map_sges_single_page(struct smbdirect_map_sges *state,
+ struct page *page, size_t off, size_t len)
+{
+ struct ib_sge *sge;
+ u64 addr;
+
+ if (state->num_sge >= state->max_sge)
+ return false;
+
+ addr = ib_dma_map_page(state->device, page,
+ off, len, state->direction);
+ if (ib_dma_mapping_error(state->device, addr))
+ return false;
+
+ sge = &state->sge[state->num_sge++];
+ sge->addr = addr;
+ sge->length = len;
+ sge->lkey = state->local_dma_lkey;
+
+ return true;
+}
+
+/*
+ * Extract page fragments from a BVEC-class iterator and add them to an ib_sge
+ * list. The pages are not pinned.
+ */
+static ssize_t smbdirect_map_sges_from_bvec(struct iov_iter *iter,
+ struct smbdirect_map_sges *state,
+ ssize_t maxsize)
+{
+ const struct bio_vec *bv = iter->bvec;
+ unsigned long start = iter->iov_offset;
+ unsigned int i;
+ ssize_t ret = 0;
+
+ for (i = 0; i < iter->nr_segs; i++) {
+ size_t off, len;
+ bool ok;
+
+ len = bv[i].bv_len;
+ if (start >= len) {
+ start -= len;
+ continue;
+ }
+
+ len = min_t(size_t, maxsize, len - start);
+ off = bv[i].bv_offset + start;
+
+ ok = smbdirect_map_sges_single_page(state,
+ bv[i].bv_page,
+ off,
+ len);
+ if (!ok)
+ return -EIO;
+
+ ret += len;
+ maxsize -= len;
+ if (state->num_sge >= state->max_sge || maxsize <= 0)
+ break;
+ start = 0;
+ }
+
+ if (ret > 0)
+ iov_iter_advance(iter, ret);
+ return ret;
+}
+
+/*
+ * Extract fragments from a KVEC-class iterator and add them to an ib_sge list.
+ * This can deal with vmalloc'd buffers as well as kmalloc'd or static buffers.
+ * The pages are not pinned.
+ */
+static ssize_t smbdirect_map_sges_from_kvec(struct iov_iter *iter,
+ struct smbdirect_map_sges *state,
+ ssize_t maxsize)
+{
+ const struct kvec *kv = iter->kvec;
+ unsigned long start = iter->iov_offset;
+ unsigned int i;
+ ssize_t ret = 0;
+
+ for (i = 0; i < iter->nr_segs; i++) {
+ struct page *page;
+ unsigned long kaddr;
+ size_t off, len, seg;
+
+ len = kv[i].iov_len;
+ if (start >= len) {
+ start -= len;
+ continue;
+ }
+
+ kaddr = (unsigned long)kv[i].iov_base + start;
+ off = kaddr & ~PAGE_MASK;
+ len = min_t(size_t, maxsize, len - start);
+ kaddr &= PAGE_MASK;
+
+ maxsize -= len;
+ do {
+ bool ok;
+
+ seg = min_t(size_t, len, PAGE_SIZE - off);
+
+ if (is_vmalloc_or_module_addr((void *)kaddr))
+ page = vmalloc_to_page((void *)kaddr);
+ else
+ page = virt_to_page((void *)kaddr);
+
+ ok = smbdirect_map_sges_single_page(state, page, off, seg);
+ if (!ok)
+ return -EIO;
+
+ ret += seg;
+ len -= seg;
+ kaddr += PAGE_SIZE;
+ off = 0;
+ } while (len > 0 && state->num_sge < state->max_sge);
+
+ if (state->num_sge >= state->max_sge || maxsize <= 0)
+ break;
+ start = 0;
+ }
+
+ if (ret > 0)
+ iov_iter_advance(iter, ret);
+ return ret;
+}
+
+/*
+ * Extract folio fragments from a FOLIOQ-class iterator and add them to an
+ * ib_sge list. The folios are not pinned.
+ */
+static ssize_t smbdirect_map_sges_from_folioq(struct iov_iter *iter,
+ struct smbdirect_map_sges *state,
+ ssize_t maxsize)
+{
+ const struct folio_queue *folioq = iter->folioq;
+ unsigned int slot = iter->folioq_slot;
+ ssize_t ret = 0;
+ size_t offset = iter->iov_offset;
+
+ if (WARN_ON_ONCE(!folioq))
+ return -EIO;
+
+ if (slot >= folioq_nr_slots(folioq)) {
+ folioq = folioq->next;
+ if (WARN_ON_ONCE(!folioq))
+ return -EIO;
+ slot = 0;
+ }
+
+ do {
+ struct folio *folio = folioq_folio(folioq, slot);
+ size_t fsize = folioq_folio_size(folioq, slot);
+
+ if (offset < fsize) {
+ size_t part = umin(maxsize, fsize - offset);
+ bool ok;
+
+ ok = smbdirect_map_sges_single_page(state,
+ folio_page(folio, 0),
+ offset,
+ part);
+ if (!ok)
+ return -EIO;
+
+ offset += part;
+ ret += part;
+ maxsize -= part;
+ }
+
+ if (offset >= fsize) {
+ offset = 0;
+ slot++;
+ if (slot >= folioq_nr_slots(folioq)) {
+ if (!folioq->next) {
+ WARN_ON_ONCE(ret < iter->count);
+ break;
+ }
+ folioq = folioq->next;
+ slot = 0;
+ }
+ }
+ } while (state->num_sge < state->max_sge && maxsize > 0);
+
+ iter->folioq = folioq;
+ iter->folioq_slot = slot;
+ iter->iov_offset = offset;
+ iter->count -= ret;
+ return ret;
+}
+
+/*
+ * Extract page fragments from up to the given amount of the source iterator
+ * and build up an ib_sge list that refers to all of those bits. The ib_sge list
+ * is appended to, up to the maximum number of elements set in the parameter
+ * block.
+ *
+ * The extracted page fragments are not pinned or ref'd in any way; if an
+ * IOVEC/UBUF-type iterator is to be used, it should be converted to a
+ * BVEC-type iterator and the pages pinned, ref'd or otherwise held in some
+ * way.
+ */
+static ssize_t smbdirect_map_sges_from_iter(struct iov_iter *iter, size_t len,
+ struct smbdirect_map_sges *state)
+{
+ ssize_t ret;
+ size_t before = state->num_sge;
+
+ if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_SOURCE))
+ return -EIO;
+
+ switch (iov_iter_type(iter)) {
+ case ITER_BVEC:
+ ret = smbdirect_map_sges_from_bvec(iter, state, len);
+ break;
+ case ITER_KVEC:
+ ret = smbdirect_map_sges_from_kvec(iter, state, len);
+ break;
+ case ITER_FOLIOQ:
+ ret = smbdirect_map_sges_from_folioq(iter, state, len);
+ break;
+ default:
+ WARN_ONCE(1, "iov_iter_type[%u]\n", iov_iter_type(iter));
+ return -EIO;
+ }
+
+ if (ret < 0) {
+ while (state->num_sge > before) {
+ struct ib_sge *sge = &state->sge[--state->num_sge];
+
+ ib_dma_unmap_page(state->device,
+ sge->addr,
+ sge->length,
+ state->direction);
+ }
+ }
+
+ return ret;
+}
diff --git a/fs/smb/smbdirect/debug.c b/fs/smb/smbdirect/debug.c
new file mode 100644
index 000000000000..3445843445bf
--- /dev/null
+++ b/fs/smb/smbdirect/debug.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+#include <linux/seq_file.h>
+
+void smbdirect_connection_legacy_debug_proc_show(struct smbdirect_socket *sc,
+ unsigned int rdma_readwrite_threshold,
+ struct seq_file *m)
+{
+ const struct smbdirect_socket_parameters *sp;
+
+ if (!sc)
+ return;
+ sp = &sc->parameters;
+
+ seq_puts(m, "\n");
+ seq_printf(m, "SMBDirect protocol version: 0x%x ",
+ SMBDIRECT_V1);
+ seq_printf(m, "transport status: %s (%u)",
+ smbdirect_socket_status_string(sc->status),
+ sc->status);
+
+ seq_puts(m, "\n");
+ seq_printf(m, "Conn receive_credit_max: %u ",
+ sp->recv_credit_max);
+ seq_printf(m, "send_credit_target: %u max_send_size: %u",
+ sp->send_credit_target,
+ sp->max_send_size);
+
+ seq_puts(m, "\n");
+ seq_printf(m, "Conn max_fragmented_recv_size: %u ",
+ sp->max_fragmented_recv_size);
+ seq_printf(m, "max_fragmented_send_size: %u max_receive_size:%u",
+ sp->max_fragmented_send_size,
+ sp->max_recv_size);
+
+ seq_puts(m, "\n");
+ seq_printf(m, "Conn keep_alive_interval: %u ",
+ sp->keepalive_interval_msec / 1000);
+ seq_printf(m, "max_readwrite_size: %u rdma_readwrite_threshold: %u",
+ sp->max_read_write_size,
+ rdma_readwrite_threshold);
+
+ seq_puts(m, "\n");
+ seq_printf(m, "Debug count_get_receive_buffer: %llu ",
+ sc->statistics.get_receive_buffer);
+ seq_printf(m, "count_put_receive_buffer: %llu count_send_empty: %llu",
+ sc->statistics.put_receive_buffer,
+ sc->statistics.send_empty);
+
+ seq_puts(m, "\n");
+ seq_printf(m, "Read Queue count_enqueue_reassembly_queue: %llu ",
+ sc->statistics.enqueue_reassembly_queue);
+ seq_printf(m, "count_dequeue_reassembly_queue: %llu ",
+ sc->statistics.dequeue_reassembly_queue);
+ seq_printf(m, "reassembly_data_length: %u ",
+ sc->recv_io.reassembly.data_length);
+ seq_printf(m, "reassembly_queue_length: %u",
+ sc->recv_io.reassembly.queue_length);
+
+ seq_puts(m, "\n");
+ seq_printf(m, "Current Credits send_credits: %u ",
+ atomic_read(&sc->send_io.credits.count));
+ seq_printf(m, "receive_credits: %u receive_credit_target: %u",
+ atomic_read(&sc->recv_io.credits.count),
+ sc->recv_io.credits.target);
+
+ seq_puts(m, "\n");
+ seq_printf(m, "Pending send_pending: %u ",
+ atomic_read(&sc->send_io.pending.count));
+
+ seq_puts(m, "\n");
+ seq_printf(m, "MR responder_resources: %u ",
+ sp->responder_resources);
+ seq_printf(m, "max_frmr_depth: %u mr_type: 0x%x",
+ sp->max_frmr_depth,
+ sc->mr_io.type);
+
+ seq_puts(m, "\n");
+ seq_printf(m, "MR mr_ready_count: %u mr_used_count: %u",
+ atomic_read(&sc->mr_io.ready.count),
+ atomic_read(&sc->mr_io.used.count));
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_legacy_debug_proc_show);
diff --git a/fs/smb/smbdirect/devices.c b/fs/smb/smbdirect/devices.c
new file mode 100644
index 000000000000..7adacbdfe12e
--- /dev/null
+++ b/fs/smb/smbdirect/devices.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (C) 2018, LG Electronics.
+ * Copyright (c) 2025 Stefan Metzmacher
+ */
+
+#include "internal.h"
+
+static u8 smbdirect_ib_device_rdma_capable_node_type(struct ib_device *ib_dev)
+{
+ if (!smbdirect_frwr_is_supported(&ib_dev->attrs))
+ return RDMA_NODE_UNSPECIFIED;
+
+ switch (ib_dev->node_type) {
+ case RDMA_NODE_IB_CA: /* Infiniband, RoCE v1 and v2 */
+ case RDMA_NODE_RNIC: /* iWarp */
+ return ib_dev->node_type;
+ }
+
+ return RDMA_NODE_UNSPECIFIED;
+}
+
+static int smbdirect_ib_client_add(struct ib_device *ib_dev)
+{
+ u8 node_type = smbdirect_ib_device_rdma_capable_node_type(ib_dev);
+ struct smbdirect_device *sdev;
+ const char *node_str;
+ const char *action;
+ u32 pidx;
+
+ switch (node_type) {
+ case RDMA_NODE_IB_CA:
+ node_str = "IB_CA";
+ action = "added";
+ break;
+ case RDMA_NODE_RNIC:
+ node_str = "RNIC";
+ action = "added";
+ break;
+ case RDMA_NODE_UNSPECIFIED:
+ node_str = "UNSPECIFIED";
+ action = "ignored";
+ break;
+ default:
+ node_str = "UNKNOWN";
+ action = "ignored";
+ node_type = RDMA_NODE_UNSPECIFIED;
+ break;
+ }
+
+ pr_info("ib_dev[%.*s]: %s: %s %s=%u %s=0x%llx %s=0x%llx %s=0x%llx\n",
+ IB_DEVICE_NAME_MAX,
+ ib_dev->name,
+ action,
+ node_str,
+ "max_fast_reg_page_list_len",
+ ib_dev->attrs.max_fast_reg_page_list_len,
+ "device_cap_flags",
+ ib_dev->attrs.device_cap_flags,
+ "kernel_cap_flags",
+ ib_dev->attrs.kernel_cap_flags,
+ "page_size_cap",
+ ib_dev->attrs.page_size_cap);
+
+ if (node_type == RDMA_NODE_UNSPECIFIED)
+ return 0;
+
+ pr_info("ib_dev[%.*s]: %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u\n",
+ IB_DEVICE_NAME_MAX,
+ ib_dev->name,
+ "num_ports",
+ rdma_end_port(ib_dev),
+ "max_qp_rd_atom",
+ ib_dev->attrs.max_qp_rd_atom,
+ "max_qp_init_rd_atom",
+ ib_dev->attrs.max_qp_init_rd_atom,
+ "max_sgl_rd",
+ ib_dev->attrs.max_sgl_rd,
+ "max_sge_rd",
+ ib_dev->attrs.max_sge_rd,
+ "max_cqe",
+ ib_dev->attrs.max_cqe,
+ "max_qp_wr",
+ ib_dev->attrs.max_qp_wr,
+ "max_send_sge",
+ ib_dev->attrs.max_send_sge,
+ "max_recv_sge",
+ ib_dev->attrs.max_recv_sge);
+
+ rdma_for_each_port(ib_dev, pidx) {
+ const struct ib_port_immutable *ib_pi =
+ ib_port_immutable_read(ib_dev, pidx);
+ u32 core_cap_flags = ib_pi ? ib_pi->core_cap_flags : 0;
+
+ pr_info("ib_dev[%.*s]PORT[%u]: %s=%u %s=%u %s=%u %s=%u %s=%u %s=0x%x\n",
+ IB_DEVICE_NAME_MAX,
+ ib_dev->name,
+ pidx,
+ "iwarp",
+ rdma_protocol_iwarp(ib_dev, pidx),
+ "ib",
+ rdma_protocol_ib(ib_dev, pidx),
+ "roce",
+ rdma_protocol_roce(ib_dev, pidx),
+ "v1",
+ rdma_protocol_roce_eth_encap(ib_dev, pidx),
+ "v2",
+ rdma_protocol_roce_udp_encap(ib_dev, pidx),
+ "core_cap_flags",
+ core_cap_flags);
+ }
+
+ sdev = kzalloc_obj(*sdev);
+ if (!sdev)
+ return -ENOMEM;
+ sdev->ib_dev = ib_dev;
+ snprintf(sdev->ib_name, ARRAY_SIZE(sdev->ib_name), "%.*s",
+ IB_DEVICE_NAME_MAX, ib_dev->name);
+
+ write_lock(&smbdirect_globals.devices.lock);
+ list_add(&sdev->list, &smbdirect_globals.devices.list);
+ write_unlock(&smbdirect_globals.devices.lock);
+
+ return 0;
+}
+
+static void smbdirect_ib_client_remove(struct ib_device *ib_dev, void *client_data)
+{
+ struct smbdirect_device *sdev, *tmp;
+
+ write_lock(&smbdirect_globals.devices.lock);
+ list_for_each_entry_safe(sdev, tmp, &smbdirect_globals.devices.list, list) {
+ if (sdev->ib_dev == ib_dev) {
+ list_del(&sdev->list);
+ pr_info("ib_dev[%.*s] removed\n",
+ IB_DEVICE_NAME_MAX, sdev->ib_name);
+ kfree(sdev);
+ break;
+ }
+ }
+ write_unlock(&smbdirect_globals.devices.lock);
+}
+
+static void smbdirect_ib_client_rename(struct ib_device *ib_dev, void *client_data)
+{
+ struct smbdirect_device *sdev;
+
+ write_lock(&smbdirect_globals.devices.lock);
+ list_for_each_entry(sdev, &smbdirect_globals.devices.list, list) {
+ if (sdev->ib_dev == ib_dev) {
+ pr_info("ib_dev[%.*s] renamed to [%.*s]\n",
+ IB_DEVICE_NAME_MAX, sdev->ib_name,
+ IB_DEVICE_NAME_MAX, ib_dev->name);
+ snprintf(sdev->ib_name, ARRAY_SIZE(sdev->ib_name), "%.*s",
+ IB_DEVICE_NAME_MAX, ib_dev->name);
+ break;
+ }
+ }
+ write_unlock(&smbdirect_globals.devices.lock);
+}
+
+static struct ib_client smbdirect_ib_client = {
+ .name = "smbdirect_ib_client",
+ .add = smbdirect_ib_client_add,
+ .remove = smbdirect_ib_client_remove,
+ .rename = smbdirect_ib_client_rename,
+};
+
+static u8 smbdirect_netdev_find_rdma_capable_node_type(struct net_device *netdev)
+{
+ struct smbdirect_device *sdev;
+ u8 node_type = RDMA_NODE_UNSPECIFIED;
+
+ read_lock(&smbdirect_globals.devices.lock);
+ list_for_each_entry(sdev, &smbdirect_globals.devices.list, list) {
+ u32 pi;
+
+ rdma_for_each_port(sdev->ib_dev, pi) {
+ struct net_device *ndev;
+
+ ndev = ib_device_get_netdev(sdev->ib_dev, pi);
+ if (!ndev)
+ continue;
+
+ if (ndev == netdev) {
+ dev_put(ndev);
+ node_type = sdev->ib_dev->node_type;
+ goto out;
+ }
+ dev_put(ndev);
+ }
+ }
+out:
+ read_unlock(&smbdirect_globals.devices.lock);
+
+ if (node_type == RDMA_NODE_UNSPECIFIED) {
+ struct ib_device *ibdev;
+
+ ibdev = ib_device_get_by_netdev(netdev, RDMA_DRIVER_UNKNOWN);
+ if (ibdev) {
+ node_type = smbdirect_ib_device_rdma_capable_node_type(ibdev);
+ ib_device_put(ibdev);
+ }
+ }
+
+ return node_type;
+}
+
+/*
+ * Returns RDMA_NODE_UNSPECIFIED when the netdev has
+ * no support for smbdirect capable rdma.
+ *
+ * Otherwise RDMA_NODE_RNIC is returned for iwarp devices
+ * and RDMA_NODE_IB_CA or Infiniband and RoCE (v1 and v2)
+ */
+u8 smbdirect_netdev_rdma_capable_node_type(struct net_device *netdev)
+{
+ struct net_device *lower_dev;
+ struct list_head *iter;
+ u8 node_type = RDMA_NODE_UNSPECIFIED;
+
+ node_type = smbdirect_netdev_find_rdma_capable_node_type(netdev);
+ if (node_type != RDMA_NODE_UNSPECIFIED)
+ return node_type;
+
+ /* check if netdev is bridge or VLAN */
+ if (netif_is_bridge_master(netdev) || netdev->priv_flags & IFF_802_1Q_VLAN)
+ netdev_for_each_lower_dev(netdev, lower_dev, iter) {
+ node_type = smbdirect_netdev_find_rdma_capable_node_type(lower_dev);
+ if (node_type != RDMA_NODE_UNSPECIFIED)
+ return node_type;
+ }
+
+ /* check if netdev is IPoIB safely without layer violation */
+ if (netdev->type == ARPHRD_INFINIBAND)
+ return RDMA_NODE_IB_CA;
+
+ return RDMA_NODE_UNSPECIFIED;
+}
+EXPORT_SYMBOL_GPL(smbdirect_netdev_rdma_capable_node_type);
+
+__init int smbdirect_devices_init(void)
+{
+ int ret;
+
+ rwlock_init(&smbdirect_globals.devices.lock);
+ INIT_LIST_HEAD(&smbdirect_globals.devices.list);
+
+ ret = ib_register_client(&smbdirect_ib_client);
+ if (ret) {
+ pr_crit("failed to ib_register_client: %d %1pe\n",
+ ret, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+__exit void smbdirect_devices_exit(void)
+{
+ struct smbdirect_device *sdev, *tmp;
+
+ /*
+ * On exist we just cleanup so that
+ * smbdirect_ib_client_remove() won't
+ * print removals of devices.
+ */
+ write_lock(&smbdirect_globals.devices.lock);
+ list_for_each_entry_safe(sdev, tmp, &smbdirect_globals.devices.list, list) {
+ list_del(&sdev->list);
+ kfree(sdev);
+ }
+ write_unlock(&smbdirect_globals.devices.lock);
+
+ ib_unregister_client(&smbdirect_ib_client);
+}
diff --git a/fs/smb/smbdirect/internal.h b/fs/smb/smbdirect/internal.h
new file mode 100644
index 000000000000..e9959e6dc13a
--- /dev/null
+++ b/fs/smb/smbdirect/internal.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#ifndef __FS_SMB_COMMON_SMBDIRECT_INTERNAL_H__
+#define __FS_SMB_COMMON_SMBDIRECT_INTERNAL_H__
+
+#define DEFAULT_SYMBOL_NAMESPACE "SMBDIRECT"
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/smbdirect.h>
+#include "pdu.h"
+
+#include <linux/mutex.h>
+
+struct smbdirect_module_state {
+ struct mutex mutex;
+
+ struct {
+ struct workqueue_struct *accept;
+ struct workqueue_struct *connect;
+ struct workqueue_struct *idle;
+ struct workqueue_struct *refill;
+ struct workqueue_struct *immediate;
+ struct workqueue_struct *cleanup;
+ } workqueues;
+
+ struct {
+ rwlock_t lock;
+ struct list_head list;
+ } devices;
+};
+
+extern struct smbdirect_module_state smbdirect_globals;
+
+#include "socket.h"
+
+struct smbdirect_device {
+ struct list_head list;
+ struct ib_device *ib_dev;
+ /*
+ * copy of ib_dev->name,
+ * in order to print renames
+ */
+ char ib_name[IB_DEVICE_NAME_MAX];
+};
+
+int smbdirect_socket_init_new(struct net *net, struct smbdirect_socket *sc);
+
+int smbdirect_socket_init_accepting(struct rdma_cm_id *id, struct smbdirect_socket *sc);
+
+void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
+ const char *macro_name,
+ unsigned int lvl,
+ const char *func,
+ unsigned int line,
+ int error,
+ enum smbdirect_socket_status *force_status);
+#define smbdirect_socket_schedule_cleanup(__sc, __error) \
+ __smbdirect_socket_schedule_cleanup(__sc, \
+ "smbdirect_socket_schedule_cleanup", SMBDIRECT_LOG_ERR, \
+ __func__, __LINE__, __error, NULL)
+#define smbdirect_socket_schedule_cleanup_lvl(__sc, __lvl, __error) \
+ __smbdirect_socket_schedule_cleanup(__sc, \
+ "smbdirect_socket_schedule_cleanup_lvl", __lvl, \
+ __func__, __LINE__, __error, NULL)
+#define smbdirect_socket_schedule_cleanup_status(__sc, __lvl, __error, __status) do { \
+ enum smbdirect_socket_status __force_status = __status; \
+ __smbdirect_socket_schedule_cleanup(__sc, \
+ "smbdirect_socket_schedule_cleanup_status", __lvl, \
+ __func__, __LINE__, __error, &__force_status); \
+} while (0)
+
+void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc);
+
+int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
+ enum smbdirect_socket_status expected_status,
+ int unexpected_errno,
+ wait_queue_head_t *waitq,
+ atomic_t *total_credits,
+ int needed);
+
+void smbdirect_connection_rdma_established(struct smbdirect_socket *sc);
+
+void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc);
+
+int smbdirect_connection_create_qp(struct smbdirect_socket *sc);
+
+void smbdirect_connection_destroy_qp(struct smbdirect_socket *sc);
+
+int smbdirect_connection_create_mem_pools(struct smbdirect_socket *sc);
+
+void smbdirect_connection_destroy_mem_pools(struct smbdirect_socket *sc);
+
+struct smbdirect_send_io *smbdirect_connection_alloc_send_io(struct smbdirect_socket *sc);
+
+void smbdirect_connection_free_send_io(struct smbdirect_send_io *msg);
+
+struct smbdirect_recv_io *smbdirect_connection_get_recv_io(struct smbdirect_socket *sc);
+
+void smbdirect_connection_put_recv_io(struct smbdirect_recv_io *msg);
+
+void smbdirect_connection_reassembly_append_recv_io(struct smbdirect_socket *sc,
+ struct smbdirect_recv_io *msg,
+ u32 data_length);
+
+struct smbdirect_recv_io *
+smbdirect_connection_reassembly_first_recv_io(struct smbdirect_socket *sc);
+
+void smbdirect_connection_negotiate_rdma_resources(struct smbdirect_socket *sc,
+ u8 peer_initiator_depth,
+ u8 peer_responder_resources,
+ const struct rdma_conn_param *param);
+
+void smbdirect_connection_idle_timer_work(struct work_struct *work);
+
+u16 smbdirect_connection_grant_recv_credits(struct smbdirect_socket *sc);
+
+int smbdirect_connection_post_send_wr(struct smbdirect_socket *sc,
+ struct ib_send_wr *wr);
+
+int smbdirect_connection_post_recv_io(struct smbdirect_recv_io *msg);
+
+void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc);
+
+int smbdirect_connection_recv_io_refill(struct smbdirect_socket *sc);
+
+int smbdirect_connection_create_mr_list(struct smbdirect_socket *sc);
+
+void smbdirect_connection_destroy_mr_list(struct smbdirect_socket *sc);
+
+int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
+ const struct rdma_conn_param *param);
+
+void smbdirect_accept_negotiate_finish(struct smbdirect_socket *sc, u32 ntstatus);
+
+__init int smbdirect_devices_init(void);
+__exit void smbdirect_devices_exit(void);
+
+#endif /* __FS_SMB_COMMON_SMBDIRECT_INTERNAL_H__ */
diff --git a/fs/smb/smbdirect/listen.c b/fs/smb/smbdirect/listen.c
new file mode 100644
index 000000000000..2f78bcaedbf8
--- /dev/null
+++ b/fs/smb/smbdirect/listen.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (C) 2018, LG Electronics.
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+
+static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *id,
+ struct rdma_cm_event *event);
+
+int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog)
+{
+ int ret;
+
+ if (backlog < 0)
+ return -EINVAL;
+ if (!backlog)
+ backlog = 1; /* use 1 as default for now */
+
+ if (sc->first_error)
+ return -EINVAL;
+
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EINVAL;
+
+ if (WARN_ON_ONCE(!sc->rdma.cm_id))
+ return -EINVAL;
+
+ if (sc->rdma.cm_id->device)
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "try to listen on addr: %pISpsfc dev: %.*s\n",
+ &sc->rdma.cm_id->route.addr.src_addr,
+ IB_DEVICE_NAME_MAX,
+ sc->rdma.cm_id->device->name);
+ else
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "try to listen on addr: %pISpsfc\n",
+ &sc->rdma.cm_id->route.addr.src_addr);
+
+ /* already checked above */
+ WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED);
+ sc->status = SMBDIRECT_SOCKET_LISTENING;
+ sc->rdma.expected_event = RDMA_CM_EVENT_CONNECT_REQUEST;
+ rdma_lock_handler(sc->rdma.cm_id);
+ sc->rdma.cm_id->event_handler = smbdirect_listen_rdma_event_handler;
+ rdma_unlock_handler(sc->rdma.cm_id);
+
+ ret = rdma_listen(sc->rdma.cm_id, backlog);
+ if (ret) {
+ sc->first_error = ret;
+ sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
+ if (sc->rdma.cm_id->device)
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "listening failed %1pe on addr: %pISpsfc dev: %.*s\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret),
+ &sc->rdma.cm_id->route.addr.src_addr,
+ IB_DEVICE_NAME_MAX,
+ sc->rdma.cm_id->device->name);
+ else
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "listening failed %1pe on addr: %pISpsfc\n",
+ SMBDIRECT_DEBUG_ERR_PTR(ret),
+ &sc->rdma.cm_id->route.addr.src_addr);
+ return ret;
+ }
+
+ /*
+ * This is a value > 0, checked above,
+ * so we are able to use sc->listen.backlog == -1,
+ * as indication that the socket was never
+ * a listener.
+ */
+ sc->listen.backlog = backlog;
+
+ if (sc->rdma.cm_id->device)
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "listening on addr: %pISpsfc dev: %.*s\n",
+ &sc->rdma.cm_id->route.addr.src_addr,
+ IB_DEVICE_NAME_MAX,
+ sc->rdma.cm_id->device->name);
+ else
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "listening on addr: %pISpsfc\n",
+ &sc->rdma.cm_id->route.addr.src_addr);
+
+ /*
+ * The rest happens async via smbdirect_listen_rdma_event_handler()
+ */
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_listen);
+
+static int smbdirect_new_rdma_event_handler(struct rdma_cm_id *new_id,
+ struct rdma_cm_event *event)
+{
+ int ret = -ESTALE;
+
+ /*
+ * This should be replaced before any real work
+ * starts! So it should never be called!
+ */
+
+ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+ ret = -ENETDOWN;
+ if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
+ ret = event->status;
+ WARN_ONCE(1,
+ "%s should not be called! event=%s status=%d => ret=%1pe\n",
+ __func__,
+ rdma_event_msg(event->event),
+ event->status,
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return -ESTALE;
+}
+
+static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
+ struct rdma_cm_id *new_id,
+ const struct rdma_cm_event *event);
+
+static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *new_id,
+ struct rdma_cm_event *event)
+{
+ struct smbdirect_socket *lsc = new_id->context;
+ int ret;
+
+ if (event->event == RDMA_CM_EVENT_CONNECT_REQUEST) {
+ new_id->context = NULL;
+ new_id->event_handler = smbdirect_new_rdma_event_handler;
+ } else
+ new_id = NULL;
+
+ /*
+ * cma_cm_event_handler() has
+ * lockdep_assert_held(&id_priv->handler_mutex);
+ *
+ * Mutexes are not allowed in interrupts,
+ * and we rely on not being in an interrupt here,
+ * as we might sleep.
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ if (event->status || event->event != lsc->rdma.expected_event) {
+ ret = -ECONNABORTED;
+
+ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+ ret = -ENETDOWN;
+ if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
+ ret = event->status;
+
+ smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+ "%s (first_error=%1pe, expected=%s) => event=%s status=%d => ret=%1pe\n",
+ smbdirect_socket_status_string(lsc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(lsc->first_error),
+ rdma_event_msg(lsc->rdma.expected_event),
+ rdma_event_msg(event->event),
+ event->status,
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+
+ /*
+ * In case of error return it and let the caller
+ * destroy new_id
+ */
+ smbdirect_socket_schedule_cleanup(lsc, ret);
+ return new_id ? ret : 0;
+ }
+
+ smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_INFO,
+ "%s (first_error=%1pe) event=%s\n",
+ smbdirect_socket_status_string(lsc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(lsc->first_error),
+ rdma_event_msg(event->event));
+
+ /*
+ * In case of error return it and let the caller
+ * destroy new_id
+ */
+ if (lsc->first_error)
+ return new_id ? lsc->first_error : 0;
+
+ switch (event->event) {
+ case RDMA_CM_EVENT_CONNECT_REQUEST:
+ WARN_ON_ONCE(lsc->status != SMBDIRECT_SOCKET_LISTENING);
+
+ /*
+ * In case of error return it and let the caller
+ * destroy new_id
+ */
+ ret = smbdirect_listen_connect_request(lsc, new_id, event);
+ if (ret)
+ return ret;
+ return 0;
+
+ default:
+ break;
+ }
+
+ /*
+ * This is an internal error
+ */
+ WARN_ON_ONCE(lsc->rdma.expected_event != RDMA_CM_EVENT_CONNECT_REQUEST);
+ smbdirect_socket_schedule_cleanup(lsc, -EINVAL);
+ return 0;
+}
+
+static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
+ struct rdma_cm_id *new_id,
+ const struct rdma_cm_event *event)
+{
+ const struct smbdirect_socket_parameters *lsp = &lsc->parameters;
+ struct smbdirect_socket *nsc;
+ unsigned long flags;
+ size_t backlog = max_t(size_t, 1, lsc->listen.backlog);
+ size_t psockets;
+ size_t rsockets;
+ int ret;
+
+ if (!smbdirect_frwr_is_supported(&new_id->device->attrs)) {
+ smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+ "Fast Registration Work Requests (FRWR) is not supported device %.*s\n",
+ IB_DEVICE_NAME_MAX,
+ new_id->device->name);
+ smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+ "Device capability flags = %llx max_fast_reg_page_list_len = %u\n",
+ new_id->device->attrs.device_cap_flags,
+ new_id->device->attrs.max_fast_reg_page_list_len);
+ return -EPROTONOSUPPORT;
+ }
+
+ if (lsp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB &&
+ !rdma_ib_or_roce(new_id->device, new_id->port_num)) {
+ smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+ "Not IB: device: %.*s IW:%u local: %pISpsfc remote: %pISpsfc\n",
+ IB_DEVICE_NAME_MAX,
+ new_id->device->name,
+ rdma_protocol_iwarp(new_id->device, new_id->port_num),
+ &new_id->route.addr.src_addr,
+ &new_id->route.addr.dst_addr);
+ return -EPROTONOSUPPORT;
+ }
+ if (lsp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW &&
+ !rdma_protocol_iwarp(new_id->device, new_id->port_num)) {
+ smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+ "Not IW: device: %.*s IB:%u local: %pISpsfc remote: %pISpsfc\n",
+ IB_DEVICE_NAME_MAX,
+ new_id->device->name,
+ rdma_ib_or_roce(new_id->device, new_id->port_num),
+ &new_id->route.addr.src_addr,
+ &new_id->route.addr.dst_addr);
+ return -EPROTONOSUPPORT;
+ }
+
+ spin_lock_irqsave(&lsc->listen.lock, flags);
+ psockets = list_count_nodes(&lsc->listen.pending);
+ rsockets = list_count_nodes(&lsc->listen.ready);
+ spin_unlock_irqrestore(&lsc->listen.lock, flags);
+
+ if (psockets > backlog ||
+ rsockets > backlog ||
+ (psockets + rsockets) > backlog) {
+ smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+ "Backlog[%d][%zu] full pending[%zu] ready[%zu]\n",
+ lsc->listen.backlog, backlog, psockets, rsockets);
+ return -EBUSY;
+ }
+
+ ret = smbdirect_socket_create_accepting(new_id, &nsc);
+ if (ret)
+ goto socket_init_failed;
+
+ nsc->logging = lsc->logging;
+ ret = smbdirect_socket_set_initial_parameters(nsc, &lsc->parameters);
+ if (ret)
+ goto set_params_failed;
+ ret = smbdirect_socket_set_kernel_settings(nsc,
+ lsc->ib.poll_ctx,
+ lsc->send_io.mem.gfp_mask);
+ if (ret)
+ goto set_settings_failed;
+
+ spin_lock_irqsave(&lsc->listen.lock, flags);
+ list_add_tail(&nsc->accept.list, &lsc->listen.pending);
+ nsc->accept.listener = lsc;
+ spin_unlock_irqrestore(&lsc->listen.lock, flags);
+
+ ret = smbdirect_accept_connect_request(nsc, &event->param.conn);
+ if (ret)
+ goto accept_connect_failed;
+
+ return 0;
+
+accept_connect_failed:
+ spin_lock_irqsave(&lsc->listen.lock, flags);
+ list_del_init(&nsc->accept.list);
+ nsc->accept.listener = NULL;
+ spin_unlock_irqrestore(&lsc->listen.lock, flags);
+set_settings_failed:
+set_params_failed:
+ /*
+ * The caller will destroy new_id
+ */
+ nsc->ib.dev = NULL;
+ nsc->rdma.cm_id = NULL;
+ smbdirect_socket_release(nsc);
+socket_init_failed:
+ return ret;
+}
diff --git a/fs/smb/smbdirect/main.c b/fs/smb/smbdirect/main.c
new file mode 100644
index 000000000000..606732fefb69
--- /dev/null
+++ b/fs/smb/smbdirect/main.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+#include <linux/module.h>
+
+struct smbdirect_module_state smbdirect_globals = {
+ .mutex = __MUTEX_INITIALIZER(smbdirect_globals.mutex),
+};
+
+static __init int smbdirect_module_init(void)
+{
+ int ret = -ENOMEM;
+
+ pr_notice("subsystem loading...\n");
+ mutex_lock(&smbdirect_globals.mutex);
+
+ smbdirect_globals.workqueues.accept = alloc_workqueue("smbdirect-accept",
+ WQ_SYSFS |
+ WQ_PERCPU |
+ WQ_POWER_EFFICIENT,
+ 0);
+ if (smbdirect_globals.workqueues.accept == NULL)
+ goto alloc_accept_wq_failed;
+
+ smbdirect_globals.workqueues.connect = alloc_workqueue("smbdirect-connect",
+ WQ_SYSFS |
+ WQ_PERCPU |
+ WQ_POWER_EFFICIENT,
+ 0);
+ if (smbdirect_globals.workqueues.connect == NULL)
+ goto alloc_connect_wq_failed;
+
+ smbdirect_globals.workqueues.idle = alloc_workqueue("smbdirect-idle",
+ WQ_SYSFS |
+ WQ_PERCPU |
+ WQ_POWER_EFFICIENT,
+ 0);
+ if (smbdirect_globals.workqueues.idle == NULL)
+ goto alloc_idle_wq_failed;
+
+ smbdirect_globals.workqueues.refill = alloc_workqueue("smbdirect-refill",
+ WQ_HIGHPRI |
+ WQ_SYSFS |
+ WQ_PERCPU |
+ WQ_POWER_EFFICIENT,
+ 0);
+ if (smbdirect_globals.workqueues.refill == NULL)
+ goto alloc_refill_wq_failed;
+
+ smbdirect_globals.workqueues.immediate = alloc_workqueue("smbdirect-immediate",
+ WQ_HIGHPRI |
+ WQ_SYSFS |
+ WQ_PERCPU |
+ WQ_POWER_EFFICIENT,
+ 0);
+ if (smbdirect_globals.workqueues.immediate == NULL)
+ goto alloc_immediate_wq_failed;
+
+ smbdirect_globals.workqueues.cleanup = alloc_workqueue("smbdirect-cleanup",
+ WQ_MEM_RECLAIM |
+ WQ_HIGHPRI |
+ WQ_SYSFS |
+ WQ_PERCPU |
+ WQ_POWER_EFFICIENT,
+ 0);
+ if (smbdirect_globals.workqueues.cleanup == NULL)
+ goto alloc_cleanup_wq_failed;
+
+ ret = smbdirect_devices_init();
+ if (ret)
+ goto devices_init_failed;
+
+ mutex_unlock(&smbdirect_globals.mutex);
+ pr_notice("subsystem loaded\n");
+ return 0;
+
+devices_init_failed:
+ destroy_workqueue(smbdirect_globals.workqueues.cleanup);
+alloc_cleanup_wq_failed:
+ destroy_workqueue(smbdirect_globals.workqueues.immediate);
+alloc_immediate_wq_failed:
+ destroy_workqueue(smbdirect_globals.workqueues.refill);
+alloc_refill_wq_failed:
+ destroy_workqueue(smbdirect_globals.workqueues.idle);
+alloc_idle_wq_failed:
+ destroy_workqueue(smbdirect_globals.workqueues.connect);
+alloc_connect_wq_failed:
+ destroy_workqueue(smbdirect_globals.workqueues.accept);
+alloc_accept_wq_failed:
+ mutex_unlock(&smbdirect_globals.mutex);
+ pr_crit("failed to loaded: %d (%1pe)\n",
+ ret, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+}
+
+static __exit void smbdirect_module_exit(void)
+{
+ pr_notice("subsystem unloading...\n");
+ mutex_lock(&smbdirect_globals.mutex);
+
+ smbdirect_devices_exit();
+
+ destroy_workqueue(smbdirect_globals.workqueues.accept);
+ destroy_workqueue(smbdirect_globals.workqueues.connect);
+ destroy_workqueue(smbdirect_globals.workqueues.idle);
+ destroy_workqueue(smbdirect_globals.workqueues.refill);
+ destroy_workqueue(smbdirect_globals.workqueues.immediate);
+ destroy_workqueue(smbdirect_globals.workqueues.cleanup);
+
+ mutex_unlock(&smbdirect_globals.mutex);
+ pr_notice("subsystem unloaded\n");
+}
+
+module_init(smbdirect_module_init);
+module_exit(smbdirect_module_exit);
+
+MODULE_DESCRIPTION("smbdirect subsystem");
+MODULE_LICENSE("GPL");
diff --git a/fs/smb/smbdirect/mr.c b/fs/smb/smbdirect/mr.c
new file mode 100644
index 000000000000..15c6363a2f97
--- /dev/null
+++ b/fs/smb/smbdirect/mr.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+
+/*
+ * Allocate MRs used for RDMA read/write
+ * The number of MRs will not exceed hardware capability in responder_resources
+ * All MRs are kept in mr_list. The MR can be recovered after it's used
+ * Recovery is done in smbd_mr_recovery_work. The content of list entry changes
+ * as MRs are used and recovered for I/O, but the list links will not change
+ */
+int smbdirect_connection_create_mr_list(struct smbdirect_socket *sc)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_mr_io *mr;
+ int ret;
+ u32 i;
+
+ if (sp->responder_resources == 0) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "responder_resources negotiated as 0\n");
+ return -EINVAL;
+ }
+
+ /* Allocate more MRs (2x) than hardware responder_resources */
+ for (i = 0; i < sp->responder_resources * 2; i++) {
+ mr = kzalloc_obj(*mr);
+ if (!mr) {
+ ret = -ENOMEM;
+ goto kzalloc_mr_failed;
+ }
+
+ kref_init(&mr->kref);
+ mutex_init(&mr->mutex);
+
+ mr->mr = ib_alloc_mr(sc->ib.pd,
+ sc->mr_io.type,
+ sp->max_frmr_depth);
+ if (IS_ERR(mr->mr)) {
+ ret = PTR_ERR(mr->mr);
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "ib_alloc_mr failed ret=%d (%1pe) type=0x%x max_frmr_depth=%u\n",
+ ret, SMBDIRECT_DEBUG_ERR_PTR(ret),
+ sc->mr_io.type, sp->max_frmr_depth);
+ goto ib_alloc_mr_failed;
+ }
+ mr->sgt.sgl = kzalloc_objs(struct scatterlist, sp->max_frmr_depth);
+ if (!mr->sgt.sgl) {
+ ret = -ENOMEM;
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "failed to allocate sgl, max_frmr_depth=%u\n",
+ sp->max_frmr_depth);
+ goto kcalloc_sgl_failed;
+ }
+ mr->state = SMBDIRECT_MR_READY;
+ mr->socket = sc;
+
+ list_add_tail(&mr->list, &sc->mr_io.all.list);
+ atomic_inc(&sc->mr_io.ready.count);
+ }
+
+ return 0;
+
+kcalloc_sgl_failed:
+ ib_dereg_mr(mr->mr);
+ib_alloc_mr_failed:
+ mutex_destroy(&mr->mutex);
+ kfree(mr);
+kzalloc_mr_failed:
+ smbdirect_connection_destroy_mr_list(sc);
+ return ret;
+}
+
+static void smbdirect_mr_io_disable_locked(struct smbdirect_mr_io *mr)
+{
+ struct smbdirect_socket *sc = mr->socket;
+
+ lockdep_assert_held(&mr->mutex);
+
+ if (mr->state == SMBDIRECT_MR_DISABLED)
+ return;
+
+ if (mr->mr)
+ ib_dereg_mr(mr->mr);
+ if (mr->sgt.nents)
+ ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir);
+ kfree(mr->sgt.sgl);
+
+ mr->mr = NULL;
+ mr->sgt.sgl = NULL;
+ mr->sgt.nents = 0;
+
+ mr->state = SMBDIRECT_MR_DISABLED;
+}
+
+static void smbdirect_mr_io_free_locked(struct kref *kref)
+{
+ struct smbdirect_mr_io *mr =
+ container_of(kref, struct smbdirect_mr_io, kref);
+
+ lockdep_assert_held(&mr->mutex);
+
+ /*
+ * smbdirect_mr_io_disable_locked() should already be called!
+ */
+ if (WARN_ON_ONCE(mr->state != SMBDIRECT_MR_DISABLED))
+ smbdirect_mr_io_disable_locked(mr);
+
+ mutex_unlock(&mr->mutex);
+ mutex_destroy(&mr->mutex);
+ kfree(mr);
+}
+
+void smbdirect_connection_destroy_mr_list(struct smbdirect_socket *sc)
+{
+ struct smbdirect_mr_io *mr, *tmp;
+ LIST_HEAD(all_list);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sc->mr_io.all.lock, flags);
+ list_splice_tail_init(&sc->mr_io.all.list, &all_list);
+ spin_unlock_irqrestore(&sc->mr_io.all.lock, flags);
+
+ list_for_each_entry_safe(mr, tmp, &all_list, list) {
+ mutex_lock(&mr->mutex);
+
+ smbdirect_mr_io_disable_locked(mr);
+ list_del(&mr->list);
+ mr->socket = NULL;
+
+ /*
+ * No kref_put_mutex() as it's already locked.
+ *
+ * If smbdirect_mr_io_free_locked() is called
+ * and the mutex is unlocked and mr is gone,
+ * in that case kref_put() returned 1.
+ *
+ * If kref_put() returned 0 we know that
+ * smbdirect_mr_io_free_locked() didn't
+ * run. Not by us nor by anyone else, as we
+ * still hold the mutex, so we need to unlock.
+ *
+ * If the mr is still registered it will
+ * be dangling (detached from the connection
+ * waiting for smbd_deregister_mr() to be
+ * called in order to free the memory.
+ */
+ if (!kref_put(&mr->kref, smbdirect_mr_io_free_locked))
+ mutex_unlock(&mr->mutex);
+ }
+}
+
+/*
+ * Get a MR from mr_list. This function waits until there is at least one MR
+ * available in the list. There may be several CPUs issuing I/O trying to get MR
+ * at the same time, mr_list_lock is used to protect this situation.
+ */
+static struct smbdirect_mr_io *
+smbdirect_connection_get_mr_io(struct smbdirect_socket *sc)
+{
+ struct smbdirect_mr_io *mr;
+ unsigned long flags;
+ int ret;
+
+again:
+ ret = wait_event_interruptible(sc->mr_io.ready.wait_queue,
+ atomic_read(&sc->mr_io.ready.count) ||
+ sc->status != SMBDIRECT_SOCKET_CONNECTED);
+ if (ret) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "wait_event_interruptible ret=%d (%1pe)\n",
+ ret, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return NULL;
+ }
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "sc->status=%s sc->first_error=%1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+ return NULL;
+ }
+
+ spin_lock_irqsave(&sc->mr_io.all.lock, flags);
+ list_for_each_entry(mr, &sc->mr_io.all.list, list) {
+ if (mr->state == SMBDIRECT_MR_READY) {
+ mr->state = SMBDIRECT_MR_REGISTERED;
+ kref_get(&mr->kref);
+ spin_unlock_irqrestore(&sc->mr_io.all.lock, flags);
+ atomic_dec(&sc->mr_io.ready.count);
+ atomic_inc(&sc->mr_io.used.count);
+ return mr;
+ }
+ }
+
+ spin_unlock_irqrestore(&sc->mr_io.all.lock, flags);
+ /*
+ * It is possible that we could fail to get MR because other processes may
+ * try to acquire a MR at the same time. If this is the case, retry it.
+ */
+ goto again;
+}
+
+static void smbdirect_connection_mr_io_register_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ struct smbdirect_mr_io *mr =
+ container_of(wc->wr_cqe, struct smbdirect_mr_io, cqe);
+ struct smbdirect_socket *sc = mr->socket;
+
+ if (wc->status != IB_WC_SUCCESS) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "wc->status=%s opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->opcode);
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ }
+}
+
+static void smbdirect_connection_mr_io_local_inv_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ struct smbdirect_mr_io *mr =
+ container_of(wc->wr_cqe, struct smbdirect_mr_io, cqe);
+ struct smbdirect_socket *sc = mr->socket;
+
+ mr->state = SMBDIRECT_MR_INVALIDATED;
+ if (wc->status != IB_WC_SUCCESS) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "invalidate failed status=%s\n",
+ ib_wc_status_msg(wc->status));
+ smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
+ }
+ complete(&mr->invalidate_done);
+}
+
+/*
+ * Transcribe the pages from an iterator into an MR scatterlist.
+ */
+static int smbdirect_iter_to_sgt(struct iov_iter *iter,
+ struct sg_table *sgt,
+ unsigned int max_sg)
+{
+ int ret;
+
+ memset(sgt->sgl, 0, max_sg * sizeof(struct scatterlist));
+
+ ret = extract_iter_to_sg(iter, iov_iter_count(iter), sgt, max_sg, 0);
+ WARN_ON(ret < 0);
+ if (sgt->nents > 0)
+ sg_mark_end(&sgt->sgl[sgt->nents - 1]);
+
+ return ret;
+}
+
+/*
+ * Register memory for RDMA read/write
+ * iter: the buffer to register memory with
+ * writing: true if this is a RDMA write (SMB read), false for RDMA read
+ * need_invalidate: true if this MR needs to be locally invalidated after I/O
+ * return value: the MR registered, NULL if failed.
+ */
+struct smbdirect_mr_io *
+smbdirect_connection_register_mr_io(struct smbdirect_socket *sc,
+ struct iov_iter *iter,
+ bool writing,
+ bool need_invalidate)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smbdirect_mr_io *mr;
+ int ret, num_pages, num_mapped;
+ struct ib_reg_wr *reg_wr;
+
+ num_pages = iov_iter_npages(iter, sp->max_frmr_depth + 1);
+ if (num_pages > sp->max_frmr_depth) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "num_pages=%d max_frmr_depth=%d\n",
+ num_pages, sp->max_frmr_depth);
+ WARN_ON_ONCE(1);
+ return NULL;
+ }
+
+ mr = smbdirect_connection_get_mr_io(sc);
+ if (!mr) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "smbdirect_connection_get_mr_io returning NULL\n");
+ return NULL;
+ }
+
+ mutex_lock(&mr->mutex);
+
+ mr->dir = writing ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+ mr->need_invalidate = need_invalidate;
+ mr->sgt.nents = 0;
+ mr->sgt.orig_nents = 0;
+
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_INFO,
+ "num_pages=%u count=%zu depth=%u\n",
+ num_pages, iov_iter_count(iter), sp->max_frmr_depth);
+ smbdirect_iter_to_sgt(iter, &mr->sgt, sp->max_frmr_depth);
+
+ num_mapped = ib_dma_map_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir);
+ if (!num_mapped) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "ib_dma_map_sg num_pages=%u dir=%x num_mapped=%d\n",
+ num_pages, mr->dir, num_mapped);
+ ret = -EIO;
+ goto dma_map_error;
+ }
+
+ ret = ib_map_mr_sg(mr->mr, mr->sgt.sgl, num_mapped, NULL, PAGE_SIZE);
+ if (ret != num_mapped) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "ib_map_mr_sg failed ret = %d num_mapped = %u\n",
+ ret, num_mapped);
+ if (ret >= 0)
+ ret = -EIO;
+ goto map_mr_error;
+ }
+
+ ib_update_fast_reg_key(mr->mr, ib_inc_rkey(mr->mr->rkey));
+ reg_wr = &mr->wr;
+ reg_wr->wr.opcode = IB_WR_REG_MR;
+ mr->cqe.done = smbdirect_connection_mr_io_register_done;
+ reg_wr->wr.wr_cqe = &mr->cqe;
+ reg_wr->wr.num_sge = 0;
+ reg_wr->wr.send_flags = IB_SEND_SIGNALED;
+ reg_wr->mr = mr->mr;
+ reg_wr->key = mr->mr->rkey;
+ reg_wr->access = writing ?
+ IB_ACCESS_REMOTE_WRITE | IB_ACCESS_LOCAL_WRITE :
+ IB_ACCESS_REMOTE_READ;
+
+ /*
+ * There is no need for waiting for complemtion on ib_post_send
+ * on IB_WR_REG_MR. Hardware enforces a barrier and order of execution
+ * on the next ib_post_send when we actually send I/O to remote peer
+ */
+ ret = ib_post_send(sc->ib.qp, &reg_wr->wr, NULL);
+ if (!ret) {
+ /*
+ * smbdirect_connection_get_mr_io() gave us a reference
+ * via kref_get(&mr->kref), we keep that and let
+ * the caller use smbdirect_connection_deregister_mr_io()
+ * to remove it again.
+ */
+ mutex_unlock(&mr->mutex);
+ return mr;
+ }
+
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "ib_post_send failed ret=%d (%1pe) reg_wr->key=0x%x\n",
+ ret, SMBDIRECT_DEBUG_ERR_PTR(ret), reg_wr->key);
+
+map_mr_error:
+ ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir);
+
+dma_map_error:
+ mr->sgt.nents = 0;
+ mr->state = SMBDIRECT_MR_ERROR;
+ atomic_dec(&sc->mr_io.used.count);
+
+ smbdirect_socket_schedule_cleanup(sc, ret);
+
+ /*
+ * smbdirect_connection_get_mr_io() gave us a reference
+ * via kref_get(&mr->kref), we need to remove it again
+ * on error.
+ *
+ * No kref_put_mutex() as it's already locked.
+ *
+ * If smbdirect_mr_io_free_locked() is called
+ * and the mutex is unlocked and mr is gone,
+ * in that case kref_put() returned 1.
+ *
+ * If kref_put() returned 0 we know that
+ * smbdirect_mr_io_free_locked() didn't
+ * run. Not by us nor by anyone else, as we
+ * still hold the mutex, so we need to unlock.
+ */
+ if (!kref_put(&mr->kref, smbdirect_mr_io_free_locked))
+ mutex_unlock(&mr->mutex);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_register_mr_io);
+
+void smbdirect_mr_io_fill_buffer_descriptor(struct smbdirect_mr_io *mr,
+ struct smbdirect_buffer_descriptor_v1 *v1)
+{
+ mutex_lock(&mr->mutex);
+ if (mr->state == SMBDIRECT_MR_REGISTERED) {
+ v1->offset = cpu_to_le64(mr->mr->iova);
+ v1->token = cpu_to_le32(mr->mr->rkey);
+ v1->length = cpu_to_le32(mr->mr->length);
+ } else {
+ v1->offset = cpu_to_le64(U64_MAX);
+ v1->token = cpu_to_le32(U32_MAX);
+ v1->length = cpu_to_le32(U32_MAX);
+ }
+ mutex_unlock(&mr->mutex);
+}
+EXPORT_SYMBOL_GPL(smbdirect_mr_io_fill_buffer_descriptor);
+
+/*
+ * Deregister a MR after I/O is done
+ * This function may wait if remote invalidation is not used
+ * and we have to locally invalidate the buffer to prevent data is being
+ * modified by remote peer after upper layer consumes it
+ */
+void smbdirect_connection_deregister_mr_io(struct smbdirect_mr_io *mr)
+{
+ struct smbdirect_socket *sc = mr->socket;
+ int ret = 0;
+
+lock_again:
+ mutex_lock(&mr->mutex);
+ if (mr->state == SMBDIRECT_MR_DISABLED)
+ goto put_kref;
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+ smbdirect_mr_io_disable_locked(mr);
+ goto put_kref;
+ }
+
+ if (mr->need_invalidate) {
+ struct ib_send_wr *wr = &mr->inv_wr;
+
+ /* Need to finish local invalidation before returning */
+ wr->opcode = IB_WR_LOCAL_INV;
+ mr->cqe.done = smbdirect_connection_mr_io_local_inv_done;
+ wr->wr_cqe = &mr->cqe;
+ wr->num_sge = 0;
+ wr->ex.invalidate_rkey = mr->mr->rkey;
+ wr->send_flags = IB_SEND_SIGNALED;
+
+ init_completion(&mr->invalidate_done);
+ ret = ib_post_send(sc->ib.qp, wr, NULL);
+ if (ret) {
+ smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
+ "ib_post_send failed ret=%d (%1pe)\n",
+ ret, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ smbdirect_mr_io_disable_locked(mr);
+ smbdirect_socket_schedule_cleanup(sc, ret);
+ goto done;
+ }
+
+ /*
+ * We still hold the reference to mr
+ * so we can unlock while waiting.
+ */
+ mutex_unlock(&mr->mutex);
+ wait_for_completion(&mr->invalidate_done);
+ mr->need_invalidate = false;
+ goto lock_again;
+ } else
+ /*
+ * For remote invalidation, just set it to SMBDIRECT_MR_INVALIDATED
+ * and defer to mr_recovery_work to recover the MR for next use
+ */
+ mr->state = SMBDIRECT_MR_INVALIDATED;
+
+ if (mr->sgt.nents) {
+ ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir);
+ mr->sgt.nents = 0;
+ }
+
+ WARN_ONCE(mr->state != SMBDIRECT_MR_INVALIDATED,
+ "mr->state[%u] != SMBDIRECT_MR_INVALIDATED[%u]\n",
+ mr->state, SMBDIRECT_MR_INVALIDATED);
+ mr->state = SMBDIRECT_MR_READY;
+ if (atomic_inc_return(&sc->mr_io.ready.count) == 1)
+ wake_up(&sc->mr_io.ready.wait_queue);
+
+done:
+ atomic_dec(&sc->mr_io.used.count);
+
+put_kref:
+ /*
+ * No kref_put_mutex() as it's already locked.
+ *
+ * If smbdirect_mr_io_free_locked() is called
+ * and the mutex is unlocked and mr is gone,
+ * in that case kref_put() returned 1.
+ *
+ * If kref_put() returned 0 we know that
+ * smbdirect_mr_io_free_locked() didn't
+ * run. Not by us nor by anyone else, as we
+ * still hold the mutex, so we need to unlock
+ * and keep the mr in SMBDIRECT_MR_READY or
+ * SMBDIRECT_MR_ERROR state.
+ */
+ if (!kref_put(&mr->kref, smbdirect_mr_io_free_locked))
+ mutex_unlock(&mr->mutex);
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_deregister_mr_io);
diff --git a/fs/smb/common/smbdirect/smbdirect_pdu.h b/fs/smb/smbdirect/pdu.h
index ae9fdb05ce23..7693ba337873 100644
--- a/fs/smb/common/smbdirect/smbdirect_pdu.h
+++ b/fs/smb/smbdirect/pdu.h
@@ -8,6 +8,10 @@
#define SMBDIRECT_V1 0x0100
+/* SMBD minimum receive size and fragmented sized defined in [MS-SMBD] */
+#define SMBDIRECT_MIN_RECEIVE_SIZE 128
+#define SMBDIRECT_MIN_FRAGMENTED_SIZE 131072
+
/* SMBD negotiation request packet [MS-SMBD] 2.2.1 */
struct smbdirect_negotiate_req {
__le16 min_version;
diff --git a/fs/smb/smbdirect/rw.c b/fs/smb/smbdirect/rw.c
new file mode 100644
index 000000000000..6fe38042cfb9
--- /dev/null
+++ b/fs/smb/smbdirect/rw.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (C) 2018, LG Electronics.
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+
+static int smbdirect_connection_wait_for_rw_credits(struct smbdirect_socket *sc,
+ int credits)
+{
+ return smbdirect_socket_wait_for_credits(sc,
+ SMBDIRECT_SOCKET_CONNECTED,
+ -ENOTCONN,
+ &sc->rw_io.credits.wait_queue,
+ &sc->rw_io.credits.count,
+ credits);
+}
+
+static int smbdirect_connection_calc_rw_credits(struct smbdirect_socket *sc,
+ const void *buf,
+ size_t len)
+{
+ return DIV_ROUND_UP(smbdirect_get_buf_page_count(buf, len),
+ sc->rw_io.credits.num_pages);
+}
+
+static int smbdirect_connection_rdma_get_sg_list(void *buf,
+ size_t size,
+ struct scatterlist *sg_list,
+ size_t nentries)
+{
+ bool high = is_vmalloc_addr(buf);
+ struct page *page;
+ size_t offset, len;
+ int i = 0;
+
+ if (size == 0 || nentries < smbdirect_get_buf_page_count(buf, size))
+ return -EINVAL;
+
+ offset = offset_in_page(buf);
+ buf -= offset;
+ while (size > 0) {
+ len = min_t(size_t, PAGE_SIZE - offset, size);
+ if (high)
+ page = vmalloc_to_page(buf);
+ else
+ page = kmap_to_page(buf);
+
+ if (!sg_list)
+ return -EINVAL;
+ sg_set_page(sg_list, page, len, offset);
+ sg_list = sg_next(sg_list);
+
+ buf += PAGE_SIZE;
+ size -= len;
+ offset = 0;
+ i++;
+ }
+
+ return i;
+}
+
+static void smbdirect_connection_rw_io_free(struct smbdirect_rw_io *msg,
+ enum dma_data_direction dir)
+{
+ struct smbdirect_socket *sc = msg->socket;
+
+ rdma_rw_ctx_destroy(&msg->rdma_ctx,
+ sc->ib.qp,
+ sc->ib.qp->port,
+ msg->sgt.sgl,
+ msg->sgt.nents,
+ dir);
+ sg_free_table_chained(&msg->sgt, SG_CHUNK_SIZE);
+ kfree(msg);
+}
+
+static void smbdirect_connection_rdma_rw_done(struct ib_cq *cq, struct ib_wc *wc,
+ enum dma_data_direction dir)
+{
+ struct smbdirect_rw_io *msg =
+ container_of(wc->wr_cqe, struct smbdirect_rw_io, cqe);
+ struct smbdirect_socket *sc = msg->socket;
+
+ if (wc->status != IB_WC_SUCCESS) {
+ msg->error = -EIO;
+ pr_err("read/write error. opcode = %d, status = %s(%d)\n",
+ wc->opcode, ib_wc_status_msg(wc->status), wc->status);
+ if (wc->status != IB_WC_WR_FLUSH_ERR)
+ smbdirect_socket_schedule_cleanup(sc, msg->error);
+ }
+
+ complete(msg->completion);
+}
+
+static void smbdirect_connection_rdma_read_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ smbdirect_connection_rdma_rw_done(cq, wc, DMA_FROM_DEVICE);
+}
+
+static void smbdirect_connection_rdma_write_done(struct ib_cq *cq, struct ib_wc *wc)
+{
+ smbdirect_connection_rdma_rw_done(cq, wc, DMA_TO_DEVICE);
+}
+
+int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
+ void *buf, size_t buf_len,
+ struct smbdirect_buffer_descriptor_v1 *desc,
+ size_t desc_len,
+ bool is_read)
+{
+ const struct smbdirect_socket_parameters *sp = &sc->parameters;
+ enum dma_data_direction direction = is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+ struct smbdirect_rw_io *msg, *next_msg;
+ size_t i;
+ int ret;
+ DECLARE_COMPLETION_ONSTACK(completion);
+ struct ib_send_wr *first_wr;
+ LIST_HEAD(msg_list);
+ u8 *desc_buf;
+ int credits_needed;
+ size_t desc_buf_len, desc_num = 0;
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
+ return -ENOTCONN;
+
+ if (buf_len > sp->max_read_write_size)
+ return -EINVAL;
+
+ /* calculate needed credits */
+ credits_needed = 0;
+ desc_buf = buf;
+ for (i = 0; i < desc_len / sizeof(*desc); i++) {
+ if (!buf_len)
+ break;
+
+ desc_buf_len = le32_to_cpu(desc[i].length);
+ if (!desc_buf_len)
+ return -EINVAL;
+
+ if (desc_buf_len > buf_len) {
+ desc_buf_len = buf_len;
+ desc[i].length = cpu_to_le32(desc_buf_len);
+ buf_len = 0;
+ }
+
+ credits_needed += smbdirect_connection_calc_rw_credits(sc,
+ desc_buf,
+ desc_buf_len);
+ desc_buf += desc_buf_len;
+ buf_len -= desc_buf_len;
+ desc_num++;
+ }
+
+ smbdirect_log_rdma_rw(sc, SMBDIRECT_LOG_INFO,
+ "RDMA %s, len %zu, needed credits %d\n",
+ str_read_write(is_read), buf_len, credits_needed);
+
+ ret = smbdirect_connection_wait_for_rw_credits(sc, credits_needed);
+ if (ret < 0)
+ return ret;
+
+ /* build rdma_rw_ctx for each descriptor */
+ desc_buf = buf;
+ for (i = 0; i < desc_num; i++) {
+ size_t page_count;
+
+ msg = kzalloc_flex(*msg, sg_list, SG_CHUNK_SIZE,
+ sc->rw_io.mem.gfp_mask);
+ if (!msg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ desc_buf_len = le32_to_cpu(desc[i].length);
+ page_count = smbdirect_get_buf_page_count(desc_buf, desc_buf_len);
+
+ msg->socket = sc;
+ msg->cqe.done = is_read ?
+ smbdirect_connection_rdma_read_done :
+ smbdirect_connection_rdma_write_done;
+ msg->completion = &completion;
+
+ msg->sgt.sgl = &msg->sg_list[0];
+ ret = sg_alloc_table_chained(&msg->sgt,
+ page_count,
+ msg->sg_list,
+ SG_CHUNK_SIZE);
+ if (ret) {
+ ret = -ENOMEM;
+ goto free_msg;
+ }
+
+ ret = smbdirect_connection_rdma_get_sg_list(desc_buf,
+ desc_buf_len,
+ msg->sgt.sgl,
+ msg->sgt.orig_nents);
+ if (ret < 0)
+ goto free_table;
+
+ ret = rdma_rw_ctx_init(&msg->rdma_ctx,
+ sc->ib.qp,
+ sc->ib.qp->port,
+ msg->sgt.sgl,
+ page_count,
+ 0,
+ le64_to_cpu(desc[i].offset),
+ le32_to_cpu(desc[i].token),
+ direction);
+ if (ret < 0) {
+ pr_err("failed to init rdma_rw_ctx: %d\n", ret);
+ goto free_table;
+ }
+
+ list_add_tail(&msg->list, &msg_list);
+ desc_buf += desc_buf_len;
+ }
+
+ /* concatenate work requests of rdma_rw_ctxs */
+ first_wr = NULL;
+ list_for_each_entry_reverse(msg, &msg_list, list) {
+ first_wr = rdma_rw_ctx_wrs(&msg->rdma_ctx,
+ sc->ib.qp,
+ sc->ib.qp->port,
+ &msg->cqe,
+ first_wr);
+ }
+
+ ret = ib_post_send(sc->ib.qp, first_wr, NULL);
+ if (ret) {
+ pr_err("failed to post send wr for RDMA R/W: %d\n", ret);
+ goto out;
+ }
+
+ msg = list_last_entry(&msg_list, struct smbdirect_rw_io, list);
+ wait_for_completion(&completion);
+ ret = msg->error;
+out:
+ list_for_each_entry_safe(msg, next_msg, &msg_list, list) {
+ list_del(&msg->list);
+ smbdirect_connection_rw_io_free(msg, direction);
+ }
+ atomic_add(credits_needed, &sc->rw_io.credits.count);
+ wake_up(&sc->rw_io.credits.wait_queue);
+ return ret;
+
+free_table:
+ sg_free_table_chained(&msg->sgt, SG_CHUNK_SIZE);
+free_msg:
+ kfree(msg);
+ goto out;
+}
+EXPORT_SYMBOL_GPL(smbdirect_connection_rdma_xmit);
diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c
new file mode 100644
index 000000000000..bb02df6158b9
--- /dev/null
+++ b/fs/smb/smbdirect/socket.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+
+bool smbdirect_frwr_is_supported(const struct ib_device_attr *attrs)
+{
+ /*
+ * Test if FRWR (Fast Registration Work Requests) is supported on the
+ * device This implementation requires FRWR on RDMA read/write return
+ * value: true if it is supported
+ */
+
+ if (!(attrs->device_cap_flags & IB_DEVICE_MEM_MGT_EXTENSIONS))
+ return false;
+ if (attrs->max_fast_reg_page_list_len == 0)
+ return false;
+ return true;
+}
+EXPORT_SYMBOL_GPL(smbdirect_frwr_is_supported);
+
+static void smbdirect_socket_cleanup_work(struct work_struct *work);
+
+static int smbdirect_socket_rdma_event_handler(struct rdma_cm_id *id,
+ struct rdma_cm_event *event)
+{
+ struct smbdirect_socket *sc = id->context;
+ int ret = -ESTALE;
+
+ /*
+ * This should be replaced before any real work
+ * starts! So it should never be called!
+ */
+
+ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+ ret = -ENETDOWN;
+ if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
+ ret = event->status;
+ pr_err("%s (first_error=%1pe, expected=%s) => event=%s status=%d => ret=%1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ rdma_event_msg(sc->rdma.expected_event),
+ rdma_event_msg(event->event),
+ event->status,
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ WARN_ONCE(1, "%s should not be called!\n", __func__);
+ sc->rdma.cm_id = NULL;
+ return -ESTALE;
+}
+
+int smbdirect_socket_init_new(struct net *net, struct smbdirect_socket *sc)
+{
+ struct rdma_cm_id *id;
+ int ret;
+
+ smbdirect_socket_init(sc);
+
+ id = rdma_create_id(net,
+ smbdirect_socket_rdma_event_handler,
+ sc,
+ RDMA_PS_TCP,
+ IB_QPT_RC);
+ if (IS_ERR(id)) {
+ pr_err("%s: rdma_create_id() failed %1pe\n", __func__, id);
+ return PTR_ERR(id);
+ }
+
+ ret = rdma_set_afonly(id, 1);
+ if (ret) {
+ rdma_destroy_id(id);
+ pr_err("%s: rdma_set_afonly() failed %1pe\n",
+ __func__, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ sc->rdma.cm_id = id;
+
+ INIT_WORK(&sc->disconnect_work, smbdirect_socket_cleanup_work);
+
+ return 0;
+}
+
+int smbdirect_socket_create_kern(struct net *net, struct smbdirect_socket **_sc)
+{
+ struct smbdirect_socket *sc;
+ int ret;
+
+ ret = -ENOMEM;
+ sc = kzalloc_obj(*sc);
+ if (!sc)
+ goto alloc_failed;
+
+ ret = smbdirect_socket_init_new(net, sc);
+ if (ret)
+ goto init_failed;
+
+ kref_init(&sc->refs.destroy);
+
+ *_sc = sc;
+ return 0;
+
+init_failed:
+ kfree(sc);
+alloc_failed:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_create_kern);
+
+int smbdirect_socket_init_accepting(struct rdma_cm_id *id, struct smbdirect_socket *sc)
+{
+ smbdirect_socket_init(sc);
+
+ sc->rdma.cm_id = id;
+ sc->rdma.cm_id->context = sc;
+ sc->rdma.cm_id->event_handler = smbdirect_socket_rdma_event_handler;
+
+ sc->ib.dev = sc->rdma.cm_id->device;
+
+ INIT_WORK(&sc->disconnect_work, smbdirect_socket_cleanup_work);
+
+ return 0;
+}
+
+int smbdirect_socket_create_accepting(struct rdma_cm_id *id, struct smbdirect_socket **_sc)
+{
+ struct smbdirect_socket *sc;
+ int ret;
+
+ ret = -ENOMEM;
+ sc = kzalloc_obj(*sc);
+ if (!sc)
+ goto alloc_failed;
+
+ ret = smbdirect_socket_init_accepting(id, sc);
+ if (ret)
+ goto init_failed;
+
+ kref_init(&sc->refs.destroy);
+
+ *_sc = sc;
+ return 0;
+
+init_failed:
+ kfree(sc);
+alloc_failed:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_create_accepting);
+
+int smbdirect_socket_set_initial_parameters(struct smbdirect_socket *sc,
+ const struct smbdirect_socket_parameters *sp)
+{
+ /*
+ * This is only allowed before connect or accept
+ */
+ WARN_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EINVAL;
+
+ if (sp->flags & ~SMBDIRECT_FLAG_PORT_RANGE_MASK)
+ return -EINVAL;
+
+ if (sp->initiator_depth > U8_MAX)
+ return -EINVAL;
+ if (sp->responder_resources > U8_MAX)
+ return -EINVAL;
+
+ if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB &&
+ sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
+ return -EINVAL;
+ else if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB)
+ rdma_restrict_node_type(sc->rdma.cm_id, RDMA_NODE_IB_CA);
+ else if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
+ rdma_restrict_node_type(sc->rdma.cm_id, RDMA_NODE_RNIC);
+
+ /*
+ * Make a copy of the callers parameters
+ * from here we only work on the copy
+ *
+ * TODO: do we want consistency checking?
+ */
+ sc->parameters = *sp;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_set_initial_parameters);
+
+const struct smbdirect_socket_parameters *
+smbdirect_socket_get_current_parameters(struct smbdirect_socket *sc)
+{
+ return &sc->parameters;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_get_current_parameters);
+
+int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
+ enum ib_poll_context poll_ctx,
+ gfp_t gfp_mask)
+{
+ /*
+ * This is only allowed before connect or accept
+ */
+ WARN_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EINVAL;
+
+ sc->ib.poll_ctx = poll_ctx;
+
+ sc->send_io.mem.gfp_mask = gfp_mask;
+ sc->recv_io.mem.gfp_mask = gfp_mask;
+ sc->rw_io.mem.gfp_mask = gfp_mask;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_set_kernel_settings);
+
+void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
+ void *private_ptr,
+ bool (*needed)(struct smbdirect_socket *sc,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls),
+ void (*vaprintf)(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls,
+ struct va_format *vaf))
+{
+ sc->logging.private_ptr = private_ptr;
+ sc->logging.needed = needed;
+ sc->logging.vaprintf = vaprintf;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_set_logging);
+
+static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
+{
+ /*
+ * Wake up all waiters in all wait queues
+ * in order to notice the broken connection.
+ */
+ wake_up_all(&sc->status_wait);
+ wake_up_all(&sc->listen.wait_queue);
+ wake_up_all(&sc->send_io.bcredits.wait_queue);
+ wake_up_all(&sc->send_io.lcredits.wait_queue);
+ wake_up_all(&sc->send_io.credits.wait_queue);
+ wake_up_all(&sc->send_io.pending.zero_wait_queue);
+ wake_up_all(&sc->recv_io.reassembly.wait_queue);
+ wake_up_all(&sc->rw_io.credits.wait_queue);
+ wake_up_all(&sc->mr_io.ready.wait_queue);
+}
+
+void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
+ const char *macro_name,
+ unsigned int lvl,
+ const char *func,
+ unsigned int line,
+ int error,
+ enum smbdirect_socket_status *force_status)
+{
+ struct smbdirect_socket *psc, *tsc;
+ unsigned long flags;
+ bool was_first = false;
+
+ if (!sc->first_error) {
+ ___smbdirect_log_generic(sc, func, line,
+ lvl,
+ SMBDIRECT_LOG_RDMA_EVENT,
+ "%s(%1pe%s%s) called from %s in line=%u status=%s\n",
+ macro_name,
+ SMBDIRECT_DEBUG_ERR_PTR(error),
+ force_status ? ", " : "",
+ force_status ? smbdirect_socket_status_string(*force_status) : "",
+ func, line,
+ smbdirect_socket_status_string(sc->status));
+ if (error)
+ sc->first_error = error;
+ else
+ sc->first_error = -ECONNABORTED;
+ was_first = true;
+ }
+
+ /*
+ * make sure other work (than disconnect_work)
+ * is not queued again but here we don't block and avoid
+ * disable[_delayed]_work_sync()
+ */
+ disable_work(&sc->connect.work);
+ disable_work(&sc->recv_io.posted.refill_work);
+ disable_work(&sc->idle.immediate_work);
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
+ disable_delayed_work(&sc->idle.timer_work);
+
+ /*
+ * In case we were a listener we need to
+ * disconnect all pending and ready sockets
+ *
+ * First we move ready sockets to pending again.
+ *
+ * Only a socket that was a listener (listen.backlog != -1) owns a
+ * populated listen.ready/pending list. Guarding on that also keeps
+ * lockdep quiet: without it, the listener holds sc->listen.lock while
+ * the loop recurses into each child psc, which takes psc->listen.lock.
+ * Those are always different instances of the same lock class -- a
+ * child never listens, so the nesting is strictly listener -> child
+ * and cannot really deadlock -- but lockdep only sees the class and
+ * reports "possible recursive locking". A child has empty listen
+ * lists and nothing to do here, so skipping it loses nothing, and a
+ * pending child stays on its listener's list for the free path
+ * (smbdirect_socket_destroy) to reap.
+ */
+ if (sc->listen.backlog != -1) { /* was a listener */
+ spin_lock_irqsave(&sc->listen.lock, flags);
+ list_splice_init(&sc->listen.ready, &sc->listen.pending);
+ list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
+ smbdirect_socket_schedule_cleanup(psc, sc->first_error);
+ spin_unlock_irqrestore(&sc->listen.lock, flags);
+ }
+
+ switch (sc->status) {
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
+ case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
+ case SMBDIRECT_SOCKET_ERROR:
+ case SMBDIRECT_SOCKET_DISCONNECTING:
+ case SMBDIRECT_SOCKET_DISCONNECTED:
+ case SMBDIRECT_SOCKET_DESTROYED:
+ /*
+ * Keep the current error status
+ */
+ break;
+
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
+ sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED;
+ break;
+
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
+ sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED;
+ break;
+
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
+ sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED;
+ break;
+
+ case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
+ case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
+ sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED;
+ break;
+
+ case SMBDIRECT_SOCKET_CREATED:
+ case SMBDIRECT_SOCKET_LISTENING:
+ sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
+ break;
+
+ case SMBDIRECT_SOCKET_CONNECTED:
+ sc->status = SMBDIRECT_SOCKET_ERROR;
+ break;
+ }
+
+ if (force_status && (was_first || *force_status > sc->status))
+ sc->status = *force_status;
+
+ /*
+ * Wake up all waiters in all wait queues
+ * in order to notice the broken connection.
+ */
+ smbdirect_socket_wake_up_all(sc);
+
+ queue_work(sc->workqueues.cleanup, &sc->disconnect_work);
+}
+
+static void smbdirect_socket_cleanup_work(struct work_struct *work)
+{
+ struct smbdirect_socket *sc =
+ container_of(work, struct smbdirect_socket, disconnect_work);
+ struct smbdirect_socket *psc, *tsc;
+ unsigned long flags;
+
+ /*
+ * This should not never be called in an interrupt!
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ if (!sc->first_error) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "%s called with first_error==0\n",
+ smbdirect_socket_status_string(sc->status));
+
+ sc->first_error = -ECONNABORTED;
+ }
+
+ /*
+ * make sure this and other work is not queued again
+ * but here we don't block and avoid
+ * disable[_delayed]_work_sync()
+ */
+ disable_work(&sc->disconnect_work);
+ disable_work(&sc->connect.work);
+ disable_work(&sc->recv_io.posted.refill_work);
+ disable_work(&sc->idle.immediate_work);
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
+ disable_delayed_work(&sc->idle.timer_work);
+
+ /*
+ * In case we were a listener we need to
+ * disconnect all pending and ready sockets
+ *
+ * First we move ready sockets to pending again.
+ *
+ * Guarded on listen.backlog != -1 for the same reason as in
+ * __smbdirect_socket_schedule_cleanup(): only a listener owns a
+ * populated listen list, and skipping the block for a child avoids
+ * nesting psc->listen.lock under a listener's listen.lock (different
+ * instances of one class -- harmless, but lockdep cannot tell).
+ */
+ if (sc->listen.backlog != -1) { /* was a listener */
+ spin_lock_irqsave(&sc->listen.lock, flags);
+ list_splice_init(&sc->listen.ready, &sc->listen.pending);
+ list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
+ smbdirect_socket_schedule_cleanup(psc, sc->first_error);
+ spin_unlock_irqrestore(&sc->listen.lock, flags);
+ }
+
+ switch (sc->status) {
+ case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
+ case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
+ case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
+ case SMBDIRECT_SOCKET_CONNECTED:
+ case SMBDIRECT_SOCKET_ERROR:
+ sc->status = SMBDIRECT_SOCKET_DISCONNECTING;
+ /*
+ * Make sure we hold the callback lock
+ * im order to coordinate with the
+ * rdma_event handlers, typically
+ * smbdirect_connection_rdma_event_handler(),
+ * and smbdirect_socket_destroy().
+ *
+ * So that the order of ib_drain_qp()
+ * and rdma_disconnect() is controlled
+ * by the mutex.
+ */
+ rdma_lock_handler(sc->rdma.cm_id);
+ rdma_disconnect(sc->rdma.cm_id);
+ rdma_unlock_handler(sc->rdma.cm_id);
+ break;
+
+ case SMBDIRECT_SOCKET_CREATED:
+ case SMBDIRECT_SOCKET_LISTENING:
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
+ /*
+ * rdma_{accept,connect}() never reached
+ * RDMA_CM_EVENT_ESTABLISHED
+ */
+ sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
+ break;
+
+ case SMBDIRECT_SOCKET_DISCONNECTING:
+ case SMBDIRECT_SOCKET_DISCONNECTED:
+ case SMBDIRECT_SOCKET_DESTROYED:
+ break;
+ }
+
+ /*
+ * Wake up all waiters in all wait queues
+ * in order to notice the broken connection.
+ */
+ smbdirect_socket_wake_up_all(sc);
+}
+
+static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
+{
+ struct smbdirect_socket *psc, *tsc;
+ size_t psockets;
+ struct smbdirect_recv_io *recv_io;
+ struct smbdirect_recv_io *recv_tmp;
+ LIST_HEAD(all_list);
+ LIST_HEAD(pending_list);
+ unsigned long flags;
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ /*
+ * This should not never be called in an interrupt!
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ if (sc->status == SMBDIRECT_SOCKET_DESTROYED)
+ return;
+
+ WARN_ONCE(sc->status != SMBDIRECT_SOCKET_DISCONNECTED,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ /*
+ * The listener should clear this before we reach this
+ */
+ WARN_ONCE(sc->accept.listener,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ /*
+ * Wake up all waiters in all wait queues
+ * in order to notice the broken connection.
+ *
+ * Most likely this was already called via
+ * smbdirect_socket_cleanup_work(), but call it again...
+ */
+ smbdirect_socket_wake_up_all(sc);
+
+ disable_work_sync(&sc->disconnect_work);
+ disable_work_sync(&sc->connect.work);
+ disable_work_sync(&sc->recv_io.posted.refill_work);
+ disable_work_sync(&sc->idle.immediate_work);
+ disable_delayed_work_sync(&sc->idle.timer_work);
+
+ if (sc->rdma.cm_id)
+ rdma_lock_handler(sc->rdma.cm_id);
+
+ if (sc->ib.qp) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "drain qp\n");
+ ib_drain_qp(sc->ib.qp);
+ }
+
+ /*
+ * In case we were a listener we need to
+ * disconnect all pending and ready sockets
+ *
+ * We move ready sockets to pending again.
+ *
+ * Capture them here -- rdma_lock_handler(sc->rdma.cm_id) is held above,
+ * so a concurrent CM CONNECT_REQUEST cannot add more; sc->listen.lock
+ * below only protects the list splice itself -- but DEFER releasing
+ * them until the listener's cm_id is destroyed:
+ *
+ * - smbdirect_socket_release() -> smbdirect_socket_destroy() takes the
+ * child's own rdma_lock_handler() lock (&id_priv->handler_mutex).
+ * The listener's and the child's cm_id are always different
+ * instances, so the nesting cannot really deadlock, but lockdep only
+ * sees one lock class and reports "possible recursive locking".
+ *
+ * - rdma_destroy_id() of a child before the listener's own
+ * rdma_destroy_id() below lets _cma_cancel_listens() walk the freed
+ * child id_priv (KASAN slab-use-after-free in __mutex_lock()).
+ *
+ * The children are independent sockets whose teardown does not need
+ * the listener's handler lock.
+ */
+ spin_lock_irqsave(&sc->listen.lock, flags);
+ list_splice_tail_init(&sc->listen.ready, &pending_list);
+ list_splice_tail_init(&sc->listen.pending, &pending_list);
+ spin_unlock_irqrestore(&sc->listen.lock, flags);
+
+ /* It's not possible for upper layer to get to reassembly */
+ if (sc->listen.backlog == -1) /* was not a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "drain the reassembly queue\n");
+ spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
+ list_splice_tail_init(&sc->recv_io.reassembly.list, &all_list);
+ spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
+ list_for_each_entry_safe(recv_io, recv_tmp, &all_list, list)
+ smbdirect_connection_put_recv_io(recv_io);
+ sc->recv_io.reassembly.data_length = 0;
+
+ if (sc->listen.backlog == -1) /* was not a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "freeing mr list\n");
+ smbdirect_connection_destroy_mr_list(sc);
+
+ if (sc->listen.backlog == -1) /* was not a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "destroying qp\n");
+ smbdirect_connection_destroy_qp(sc);
+ if (sc->rdma.cm_id) {
+ rdma_unlock_handler(sc->rdma.cm_id);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "destroying cm_id\n");
+ rdma_destroy_id(sc->rdma.cm_id);
+ sc->rdma.cm_id = NULL;
+ }
+
+ /*
+ * The listener's rdma_lock_handler() lock is dropped and its cm_id is
+ * destroyed, so it is safe to release the child sockets captured
+ * above: each release recurses into smbdirect_socket_destroy() and
+ * takes that child's own handler_mutex without nesting it under the
+ * listener's, and _cma_cancel_listens() can no longer reach them.
+ */
+ psockets = list_count_nodes(&pending_list);
+ if (sc->listen.backlog != -1) /* was a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "release %zu pending sockets\n", psockets);
+ list_for_each_entry_safe(psc, tsc, &pending_list, accept.list) {
+ list_del_init(&psc->accept.list);
+ psc->accept.listener = NULL;
+ smbdirect_socket_release(psc);
+ }
+ if (sc->listen.backlog != -1) /* was a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "released %zu pending sockets\n", psockets);
+
+ if (sc->listen.backlog == -1) /* was not a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "destroying mem pools\n");
+ smbdirect_connection_destroy_mem_pools(sc);
+
+ sc->status = SMBDIRECT_SOCKET_DESTROYED;
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "rdma session destroyed\n");
+}
+
+void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
+{
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ /*
+ * This should not never be called in an interrupt!
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ /*
+ * First we try to disable the work
+ * without disable_work_sync() in a
+ * non blocking way, if it's already
+ * running it will be handles by
+ * disable_work_sync() below.
+ *
+ * Here we just want to make sure queue_work() in
+ * smbdirect_socket_schedule_cleanup_lvl()
+ * is a no-op.
+ */
+ disable_work(&sc->disconnect_work);
+
+ if (!sc->first_error)
+ /*
+ * SMBDIRECT_LOG_INFO is enough here
+ * as this is the typical case where
+ * we terminate the connection ourself.
+ */
+ smbdirect_socket_schedule_cleanup_lvl(sc,
+ SMBDIRECT_LOG_INFO,
+ -ESHUTDOWN);
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "cancelling and disable disconnect_work\n");
+ disable_work_sync(&sc->disconnect_work);
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "destroying rdma session\n");
+ if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING)
+ smbdirect_socket_cleanup_work(&sc->disconnect_work);
+ if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "wait for transport being disconnected\n");
+ wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "waited for transport being disconnected\n");
+ }
+
+ /*
+ * Once we reached SMBDIRECT_SOCKET_DISCONNECTED,
+ * we should call smbdirect_socket_destroy()
+ */
+ smbdirect_socket_destroy(sc);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+}
+
+int smbdirect_socket_bind(struct smbdirect_socket *sc, struct sockaddr *addr)
+{
+ int ret;
+
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EINVAL;
+
+ ret = rdma_bind_addr(sc->rdma.cm_id, addr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_bind);
+
+void smbdirect_socket_shutdown(struct smbdirect_socket *sc)
+{
+ smbdirect_socket_schedule_cleanup_lvl(sc, SMBDIRECT_LOG_INFO, -ESHUTDOWN);
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_shutdown);
+
+static void smbdirect_socket_release_disconnect(struct kref *kref)
+{
+ struct smbdirect_socket *sc =
+ container_of(kref, struct smbdirect_socket, refs.disconnect);
+
+ /*
+ * For now do a sync disconnect/destroy
+ */
+ smbdirect_socket_destroy_sync(sc);
+}
+
+static void smbdirect_socket_release_destroy(struct kref *kref)
+{
+ struct smbdirect_socket *sc =
+ container_of(kref, struct smbdirect_socket, refs.destroy);
+
+ /*
+ * Do a sync disconnect/destroy...
+ * hopefully a no-op, as it should be already
+ * in DESTROYED state, before we free the memory.
+ */
+ smbdirect_socket_destroy_sync(sc);
+ kfree(sc);
+}
+
+void smbdirect_socket_release(struct smbdirect_socket *sc)
+{
+ /*
+ * We expect only 1 disconnect reference
+ * and if it is already 0, it's a use after free!
+ */
+ WARN_ON_ONCE(kref_read(&sc->refs.disconnect) != 1);
+ WARN_ON(!kref_put(&sc->refs.disconnect, smbdirect_socket_release_disconnect));
+
+ /*
+ * This may not trigger smbdirect_socket_release_destroy(),
+ * if struct smbdirect_socket is embedded in another structure
+ * indicated by REFCOUNT_MAX.
+ */
+ kref_put(&sc->refs.destroy, smbdirect_socket_release_destroy);
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_release);
+
+int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
+ enum smbdirect_socket_status expected_status,
+ int unexpected_errno,
+ wait_queue_head_t *waitq,
+ atomic_t *total_credits,
+ int needed)
+{
+ int ret;
+
+ if (WARN_ON_ONCE(needed < 0))
+ return -EINVAL;
+
+ do {
+ if (atomic_sub_return(needed, total_credits) >= 0)
+ return 0;
+
+ atomic_add(needed, total_credits);
+ ret = wait_event_interruptible(*waitq,
+ atomic_read(total_credits) >= needed ||
+ sc->status != expected_status);
+
+ if (sc->status != expected_status)
+ return unexpected_errno;
+ else if (ret < 0)
+ return ret;
+ } while (true);
+}
diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/smbdirect/socket.h
index 95265192bb01..c09eddd8ad16 100644
--- a/fs/smb/common/smbdirect/smbdirect_socket.h
+++ b/fs/smb/smbdirect/socket.h
@@ -6,10 +6,18 @@
#ifndef __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_SOCKET_H__
#define __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_SOCKET_H__
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/kref.h>
+#include <linux/mempool.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/completion.h>
#include <rdma/rw.h>
enum smbdirect_socket_status {
SMBDIRECT_SOCKET_CREATED,
+ SMBDIRECT_SOCKET_LISTENING,
SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED,
SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING,
SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED,
@@ -35,6 +43,8 @@ const char *smbdirect_socket_status_string(enum smbdirect_socket_status status)
switch (status) {
case SMBDIRECT_SOCKET_CREATED:
return "CREATED";
+ case SMBDIRECT_SOCKET_LISTENING:
+ return "LISTENING";
case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
return "RESOLVE_ADDR_NEEDED";
case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
@@ -99,19 +109,60 @@ struct smbdirect_socket {
int first_error;
/*
- * This points to the workqueue to
+ * This points to the workqueues to
* be used for this socket.
- * It can be per socket (on the client)
- * or point to a global workqueue (on the server)
*/
- struct workqueue_struct *workqueue;
+ struct {
+ struct workqueue_struct *accept;
+ struct workqueue_struct *connect;
+ struct workqueue_struct *idle;
+ struct workqueue_struct *refill;
+ struct workqueue_struct *immediate;
+ struct workqueue_struct *cleanup;
+ } workqueues;
struct work_struct disconnect_work;
+ /*
+ * The reference counts.
+ */
+ struct {
+ /*
+ * This holds the references by the
+ * frontend, typically the smb layer.
+ *
+ * It is typically 1 and a disconnect
+ * will happen if it reaches 0.
+ */
+ struct kref disconnect;
+
+ /*
+ * This holds the reference by the
+ * backend, the code that manages
+ * the lifetime of the whole
+ * struct smbdirect_socket,
+ * if this reaches 0 it can will
+ * be freed.
+ *
+ * Can be REFCOUNT_MAX is part
+ * of another structure.
+ *
+ * This is equal or higher than
+ * the disconnect refcount.
+ */
+ struct kref destroy;
+ } refs;
+
/* RDMA related */
struct {
struct rdma_cm_id *cm_id;
/*
+ * The expected event in our current
+ * cm_id->event_handler, all other events
+ * are treated as an error.
+ */
+ enum rdma_cm_event_type expected_event;
+ /*
* This is for iWarp MPA v1
*/
bool legacy_iwarp;
@@ -120,6 +171,7 @@ struct smbdirect_socket {
/* IB verbs related */
struct {
struct ib_pd *pd;
+ enum ib_poll_context poll_ctx;
struct ib_cq *send_cq;
struct ib_cq *recv_cq;
@@ -150,6 +202,35 @@ struct smbdirect_socket {
} idle;
/*
+ * The state for listen sockets
+ */
+ struct {
+ spinlock_t lock;
+ struct list_head pending;
+ struct list_head ready;
+ wait_queue_head_t wait_queue;
+ /*
+ * This starts as -1 and a value != -1
+ * means this socket was in LISTENING state
+ * before. Note the valid backlog can
+ * only be > 0.
+ */
+ int backlog;
+ } listen;
+
+ /*
+ * The state for sockets waiting
+ * for accept, either still waiting
+ * for the negotiation to finish
+ * or already ready with a usable
+ * connection.
+ */
+ struct {
+ struct smbdirect_socket *listener;
+ struct list_head list;
+ } accept;
+
+ /*
* The state for posted send buffers
*/
struct {
@@ -158,8 +239,9 @@ struct smbdirect_socket {
* smbdirect_send_io buffers
*/
struct {
- struct kmem_cache *cache;
- mempool_t *pool;
+ struct kmem_cache *cache;
+ mempool_t *pool;
+ gfp_t gfp_mask;
} mem;
/*
@@ -195,10 +277,6 @@ struct smbdirect_socket {
struct {
atomic_t count;
/*
- * woken when count is decremented
- */
- wait_queue_head_t dec_wait_queue;
- /*
* woken when count reached zero
*/
wait_queue_head_t zero_wait_queue;
@@ -223,8 +301,9 @@ struct smbdirect_socket {
* smbdirect_recv_io buffers
*/
struct {
- struct kmem_cache *cache;
- mempool_t *pool;
+ struct kmem_cache *cache;
+ mempool_t *pool;
+ gfp_t gfp_mask;
} mem;
/*
@@ -310,13 +389,6 @@ struct smbdirect_socket {
struct {
atomic_t count;
} used;
-
- struct work_struct recovery_work;
-
- /* Used by transport to wait until all MRs are returned */
- struct {
- wait_queue_head_t wait_queue;
- } cleanup;
} mr_io;
/*
@@ -324,6 +396,14 @@ struct smbdirect_socket {
*/
struct {
/*
+ * Memory hints for
+ * smbdirect_rw_io structs
+ */
+ struct {
+ gfp_t gfp_mask;
+ } mem;
+
+ /*
* The credit state for the send side
*/
struct {
@@ -350,6 +430,21 @@ struct smbdirect_socket {
u64 dequeue_reassembly_queue;
u64 send_empty;
} statistics;
+
+ struct {
+ void *private_ptr;
+ bool (*needed)(struct smbdirect_socket *sc,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls);
+ void (*vaprintf)(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls,
+ struct va_format *vaf);
+ } logging;
};
static void __smbdirect_socket_disabled_work(struct work_struct *work)
@@ -360,6 +455,100 @@ static void __smbdirect_socket_disabled_work(struct work_struct *work)
WARN_ON_ONCE(1);
}
+static bool __smbdirect_log_needed(struct smbdirect_socket *sc,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls)
+{
+ /*
+ * Should never be called, the caller should
+ * set it's own functions.
+ */
+ WARN_ON_ONCE(1);
+ return false;
+}
+
+static void __smbdirect_log_vaprintf(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls,
+ struct va_format *vaf)
+{
+ /*
+ * Should never be called, the caller should
+ * set it's own functions.
+ */
+ WARN_ON_ONCE(1);
+}
+
+__printf(6, 7)
+static void __smbdirect_log_printf(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ unsigned int lvl,
+ unsigned int cls,
+ const char *fmt,
+ ...);
+__maybe_unused
+static void __smbdirect_log_printf(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ unsigned int lvl,
+ unsigned int cls,
+ const char *fmt,
+ ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ sc->logging.vaprintf(sc,
+ func,
+ line,
+ sc->logging.private_ptr,
+ lvl,
+ cls,
+ &vaf);
+ va_end(args);
+}
+
+#define ___smbdirect_log_generic(sc, func, line, lvl, cls, fmt, args...) do { \
+ if (sc->logging.needed(sc, sc->logging.private_ptr, lvl, cls)) { \
+ __smbdirect_log_printf(sc, func, line, lvl, cls, fmt, ##args); \
+ } \
+} while (0)
+#define __smbdirect_log_generic(sc, lvl, cls, fmt, args...) \
+ ___smbdirect_log_generic(sc, __func__, __LINE__, lvl, cls, fmt, ##args)
+
+#define smbdirect_log_outgoing(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_OUTGOING, fmt, ##args)
+#define smbdirect_log_incoming(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_INCOMING, fmt, ##args)
+#define smbdirect_log_read(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_READ, fmt, ##args)
+#define smbdirect_log_write(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_WRITE, fmt, ##args)
+#define smbdirect_log_rdma_send(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_SEND, fmt, ##args)
+#define smbdirect_log_rdma_recv(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_RECV, fmt, ##args)
+#define smbdirect_log_keep_alive(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_KEEP_ALIVE, fmt, ##args)
+#define smbdirect_log_rdma_event(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_EVENT, fmt, ##args)
+#define smbdirect_log_rdma_mr(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_MR, fmt, ##args)
+#define smbdirect_log_rdma_rw(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_RW, fmt, ##args)
+#define smbdirect_log_negotiate(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_NEGOTIATE, fmt, ##args)
+
static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
{
/*
@@ -370,9 +559,23 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
init_waitqueue_head(&sc->status_wait);
+ sc->workqueues.accept = smbdirect_globals.workqueues.accept;
+ sc->workqueues.connect = smbdirect_globals.workqueues.connect;
+ sc->workqueues.idle = smbdirect_globals.workqueues.idle;
+ sc->workqueues.refill = smbdirect_globals.workqueues.refill;
+ sc->workqueues.immediate = smbdirect_globals.workqueues.immediate;
+ sc->workqueues.cleanup = smbdirect_globals.workqueues.cleanup;
+
INIT_WORK(&sc->disconnect_work, __smbdirect_socket_disabled_work);
disable_work_sync(&sc->disconnect_work);
+ kref_init(&sc->refs.disconnect);
+ sc->refs.destroy = (struct kref) KREF_INIT(REFCOUNT_MAX);
+
+ sc->rdma.expected_event = RDMA_CM_EVENT_INTERNAL;
+
+ sc->ib.poll_ctx = IB_POLL_UNBOUND_WORKQUEUE;
+
spin_lock_init(&sc->connect.lock);
INIT_WORK(&sc->connect.work, __smbdirect_socket_disabled_work);
disable_work_sync(&sc->connect.work);
@@ -382,6 +585,16 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
INIT_DELAYED_WORK(&sc->idle.timer_work, __smbdirect_socket_disabled_work);
disable_delayed_work_sync(&sc->idle.timer_work);
+ spin_lock_init(&sc->listen.lock);
+ INIT_LIST_HEAD(&sc->listen.pending);
+ INIT_LIST_HEAD(&sc->listen.ready);
+ sc->listen.backlog = -1; /* not a listener */
+ init_waitqueue_head(&sc->listen.wait_queue);
+
+ INIT_LIST_HEAD(&sc->accept.list);
+
+ sc->send_io.mem.gfp_mask = GFP_KERNEL;
+
atomic_set(&sc->send_io.bcredits.count, 0);
init_waitqueue_head(&sc->send_io.bcredits.wait_queue);
@@ -392,9 +605,10 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
init_waitqueue_head(&sc->send_io.credits.wait_queue);
atomic_set(&sc->send_io.pending.count, 0);
- init_waitqueue_head(&sc->send_io.pending.dec_wait_queue);
init_waitqueue_head(&sc->send_io.pending.zero_wait_queue);
+ sc->recv_io.mem.gfp_mask = GFP_KERNEL;
+
INIT_LIST_HEAD(&sc->recv_io.free.list);
spin_lock_init(&sc->recv_io.free.lock);
@@ -409,6 +623,7 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
spin_lock_init(&sc->recv_io.reassembly.lock);
init_waitqueue_head(&sc->recv_io.reassembly.wait_queue);
+ sc->rw_io.mem.gfp_mask = GFP_KERNEL;
atomic_set(&sc->rw_io.credits.count, 0);
init_waitqueue_head(&sc->rw_io.credits.wait_queue);
@@ -417,9 +632,10 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
atomic_set(&sc->mr_io.ready.count, 0);
init_waitqueue_head(&sc->mr_io.ready.wait_queue);
atomic_set(&sc->mr_io.used.count, 0);
- INIT_WORK(&sc->mr_io.recovery_work, __smbdirect_socket_disabled_work);
- disable_work_sync(&sc->mr_io.recovery_work);
- init_waitqueue_head(&sc->mr_io.cleanup.wait_queue);
+
+ sc->logging.private_ptr = NULL;
+ sc->logging.needed = __smbdirect_log_needed;
+ sc->logging.vaprintf = __smbdirect_log_vaprintf;
}
#define __SMBDIRECT_CHECK_STATUS_FAILED(__sc, __expected_status, __error_cmd, __unexpected_cmd) ({ \
@@ -436,7 +652,6 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
#define __SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, __unexpected_cmd) \
__SMBDIRECT_CHECK_STATUS_FAILED(__sc, __expected_status, \
- , \
{ \
const struct sockaddr_storage *__src = NULL; \
const struct sockaddr_storage *__dst = NULL; \
@@ -444,6 +659,26 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
__src = &(__sc)->rdma.cm_id->route.addr.src_addr; \
__dst = &(__sc)->rdma.cm_id->route.addr.dst_addr; \
} \
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, \
+ "expected[%s] != %s first_error=%1pe local=%pISpsfc remote=%pISpsfc\n", \
+ smbdirect_socket_status_string(__expected_status), \
+ smbdirect_socket_status_string((__sc)->status), \
+ SMBDIRECT_DEBUG_ERR_PTR((__sc)->first_error), \
+ __src, __dst); \
+ }, \
+ { \
+ const struct sockaddr_storage *__src = NULL; \
+ const struct sockaddr_storage *__dst = NULL; \
+ if ((__sc)->rdma.cm_id) { \
+ __src = &(__sc)->rdma.cm_id->route.addr.src_addr; \
+ __dst = &(__sc)->rdma.cm_id->route.addr.dst_addr; \
+ } \
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, \
+ "expected[%s] != %s first_error=%1pe local=%pISpsfc remote=%pISpsfc\n", \
+ smbdirect_socket_status_string(__expected_status), \
+ smbdirect_socket_status_string((__sc)->status), \
+ SMBDIRECT_DEBUG_ERR_PTR((__sc)->first_error), \
+ __src, __dst); \
WARN_ONCE(1, \
"expected[%s] != %s first_error=%1pe local=%pISpsfc remote=%pISpsfc\n", \
smbdirect_socket_status_string(__expected_status), \
@@ -456,6 +691,11 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
#define SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status) \
__SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, /* nothing */)
+#ifndef __SMBDIRECT_SOCKET_DISCONNECT
+#define __SMBDIRECT_SOCKET_DISCONNECT(__sc) \
+ smbdirect_socket_schedule_cleanup(__sc, -ECONNABORTED)
+#endif /* ! __SMBDIRECT_SOCKET_DISCONNECT */
+
#define SMBDIRECT_CHECK_STATUS_DISCONNECT(__sc, __expected_status) \
__SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, \
__SMBDIRECT_SOCKET_DISCONNECT(__sc);)
@@ -574,4 +814,19 @@ struct smbdirect_rw_io {
struct scatterlist sg_list[];
};
+static inline size_t smbdirect_get_buf_page_count(const void *buf, size_t size)
+{
+ return DIV_ROUND_UP((uintptr_t)buf + size, PAGE_SIZE) -
+ (uintptr_t)buf / PAGE_SIZE;
+}
+
+/*
+ * Maximum number of retries on data transfer operations
+ */
+#define SMBDIRECT_RDMA_CM_RETRY 6
+/*
+ * No need to retry on Receiver Not Ready since SMB_DIRECT manages credits
+ */
+#define SMBDIRECT_RDMA_CM_RNR_RETRY 0
+
#endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_SOCKET_H__ */