This contains a set of 142 SMB server updates focused on SMB2 command

sequencing, SMB3 request replay and encryption, Apple Time Machine
 interoperability, protocol-compatibility fixes validated with smbtorture,
 security hardening, SMB Direct transport support, connection reliability,
 and other correctness improvements.
 
 New features:
 
  - Implement the SMB2 command sequence window.
    Enforce the credit-based MessageId range for each connection, rejecting
    out-of-window, duplicate, and wrapped sequence numbers. This prevents
    invalid requests and same-channel replays from being processed.
 
  - Add SMB3 request replay support.
    SMB3 clients may resend requests with SMB2_FLAGS_REPLAY_OPERATION after
    a channel disconnect when the original response was lost. Track the
    required channel and open state to safely handle durable CREATE replays
    and make oplock, lease, and lock replays idempotent, avoiding duplicate
    state changes and improving multichannel reconnect reliability.
 
  - Add opt-in Apple Time Machine support.
    Implement the AAPL negotiation and related Finder, stream, COPYCHUNK,
    sparse-file, CHANGE_NOTIFY, and RPC compatibility required for Time
    Machine shares, allowing macOS backupd to use ksmbd for backups.
 
  - Add per-share SMB3 encryption support.
    Allow individual shares to require SMB3 encryption by advertising
    SMB2_SHAREFLAG_ENCRYPT_DATA in TREE_CONNECT responses and rejecting
    unencrypted tree connects and plaintext requests for protected shares.
 
  - Add SMB Direct RDMA encryption support.
    Extend SMB Direct to support SMB3 encrypted payloads over RDMA, with
    transform negotiation and encryption/decryption for RDMA READ/WRITE.
 
 Other changes:
 
  - Parse and retain AppInstanceVersion contexts, enforce version ordering,
    close older active handles for newer takeovers, and reject invalid or
    unversioned opens according to the SMB2 semantics.
 
  - Accept durable reconnect requests that omit VolatileFileId when the
    persistent ID and reconnect context identify the handle, while
    continuing to reject explicit volatile-ID mismatches.
 
  - Fix SMB2/SMB3 protocol validation and security issues, including request
    offsets, file and object IDs, IPC responses, output buffer sizes,
    SMB3.1.1 binding validation, signing-required handling, durable handles,
    ACLs, maximal access, and security information.
 
  - Fix heap out-of-bounds accesses, use-after-free bugs, memory leaks,
    invalid pointer dereferences, and sensitive-data lifetime issues in
    authentication, Kerberos, preauthentication, sessions, connections,
    and module teardown.
 
  - Correct alternate-data-stream and named-stream handling, COPYCHUNK
    behavior, sparse-file and compression attributes, allocated-range
    queries, file trimming, duplicate extents, DOS attributes, snapshots,
    normalized names, and partial information responses.
 
  - Fix locking, lease, oplock, durable reconnect, async request, and
    CHANGE_NOTIFY races, including deferred-lock rollback, parent directory
    lease notifications, and connection teardown lifetime bugs.
 
  - Fix SMB3 encryption handling for compressed requests, expired encrypted
    sessions, interim responses, bound multichannel connections, and
    decryption failures.
 
  - Fix SMB3 multichannel session lookup and session state transitions so
    changes are scoped to the correct bound connections and cannot revive
    connections that are already shutting down.
 
  - Fix DACL access checks so ACE walks are bounded by the declared DACL
    size, preventing data beyond the DACL boundary from being interpreted
    during access validation.
 
  - Fix session accounting and lifetime issues, including session counter
    updates during publication and removal, session leaks on registration
    failure, and procfs creation diagnostics.
 
  - Improve TCP connection reliability by enabling TCP keepalive for
    accepted connections and preserving TCP timers for kernel sockets,
    preventing silent peers from holding connections indefinitely.
 
  - Fix smbdirect RDMA cleanup ordering for completion queues, QPs, child
    sockets, and listener locking.
 
  - Improve async response framing, multi-iovec signing, RPC pipe status
    handling, and ksmbd procfs monitoring for server, share, connection,
    session, and open-file state.
 
  - Remove the obsolete DES crypto header and Kconfig dependency now that
    NTLMv1 support has been removed.
 
  - Update the ksmbd repository URL in MAINTAINERS and add an additional
    KSMBD reviewer.
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmqKrvwWHGxpbmtpbmpl
 b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCExxEACUk9E89TrgHUlSni/U4WEcg95n
 YNVm+oeRNnpqu7axGtIqmzgc7eU96ofR8un0pNLjkdWSD27hv6bmUv0e2+uOI7LU
 04FIwJfOLPR4roKgwyLhC+I88zjHWBxU2fAx1l0ksFpTAF4vFuo2Il/TsaM57QEm
 qhE0laiLV95jrtafnW8l2E+Be+qsRy7wRrj69IThfBBpwEMvehL3JK42yDItLKUm
 ETKJuO4O3QCN1tyMlntzshdLhuFIWrnNspTwKqMEE9/Ba4cuzGrcm6Y4tLDHDX/j
 Vr45w6rFsULJhw2mSz8cRYV3HcrPea7CVynJVDYtdIAngez0O3iLE5qAM1gff0HG
 5H4N57ye3iabySD9B9Hy8ISqreCGW7dNZf3LACmZyeXjNGmxGB4nrgTc9fz1SAcl
 JizpgMxrh2xj0YmABShgfqHFn9u+QvWaMIDYUddIQ+fX1Y+TxHDPssGJ0ygXCyJ0
 BrIwqRHgL7XONY2sx8bPWExAzVZq/4gPWVZACM7X138iuR1kDIEpnPqvMMpPx8am
 x5jSgvQ8NdABxLUZWU3BhcJseGG4gbvq0HzYibR6MPNy6P5ct3q+ifiWuY/JZ8Op
 ZTxcyShSXziy8uk7iKvGgS3eJ5bDSb2Wo9flkvoCifG7VgPfDec3d/V4f1n9cTix
 2TpmzQRF98f6arzzSQ==
 =uocO
 -----END PGP SIGNATURE-----

Merge tag 'ksmbd-for-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/smb

