summaryrefslogtreecommitdiff
path: root/fs/smb/server
diff options
context:
space:
mode:
Diffstat (limited to 'fs/smb/server')
-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
44 files changed, 9608 insertions, 4816 deletions
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);