Pull smb server updates from Namjae Jeon:
 "This contains server updates focused on SMB2 command sequencing, SMB3
  request replay and encryption, Apple Time Machine interoperability,
  protocol-compatibility fixes validated with smbtorture, security
  hardening, SMB Direct transport support, connection reliability, and
  other correctness improvements.

  New features:

   - Implement the SMB2 command sequence window

     Enforce the credit-based MessageId range for each connection,
     rejecting out-of-window, duplicate, and wrapped sequence numbers.
     This prevents invalid requests and same-channel replays from being
     processed

   - Add SMB3 request replay support

     SMB3 clients may resend requests with SMB2_FLAGS_REPLAY_OPERATION
     after a channel disconnect when the original response was lost.
     Track the required channel and open state to safely handle durable
     CREATE replays and make oplock, lease, and lock replays idempotent,
     avoiding duplicate state changes and improving multichannel
     reconnect reliability

   - Add opt-in Apple Time Machine support

     Implement the AAPL negotiation and related Finder, stream,
     COPYCHUNK, sparse-file, CHANGE_NOTIFY, and RPC compatibility
     required for Time Machine shares, allowing macOS backupd to use
     ksmbd for backups

   - Add per-share SMB3 encryption support

     Allow individual shares to require SMB3 encryption by advertising
     SMB2_SHAREFLAG_ENCRYPT_DATA in TREE_CONNECT responses and rejecting
     unencrypted tree connects and plaintext requests for protected
     shares

   - Add SMB Direct RDMA encryption support

     Extend SMB Direct to support SMB3 encrypted payloads over RDMA,
     with transform negotiation and encryption/decryption for RDMA
     READ/WRITE

  Other changes:

   - Parse and retain AppInstanceVersion contexts, enforce version
     ordering, close older active handles for newer takeovers, and
     reject invalid or unversioned opens according to the SMB2 semantics

   - Accept durable reconnect requests that omit VolatileFileId when the
     persistent ID and reconnect context identify the handle, while
     continuing to reject explicit volatile-ID mismatches

   - Fix SMB2/SMB3 protocol validation and security issues, including
     request offsets, file and object IDs, IPC responses, output buffer
     sizes, SMB3.1.1 binding validation, signing-required handling,
     durable handles, ACLs, maximal access, and security information

   - Fix heap out-of-bounds accesses, use-after-free bugs, memory leaks,
     invalid pointer dereferences, and sensitive-data lifetime issues in
     authentication, Kerberos, preauthentication, sessions, connections,
     and module teardown

   - Correct alternate-data-stream and named-stream handling, COPYCHUNK
     behavior, sparse-file and compression attributes, allocated-range
     queries, file trimming, duplicate extents, DOS attributes,
     snapshots, normalized names, and partial information responses

   - Fix locking, lease, oplock, durable reconnect, async request, and
     CHANGE_NOTIFY races, including deferred-lock rollback, parent
     directory lease notifications, and connection teardown lifetime
     bugs

   - Fix SMB3 encryption handling for compressed requests, expired
     encrypted sessions, interim responses, bound multichannel
     connections, and decryption failures

   - Fix SMB3 multichannel session lookup and session state transitions
     so changes are scoped to the correct bound connections and cannot
     revive connections that are already shutting down

   - Fix DACL access checks so ACE walks are bounded by the declared
     DACL size, preventing data beyond the DACL boundary from being
     interpreted during access validation

   - Fix session accounting and lifetime issues, including session
     counter updates during publication and removal, session leaks on
     registration failure, and procfs creation diagnostics

   - Improve TCP connection reliability by enabling TCP keepalive for
     accepted connections and preserving TCP timers for kernel sockets,
     preventing silent peers from holding connections indefinitely

   - Fix smbdirect RDMA cleanup ordering for completion queues, QPs,
     child sockets, and listener locking

   - Improve async response framing, multi-iovec signing, RPC pipe
     status handling, and ksmbd procfs monitoring for server, share,
     connection, session, and open-file state

   - Remove the obsolete DES crypto header and Kconfig dependency now
     that NTLMv1 support has been removed

   - Update the ksmbd repository URL in MAINTAINERS and add an
     additional KSMBD reviewer"

* tag 'ksmbd-for-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/smb: (142 commits)
  MAINTAINERS: update ksmbd repository URL
  MAINTAINERS: add myself as KSMBD reviewer
  smb: server: remove unused DES crypto header
  smb: server: Remove obsolete "select CRYPTO_LIB_DES" from Kconfig file
  ksmbd: keep TCP timers alive for kernel sockets
  ksmbd: enable TCP keepalive for accepted connections
  smb/server: fix session counter on session removal
  smb/server: update session counter under sessions table lock
  smb/server: fix session leak in ksmbd_session_register()
  smb/server: warn if ksmbd_proc_create() fails
  ksmbd: bound smb_check_perm_dacl() ACE walks by DACL size
  ksmbd: make RDMA encryption diagnostics conditional
  ksmbd: add SMB Direct RDMA encryption transform
  ksmbd: handle encrypted compressed requests
  ksmbd: decrypt requests from expired encrypted sessions
  ksmbd: disconnect on SMB3 decryption failure
  ksmbd: encrypt interim responses to encrypted requests
  ksmbd: scope session state changes to bound connections
  ksmbd: fix encrypted request lookup on bound channels
  ksmbd: add per-share SMB3 encryption enforcement
  ...
This commit is contained in:
Linus Torvalds 2026-08-23 08:41:36 -07:00
commit 61a09cfc12
49 changed files with 5447 additions and 875 deletions

View File

@ -14183,9 +14183,11 @@ M: Steve French <smfrench@gmail.com>
M: Steve French <sfrench@samba.org>
R: Sergey Senozhatsky <senozhatsky@chromium.org>
R: Tom Talpey <tom@talpey.com>
R: ChenXiaoSong <chenxiaosong@chenxiaosong.com>
R: ChenXiaoSong <chenxiaosong@kylinos.cn>
L: linux-cifs@vger.kernel.org
S: Maintained
T: git https://git.samba.org/ksmbd.git ksmbd-for-next
T: git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/smb.git ksmbd-for-next
F: Documentation/filesystems/smb/ksmbd.rst
F: fs/smb/common/
F: fs/smb/server/

View File

@ -21,30 +21,6 @@
/* The total header size for SMB2 read and write */
#define SMB2_READWRITE_PDU_HEADER_SIZE (48 + sizeof(struct smb2_hdr))
/* See MS-SMB2 2.2.43 */
struct smb2_rdma_transform {
__le16 RdmaDescriptorOffset;
__le16 RdmaDescriptorLength;
__le32 Channel; /* for values see channel description in smb2 read above */
__le16 TransformCount;
__le16 Reserved1;
__le32 Reserved2;
} __packed;
/* TransformType */
#define SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION 0x0001
#define SMB2_RDMA_TRANSFORM_TYPE_SIGNING 0x0002
struct smb2_rdma_crypto_transform {
__le16 TransformType;
__le16 SignatureLength;
__le16 NonceLength;
__u16 Reserved;
__u8 Signature[]; /* variable length */
/* u8 Nonce[] */
/* followed by padding */
} __packed;
/*
* Definitions for SMB2 Protocol Data Units (network frames)
*

View File

@ -202,6 +202,21 @@ struct file_zero_data_information {
__le64 BeyondFinalZero;
} __packed;
struct file_level_trim_range {
__le64 Offset;
__le64 Length;
} __packed;
struct file_level_trim {
__le32 Key;
__le32 NumRanges;
struct file_level_trim_range Ranges[];
} __packed;
struct file_level_trim_output {
__le32 NumRangesProcessed;
} __packed;
/*
* This level 18, although with struct with same name is different from cifs
* level 0x107. Level 0x107 has an extra u64 between AccessFlags and

View File

@ -370,7 +370,8 @@ struct smb2_tree_connect_req {
#define SMB2_SHAREFLAG_FORCE_LEVELII_OPLOCK 0x00001000
#define SMB2_SHAREFLAG_ENABLE_HASH_V1 0x00002000
#define SMB2_SHAREFLAG_ENABLE_HASH_V2 0x00004000
#define SHI1005_FLAGS_ENCRYPT_DATA 0x00008000
#define SMB2_SHAREFLAG_ENCRYPT_DATA 0x00008000
#define SHI1005_FLAGS_ENCRYPT_DATA SMB2_SHAREFLAG_ENCRYPT_DATA
#define SMB2_SHAREFLAG_IDENTITY_REMOTING 0x00040000 /* 3.1.1 */
#define SMB2_SHAREFLAG_COMPRESS_DATA 0x00100000 /* 3.1.1 */
#define SMB2_SHAREFLAG_ISOLATED_TRANSPORT 0x00200000
@ -742,6 +743,28 @@ struct smb2_close_rsp {
#define SMB2_CHANNEL_RDMA_V1_INVALIDATE cpu_to_le32(0x00000002)
#define SMB2_CHANNEL_RDMA_TRANSFORM cpu_to_le32(0x00000003)
/* See MS-SMB2 2.2.43. */
struct smb2_rdma_transform {
__le16 RdmaDescriptorOffset;
__le16 RdmaDescriptorLength;
__le32 Channel;
__le16 TransformCount;
__le16 Reserved1;
__le32 Reserved2;
} __packed;
#define SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION 0x0001
#define SMB2_RDMA_TRANSFORM_TYPE_SIGNING 0x0002
struct smb2_rdma_crypto_transform {
__le16 TransformType;
__le16 SignatureLength;
__le16 NonceLength;
__le16 Reserved;
__u8 Signature[];
/* Followed by Nonce[] and optional alignment padding. */
} __packed;
/* SMB2 read request without RFC1001 length at the beginning */
struct smb2_read_req {
struct smb2_hdr hdr;
@ -847,8 +870,8 @@ struct smb2_lock_req {
__le16 StructureSize; /* Must be 48 */
__le16 LockCount;
/*
* The least significant four bits are the index, the other 28 bits are
* the lock sequence number (0 to 64). See MS-SMB2 2.2.26
* The least significant four bits are the lock sequence number. The
* other 28 bits are the index (0 to 64). See MS-SMB2 2.2.26.
*/
__le32 LockSequenceNumber;
__u64 PersistentFileId;
@ -1261,6 +1284,14 @@ struct create_mxac_req {
#define SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE 2
#define SMB2_CRTCTX_AAPL_UNIX_BASED 4
#define SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE 8
/*
* V2 extends the same inline-FinderInfo mechanism as
* SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR with an added flags field,
* confirmed byte-identical to V1 otherwise against AAPL's actual
* public client behavior. Mutually exclusive with the V1 bit on
* the wire, not both set together.
*/
#define SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2 16
/* "AAPL" Volume Capabilities bitmap */
#define SMB2_CRTCTX_AAPL_SUPPORT_RESOLVE_ID 1
@ -1452,7 +1483,7 @@ struct resume_key_ioctl_rsp {
__u64 ResumeKeyU64[3];
};
__le32 ContextLength; /* MBZ */
char Context[]; /* ignored, Windows sets to 4 bytes of zero */
char Context[4]; /* ignored, Windows sets to 4 bytes of zero */
} __packed;
struct smb2_ioctl_rsp {

View File

@ -1049,6 +1049,7 @@ struct ntstatus {
#define STATUS_WOW_ASSERTION cpu_to_le32(0xC0009898) // -EIO
#define STATUS_INVALID_SIGNATURE cpu_to_le32(0xC000A000) // -EIO
#define STATUS_HMAC_NOT_SUPPORTED cpu_to_le32(0xC000A001) // -EIO
#define STATUS_AUTH_TAG_MISMATCH cpu_to_le32(0xC000A002) // -EBADMSG
#define STATUS_IPSEC_QUEUE_OVERFLOW cpu_to_le32(0xC000A010) // -EIO
#define STATUS_ND_QUEUE_OVERFLOW cpu_to_le32(0xC000A011) // -EIO
#define STATUS_HOPLIMIT_EXCEEDED cpu_to_le32(0xC000A012) // -EIO

View File

@ -119,6 +119,7 @@
#define FSCTL_SRV_ENUMERATE_SNAPSHOTS 0x00144064
/* Retrieve an opaque file reference for server-side data movement ie copy */
#define FSCTL_SRV_REQUEST_RESUME_KEY 0x00140078
#define FSCTL_SRV_ENUM_SNAPS 0x00144064
#define FSCTL_LMR_REQUEST_RESILIENCY 0x001401D4
#define FSCTL_LMR_GET_LINK_TRACK_INF 0x001400E8 /* BB add struct */
#define FSCTL_LMR_SET_LINK_TRACK_INF 0x001400EC /* BB add struct */

View File

@ -39,6 +39,7 @@ struct smb_version_values {
size_t create_mxac_size;
size_t create_disk_id_size;
size_t create_posix_size;
size_t create_aapl_size;
};
static inline unsigned int get_rfc1002_len(void *buf)

View File

@ -9,7 +9,6 @@ config SMB_SERVER
select CRYPTO
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

View File

@ -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;
}
@ -439,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;
@ -462,9 +464,11 @@ int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
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
@ -556,6 +560,7 @@ static void generate_key(struct ksmbd_conn *conn, const char *sess_key,
hmac_sha256_final(&ctx, prfhash);
memcpy(key, prfhash, key_size);
memzero_explicit(prfhash, sizeof(prfhash));
}
static int generate_smb3signingkey(struct ksmbd_session *sess,
@ -716,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;
@ -814,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)
{
@ -848,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 ||
@ -928,5 +1130,8 @@ int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
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;
}

View File

@ -38,6 +38,10 @@ 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,

View File

@ -14,24 +14,14 @@
#define SMB_COMPRESS_MIN_LEN PAGE_SIZE
/**
* 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)
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(conn->request_buf);
unsigned int pdu_size = get_rfc1002_len(request_buf);
u32 orig_size, offset, out_size;
u32 max_allowed_pdu_size;
char *buf, *out;
char *out;
int rc;
if (pdu_size < sizeof(struct smb2_compression_hdr))
@ -41,7 +31,7 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn)
conn->compress_algorithm == SMB3_COMPRESS_NONE)
return -EINVAL;
hdr = smb_get_msg(conn->request_buf);
hdr = smb_get_msg(request_buf);
if (hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID)
return -EINVAL;
@ -74,19 +64,69 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn)
if (!out)
return -ENOMEM;
buf = (char *)hdr;
*(__be32 *)out = cpu_to_be32(out_size);
rc = smb_compression_decompress(conn->compress_algorithm,
conn->compress_chained,
conn->compress_pattern,
buf, pdu_size, out + 4, out_size);
(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;
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;
}

View File

@ -11,6 +11,7 @@
#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__ */

View File

@ -11,6 +11,7 @@
#include "server.h"
#include "smb_common.h"
#include "mgmt/ksmbd_ida.h"
#include "mgmt/user_session.h"
#include "connection.h"
#include "compress.h"
#include "transport_tcp.h"
@ -27,33 +28,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;
@ -117,7 +192,7 @@ static void __ksmbd_conn_release_work(struct work_struct *work)
ida_destroy(&conn->async_ida);
conn->transport->ops->free_transport(conn->transport);
kfree(conn);
kfree_sensitive(conn);
}
/**
@ -183,8 +258,9 @@ void ksmbd_conn_free(struct ksmbd_conn *conn)
*/
xa_destroy(&conn->sessions);
kvfree(conn->request_buf);
kfree(conn->preauth_info);
kfree_sensitive(conn->preauth_info);
kfree(conn->mechToken);
ksmbd_preauth_session_destroy(conn);
ksmbd_conn_put(conn);
}
@ -219,10 +295,19 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
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);
@ -291,6 +376,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 (work->state != KSMBD_WORK_ACTIVE)
continue;
ksmbd_debug(CONN, "Cancel async request id %d\n",
work->async_id);
work->state = KSMBD_WORK_CANCELLED;
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);
@ -301,25 +406,64 @@ 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;
@ -331,7 +475,7 @@ int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id)
down_read(&conn_list_lock);
hash_for_each(conn_list, bkt, conn, hlist) {
if (conn->binding || xa_load(&conn->sessions, sess_id)) {
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,
@ -350,7 +494,8 @@ int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id)
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;
@ -366,12 +511,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) {
@ -382,6 +529,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,
@ -566,6 +729,7 @@ int ksmbd_conn_handler_loop(void *p)
}
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);
@ -623,7 +787,8 @@ 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;
}
@ -657,8 +822,10 @@ static void stop_sessions(void)
* handler exited its receive loop for an unrelated
* reason).
*/
if (READ_ONCE(conn->status) != KSMBD_SESS_RELEASING)
spin_lock(&conn->request_lock);
if (!ksmbd_conn_releasing(conn))
ksmbd_conn_set_exiting(conn);
spin_unlock(&conn->request_lock);
target = conn;
break;
}
@ -671,7 +838,7 @@ static void stop_sessions(void)
if (atomic_dec_and_test(&target->refcnt)) {
ida_destroy(&target->async_ida);
t->ops->free_transport(t);
kfree(target);
kfree_sensitive(target);
}
goto again;
}

View File

@ -17,14 +17,25 @@
#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,
@ -74,6 +85,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*/
@ -118,12 +139,17 @@ struct ksmbd_conn {
/* 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;
};
@ -132,14 +158,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 {
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,
@ -166,15 +200,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,
@ -279,5 +316,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__ */

View File

@ -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.

View File

@ -11,6 +11,7 @@
#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;
@ -56,6 +57,7 @@ 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 = ARRAY_SIZE(work->iov_inline);
work->iov = work->iov_inline;
@ -85,6 +87,9 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
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);
}

View File

@ -12,6 +12,7 @@
struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_tree_connect;
struct ksmbd_file;
#define KSMBD_WORK_INLINE_IOVS 4
@ -90,7 +91,11 @@ struct ksmbd_work {
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 */
@ -98,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;
};
/**

View File

@ -29,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);
@ -157,8 +214,14 @@ 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;
@ -205,7 +268,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;

View File

@ -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__ */

View File

@ -88,6 +88,7 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
goto out_error;
}
ksmbd_counter_inc(KSMBD_COUNTER_TREE_CONNS);
ksmbd_share_tree_conn_inc(sc);
kvfree(resp);
return status;
@ -116,6 +117,7 @@ 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_counter_dec(KSMBD_COUNTER_TREE_CONNS);
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);

View File

@ -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;
@ -70,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;
}
@ -80,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);
}

View File

@ -78,6 +78,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 +112,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 +204,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 +222,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 +231,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);
@ -308,8 +323,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 +337,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)
@ -389,10 +407,16 @@ void ksmbd_session_destroy(struct ksmbd_session *sess)
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);
ida_destroy(&sess->tree_conn_ida);
kfree(sess);
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)
@ -421,7 +445,7 @@ static void ksmbd_expire_session(struct ksmbd_conn *conn)
time_after(jiffies,
sess->last_active + SMB2_SESSION_TIMEOUT))) {
xa_erase(&conn->sessions, sess->id);
hash_del(&sess->hlist);
ksmbd_session_remove_from_table(sess);
ksmbd_session_destroy(sess);
continue;
}
@ -433,10 +457,21 @@ static void ksmbd_expire_session(struct ksmbd_conn *conn)
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));
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;
}
static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
@ -464,7 +499,7 @@ void ksmbd_sessions_deregister(struct ksmbd_conn *conn)
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);
ksmbd_session_remove_from_table(sess);
down_write(&conn->session_lock);
xa_erase(&conn->sessions, sess->id);
up_write(&conn->session_lock);
@ -475,18 +510,10 @@ void ksmbd_sessions_deregister(struct ksmbd_conn *conn)
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);
}
@ -539,19 +566,35 @@ struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id)
return sess;
}
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) {
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(conn, id);
if (!sess && conn->binding) {
sess = ksmbd_session_lookup_slowpath(id);
if (sess && !xa_load(&sess->ksmbd_chann_list, (long)conn)) {
ksmbd_user_session_put(sess);
sess = NULL;
}
}
sess = ksmbd_session_lookup_all_states(conn, id);
if (sess && sess->state != SMB2_SESSION_VALID) {
ksmbd_user_session_put(sess);
sess = NULL;
@ -592,6 +635,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)
{
@ -612,16 +666,17 @@ 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);
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);
ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP);
goto out;
}
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);
@ -691,10 +746,11 @@ static struct ksmbd_session *__session_create(int protocol)
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:

View File

@ -47,6 +47,7 @@ struct ksmbd_session {
__u8 *Preauth_HashValue;
char sess_key[CIFS_KEY_SIZE];
u64 kerberos_expiry;
struct hlist_node hlist;
struct rw_semaphore chann_lock;
@ -100,10 +101,13 @@ void ksmbd_sessions_deregister(struct ksmbd_conn *conn);
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);

View File

@ -43,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,
@ -53,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

View File

@ -16,6 +16,7 @@
#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);
@ -89,6 +90,7 @@ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
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;
@ -545,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);
@ -734,12 +745,18 @@ static bool wait_for_break_ack(struct oplock_info *opinfo)
/* is this a timeout ? */
if (!rc) {
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;
}
@ -754,9 +771,35 @@ 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;
@ -765,9 +808,12 @@ static int oplock_break_pending(struct oplock_info *opinfo, int req_op_level)
/* 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;
@ -989,38 +1035,71 @@ static void __smb2_lease_break_noti(struct work_struct *wk)
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
* @wait_ack: wait for lease break acknowledgment from the client
* @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, bool wait_ack,
static int smb2_lease_break_noti(struct oplock_info *opinfo, bool sync,
bool inc_epoch)
{
struct ksmbd_conn *conn;
struct ksmbd_work *work;
struct lease_break_info *br_info;
struct lease *lease = opinfo->o_lease;
int ret = 0;
conn = READ_ONCE(opinfo->conn);
if (lease->version == 2 && lease->l_lb && lease->l_lb->conn &&
!ksmbd_conn_releasing(lease->l_lb->conn))
conn = lease->l_lb->conn;
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;
}
@ -1036,16 +1115,17 @@ static int smb2_lease_break_noti(struct oplock_info *opinfo, bool wait_ack,
memcpy(br_info->lease_key, lease->lease_key, SMB2_LEASE_KEY_SIZE);
work->request_buf = (char *)br_info;
work->conn = ksmbd_conn_get(conn);
/* 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);
if (wait_ack) {
if (wait_for_break_ack(opinfo))
ret = ksmbd_invalidate_durable_fd(opinfo->fid);
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);
@ -1054,7 +1134,7 @@ static int smb2_lease_break_noti(struct oplock_info *opinfo, bool wait_ack,
lease_update_oplock_levels(opinfo->o_lease);
}
}
return ret;
return 0;
}
static void wait_lease_breaking(struct oplock_info *opinfo)
@ -1075,7 +1155,8 @@ 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, bool share_break)
struct ksmbd_work *in_work, bool share_break,
bool sync_lease_break)
{
int err = 0;
bool sent_interim = false;
@ -1136,16 +1217,13 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
}
}
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 (lease->state & (SMB2_LEASE_WRITE_CACHING_LE |
SMB2_LEASE_HANDLE_CACHING_LE)) {
brk_opinfo->op_state = OPLOCK_ACK_WAIT;
if (!oplock_break_set_ack_wait(brk_opinfo)) {
atomic_dec_if_positive(&brk_opinfo->breaking_cnt);
wake_up_oplock_break(brk_opinfo);
return -ENOENT;
}
} else
atomic_dec(&brk_opinfo->breaking_cnt);
@ -1156,8 +1234,16 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
inc_epoch = false;
lease->reuse_epoch = false;
}
err = smb2_lease_break_noti(brk_opinfo, wait_ack, inc_epoch);
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)
@ -1192,8 +1278,24 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
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);
}
}
err = smb2_oplock_break_noti(brk_opinfo);
@ -1229,7 +1331,8 @@ static void oplock_break_drain_none(struct list_head *head)
struct oplock_break_entry *ent, *tmp;
list_for_each_entry_safe(ent, tmp, head, list) {
oplock_break(ent->opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
oplock_break(ent->opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false,
false);
list_del(&ent->list);
opinfo_put(ent->opinfo);
kfree(ent);
@ -1347,7 +1450,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
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);
@ -1360,9 +1463,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;
@ -1435,12 +1539,13 @@ 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)
{
int err = 0;
int break_level = SMB2_OPLOCK_LEVEL_II;
@ -1453,6 +1558,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
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 */
@ -1524,6 +1630,21 @@ 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) {
err = share_ret;
@ -1531,8 +1652,11 @@ 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;
}
@ -1542,7 +1666,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
prev_fid = prev_op_snapshot.fid;
err = oplock_break(prev_opinfo, break_level, work,
share_ret < 0 && prev_opinfo->is_lease);
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);
@ -1555,7 +1679,14 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
goto set_lev;
}
if (err == -ENOENT) {
if (req_op_level != SMB2_OPLOCK_LEVEL_NONE)
/*
* 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;
}
@ -1640,7 +1771,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
}
brk_opinfo->open_trunc = is_trunc;
oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work, false);
oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work, false, false);
sent_break = true;
opinfo_put(brk_opinfo);
@ -1655,10 +1786,12 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
* @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
*/
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 send_interim, bool send_oplock_break,
bool sync_lease_break)
{
struct oplock_info *op, *brk_op;
struct oplock_break_entry *ent, *tmp;
@ -1725,13 +1858,16 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
if (!brk_op->is_lease && !send_oplock_break) {
brk_op->level = SMB2_OPLOCK_LEVEL_NONE;
brk_op->op_state = OPLOCK_STATE_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,
brk_op->is_lease && !is_trunc ?
SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE,
send_interim && !sent_interim ? work : NULL,
false);
false, sync_lease_break);
}
sent_interim = true;
list_del(&ent->list);
@ -1746,19 +1882,24 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
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);
__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);
__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);
__smb_break_all_levII_oplock(work, fp, 0, false, false, false);
}
/**
@ -1775,7 +1916,7 @@ void smb_break_all_oplock(struct ksmbd_work *work, struct ksmbd_file *fp)
return;
sent_break = smb_break_all_write_oplock(work, fp, 1);
__smb_break_all_levII_oplock(work, fp, 1, !sent_break, true);
__smb_break_all_levII_oplock(work, fp, 1, !sent_break, true, false);
}
/**
@ -2012,12 +2153,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';
@ -2138,6 +2279,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.
@ -2182,7 +2407,6 @@ struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
if (!atomic_inc_not_zero(&opinfo->refcount))
continue;
ret_op = opinfo;
break;
}
spin_unlock(&lease->lock);
if (ret_op) {

View File

@ -66,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;
@ -96,9 +97,10 @@ 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,
@ -125,6 +127,8 @@ 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);

View File

@ -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,127 @@ 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, "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 +239,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 +257,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;
}

View File

@ -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"
@ -23,6 +24,8 @@
#include "auth.h"
#include "stats.h"
#include "compress.h"
#include "mgmt/share_config.h"
#include "mgmt/tree_connect.h"
int ksmbd_debug_types;
@ -155,8 +158,11 @@ static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn,
}
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);
@ -182,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))
@ -204,6 +234,9 @@ 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);
@ -211,7 +244,11 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
struct smb2_hdr *rsp_hdr;
rsp_hdr = ksmbd_resp_buf_curr(work);
rsp_hdr->Flags |= SMB2_FLAGS_SIGNED;
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) {
@ -225,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
@ -243,10 +291,13 @@ 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 &&
@ -262,6 +313,7 @@ static void __handle_ksmbd_work(struct ksmbd_work *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
@ -579,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);
@ -603,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)
@ -660,6 +724,8 @@ static int __init ksmbd_server_init(void)
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);

View File

@ -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;

View File

@ -372,6 +372,75 @@ static int smb2_validate_credit_charge(struct ksmbd_work *work,
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);
@ -476,6 +545,16 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
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;
}

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -66,6 +66,80 @@ struct preauth_integrity_info {
/* 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 {
@ -199,6 +273,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;
@ -347,6 +428,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);

View File

@ -164,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;
}

View File

@ -135,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);

View File

@ -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 = {
@ -1432,7 +1435,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;
@ -1442,14 +1446,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,
@ -1479,12 +1486,15 @@ 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 (aces_size < offsetof(struct smb_ace, sid) +
CIFS_SID_BASE_SIZE)
@ -1495,17 +1505,53 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
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 (aces_size < offsetof(struct smb_ace, sid) +
CIFS_SID_BASE_SIZE)
@ -1527,25 +1573,16 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
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;
@ -1583,19 +1620,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;

View File

@ -95,7 +95,8 @@ 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);

View File

@ -9,12 +9,24 @@
#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_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 +62,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 +117,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

View File

@ -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,6 +506,9 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
{
struct ksmbd_rpc_command *resp = entry->response;
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;
@ -506,6 +518,9 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
{
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;
@ -514,6 +529,9 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
{
struct ksmbd_share_config_response *resp = entry->response;
if (entry->msg_sz < sizeof(struct ksmbd_share_config_response))
return -EINVAL;
if (resp->payload_sz) {
if (resp->payload_sz < resp->veto_list_sz)
return -EINVAL;
@ -528,6 +546,9 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
{
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) {
@ -658,7 +679,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;
@ -839,7 +860,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;
@ -898,7 +919,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;

View File

@ -76,6 +76,8 @@ static int smb_direct_max_receive_size = 1364;
static int smb_direct_max_read_write_size = SMBD_DEFAULT_IOSIZE;
static bool smb_direct_enabled;
static struct smb_direct_listener {
int port;
@ -239,17 +241,18 @@ static int smb_direct_read(struct ksmbd_transport *t, char *buf,
}
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 iov_iter iter;
iov_iter_kvec(&iter, ITER_SOURCE, iov, niovs, buflen);
iov_iter_kvec(&iter, ITER_SOURCE, tx->iov, tx->iov_cnt,
tx->size);
return smbdirect_connection_send_iter(sc, &iter, 0,
need_invalidate, remote_key);
tx->need_invalidate_rkey,
tx->remote_key);
}
static int smb_direct_rdma_write(struct ksmbd_transport *t,
@ -511,18 +514,26 @@ int ksmbd_rdma_init(void)
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();
return ret;
}
void ksmbd_rdma_stop_listening(void)
{
WRITE_ONCE(smb_direct_enabled, false);
smb_direct_listener_destroy(&smb_direct_ib_listener);
smb_direct_listener_destroy(&smb_direct_iw_listener);
}
bool ksmbd_rdma_enabled(void)
{
return READ_ONCE(smb_direct_enabled);
}
bool ksmbd_rdma_capable_netdev(struct net_device *netdev)
{
u8 node_type = smbdirect_netdev_rdma_capable_node_type(netdev);

View File

@ -14,12 +14,14 @@
#ifdef CONFIG_SMB_SERVER_SMBDIRECT
int ksmbd_rdma_init(void);
void ksmbd_rdma_stop_listening(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 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; }

View File

@ -292,6 +292,12 @@ static int ksmbd_kthread_fn(void *p)
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);
}
@ -417,14 +423,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)
@ -434,6 +441,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;
@ -511,6 +523,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);
@ -680,5 +698,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,
};

View File

@ -17,10 +17,10 @@
#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/splice.h>
#include <linux/fileattr.h>
#include "glob.h"
@ -299,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;
@ -347,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))
@ -476,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;
@ -663,15 +661,35 @@ int ksmbd_vfs_link(struct ksmbd_work *work, const char *oldname,
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 err, lookup_flags = LOOKUP_NO_SYMLINKS;
if (ksmbd_override_fsids(work))
@ -703,25 +721,28 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
if (err)
goto out_drop_write;
if (!work->tcon->posix_extensions && d_is_dir(old_child) &&
ksmbd_has_open_files(old_child)) {
if (d_is_dir(old_child) && ksmbd_has_nonposix_open_child(old_fp)) {
err = -EACCES;
goto out3;
}
parent_fp = ksmbd_lookup_fd_inode(old_child->d_parent);
if (parent_fp) {
if ((parent_fp->daccess & FILE_DELETE_LE) ||
(!parent_fp->attrib_only &&
!(parent_fp->saccess & FILE_SHARE_DELETE_LE))) {
pr_err("parent dir blocks delete sharing\n");
err = -ESHARE;
ksmbd_fd_put(work, parent_fp);
goto out3;
}
ksmbd_fd_put(work, parent_fp);
/*
* 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;
@ -930,54 +951,205 @@ 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 (!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)
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
err = vfs_fallocate(fp->filp,
FALLOC_FL_ZERO_RANGE | 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_zero_holes(struct ksmbd_file *fp)
{
struct file *f = fp->filp;
const struct cred *saved_cred;
loff_t size, pos = 0;
char *zero_buf;
int err;
err = file_write_and_wait(f);
if (err)
return err;
size = i_size_read(file_inode(f));
if (!size)
return 0;
/*
* 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.
*/
zero_buf = kvzalloc(SZ_64K, GFP_KERNEL);
if (!zero_buf)
return -ENOMEM;
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_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)
{
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;
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;
/*
* Shrink request scope to what the fs can actually handle.
*/
if (length > maxbytes || (maxbytes - length) < start)
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;
if (start + length > inode->i_size)
length = inode->i_size - start;
*out_count = 0;
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;
}
@ -987,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;
@ -996,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;
}
@ -1689,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;
}
@ -1739,6 +1942,71 @@ 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;
}
int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
struct ksmbd_file *src_fp,
struct ksmbd_file *dst_fp,
@ -1766,9 +2034,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) {
@ -1786,35 +2051,138 @@ 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 (src_off < 0)
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
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, 0);
if (ret == -EOPNOTSUPP || ret == -EXDEV)
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;
@ -1907,15 +2275,11 @@ void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr)
struct file_kattr fa = { .flags_valid = true };
rc = vfs_fileattr_get(dentry, &fa);
if (rc == -ENOIOCTLCMD)
*fattr &= ~FILE_ATTRIBUTE_COMPRESSED_LE;
if (rc)
return;
if (fa.flags & FS_COMPR_FL)
*fattr |= FILE_ATTRIBUTE_COMPRESSED_LE;
else
*fattr &= ~FILE_ATTRIBUTE_COMPRESSED_LE;
}
int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt)
@ -1924,15 +2288,19 @@ int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt)
int rc;
rc = vfs_fileattr_get(fp->filp->f_path.dentry, &fa);
if (rc == -ENOIOCTLCMD) {
*fmt = COMPRESSION_FORMAT_NONE;
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;
if (fa.flags & FS_COMPR_FL)
if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_COMPRESSED_LE)
*fmt = COMPRESSION_FORMAT_LZNT1;
else
*fmt = COMPRESSION_FORMAT_NONE;
@ -1941,7 +2309,9 @@ int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt)
return rc;
}
int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u16 fmt)
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;
@ -1951,13 +2321,23 @@ int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u1
__le32 old_fattr;
int rc;
if (!(fp->daccess & FILE_WRITE_DATA_LE)) {
if (check_access && !(fp->daccess & FILE_WRITE_DATA_LE)) {
rc = -EACCES;
goto out;
}
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;
@ -1967,9 +2347,6 @@ int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u1
} else if (fmt == COMPRESSION_FORMAT_DEFAULT ||
fmt == COMPRESSION_FORMAT_LZNT1) {
flags |= FS_COMPR_FL;
} else {
rc = -EINVAL;
goto out;
}
if (flags != fa.flags) {
@ -1980,28 +2357,34 @@ int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u1
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 &&
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) {
struct xattr_dos_attrib da = {0};
rc = ksmbd_vfs_get_dos_attrib_xattr(idmap, dentry, &da);
if (rc <= 0) {
rc = 0;
goto out;
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);
@ -2014,3 +2397,15 @@ int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u1
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);
}

View File

@ -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,
@ -171,4 +179,6 @@ int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap,
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__ */

View File

@ -66,33 +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 ? fp->tcon->id : 0,
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);
if (opinfo) {
const struct ksmbd_const_name *const_names;
const char *name;
int count;
unsigned int level;
@ -106,16 +146,32 @@ static int proc_show_files(struct seq_file *m, void *v)
level = opinfo->level;
}
rcu_read_unlock();
ksmbd_proc_show_const_name(m, " %-15s",
const_names, count, level);
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_printf(m, " %-15s", " ");
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);
@ -124,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
@ -135,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());
@ -232,6 +299,12 @@ bool ksmbd_inode_pending_delete(struct ksmbd_file *fp)
down_read(&ci->m_lock);
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;
}
@ -294,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 +
@ -414,6 +521,19 @@ 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;
@ -497,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);
@ -510,8 +631,9 @@ 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) {
struct ksmbd_conn *conn = smb_lock->conn;
@ -524,11 +646,58 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
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
@ -629,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)
{
@ -822,16 +996,15 @@ bool ksmbd_has_other_active_fd(struct ksmbd_file *fp)
return ret;
}
static struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id)
struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id)
{
struct ksmbd_file *fp = NULL;
unsigned int id;
if (!memchr_inv(app_instance_id, 0, SMB2_CREATE_GUID_SIZE))
return NULL;
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);
@ -966,8 +1139,36 @@ struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry)
return NULL;
}
bool ksmbd_has_open_files(struct dentry *dentry)
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;
@ -980,6 +1181,8 @@ bool ksmbd_has_open_files(struct dentry *dentry)
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;
@ -1050,6 +1253,7 @@ 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);
@ -1446,9 +1650,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);
}
@ -1471,7 +1678,7 @@ void ksmbd_stop_durable_scavenger(void)
}
/*
* ksmbd_vfs_copy_durable_owner - Copy owner info for durable reconnect
* 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
*
@ -1480,10 +1687,10 @@ void ksmbd_stop_durable_scavenger(void)
*
* Return: 0 on success, or negative error code on failure
*/
static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user)
int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user)
{
char *name;
char *name, *old_name;
if (!user)
return -EINVAL;
@ -1494,10 +1701,12 @@ static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
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;
}
@ -1552,7 +1761,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
if (WARN_ON_ONCE(!fp->conn))
return false;
if (ksmbd_vfs_copy_durable_owner(fp, user))
if (ksmbd_vfs_set_durable_owner(fp, user))
return false;
/*
@ -1620,7 +1829,8 @@ void ksmbd_close_session_fds(struct ksmbd_work *work)
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);
}
@ -1673,7 +1883,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
unsigned int old_f_state;
write_lock(&global_ft.lock);
if (!fp->is_durable || fp->conn || fp->tcon) {
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;
@ -1722,7 +1932,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
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)
if (op->conn || op->o_fp != fp)
continue;
op->conn = ksmbd_conn_get(fp->conn);
op->sess = work->sess;

View File

@ -82,6 +82,13 @@ struct durable_owner {
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;
@ -101,6 +108,7 @@ struct ksmbd_file {
__le32 saccess;
__le32 coption;
__le32 cdoption;
__le32 create_file_attributes;
__u64 create_time;
__u64 change_time;
__u64 allocation_size;
@ -109,6 +117,7 @@ struct ksmbd_file {
bool is_nt_open;
bool attrib_only;
bool allocation_size_set;
char client_guid[16];
char create_guid[16];
@ -118,9 +127,18 @@ 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;
@ -131,10 +149,25 @@ struct ksmbd_file {
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,
@ -167,6 +200,9 @@ 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);
@ -177,14 +213,17 @@ 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_open_files(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);
int ksmbd_close_inode_fds(struct ksmbd_work *work, struct inode *inode);
@ -214,6 +253,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);

View File

@ -146,11 +146,21 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
rdma_accept_failed:
/*
* smbdirect_connection_destroy_qp() calls ib_drain_qp(),
* so that smbdirect_accept_negotiate_recv_done() will
* call smbdirect_connection_put_recv_io()
* The recv_io posted above is now owned by the QP (recv_io was set to
* NULL after a successful post). smbdirect_connection_destroy_qp()
* calls ib_drain_qp(), whose completion
* (smbdirect_accept_negotiate_recv_done) returns the recv_io to the
* free list via smbdirect_connection_put_recv_io(). It therefore MUST
* run BEFORE smbdirect_connection_destroy_mem_pools(): otherwise the
* posted recv_io is still outstanding when kmem_cache_destroy() runs
* ("Slab cache still has objects") and is later freed into an
* already-destroyed mempool (mempool_free_bulk NULL-ptr-deref).
*/
smbdirect_connection_destroy_qp(sc);
smbdirect_connection_destroy_mem_pools(sc);
return ret;
post_recv_io_failed:
/* post failed: recv_io was not accepted by the QP, still in hand */
if (recv_io)
smbdirect_connection_put_recv_io(recv_io);
get_recv_io_failed:

View File

@ -403,12 +403,21 @@ void smbdirect_connection_destroy_qp(struct smbdirect_socket *sc)
sc->ib.qp = NULL;
rdma_destroy_qp(sc->rdma.cm_id);
}
/*
* These CQs were created with ib_alloc_cq_any(), which arms an internal
* completion handler (ib_cq_poll_work for IB_POLL_WORKQUEUE). They MUST be
* torn down with ib_free_cq(), which cancel_work_sync()es that poll work
* before freeing the CQ. ib_destroy_cq() skips that step, so a completion
* posted late by the (software) provider e.g. rxe posting an RNR error
* from rxe_receiver after rdma_destroy_qp() re-queues ib_cq_poll_work on
* an already-freed CQ (KASAN slab-use-after-free in ib_cq_poll_work).
*/
if (sc->ib.recv_cq) {
ib_destroy_cq(sc->ib.recv_cq);
ib_free_cq(sc->ib.recv_cq);
sc->ib.recv_cq = NULL;
}
if (sc->ib.send_cq) {
ib_destroy_cq(sc->ib.send_cq);
ib_free_cq(sc->ib.send_cq);
sc->ib.send_cq = NULL;
}
if (sc->ib.pd) {

View File

@ -305,12 +305,26 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
* disconnect all pending and ready sockets
*
* First we move ready sockets to pending again.
*
* Only a socket that was a listener (listen.backlog != -1) owns a
* populated listen.ready/pending list. Guarding on that also keeps
* lockdep quiet: without it, the listener holds sc->listen.lock while
* the loop recurses into each child psc, which takes psc->listen.lock.
* Those are always different instances of the same lock class -- a
* child never listens, so the nesting is strictly listener -> child
* and cannot really deadlock -- but lockdep only sees the class and
* reports "possible recursive locking". A child has empty listen
* lists and nothing to do here, so skipping it loses nothing, and a
* pending child stays on its listener's list for the free path
* (smbdirect_socket_destroy) to reap.
*/
spin_lock_irqsave(&sc->listen.lock, flags);
list_splice_init(&sc->listen.ready, &sc->listen.pending);
list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
smbdirect_socket_schedule_cleanup(psc, sc->first_error);
spin_unlock_irqrestore(&sc->listen.lock, flags);
if (sc->listen.backlog != -1) { /* was a listener */
spin_lock_irqsave(&sc->listen.lock, flags);
list_splice_init(&sc->listen.ready, &sc->listen.pending);
list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
smbdirect_socket_schedule_cleanup(psc, sc->first_error);
spin_unlock_irqrestore(&sc->listen.lock, flags);
}
switch (sc->status) {
case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
@ -405,12 +419,20 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work)
* disconnect all pending and ready sockets
*
* First we move ready sockets to pending again.
*
* Guarded on listen.backlog != -1 for the same reason as in
* __smbdirect_socket_schedule_cleanup(): only a listener owns a
* populated listen list, and skipping the block for a child avoids
* nesting psc->listen.lock under a listener's listen.lock (different
* instances of one class -- harmless, but lockdep cannot tell).
*/
spin_lock_irqsave(&sc->listen.lock, flags);
list_splice_init(&sc->listen.ready, &sc->listen.pending);
list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
smbdirect_socket_schedule_cleanup(psc, sc->first_error);
spin_unlock_irqrestore(&sc->listen.lock, flags);
if (sc->listen.backlog != -1) { /* was a listener */
spin_lock_irqsave(&sc->listen.lock, flags);
list_splice_init(&sc->listen.ready, &sc->listen.pending);
list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
smbdirect_socket_schedule_cleanup(psc, sc->first_error);
spin_unlock_irqrestore(&sc->listen.lock, flags);
}
switch (sc->status) {
case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
@ -473,6 +495,7 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
struct smbdirect_recv_io *recv_io;
struct smbdirect_recv_io *recv_tmp;
LIST_HEAD(all_list);
LIST_HEAD(pending_list);
unsigned long flags;
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
@ -530,24 +553,29 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
* disconnect all pending and ready sockets
*
* We move ready sockets to pending again.
*
* Capture them here -- rdma_lock_handler(sc->rdma.cm_id) is held above,
* so a concurrent CM CONNECT_REQUEST cannot add more; sc->listen.lock
* below only protects the list splice itself -- but DEFER releasing
* them until the listener's cm_id is destroyed:
*
* - smbdirect_socket_release() -> smbdirect_socket_destroy() takes the
* child's own rdma_lock_handler() lock (&id_priv->handler_mutex).
* The listener's and the child's cm_id are always different
* instances, so the nesting cannot really deadlock, but lockdep only
* sees one lock class and reports "possible recursive locking".
*
* - rdma_destroy_id() of a child before the listener's own
* rdma_destroy_id() below lets _cma_cancel_listens() walk the freed
* child id_priv (KASAN slab-use-after-free in __mutex_lock()).
*
* The children are independent sockets whose teardown does not need
* the listener's handler lock.
*/
spin_lock_irqsave(&sc->listen.lock, flags);
list_splice_tail_init(&sc->listen.ready, &all_list);
list_splice_tail_init(&sc->listen.pending, &all_list);
list_splice_tail_init(&sc->listen.ready, &pending_list);
list_splice_tail_init(&sc->listen.pending, &pending_list);
spin_unlock_irqrestore(&sc->listen.lock, flags);
psockets = list_count_nodes(&all_list);
if (sc->listen.backlog != -1) /* was a listener */
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"release %zu pending sockets\n", psockets);
list_for_each_entry_safe(psc, tsc, &all_list, accept.list) {
list_del_init(&psc->accept.list);
psc->accept.listener = NULL;
smbdirect_socket_release(psc);
}
if (sc->listen.backlog != -1) /* was a listener */
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"released %zu pending sockets\n", psockets);
INIT_LIST_HEAD(&all_list);
/* It's not possible for upper layer to get to reassembly */
if (sc->listen.backlog == -1) /* was not a listener */
@ -577,6 +605,26 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
sc->rdma.cm_id = NULL;
}
/*
* The listener's rdma_lock_handler() lock is dropped and its cm_id is
* destroyed, so it is safe to release the child sockets captured
* above: each release recurses into smbdirect_socket_destroy() and
* takes that child's own handler_mutex without nesting it under the
* listener's, and _cma_cancel_listens() can no longer reach them.
*/
psockets = list_count_nodes(&pending_list);
if (sc->listen.backlog != -1) /* was a listener */
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"release %zu pending sockets\n", psockets);
list_for_each_entry_safe(psc, tsc, &pending_list, accept.list) {
list_del_init(&psc->accept.list);
psc->accept.listener = NULL;
smbdirect_socket_release(psc);
}
if (sc->listen.backlog != -1) /* was a listener */
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"released %zu pending sockets\n", psockets);
if (sc->listen.backlog == -1) /* was not a listener */
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"destroying mem pools\n");