- Add extended SMB2 directory information support.

Implement FileIdExtdDirectoryInformation,
    FileId64ExtdDirectoryInformation,
    FileId64ExtdBothDirectoryInformation,
    FileIdAllExtdDirectoryInformation, and
    FileIdAllExtdBothDirectoryInformation, including reparse tags,
    file attributes, and initialized response padding.
 
  - Track unsigned 64-bit lock ranges in the server lock index, with
    conflict detection, blocking waits, cancellation, rollback, and
    cleanup for ranges beyond the VFS signed-offset limit.
 
  - Add configurable per-connection session limits.
    Reserve slots for new sessions and multichannel bindings, release
    them during failure and cleanup, and expose the configured limit
    through procfs.
 
  - Validate POSIX negotiate and CREATE contexts, including payload
    lengths, extension tags, duplicate contexts, dialect requirements,
    and share eligibility. Require POSIX opens for POSIX file,
    directory, and filesystem information queries.
 
  - Apply requested POSIX create modes, including mode zero, and NFS
    mode ACEs with special permission bits. Enforce append-only writes
    and base byte-range lock checks on each open's POSIX state.
 
  - Complete POSIX metadata responses with filenames, file types,
    permission bits, and filesystem device identifiers. Query filesystem
    information through the requested open and report the filesystem
    fragment size and response length.
 
  - Validate SMB3 encryption transform flags and ensure decrypted
    compound requests use the session authenticated by the transform,
    including requests containing compressed payloads.
 
  - Serialize negotiation with receive processing, size CREATE responses
    for their contexts, reserve UTF-16 terminator space in stream
    information, and validate WRITE data against the active compound
    request to prevent buffer overruns and stale-data writes.
 
  - Validate security descriptor offsets before pointer arithmetic,
    handle DACL offset overflow on 32-bit systems, and propagate inode
    ACL lookup failures safely.
 
  - Deny access through empty DACLs while retaining the owner's implicit
    security-management rights. Add KUnit regression coverage for DACL
    walk boundaries and maximal-access calculations.
 
  - Validate RPC pipe ownership for IPC$ reads and writes, and fix
    FSCTL_PIPE_TRANSCEIVE handle lookup and response file IDs.
 
  - Fix durable reconnect path validation for symlinked share paths,
    shares exporting "/", and handles opened at the share root.
    Check path boundaries before comparisons to prevent out-of-bounds
    reads and cross-share confusion.
 
  - Rebind completed durable CREATE replays to surviving channels
    without draining unrelated requests on the old channel. Transfer
    open accounting with the binding and avoid consuming additional
    open-file budget during durable reconnects.
 
  - Scope AppInstanceId takeovers to the matching share, path, stream,
    and client identity. Check access and AppInstanceVersion before
    closing an existing open.
 
  - Retry oplock and lease break notifications through other live
    channels, track queued TCP delivery, serialize acknowledgment
    updates, and expire unacknowledged breaks while waking blocked
    requests.
 
  - Keep lease tables and byte-range lock indexes independent of their
    original connection so surviving channels and detached durable
    opens retain access to lease and lock state.
 
  - Reclaim ended sessions after logoff and expiration, hold connection
    references for session channels, and drain procfs readers before
    releasing session table references. Avoid deadlocks during
    previous-session teardown and connection scavenging.
 
  - Prevent file publication after tree disconnect and retain tree
    references until final file release. Take parent dentry references
    during directory enumeration and serialize directory emptiness
    checks with readdir.
 
  - Prevent share-root removal through empty removal paths and return
    appropriate errors for directory zero-data requests and temporary
    directory attributes.
 
  - Fix named-stream writes, EOF changes, end-of-file reads, and content
    copying into rename targets. Reuse existing xattr names, report
    sizes by exact xattr name, and avoid emptying streams after lookup
    or permission failures.
 
  - Round allocation requests to the advertised allocation unit and
    avoid allocation changes that leave on-disk EOF inconsistent with
    the in-memory size.
 
  - Inherit FileIds across related compound requests, including
    CHANGE_NOTIFY, and use the existing async request machinery for
    notify waiting and cancellation.
 
  - Restore share configuration and Kerberos authentication compatibility
    with released ksmbd-tools. Advertise extended session security for
    raw NTLMSSP when requested by the client.
 
  - Improve Windows chained-compression compatibility, synthesized
    AFP_AfpInfo headers, generated short names, and UTF-16 alignment
    in partial normalized-name responses.
 
  - Fix interface-query locking, response chaining, link-speed reporting,
    and independent IPv4/IPv6 address enumeration. Display native IPv6
    client addresses accurately in procfs.
 
  - Improve SMB Direct socket cleanup by reclaiming failed pending
    connections, skipping failed sockets during accept, avoiding
    indefinite disconnect waits, and handling unexpected receive
    opcodes without triggering kernel warnings.
 
  - Select CRYPTO_AES for SMB3 encryption, replace deprecated system
    workqueues, and update feature status documentation and POSIX
    specification references.
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmra6EcWHGxpbmtpbmpl
 b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCFbNEACB5NYhv02Sx7MfBw2teeb0z3HQ
 CxMYS+JCkgfuFAuomTuF2gM6isTIIybeOtRGRLFFG2TAzrjOAVQfJmiRXDs/Rmy9
 9St1yAleOYUmFxa/Aj3us+GsbalsNTd3B5jcC6kIYKsuhlPzxtZRXNPiGLq2GrpX
 h3W18ivSJ27V4QcxwTX2TGSCF9TKC3/KqpjlRtavdiAdem++aEiTeU0fQITpw0iO
 3Aigk8WV1umpWDVp7f9fw99Vb1jo2TxicwQPUveW/+FhRSOg5KihOifTJdEh/BCM
 crIJ0uT13gAJtQwdv2P9Y0fzWGaVZLjc7dIu3eunnrhUz1oNbahtjbIX4/CGrCS5
 H/N71QlhSHnguoky3rSl/kTZ3AMUR16XPF9YqOyD0OVnVtygQ3ni4B409oPBGLBK
 j/sZlu7eqreMrlZP9fGpIgUIwk2ErmoW3ELYSR3WDjTW/Mx49lr5wtz42jAkGSk6
 u9C4Bb2QlQRrj0THnOPDzCsZxjpmWv7Yjx8ptDCUCHuwBl+K0MDfsUKMKbtGxuRp
 WmpnLVQLnQ9js11huCXuUlTUHQYSZ8FxWUNv8AgbdEO2dhEY/DU0IC5Uqea789Gc
 vHWBr/BIYzfOHXNvipeEdYWq7lE/Fq46+xKcI/YkX2IhgblBJX0PMBnkH+fWSGLI
 4AISnYrhwZC9/bxt3w==
 =ZQaY
 -----END PGP SIGNATURE-----

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

Pull smb server updates from Namjae Jeon:

 - Add extended SMB2 directory information support.

   Implement FileIdExtdDirectoryInformation, FileId64ExtdDirectoryInformation,
   FileId64ExtdBothDirectoryInformation, FileIdAllExtdDirectoryInformation,
   and FileIdAllExtdBothDirectoryInformation, including reparse tags, file
   attributes, and initialized response padding.

 - Track unsigned 64-bit lock ranges in the server lock index, with
   conflict detection, blocking waits, cancellation, rollback, and
   cleanup for ranges beyond the VFS signed-offset limit.

 - Add configurable per-connection session limits.

   Reserve slots for new sessions and multichannel bindings, release
   them during failure and cleanup, and expose the configured limit
   through procfs.

 - Validate POSIX negotiate and CREATE contexts, including payload
   lengths, extension tags, duplicate contexts, dialect
   requirements, and share eligibility. Require POSIX opens for
   POSIX file, directory, and filesystem information queries.

 - Apply requested POSIX create modes, including mode zero, and NFS
   mode ACEs with special permission bits. Enforce append-only
   writes and base byte-range lock checks on each open's POSIX
   state.

 - Complete POSIX metadata responses with filenames, file types,
   permission bits, and filesystem device identifiers. Query
   filesystem information through the requested open and report the
   filesystem fragment size and response length.

 - Validate SMB3 encryption transform flags and ensure decrypted
   compound requests use the session authenticated by the transform,
   including requests containing compressed payloads.

 - Serialize negotiation with receive processing, size CREATE
   responses for their contexts, reserve UTF-16 terminator space in
   stream information, and validate WRITE data against the active
   compound request to prevent buffer overruns and stale-data
   writes.

 - Validate security descriptor offsets before pointer arithmetic,
   handle DACL offset overflow on 32-bit systems, and propagate
   inode ACL lookup failures safely.

 - Deny access through empty DACLs while retaining the owner's
   implicit security-management rights. Add KUnit regression
   coverage for DACL walk boundaries and maximal-access
   calculations.

 - Validate RPC pipe ownership for IPC$ reads and writes, and fix
   FSCTL_PIPE_TRANSCEIVE handle lookup and response file IDs.

 - Fix durable reconnect path validation for symlinked share paths,
   shares exporting "/", and handles opened at the share root. Check
   path boundaries before comparisons to prevent out-of-bounds reads
   and cross-share confusion.

 - Rebind completed durable CREATE replays to surviving channels
   without draining unrelated requests on the old channel. Transfer
   open accounting with the binding and avoid consuming additional
   open-file budget during durable reconnects.

 - Scope AppInstanceId takeovers to the matching share, path,
   stream, and client identity. Check access and AppInstanceVersion
   before closing an existing open.

 - Retry oplock and lease break notifications through other live
   channels, track queued TCP delivery, serialize acknowledgment
   updates, and expire unacknowledged breaks while waking blocked
   requests.

 - Keep lease tables and byte-range lock indexes independent of
   their original connection so surviving channels and detached
   durable opens retain access to lease and lock state.

 - Reclaim ended sessions after logoff and expiration, hold
   connection references for session channels, and drain procfs
   readers before releasing session table references. Avoid
   deadlocks during previous-session teardown and connection
   scavenging.

 - Prevent file publication after tree disconnect and retain tree
   references until final file release. Take parent dentry
   references during directory enumeration and serialize directory
   emptiness checks with readdir.

 - Prevent share-root removal through empty removal paths and return
   appropriate errors for directory zero-data requests and temporary
   directory attributes.

 - Fix named-stream writes, EOF changes, end-of-file reads, and
   content copying into rename targets. Reuse existing xattr names,
   report sizes by exact xattr name, and avoid emptying streams
   after lookup or permission failures.

 - Round allocation requests to the advertised allocation unit and
   avoid allocation changes that leave on-disk EOF inconsistent with
   the in-memory size.

 - Inherit FileIds across related compound requests, including
   CHANGE_NOTIFY, and use the existing async request machinery for
   notify waiting and cancellation.

 - Restore share configuration and Kerberos authentication
   compatibility with released ksmbd-tools. Advertise extended
   session security for raw NTLMSSP when requested by the client.

 - Improve Windows chained-compression compatibility, synthesized
   AFP_AfpInfo headers, generated short names, and UTF-16 alignment
   in partial normalized-name responses.

 - Fix interface-query locking, response chaining, link-speed
   reporting, and independent IPv4/IPv6 address enumeration. Display
   native IPv6 client addresses accurately in procfs.

 - Improve SMB Direct socket cleanup by reclaiming failed pending
   connections, skipping failed sockets during accept, avoiding
   indefinite disconnect waits, and handling unexpected receive
   opcodes without triggering kernel warnings.

 - Select CRYPTO_AES for SMB3 encryption, replace deprecated system
   workqueues, and update feature status documentation and POSIX
   specification references.

* tag 'ksmbd-for-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/smb: (95 commits)
  ksmbd: select CRYPTO_AES
  ksmbd: prevent share root removal through empty paths
  ksmbd: serialize directory emptiness checks with readdir
  ksmbd: fix parent dentry lifetime in directory enumeration
  smb/server: prevent file publication after tree disconnect
  ksmbd: restore Kerberos logins with released ksmbd-tools
  smb/server: replace deprecated system workqueues
  ksmbd: reject FILE_ATTRIBUTE_TEMPORARY on directories
  ksmbd: deny access for an empty DACL instead of granting the request
  ksmbd: reject foreign RPC pipe handles on IPC$ read and write
  ksmbd: fix fd_limit accounting across durable reconnects
  ksmbd: drain session procfs readers before dropping table references
  ksmbd: reclaim ended sessions on logoff and expiration
  ksmbd: limit sessions on each connection
  ksmbd: hold a connection reference for each session channel
  ksmbd: defer connection scavenging while session locks are busy
  ksmbd: accept share config responses from ksmbd-tools before 3.5.3
  ksmbd: round SetAllocation to the advertised allocation unit
  ksmbd: avoid restoring only in-memory EOF after allocation change
  ksmbd: inherit FileIds in related compound requests
  ...
This commit is contained in:
Linus Torvalds 2026-10-25 15:47:41 -10:00
commit 4e1b4fdf61
47 changed files with 4506 additions and 1723 deletions

View File

@ -82,10 +82,12 @@ Signing Update Supported.
Pre-authentication integrity Supported.
SMB3 encryption(CCM, GCM) Supported. (CCM/GCM128 and CCM/GCM256 supported)
SMB direct(RDMA) Supported.
SMB3 Multi-channel Partially Supported. Planned to implement
replay/retry mechanisms for future.
SMB3 Multi-channel Supported.
Receive Side Scaling mode Supported.
SMB3.1.1 POSIX extension Supported.
MSDFS Planned for future.
Continuous Availability (CA) Under development.
AD/DC Under development.
ACLs Partially Supported. only DACLs available, SACLs
(auditing) is planned for the future. For
ownership (SIDs) ksmbd generates random subauth
@ -98,8 +100,9 @@ ACLs Partially Supported. only DACLs available, SACLs
member.
Kerberos Supported.
Durable handle v1,v2 Supported.
Persistent handle Planned for future.
SMB2 notify Planned for future.
Persistent handle Under development.
Resilient handle Planned for future.
SMB2 notify Under development.
Sparse file support Supported.
DCE/RPC support Partially Supported. a few calls(NetShareEnumAll,
NetServerGetInfo, SAMR, LSARPC) that are needed
@ -113,7 +116,8 @@ ksmbd/nfsd interoperability Planned for future. The features that ksmbd
support are Leases, Notify, ACLs and Share modes.
SMB3.1.1 Compression Supported.
SMB3.1.1 over QUIC Planned for future.
Signing/Encryption over RDMA Planned for future.
Signing over RDMA Under development.
Encryption over RDMA Supported.
SMB3.1.1 GMAC signing support Planned for future.
============================== =================================================

View File

@ -130,12 +130,18 @@ static int smb_decompress_chained(__le16 alg, bool allow_chained,
len = le32_to_cpu(payload->Length);
/*
* CHAINED marks only the first payload. Requiring NONE on every
* later payload rejects ambiguous or independently chained data.
* Conforming chains must set CHAINED on the first payload.
* Windows 11 clients leave uninitialized residual bits in
* Flags on subsequent payload headers. Only reject trailing
* payloads that attempt to initiate an invalid nested chain.
*/
if ((first && flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) ||
(!first && flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)))
return -EINVAL;
if (first) {
if (flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED))
return -EINVAL;
} else {
if (flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED))
return -EINVAL;
}
src += SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
remaining -= SMB2_COMPRESSION_PAYLOAD_BASE_LEN;

View File

@ -399,7 +399,7 @@ static_assert(offsetof(struct smb2_file_rename_info, FileName) == sizeof(struct
#define FS_OBJECT_ID_INFORMATION 8 /* Query, Set */
#define FS_DRIVER_PATH_INFORMATION 9 /* Query */
#define FS_SECTOR_SIZE_INFORMATION 11 /* SMB3 or later. Query */
/* See POSIX Extensions to MS-FSCC 2.3.1.1 */
/* See POSIX-FSCC 2.3 */
#define FS_POSIX_INFORMATION 100 /* SMB3.1.1 POSIX. Query */
/* See MS-FSCC 2.5.1 */
@ -575,8 +575,7 @@ struct file_notify_information {
} __packed;
/*
* See POSIX Extensions to MS-FSCC 2.3.2.1
* Link: https://gitlab.com/samba-team/smb3-posix-spec/-/blob/master/fscc_posix_extensions.md
* See POSIX-FSCC 2.3.1
*/
typedef struct {
/* For undefined recommended transfer size return -1 in that field */

View File

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

View File

@ -16,6 +16,7 @@ config SMB_SERVER
select CRYPTO_AEAD2
select CRYPTO_CCM
select CRYPTO_GCM
select CRYPTO_AES
select ASN1
select OID_REGISTRY
select CRC32
@ -71,3 +72,5 @@ config SMB_SERVER_KERBEROS5
bool "Support for Kerberos 5"
depends on SMB_SERVER
default y
source "fs/smb/server/tests/Kconfig"

View File

@ -19,3 +19,4 @@ $(obj)/ksmbd_spnego_negtokentarg.asn1.o: $(obj)/ksmbd_spnego_negtokentarg.asn1.c
ksmbd-$(CONFIG_SMB_SERVER_SMBDIRECT) += transport_rdma.o
ksmbd-$(CONFIG_PROC_FS) += proc.o
obj-$(CONFIG_SMB_SERVER_KUNIT_TESTS) += tests/

View File

@ -336,8 +336,7 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
if (cflags & NTLMSSP_REQUEST_TARGET)
flags |= NTLMSSP_REQUEST_TARGET;
if (conn->use_spnego &&
(cflags & NTLMSSP_NEGOTIATE_EXTENDED_SEC))
if (cflags & NTLMSSP_NEGOTIATE_EXTENDED_SEC)
flags |= NTLMSSP_NEGOTIATE_EXTENDED_SEC;
if (cflags & NTLMSSP_NEGOTIATE_KEY_XCH)
@ -464,7 +463,8 @@ 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;
sess->kerberos_expiry = (u64)resp->login_response.session_expiry_hi << 32 |
resp->login_response.session_expiry_lo;
retval = 0;
out:
kvfree_sensitive(resp, sizeof(*resp) + resp->session_key_len +

View File

@ -65,6 +65,7 @@ static int __ksmbd_decompress_request(struct ksmbd_conn *conn,
return -ENOMEM;
*(__be32 *)out = cpu_to_be32(out_size);
out[out_size + 4] = 0;
rc = smb_compression_decompress(conn->compress_algorithm,
conn->compress_chained,
conn->compress_pattern,

View File

@ -114,7 +114,7 @@ static int proc_show_clients(struct seq_file *m, void *v)
sessions++;
rcu_read_unlock();
#if IS_ENABLED(CONFIG_IPV6)
if (!conn->inet_addr)
if (conn->is_ipv6)
seq_printf(m, "client:\t%pI6c\n", &conn->inet6_addr);
else
#endif
@ -306,6 +306,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
atomic_set(&conn->req_running, 0);
atomic_set(&conn->r_count, 0);
atomic_set(&conn->refcnt, 1);
atomic64_set(&conn->session_count, 0);
conn->total_credits = 1;
conn->outstanding_credits = 0;
@ -327,8 +328,6 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
ida_init(&conn->async_ida);
xa_init(&conn->sessions);
spin_lock_init(&conn->llist_lock);
INIT_LIST_HEAD(&conn->lock_list);
init_rwsem(&conn->session_lock);
@ -457,17 +456,13 @@ void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status)
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)) {
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)
if (conn->transport->ops->shutdown)
conn->transport->ops->shutdown(conn->transport);
}
@ -534,12 +529,19 @@ static int __ksmbd_conn_write(struct ksmbd_work *work,
tx->need_invalidate_rkey = work->need_invalidate_rkey;
tx->remote_key = work->remote_key;
ksmbd_conn_lock(conn);
if (tx->tracking) {
/* A busy writer must not consume the break's retry budget. */
if (!mutex_trylock(&conn->srv_mutex))
return -EAGAIN;
} else {
ksmbd_conn_lock(conn);
}
sent = conn->transport->ops->writev(conn->transport, tx);
ksmbd_conn_unlock(conn);
if (sent < 0) {
pr_err("Failed to send message: %d\n", sent);
if (!tx->tracking || sent != -EAGAIN)
pr_err("Failed to send message: %d\n", sent);
return sent;
}
@ -553,6 +555,20 @@ int ksmbd_conn_write(struct ksmbd_work *work)
return __ksmbd_conn_write(work, &tx);
}
int ksmbd_conn_write_tracked(struct ksmbd_work *work,
struct ksmbd_tx_tracking *tracking)
{
struct ksmbd_transport_write tx = {};
tracking->valid = false;
/* Transports without delivery tracking keep their synchronous send path. */
if (work->conn->transport->ops->is_acked) {
tx.msg_flags = MSG_DONTWAIT;
tx.tracking = tracking;
}
return __ksmbd_conn_write(work, &tx);
}
int ksmbd_conn_write_eor(struct ksmbd_work *work)
{
struct ksmbd_transport_write tx = {
@ -641,6 +657,7 @@ static void ksmbd_session_expiration_worker(struct work_struct *work)
goto reschedule;
ksmbd_expire_sessions();
ksmbd_reclaim_ended_sessions();
/*
* An old connection without a Valid or Expired session must be
@ -752,6 +769,7 @@ int ksmbd_conn_handler_loop(void *p)
if (!conn->request_buf)
break;
conn->request_buf[pdu_size + 4] = 0;
memcpy(conn->request_buf, hdr_buf, sizeof(hdr_buf));
/*
@ -804,7 +822,12 @@ int ksmbd_conn_handler_loop(void *p)
}
}
/* Exclude new break senders before draining their work references. */
spin_lock(&conn->request_lock);
ksmbd_conn_set_releasing(conn);
spin_unlock(&conn->request_lock);
/* TCP writers waiting for queue space must release their work references. */
wake_up_all(&conn->req_running_q);
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));
@ -831,6 +854,20 @@ void ksmbd_conn_r_count_inc(struct ksmbd_conn *conn)
atomic_inc(&conn->r_count);
}
/* The caller already owns a connection reference. */
bool ksmbd_conn_r_count_inc_if_good(struct ksmbd_conn *conn)
{
bool ret = false;
spin_lock(&conn->request_lock);
if (ksmbd_conn_good(conn)) {
atomic_inc(&conn->r_count);
ret = true;
}
spin_unlock(&conn->request_lock);
return ret;
}
void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn)
{
/*

View File

@ -68,6 +68,9 @@ struct ksmbd_conn {
u8 inet6_addr[16];
#endif
};
#if IS_ENABLED(CONFIG_IPV6)
bool is_ipv6;
#endif
unsigned int inet_hash;
char *request_buf;
struct ksmbd_transport *transport;
@ -77,6 +80,8 @@ struct ksmbd_conn {
struct rw_semaphore session_lock;
/* smb session 1 per user */
struct xarray sessions;
/* New sessions and SMB3 channels each reserve one connection slot. */
atomic64_t session_count;
unsigned long creation_time;
unsigned long last_active;
/* How many request are running currently */
@ -100,6 +105,8 @@ struct ksmbd_conn {
wait_queue_head_t r_count_q;
/* Lock to protect requests list*/
spinlock_t request_lock;
/* Set under request_lock at teardown start and kept set thereafter. */
bool releasing;
struct list_head requests;
struct list_head async_requests;
int connection_type;
@ -107,8 +114,6 @@ struct ksmbd_conn {
char ClientGUID[SMB2_CLIENT_GUID_SIZE];
struct ntlmssp_auth ntlmssp;
spinlock_t llist_lock;
struct list_head lock_list;
struct preauth_integrity_info *preauth_info;
@ -159,6 +164,13 @@ struct ksmbd_conn_ops {
int (*terminate_fn)(struct ksmbd_conn *conn);
};
struct ksmbd_tx_tracking {
/* Required TCP byte count, including transport and socket queues. */
u64 end;
unsigned long retry_after;
bool valid;
};
struct ksmbd_transport_write {
struct kvec *iov;
int iov_cnt;
@ -166,6 +178,7 @@ struct ksmbd_transport_write {
bool need_invalidate_rkey;
unsigned int remote_key;
int msg_flags;
struct ksmbd_tx_tracking *tracking;
};
struct ksmbd_transport_ops {
@ -173,8 +186,12 @@ struct ksmbd_transport_ops {
void (*shutdown)(struct ksmbd_transport *t);
int (*read)(struct ksmbd_transport *t, char *buf,
unsigned int size, int max_retries);
/* Retained bytes must be privately owned before a full result is returned. */
int (*writev)(struct ksmbd_transport *t,
const struct ksmbd_transport_write *tx);
/* Optional delivery tracking for nonblocking break sends. */
bool (*is_acked)(struct ksmbd_transport *t,
const struct ksmbd_tx_tracking *tracking);
int (*rdma_read)(struct ksmbd_transport *t,
void *buf, unsigned int len,
struct smbdirect_buffer_descriptor_v1 *desc,
@ -214,6 +231,8 @@ 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_tracked(struct ksmbd_work *work,
struct ksmbd_tx_tracking *tracking);
int ksmbd_conn_write_eor(struct ksmbd_work *work);
int ksmbd_conn_rdma_read(struct ksmbd_conn *conn,
void *buf, unsigned int buflen,
@ -232,6 +251,7 @@ void ksmbd_conn_transport_destroy(void);
void ksmbd_conn_lock(struct ksmbd_conn *conn);
void ksmbd_conn_unlock(struct ksmbd_conn *conn);
void ksmbd_conn_r_count_inc(struct ksmbd_conn *conn);
bool ksmbd_conn_r_count_inc_if_good(struct ksmbd_conn *conn);
void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn);
/*
@ -247,7 +267,8 @@ static inline bool ksmbd_conn_new(struct ksmbd_conn *conn)
static inline bool ksmbd_conn_good(struct ksmbd_conn *conn)
{
return READ_ONCE(conn->status) == KSMBD_SESS_GOOD;
return !READ_ONCE(conn->releasing) &&
READ_ONCE(conn->status) == KSMBD_SESS_GOOD;
}
static inline unsigned int
@ -281,7 +302,8 @@ static inline bool ksmbd_conn_exiting(struct ksmbd_conn *conn)
static inline bool ksmbd_conn_releasing(struct ksmbd_conn *conn)
{
return READ_ONCE(conn->status) == KSMBD_SESS_RELEASING;
return READ_ONCE(conn->releasing) ||
READ_ONCE(conn->status) == KSMBD_SESS_RELEASING;
}
static inline void ksmbd_conn_set_new(struct ksmbd_conn *conn)
@ -316,6 +338,7 @@ static inline void ksmbd_conn_set_exiting(struct ksmbd_conn *conn)
static inline void ksmbd_conn_set_releasing(struct ksmbd_conn *conn)
{
WRITE_ONCE(conn->releasing, true);
WRITE_ONCE(conn->status, KSMBD_SESS_RELEASING);
}

View File

@ -114,7 +114,8 @@ struct ksmbd_startup_request {
__s8 bind_interfaces_only;
__u32 max_ip_connections; /* Number of maximum connection per ip address */
__s8 aapl_model[32]; /* AAPL model string for Finder icon, e.g. "Xserve" */
__s8 reserved[467]; /* Reserved room */
__u32 max_sessions_per_connection; /* Maximum sessions per connection */
__s8 reserved[463]; /* Reserved room */
__u32 ifc_list_sz; /* interfaces list size */
__s8 ____payload[];
} __packed;
@ -148,7 +149,9 @@ struct ksmbd_login_response {
__u16 status;
__u16 hash_sz; /* hash size */
__s8 hash[KSMBD_REQ_MAX_HASH_SZ]; /* password hash */
__u32 reserved[16]; /* Reserved room */
__u32 session_expiry_lo; /* Kerberos ticket expiry, SPNEGO only */
__u32 session_expiry_hi;
__u32 reserved[14]; /* Reserved room */
};
/*
@ -286,7 +289,6 @@ 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 */
};

View File

@ -16,6 +16,7 @@
static struct kmem_cache *work_cache;
static struct workqueue_struct *ksmbd_wq;
static struct workqueue_struct *ksmbd_break_wq;
static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
{
@ -57,7 +58,6 @@ 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;
@ -87,8 +87,6 @@ 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);
}
@ -113,6 +111,12 @@ int ksmbd_workqueue_init(void)
ksmbd_wq = alloc_workqueue("ksmbd-io", WQ_PERCPU, 0);
if (!ksmbd_wq)
return -ENOMEM;
ksmbd_break_wq = alloc_workqueue("ksmbd-break", WQ_UNBOUND, 0);
if (!ksmbd_break_wq) {
destroy_workqueue(ksmbd_wq);
ksmbd_wq = NULL;
return -ENOMEM;
}
return 0;
}
@ -120,6 +124,8 @@ void ksmbd_workqueue_destroy(void)
{
destroy_workqueue(ksmbd_wq);
ksmbd_wq = NULL;
destroy_workqueue(ksmbd_break_wq);
ksmbd_break_wq = NULL;
}
bool ksmbd_queue_work(struct ksmbd_work *work)
@ -127,6 +133,16 @@ bool ksmbd_queue_work(struct ksmbd_work *work)
return queue_work(ksmbd_wq, &work->work);
}
void ksmbd_queue_io_work(struct work_struct *work)
{
queue_work(ksmbd_wq, work);
}
void ksmbd_queue_break_work(struct work_struct *work)
{
queue_work(ksmbd_break_wq, work);
}
static inline void __ksmbd_iov_pin(struct ksmbd_work *work, void *ib,
unsigned int ib_len)
{

View File

@ -54,10 +54,7 @@ struct ksmbd_work {
/* Current cmd hdr in compound rsp buf*/
int curr_smb2_rsp_hdr_off;
/*
* Current Local FID assigned compound response if SMB2 CREATE
* command is present in compound request
*/
/* FileId generated or used by the previous operation in a compound. */
u64 compound_fid;
u64 compound_pfid;
u64 compound_sid;
@ -91,11 +88,15 @@ 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;
/* The previous command carries a FileId or is a CREATE. */
bool compound_fid_present:1;
/* A related command failed because there was no FileId to inherit. */
bool compound_fid_missing:1;
bool session_setup_reauth:1;
/* Retain the binding decision for response signing and preauth hashing. */
bool session_setup_binding:1;
unsigned int remote_key;
/* cancel works */
@ -115,9 +116,8 @@ struct ksmbd_work {
struct list_head request_entry;
/* List head at conn->async_requests */
struct list_head async_request_entry;
/* List head at ksmbd_file->blocked_works */
struct list_head fp_entry;
/* List head at ksmbd_file->notify_pendings */
struct list_head notify_entry;
};
/**
@ -156,6 +156,8 @@ int ksmbd_work_pool_init(void);
int ksmbd_workqueue_init(void);
void ksmbd_workqueue_destroy(void);
bool ksmbd_queue_work(struct ksmbd_work *work);
void ksmbd_queue_io_work(struct work_struct *work);
void ksmbd_queue_break_work(struct work_struct *work);
int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
void *aux_buf, unsigned int aux_size);
int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len);

View File

@ -8,6 +8,7 @@
#include <linux/slab.h>
#include <linux/rwsem.h>
#include <linux/parser.h>
#include <linux/dcache.h>
#include <linux/namei.h>
#include <linux/fs_struct.h>
#include <linux/sched.h>
@ -108,6 +109,7 @@ static void kill_share(struct ksmbd_share_config *share)
path_put(&share->vfs_path);
kfree(share->name);
kfree(share->path);
kfree(share->real_path);
kfree(share);
}
@ -182,6 +184,43 @@ static int parse_veto_list(struct ksmbd_share_config *share,
return 0;
}
static int share_config_resolve_path(struct ksmbd_share_config *share)
{
char *buf, *path;
int ret;
ret = kern_path(share->path, 0, &share->vfs_path);
if (ret)
return ret;
buf = kmalloc(PATH_MAX, KSMBD_DEFAULT_GFP);
if (!buf) {
ret = -ENOMEM;
goto out;
}
path = d_path(&share->vfs_path, buf, PATH_MAX);
if (IS_ERR(path)) {
ret = PTR_ERR(path);
goto out_buf;
}
path = kstrdup(path, KSMBD_DEFAULT_GFP);
if (!path) {
ret = -ENOMEM;
goto out_buf;
}
share->real_path = path;
share->real_path_sz = strlen(path);
out_buf:
kfree(buf);
out:
if (ret)
path_put(&share->vfs_path);
return ret;
}
static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
const char *name)
{
@ -272,12 +311,12 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
}
scoped_with_init_fs()
ret = kern_path(share->path, 0, &share->vfs_path);
ret = share_config_resolve_path(share);
ksmbd_revert_fsids(work);
if (ret) {
ksmbd_debug(SMB, "failed to access '%s'\n",
share->path);
/* Avoid put_path() */
/* No path reference is retained on failure. */
kfree(share->path);
share->path = NULL;
}

View File

@ -16,8 +16,10 @@ struct ksmbd_work;
struct ksmbd_share_config {
char *name;
char *path;
char *real_path;
unsigned int path_sz;
unsigned int real_path_sz;
unsigned int flags;
struct list_head veto_list;
@ -43,7 +45,7 @@ static inline umode_t
share_config_create_mode(struct ksmbd_share_config *share,
umode_t posix_mode)
{
umode_t mode = (posix_mode ?: (umode_t)-1) & share->create_mask;
umode_t mode = posix_mode & share->create_mask;
return mode | share->force_create_mode;
}
@ -52,7 +54,7 @@ static inline umode_t
share_config_directory_mode(struct ksmbd_share_config *share,
umode_t posix_mode)
{
umode_t mode = (posix_mode ?: (umode_t)-1) & share->directory_mask;
umode_t mode = posix_mode & share->directory_mask;
return mode | share->force_directory_mode;
}

View File

@ -103,6 +103,13 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
return status;
}
struct ksmbd_tree_connect *
ksmbd_tree_connect_get(struct ksmbd_tree_connect *tcon)
{
atomic_inc(&tcon->refcount);
return tcon;
}
void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon)
{
if (atomic_dec_and_test(&tcon->refcount)) {

View File

@ -51,6 +51,8 @@ struct ksmbd_session;
struct ksmbd_tree_conn_status
ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name);
struct ksmbd_tree_connect *
ksmbd_tree_connect_get(struct ksmbd_tree_connect *tcon);
void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon);
int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,

View File

@ -3,6 +3,7 @@
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
*/
#include <linux/atomic.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/rwsem.h>
@ -18,6 +19,7 @@
#include "../vfs_cache.h"
#include "../misc.h"
#include "../stats.h"
#include "../server.h"
static DEFINE_IDA(session_ida);
@ -26,6 +28,21 @@ static DEFINE_IDA(session_ida);
static DEFINE_HASHTABLE(sessions_table, SESSION_HASH_BITS);
static DECLARE_RWSEM(sessions_table_lock);
int ksmbd_session_reserve_slot(struct ksmbd_conn *conn)
{
if (atomic64_inc_return(&conn->session_count) >
READ_ONCE(server_conf.max_sessions_per_connection)) {
atomic64_dec(&conn->session_count);
return -ENOSPC;
}
return 0;
}
void ksmbd_session_release_slot(struct ksmbd_conn *conn)
{
atomic64_dec(&conn->session_count);
}
struct ksmbd_session_rpc {
int id;
unsigned int method;
@ -95,8 +112,10 @@ static unsigned int session_open_file_count(struct ksmbd_session *session)
unsigned int id;
read_lock(&session->file_table.lock);
idr_for_each_entry(session->file_table.idr, fp, id)
count++;
if (session->file_table.idr) {
idr_for_each_entry(session->file_table.idr, fp, id)
count++;
}
read_unlock(&session->file_table.lock);
return count;
}
@ -130,7 +149,7 @@ static int show_proc_session(struct seq_file *m, void *v)
const char *name;
#if IS_ENABLED(CONFIG_IPV6)
if (chan->conn->inet_addr)
if (!chan->conn->is_ipv6)
seq_printf(m, "client:\t%pI4\n",
&chan->conn->inet_addr);
else
@ -212,8 +231,10 @@ static int create_proc_session(struct ksmbd_session *sess)
static void delete_proc_session(struct ksmbd_session *sess)
{
if (sess->proc_entry)
if (sess->proc_entry) {
proc_remove(sess->proc_entry);
sess->proc_entry = NULL;
}
}
static int show_proc_sessions(struct seq_file *m, void *v)
@ -231,7 +252,7 @@ static int show_proc_sessions(struct seq_file *m, void *v)
ksmbd_user_session_get(session);
#if IS_ENABLED(CONFIG_IPV6)
if (!chan->conn->inet_addr)
if (chan->conn->is_ipv6)
seq_printf(m, "client:\t%pI6c\n", &chan->conn->inet6_addr);
else
#endif
@ -271,6 +292,8 @@ static void free_channel_list(struct ksmbd_session *sess)
down_write(&sess->chann_lock);
xa_for_each(&sess->ksmbd_chann_list, index, chann) {
xa_erase(&sess->ksmbd_chann_list, index);
ksmbd_session_release_slot(chann->conn);
ksmbd_conn_put(chann->conn);
kfree_sensitive(chann);
}
@ -397,9 +420,12 @@ int ksmbd_session_rpc_method(struct ksmbd_session *sess, int id)
void ksmbd_session_destroy(struct ksmbd_session *sess)
{
struct ksmbd_conn *setup_conn;
if (!sess)
return;
setup_conn = sess->setup_conn;
delete_proc_session(sess);
ksmbd_tree_conn_session_logoff(sess);
ksmbd_destroy_file_table(sess);
@ -409,9 +435,14 @@ void ksmbd_session_destroy(struct ksmbd_session *sess)
ksmbd_session_rpc_clear_list(sess);
free_channel_list(sess);
kfree_sensitive(sess->Preauth_HashValue);
ksmbd_release_id(&session_ida, sess->id);
if (sess->id)
ksmbd_release_id(&session_ida, sess->id);
ida_destroy(&sess->tree_conn_ida);
kfree_sensitive(sess);
if (setup_conn) {
ksmbd_session_release_slot(setup_conn);
ksmbd_conn_put(setup_conn);
}
}
static void ksmbd_session_remove_from_table(struct ksmbd_session *sess)
@ -420,6 +451,17 @@ static void ksmbd_session_remove_from_table(struct ksmbd_session *sess)
ksmbd_counter_dec(KSMBD_COUNTER_SESSIONS);
}
static void ksmbd_dispose_sessions(struct list_head *dispose)
{
struct ksmbd_session *sess, *tmp;
list_for_each_entry_safe(sess, tmp, dispose, dispose_entry) {
list_del_init(&sess->dispose_entry);
delete_proc_session(sess);
ksmbd_user_session_put(sess);
}
}
struct ksmbd_session *__session_lookup(unsigned long long id)
{
struct ksmbd_session *sess;
@ -433,8 +475,9 @@ struct ksmbd_session *__session_lookup(unsigned long long id)
return NULL;
}
static bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn)
bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn)
{
LIST_HEAD(dispose);
unsigned long id;
struct ksmbd_session *sess;
unsigned int pending = 0;
@ -445,18 +488,20 @@ static bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn)
if (READ_ONCE(sess->state) != SMB2_SESSION_IN_PROGRESS)
continue;
if (atomic_read(&sess->refcnt) <= 1 &&
/* Keep response signing keys until every request has dropped its reference. */
if (atomic_read(&sess->refcnt) == 1 &&
time_after(jiffies, sess->last_active +
KSMBD_UNAUTHENTICATED_CONN_TIMEOUT)) {
xa_erase(&conn->sessions, sess->id);
ksmbd_session_remove_from_table(sess);
ksmbd_session_destroy(sess);
list_add_tail(&sess->dispose_entry, &dispose);
continue;
}
pending++;
}
up_write(&conn->session_lock);
up_write(&sessions_table_lock);
ksmbd_dispose_sessions(&dispose);
return pending >= KSMBD_MAX_PENDING_SESSIONS;
}
@ -467,24 +512,22 @@ int ksmbd_session_register(struct ksmbd_conn *conn,
sess->dialect = conn->dialect;
memcpy(sess->ClientGUID, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
/* Bound abandoned SessionId-zero authentication exchanges. */
if (ksmbd_too_many_session_setups(conn))
ret = -ENOSPC;
else
ret = xa_err(xa_store(&conn->sessions, sess->id, sess,
KSMBD_DEFAULT_GFP));
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);
delete_proc_session(sess);
ksmbd_user_session_put(sess);
}
return ret;
}
void ksmbd_session_unregister(struct ksmbd_conn *conn,
struct ksmbd_session *sess)
/* The caller holds sessions_table_lock for writing. */
static bool __ksmbd_session_unregister(struct ksmbd_conn *conn,
struct ksmbd_session *sess)
{
struct ksmbd_conn *session_conns[KSMBD_MAX_CHANNELS];
struct channel *chann;
@ -492,10 +535,13 @@ void ksmbd_session_unregister(struct ksmbd_conn *conn,
unsigned int nr_conns = 0, i;
bool removed = false;
down_write(&sessions_table_lock);
lockdep_assert_held_write(&sessions_table_lock);
if (!hlist_unhashed(&sess->hlist)) {
/* Keep each channel connection stable under sessions_table_lock. */
/* Existing references stay valid under sessions_table_lock. */
down_read(&sess->chann_lock);
/* SMB2.0/2.1 sessions have no channel holding their connection. */
if (sess->setup_conn)
session_conns[nr_conns++] = sess->setup_conn;
xa_for_each(&sess->ksmbd_chann_list, index, chann) {
if (nr_conns == ARRAY_SIZE(session_conns))
break;
@ -507,10 +553,12 @@ void ksmbd_session_unregister(struct ksmbd_conn *conn,
removed = true;
}
down_write(&conn->session_lock);
if (xa_load(&conn->sessions, sess->id) == sess)
xa_erase(&conn->sessions, sess->id);
up_write(&conn->session_lock);
if (conn) {
down_write(&conn->session_lock);
if (xa_load(&conn->sessions, sess->id) == sess)
xa_erase(&conn->sessions, sess->id);
up_write(&conn->session_lock);
}
for (i = 0; i < nr_conns; i++) {
if (session_conns[i] == conn)
continue;
@ -519,10 +567,47 @@ void ksmbd_session_unregister(struct ksmbd_conn *conn,
xa_erase(&session_conns[i]->sessions, sess->id);
up_write(&session_conns[i]->session_lock);
}
return removed;
}
void ksmbd_session_unregister(struct ksmbd_conn *conn,
struct ksmbd_session *sess)
{
bool removed;
down_write(&sessions_table_lock);
removed = __ksmbd_session_unregister(conn, sess);
up_write(&sessions_table_lock);
if (removed)
if (removed) {
/*
* Keep the table reference until procfs readers have finished,
* so none can remove its own proc entry on the last put.
*/
delete_proc_session(sess);
ksmbd_user_session_put(sess);
}
}
void ksmbd_reclaim_ended_sessions(void)
{
LIST_HEAD(dispose);
struct ksmbd_session *sess;
struct hlist_node *tmp;
int bkt;
down_write(&sessions_table_lock);
hash_for_each_safe(sessions_table, bkt, tmp, sess, hlist) {
if (READ_ONCE(sess->state) != SMB2_SESSION_EXPIRED ||
READ_ONCE(sess->kerberos_expiry) ||
atomic_read(&sess->refcnt) != 1)
continue;
__ksmbd_session_unregister(NULL, sess);
list_add_tail(&sess->dispose_entry, &dispose);
}
up_write(&sessions_table_lock);
ksmbd_dispose_sessions(&dispose);
}
bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn)
@ -532,7 +617,12 @@ bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn)
int state, bkt;
bool found = false;
down_read(&conn->session_lock);
/*
* The expiration worker holds conn_list_lock, which session teardown
* takes after the session locks. Defer scavenging if either is busy.
*/
if (!down_read_trylock(&conn->session_lock))
return true;
xa_for_each(&conn->sessions, id, sess) {
state = READ_ONCE(sess->state);
if (state == SMB2_SESSION_VALID ||
@ -546,7 +636,8 @@ bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn)
return true;
/* A session bound through SMB3 multichannel is not in conn->sessions. */
down_read(&sessions_table_lock);
if (!down_read_trylock(&sessions_table_lock))
return true;
hash_for_each(sessions_table, bkt, sess, hlist) {
state = READ_ONCE(sess->state);
if (state != SMB2_SESSION_VALID &&
@ -592,12 +683,15 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
if (!chann)
return -ENOENT;
ksmbd_session_release_slot(chann->conn);
ksmbd_conn_put(chann->conn);
kfree_sensitive(chann);
return 0;
}
void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn)
{
LIST_HEAD(dispose);
struct ksmbd_session *sess;
unsigned long id;
struct hlist_node *tmp;
@ -611,8 +705,7 @@ void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn)
down_write(&conn->session_lock);
xa_erase(&conn->sessions, sess->id);
up_write(&conn->session_lock);
if (atomic_dec_and_test(&sess->refcnt))
ksmbd_session_destroy(sess);
list_add_tail(&sess->dispose_entry, &dispose);
}
}
@ -622,12 +715,12 @@ void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn)
if (xa_empty(&sess->ksmbd_chann_list)) {
xa_erase(&conn->sessions, sess->id);
ksmbd_session_remove_from_table(sess);
if (atomic_dec_and_test(&sess->refcnt))
ksmbd_session_destroy(sess);
list_add_tail(&sess->dispose_entry, &dispose);
}
}
up_write(&conn->session_lock);
up_write(&sessions_table_lock);
ksmbd_dispose_sessions(&dispose);
}
bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn,
@ -674,6 +767,37 @@ struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id)
return sess;
}
/* The caller holds a session reference and must put each returned connection. */
unsigned int ksmbd_session_get_channels(struct ksmbd_session *sess,
struct ksmbd_conn **conns,
unsigned int max_conns)
{
struct channel *chann;
unsigned long index;
unsigned int nr_conns = 0;
/*
* Channel connections are borrowed. Unregister can leave the channel
* list in a pinned session after removing it from the registry, so
* chann_lock alone does not keep those connections alive. Only collect
* registered sessions, excluding both unregister and connection cleanup
* until the connection references have been taken.
*/
down_read(&sessions_table_lock);
if (hlist_unhashed(&sess->hlist))
goto out;
down_read(&sess->chann_lock);
xa_for_each(&sess->ksmbd_chann_list, index, chann) {
if (nr_conns == max_conns)
break;
conns[nr_conns++] = ksmbd_conn_get(chann->conn);
}
up_read(&sess->chann_lock);
out:
up_read(&sessions_table_lock);
return nr_conns;
}
struct ksmbd_session *ksmbd_session_lookup_all_states(struct ksmbd_conn *conn,
unsigned long long id)
{
@ -759,6 +883,7 @@ void destroy_previous_session(struct ksmbd_conn *conn,
{
struct ksmbd_session *prev_sess;
struct ksmbd_user *prev_user;
bool pinned = false;
int err;
down_write(&sessions_table_lock);
@ -781,9 +906,21 @@ void destroy_previous_session(struct ksmbd_conn *conn,
}
prev_sess->tearing_down = true;
up_write(&prev_sess->chann_lock);
ksmbd_user_session_get(prev_sess);
pinned = true;
up_write(&conn->session_lock);
up_write(&sessions_table_lock);
ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_RECONNECT);
/*
* The caller has finished using the connection's authentication state.
* Drop srv_mutex so drained requests can write their responses.
*/
ksmbd_conn_unlock(conn);
err = ksmbd_conn_wait_idle_sess(conn, prev_sess);
ksmbd_conn_lock(conn);
down_write(&sessions_table_lock);
down_write(&conn->session_lock);
if (err) {
down_write(&prev_sess->chann_lock);
prev_sess->tearing_down = false;
@ -800,6 +937,8 @@ void destroy_previous_session(struct ksmbd_conn *conn,
out:
up_write(&conn->session_lock);
up_write(&sessions_table_lock);
if (pinned)
ksmbd_user_session_put(prev_sess);
}
static bool ksmbd_preauth_session_id_match(struct preauth_session *sess,
@ -830,7 +969,7 @@ static int __init_smb2_session(struct ksmbd_session *sess)
return 0;
}
static struct ksmbd_session *__session_create(int protocol)
static struct ksmbd_session *__session_create(struct ksmbd_conn *conn, int protocol)
{
struct ksmbd_session *sess;
int ret;
@ -838,27 +977,33 @@ static struct ksmbd_session *__session_create(int protocol)
if (protocol != CIFDS_SESSION_FLAG_SMB2)
return NULL;
sess = kzalloc_obj(struct ksmbd_session, KSMBD_DEFAULT_GFP);
if (!sess)
if (ksmbd_session_reserve_slot(conn))
return NULL;
sess = kzalloc_obj(struct ksmbd_session, KSMBD_DEFAULT_GFP);
if (!sess) {
ksmbd_session_release_slot(conn);
return NULL;
}
sess->setup_conn = ksmbd_conn_get(conn);
ida_init(&sess->tree_conn_ida);
if (ksmbd_init_file_table(&sess->file_table))
goto error;
sess->last_active = jiffies;
sess->state = SMB2_SESSION_IN_PROGRESS;
set_session_flag(sess, protocol);
xa_init(&sess->tree_conns);
xa_init(&sess->ksmbd_chann_list);
xa_init(&sess->rpc_handle_list);
INIT_LIST_HEAD(&sess->dispose_entry);
sess->sequence_number = 1;
atomic_set(&sess->refcnt, 2);
init_rwsem(&sess->tree_conns_lock);
init_rwsem(&sess->rpc_lock);
init_rwsem(&sess->chann_lock);
if (ksmbd_init_file_table(&sess->file_table))
goto error;
ret = __init_smb2_session(sess);
if (ret)
goto error;
@ -877,9 +1022,9 @@ static struct ksmbd_session *__session_create(int protocol)
return NULL;
}
struct ksmbd_session *ksmbd_smb2_session_create(void)
struct ksmbd_session *ksmbd_smb2_session_create(struct ksmbd_conn *conn)
{
return __session_create(CIFDS_SESSION_FLAG_SMB2);
return __session_create(conn, CIFDS_SESSION_FLAG_SMB2);
}
int ksmbd_acquire_tree_conn_id(struct ksmbd_session *sess)

View File

@ -37,6 +37,8 @@ struct ksmbd_session {
char ClientGUID[SMB2_CLIENT_GUID_SIZE];
struct ksmbd_user *user;
/* Holds the connection slot until an SMB3 channel takes it over. */
struct ksmbd_conn *setup_conn;
unsigned int sequence_number;
unsigned int flags;
@ -51,6 +53,7 @@ struct ksmbd_session {
u64 kerberos_expiry;
struct hlist_node hlist;
struct list_head dispose_entry;
struct rw_semaphore chann_lock;
struct xarray ksmbd_chann_list;
struct xarray tree_conns;
@ -89,15 +92,21 @@ static inline void clear_session_flag(struct ksmbd_session *sess, int bit)
sess->flags &= ~bit;
}
struct ksmbd_session *ksmbd_smb2_session_create(void);
struct ksmbd_session *ksmbd_smb2_session_create(struct ksmbd_conn *conn);
int ksmbd_session_reserve_slot(struct ksmbd_conn *conn);
void ksmbd_session_release_slot(struct ksmbd_conn *conn);
void ksmbd_session_destroy(struct ksmbd_session *sess);
struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id);
unsigned int ksmbd_session_get_channels(struct ksmbd_session *sess,
struct ksmbd_conn **conns,
unsigned int max_conns);
struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn,
unsigned long long id);
bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn,
unsigned long long id);
bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn);
int ksmbd_session_register(struct ksmbd_conn *conn,
struct ksmbd_session *sess);
void ksmbd_session_unregister(struct ksmbd_conn *conn,
@ -105,6 +114,7 @@ void ksmbd_session_unregister(struct ksmbd_conn *conn,
void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn);
bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn);
void ksmbd_expire_sessions(void);
void ksmbd_reclaim_ended_sessions(void);
struct ksmbd_session *__session_lookup(unsigned long long id);
struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
unsigned long long id);

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,6 @@ struct lease_ctx_info {
struct lease_table {
char client_guid[SMB2_CLIENT_GUID_SIZE];
struct ksmbd_conn *conn;
struct list_head lease_list;
struct list_head l_entry;
spinlock_t lb_lock;
@ -51,6 +50,9 @@ struct lease {
unsigned short epoch;
bool is_dir;
bool reuse_epoch;
bool breaking;
bool break_timeout_set;
unsigned long break_timeout;
struct ksmbd_inode *ci;
struct lease_table *l_lb;
struct list_head l_entry;
@ -62,11 +64,15 @@ struct lease {
struct oplock_info {
struct ksmbd_conn *conn;
struct ksmbd_session *sess;
u64 session_id;
struct ksmbd_work *work;
struct ksmbd_file *o_fp;
int level;
int op_state;
spinlock_t state_lock;
unsigned long oplock_timeout;
bool oplock_timeout_set;
bool oplock_timed_out;
unsigned long pending_break;
u64 fid;
atomic_t breaking_cnt;
@ -82,19 +88,6 @@ struct oplock_info {
struct rcu_head rcu;
};
struct lease_break_info {
__le32 curr_state;
__le32 new_state;
__le16 epoch;
char lease_key[SMB2_LEASE_KEY_SIZE];
};
struct oplock_break_info {
int level;
int open_trunc;
int fid;
};
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, bool replay);
@ -134,7 +127,7 @@ struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
char *lease_key);
int find_same_lease_key(struct ksmbd_conn *conn, struct ksmbd_inode *ci,
struct lease_ctx_info *lctx);
void destroy_lease_table(struct ksmbd_conn *conn);
void destroy_lease_table(void);
void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
struct lease_ctx_info *lctx);
void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp);

View File

@ -161,6 +161,8 @@ static int proc_show_ksmbd_stats(struct seq_file *m, void *v)
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_sessions_per_connection:\t%u\n",
server_conf.max_sessions_per_connection);
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);

View File

@ -157,7 +157,10 @@ static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn,
}
}
ret = cmds->proc(work);
if (smb2_check_compound_fid(work))
ret = -EBADF;
else
ret = cmds->proc(work);
if (conn->ops->inc_reqs) {
struct smb2_hdr *rsp = ksmbd_resp_buf_curr(work);
@ -187,6 +190,16 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (conn->ops->is_transform_hdr &&
conn->ops->is_transform_hdr(work->request_buf)) {
u64 tr_sess_id;
if (get_rfc1002_len(work->request_buf) <
sizeof(struct smb2_transform_hdr)) {
ksmbd_conn_abort(conn);
return;
}
tr_sess_id = le64_to_cpu(((struct smb2_transform_hdr *)
smb_get_msg(work->request_buf))->SessionId);
rc = conn->ops->decrypt_req(work);
if (rc < 0) {
ksmbd_conn_abort(conn);
@ -215,6 +228,12 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
ksmbd_conn_abort(conn);
return;
}
rc = ksmbd_check_transform_session(work, tr_sess_id);
if (rc < 0) {
ksmbd_conn_abort(conn);
return;
}
}
if (conn->ops->allocate_rsp_buf(work))
@ -382,6 +401,7 @@ static void handle_ksmbd_work(struct work_struct *wk)
static int queue_ksmbd_work(struct ksmbd_conn *conn)
{
struct ksmbd_work *work;
bool wait_for_neg = ksmbd_conn_new(conn) || ksmbd_conn_need_negotiate(conn);
int err;
err = ksmbd_init_smb_server(conn);
@ -404,6 +424,10 @@ static int queue_ksmbd_work(struct ksmbd_conn *conn)
conn->last_active = jiffies;
INIT_WORK(&work->work, handle_ksmbd_work);
ksmbd_queue_work(work);
/* Negotiation can change conn->ops, finish it before reading another PDU. */
if (wait_for_neg)
wait_event(conn->req_running_q,
atomic_read(&conn->req_running) == 0);
return 0;
}
@ -415,7 +439,6 @@ static int ksmbd_server_process_request(struct ksmbd_conn *conn)
static int ksmbd_server_terminate_conn(struct ksmbd_conn *conn)
{
ksmbd_conn_sessions_cleanup(conn);
destroy_lease_table(conn);
return 0;
}
@ -451,6 +474,8 @@ static int server_conf_init(void)
KSMBD_AUTH_MSKRB5;
#endif
server_conf.max_inflight_req = SMB2_MAX_CREDITS;
server_conf.max_sessions_per_connection =
KSMBD_DEFAULT_MAX_SESSIONS_PER_CONNECTION;
return 0;
}
@ -643,7 +668,7 @@ static int ksmbd_server_shutdown(void)
ksmbd_proc_cleanup();
ksmbd_crypto_destroy();
ksmbd_free_global_file_table();
destroy_lease_table(NULL);
destroy_lease_table();
ksmbd_work_pool_destroy();
ksmbd_exit_file_cache();
server_conf_free();

View File

@ -8,6 +8,8 @@
#include "smbacl.h"
#define KSMBD_DEFAULT_MAX_SESSIONS_PER_CONNECTION 64
/*
* Server state type
*/
@ -44,6 +46,7 @@ struct ksmbd_server_config {
unsigned int max_connections;
unsigned int max_inflight_req;
unsigned int max_ip_connections;
unsigned int max_sessions_per_connection;
char *conf[SERVER_CONF_WORK_GROUP + 1];
struct task_struct *dh_task;

View File

@ -13,6 +13,35 @@
#include "server.h"
#include "stats.h"
/*
* work->response_sz includes the RFC1002 length field while smb_get_msg()
* skips over it. Durable v1 and v2 response contexts are mutually exclusive,
* and POSIX CREATE contexts are only negotiated for SMB3.1.1.
*/
#define SMB2_CREATE_RSP_SIZE(lease_size, durable_size) \
(sizeof(__be32) + offsetof(struct smb2_create_rsp, Buffer) + \
(lease_size) + (durable_size) + \
sizeof(struct create_mxac_rsp) + \
sizeof(struct create_disk_id_rsp) + \
AAPL_RSP_MAX_SIZE)
#define SMB21_CREATE_RSP_SIZE \
SMB2_CREATE_RSP_SIZE(sizeof(struct create_lease), \
sizeof(struct create_durable_rsp))
#define SMB3_CREATE_DURABLE_RSP_SIZE \
((sizeof(struct create_durable_rsp) > \
sizeof(struct create_durable_rsp_v2)) ? \
sizeof(struct create_durable_rsp) : \
sizeof(struct create_durable_rsp_v2))
#define SMB3_CREATE_RSP_SIZE \
SMB2_CREATE_RSP_SIZE(sizeof(struct create_lease_v2), \
SMB3_CREATE_DURABLE_RSP_SIZE)
#define SMB311_CREATE_RSP_SIZE \
(SMB3_CREATE_RSP_SIZE + sizeof(struct create_posix_rsp))
static struct smb_version_values smb21_server_values = {
.version_string = SMB21_VERSION_STRING,
.protocol_id = SMB21_PROT_ID,
@ -38,6 +67,7 @@ static struct smb_version_values smb21_server_values = {
.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,
.create_rsp_size = SMB21_CREATE_RSP_SIZE,
};
static struct smb_version_values smb30_server_values = {
@ -66,6 +96,7 @@ static struct smb_version_values smb30_server_values = {
.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,
.create_rsp_size = SMB3_CREATE_RSP_SIZE,
};
static struct smb_version_values smb302_server_values = {
@ -94,6 +125,7 @@ static struct smb_version_values smb302_server_values = {
.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,
.create_rsp_size = SMB3_CREATE_RSP_SIZE,
};
static struct smb_version_values smb311_server_values = {
@ -122,6 +154,7 @@ static struct smb_version_values smb311_server_values = {
.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,
.create_rsp_size = SMB311_CREATE_RSP_SIZE,
};
static struct smb_version_ops smb2_0_server_ops = {

File diff suppressed because it is too large Load Diff

View File

@ -156,8 +156,7 @@ struct create_durable_rsp {
} __packed;
/*
* See POSIX-SMB2 2.2.14.2.16
* Link: https://gitlab.com/samba-team/smb3-posix-spec/-/blob/master/smb3_posix_extensions.md
* See POSIX-SMB2 2.1.3.2.1
*/
struct create_posix_rsp {
struct create_context_hdr ccontext;
@ -169,6 +168,8 @@ struct create_posix_rsp {
u8 SidBuffer[44];
} __packed;
__le32 smb2_get_reparse_tag_special_file(umode_t mode);
#define SMB2_0_IOCTL_IS_FSCTL 0x00000001
struct sockaddr_storage_rsp {
@ -199,7 +200,7 @@ struct file_sparse {
#define FILE_INTERNAL_INFORMATION_SIZE 8
#define FILE_EA_INFORMATION_SIZE 4
#define FILE_ACCESS_INFORMATION_SIZE 4
#define FILE_NAME_INFORMATION_SIZE 9
#define FILE_NAME_INFORMATION_SIZE 8
#define FILE_RENAME_INFORMATION_SIZE 10
#define FILE_LINK_INFORMATION_SIZE 11
#define FILE_NAMES_INFORMATION_SIZE 12
@ -211,8 +212,6 @@ struct file_sparse {
#define FILE_ALL_INFORMATION_SIZE 104
#define FILE_ALLOCATION_INFORMATION_SIZE 19
#define FILE_END_OF_FILE_INFORMATION_SIZE 20
#define FILE_ALTERNATE_NAME_INFORMATION_SIZE 8
#define FILE_NORMALIZED_NAME_INFORMATION_SIZE 8
#define FILE_STREAM_INFORMATION_SIZE 32
#define FILE_PIPE_INFORMATION_SIZE 23
#define FILE_PIPE_LOCAL_INFORMATION_SIZE 24
@ -259,7 +258,7 @@ struct smb2_file_alignment_info {
__le32 AlignmentRequirement;
} __packed;
struct smb2_file_alt_name_info {
struct smb2_file_name_info {
__le32 FileNameLength;
char FileName[];
} __packed;
@ -351,6 +350,7 @@ struct create_sd_buf_req {
struct smb_ntsd ntsd;
} __packed;
/* See POSIX-FSCC 2.2.1 */
struct smb2_posix_info {
__le32 NextEntryOffset;
__u32 Ignored;
@ -364,20 +364,18 @@ struct smb2_posix_info {
__le64 Inode;
__le32 DeviceId;
__le32 Zero;
/* beginning of POSIX Create Context Response */
/*
* Beginning of POSIX Create Context Response
* See POSIX-SMB2 2.1.3.2.1
*/
__le32 HardLinks;
__le32 ReparseTag;
__le32 Mode;
/* SidBuffer contain two sids (UNIX user sid(16), UNIX group sid(16)) */
u8 SidBuffer[32];
/* End of POSIX Create Context Response */
__le32 name_len;
u8 name[];
/*
* var sized owner SID
* var sized group SID
* le32 filenamelength
* u8 filename[]
*/
} __packed;
/* functions */
@ -397,6 +395,7 @@ bool is_smb2_rsp(struct ksmbd_work *work);
u16 get_smb2_cmd_val(struct ksmbd_work *work);
void set_smb2_rsp_status(struct ksmbd_work *work, __le32 err);
int init_smb2_rsp_hdr(struct ksmbd_work *work);
bool smb2_check_compound_fid(struct ksmbd_work *work);
int smb2_allocate_rsp_buf(struct ksmbd_work *work);
bool is_chained_smb2_message(struct ksmbd_work *work);
int init_smb2_neg_rsp(struct ksmbd_work *work);
@ -420,6 +419,7 @@ struct channel *lookup_chann_list(struct ksmbd_session *sess,
void smb3_preauth_hash_rsp(struct ksmbd_work *work);
bool smb3_is_transform_hdr(void *buf);
int smb3_decrypt_req(struct ksmbd_work *work);
int ksmbd_check_transform_session(struct ksmbd_work *work, u64 tr_sess_id);
int smb3_encrypt_resp(struct ksmbd_work *work);
bool smb3_11_final_sess_setup_resp(struct ksmbd_work *work);
int smb2_set_rsp_credits(struct ksmbd_work *work);

View File

@ -478,16 +478,17 @@ int ksmbd_populate_dot_dotdot_entries(struct ksmbd_work *work, int info_level,
if (i == 0) {
d_info->name = ".";
d_info->name_len = 1;
dentry = dir->filp->f_path.dentry;
dentry = dget(dir->filp->f_path.dentry);
} else {
d_info->name = "..";
d_info->name_len = 2;
dentry = dir->filp->f_path.dentry->d_parent;
dentry = dget_parent(dir->filp->f_path.dentry);
}
if (!match_pattern(d_info->name, d_info->name_len,
search_pattern)) {
dir->dot_dotdot[i] = 1;
dput(dentry);
continue;
}
@ -496,6 +497,7 @@ int ksmbd_populate_dot_dotdot_entries(struct ksmbd_work *work, int info_level,
idmap,
dentry,
&ksmbd_kstat);
dput(dentry);
if (rc)
break;
@ -523,8 +525,6 @@ int ksmbd_populate_dot_dotdot_entries(struct ksmbd_work *work, int info_level,
* @shortname: destination short filename
*
* Return: shortname length or 0 when source long name is '.' or '..'
* TODO: Though this function conforms the restriction of 8.3 Filename spec,
* but the result is different with Windows 7's one. need to check.
*/
int ksmbd_extract_shortname(struct ksmbd_conn *conn, const char *longname,
char *shortname)
@ -588,7 +588,7 @@ int ksmbd_extract_shortname(struct ksmbd_conn *conn, const char *longname,
if (dot_present)
memcpy(out + baselen + 4, extension, 4);
else
out[baselen + 4] = '\0';
out[baselen + 3] = '\0';
smbConvertToUTF16((__le16 *)shortname, out, PATH_MAX,
conn->local_nls, 0);
len = strlen(out) * 2;

View File

@ -133,6 +133,104 @@ struct file_id_both_directory_info {
char FileName[];
} __packed;
/* MS-FSCC 2.4.23 FileIdExtdDirectoryInformation */
struct file_id_extd_directory_info {
__le32 NextEntryOffset;
__u32 FileIndex;
__le64 CreationTime;
__le64 LastAccessTime;
__le64 LastWriteTime;
__le64 ChangeTime;
__le64 EndOfFile;
__le64 AllocationSize;
__le32 ExtFileAttributes;
__le32 FileNameLength;
__le32 EaSize;
__le32 ReparsePointTag;
__u8 FileId[16];
char FileName[];
} __packed;
/* MS-FSCC 2.4.19 FileId64ExtdDirectoryInformation */
struct file_id_64_extd_directory_info {
__le32 NextEntryOffset;
__u32 FileIndex;
__le64 CreationTime;
__le64 LastAccessTime;
__le64 LastWriteTime;
__le64 ChangeTime;
__le64 EndOfFile;
__le64 AllocationSize;
__le32 ExtFileAttributes;
__le32 FileNameLength;
__le32 EaSize;
__le32 ReparsePointTag;
__le64 FileId;
char FileName[];
} __packed;
/* MS-FSCC 2.4.18 FileId64ExtdBothDirectoryInformation */
struct file_id_64_extd_both_directory_info {
__le32 NextEntryOffset;
__u32 FileIndex;
__le64 CreationTime;
__le64 LastAccessTime;
__le64 LastWriteTime;
__le64 ChangeTime;
__le64 EndOfFile;
__le64 AllocationSize;
__le32 ExtFileAttributes;
__le32 FileNameLength;
__le32 EaSize;
__le32 ReparsePointTag;
__le64 FileId;
__u8 ShortNameLength;
__u8 Reserved1;
__u8 ShortName[24];
char FileName[];
} __packed;
/* MS-FSCC 2.4.21 FileIdAllExtdDirectoryInformation */
struct file_id_all_extd_directory_info {
__le32 NextEntryOffset;
__u32 FileIndex;
__le64 CreationTime;
__le64 LastAccessTime;
__le64 LastWriteTime;
__le64 ChangeTime;
__le64 EndOfFile;
__le64 AllocationSize;
__le32 ExtFileAttributes;
__le32 FileNameLength;
__le32 EaSize;
__le32 ReparsePointTag;
__le64 FileId;
__u8 FileId128[16];
char FileName[];
} __packed;
/* MS-FSCC 2.4.20 FileIdAllExtdBothDirectoryInformation */
struct file_id_all_extd_both_directory_info {
__le32 NextEntryOffset;
__u32 FileIndex;
__le64 CreationTime;
__le64 LastAccessTime;
__le64 LastWriteTime;
__le64 ChangeTime;
__le64 EndOfFile;
__le64 AllocationSize;
__le32 ExtFileAttributes;
__le32 FileNameLength;
__le32 EaSize;
__le32 ReparsePointTag;
__le64 FileId;
__u8 FileId128[16];
__u8 ShortNameLength;
__u8 Reserved1;
__u8 ShortName[24];
char FileName[];
} __packed;
struct smb_version_ops {
u16 (*get_cmd_val)(struct ksmbd_work *swork);
void (*inc_reqs)(unsigned int cmd, __le32 status);

View File

@ -7,6 +7,7 @@
*/
#include <linux/fs.h>
#include <kunit/visibility.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/mnt_idmapping.h>
@ -476,10 +477,12 @@ static int parse_dacl(const struct mnt_idmap *idmap,
ppace[i]->access_req =
smb_map_generic_desired_access(ppace[i]->access_req);
if (ppace[i]->sid.num_subauth >= 3 &&
if (ppace[i]->type == ACCESS_ALLOWED &&
ppace[i]->sid.num_subauth >= 3 &&
!(compare_sids(&ppace[i]->sid, &sid_unix_NFS_mode))) {
fattr->cf_mode =
le32_to_cpu(ppace[i]->sid.sub_auth[2]);
fattr->cf_posix_mode = true;
break;
} else if (!compare_sids(&ppace[i]->sid, pownersid)) {
acl_mode = access_flags_to_mode(fattr,
@ -545,6 +548,10 @@ static int parse_dacl(const struct mnt_idmap *idmap,
((acl_mode & 0700) >> 6) | 0004;
}
}
if (fattr->cf_posix_mode) {
ret = 0;
goto out;
}
if (owner_found) {
/* The owner must be set to at least read-only. */
@ -899,7 +906,7 @@ int parse_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
int acl_len, struct smb_fattr *fattr)
{
int rc = 0;
struct smb_sid *owner_sid_ptr, *group_sid_ptr;
struct smb_sid *owner_sid_ptr = NULL, *group_sid_ptr = NULL;
struct smb_acl *dacl_ptr; /* no need for SACL ptr */
char *end_of_acl = ((char *)pntsd) + acl_len;
__u32 dacloffset;
@ -911,12 +918,7 @@ int parse_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
if (acl_len < sizeof(struct smb_ntsd))
return -EINVAL;
owner_sid_ptr = (struct smb_sid *)((char *)pntsd +
le32_to_cpu(pntsd->osidoffset));
group_sid_ptr = (struct smb_sid *)((char *)pntsd +
le32_to_cpu(pntsd->gsidoffset));
dacloffset = le32_to_cpu(pntsd->dacloffset);
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
ksmbd_debug(SMB,
"revision %d type 0x%x ooffset 0x%x goffset 0x%x sacloffset 0x%x dacloffset 0x%x\n",
pntsd->revision, pntsd->type, le32_to_cpu(pntsd->osidoffset),
@ -932,9 +934,12 @@ int parse_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
pntsd->type = cpu_to_le16(DACL_PRESENT);
if (pntsd->osidoffset) {
if (le32_to_cpu(pntsd->osidoffset) < sizeof(struct smb_ntsd))
if (le32_to_cpu(pntsd->osidoffset) < sizeof(struct smb_ntsd) ||
le32_to_cpu(pntsd->osidoffset) >= acl_len)
return -EINVAL;
owner_sid_ptr = (struct smb_sid *)((char *)pntsd +
le32_to_cpu(pntsd->osidoffset));
rc = parse_sid(owner_sid_ptr, end_of_acl);
if (rc) {
pr_err("%s: Error %d parsing Owner SID\n", __func__, rc);
@ -950,9 +955,12 @@ int parse_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
}
if (pntsd->gsidoffset) {
if (le32_to_cpu(pntsd->gsidoffset) < sizeof(struct smb_ntsd))
if (le32_to_cpu(pntsd->gsidoffset) < sizeof(struct smb_ntsd) ||
le32_to_cpu(pntsd->gsidoffset) >= acl_len)
return -EINVAL;
group_sid_ptr = (struct smb_sid *)((char *)pntsd +
le32_to_cpu(pntsd->gsidoffset));
rc = parse_sid(group_sid_ptr, end_of_acl);
if (rc) {
pr_err("%s: Error %d mapping Owner SID to gid\n",
@ -974,9 +982,10 @@ int parse_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
pntsd->type |= cpu_to_le16(DACL_PROTECTED);
if (dacloffset) {
if (dacloffset < sizeof(struct smb_ntsd))
if (dacloffset < sizeof(struct smb_ntsd) || dacloffset >= acl_len)
return -EINVAL;
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
rc = parse_dacl(idmap, dacl_ptr, end_of_acl,
owner_sid_ptr, group_sid_ptr, fattr);
if (rc)
@ -1096,9 +1105,12 @@ int build_sec_desc(const struct mnt_idmap *idmap,
struct smb_acl *ppdacl_ptr;
unsigned int dacl_offset = le32_to_cpu(ppntsd->dacloffset);
int ppdacl_size, ntacl_size = ppntsd_size - dacl_offset;
size_t dacl_struct_end;
if (!dacl_offset ||
(dacl_offset + sizeof(struct smb_acl) > ppntsd_size))
check_add_overflow(dacl_offset, sizeof(struct smb_acl),
&dacl_struct_end) ||
dacl_struct_end > (size_t)ppntsd_size)
goto out;
ppdacl_ptr = (struct smb_acl *)((char *)ppntsd + dacl_offset);
@ -1490,21 +1502,27 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
if (pdacl_size > acl_size || pdacl_size < sizeof(struct smb_acl))
goto err_out;
if (!pdacl->num_aces) {
if (!(pdacl_size - sizeof(struct smb_acl)) &&
*pdaccess & ~(FILE_READ_CONTROL_LE | FILE_WRITE_DAC_LE)) {
rc = -EACCES;
goto err_out;
}
goto err_out;
}
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 (!pdacl->num_aces) {
/*
* An empty (present, zero-ACE) DACL grants no access to
* anyone except the object owner's implicit READ_CONTROL
* and WRITE_DAC (MS-DTYP 2.4.5). Deny every other caller,
* deny the owner any access beyond those two bits, and do
* not let trailing bytes after a zero-ACE DACL become an
* implicit grant.
*/
if (!is_owner ||
(*pdaccess & ~(FILE_READ_CONTROL_LE | FILE_WRITE_DAC_LE)))
rc = -EACCES;
goto err_out;
}
if (*pdaccess & FILE_MAXIMAL_ACCESS_LE) {
ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
aces_size = pdacl_size - sizeof(struct smb_acl);
@ -1665,10 +1683,12 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
kfree(pntsd);
return rc;
}
EXPORT_SYMBOL_IF_KUNIT(smb_check_perm_dacl);
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)
bool type_check, bool get_write, bool posix_open,
bool dacl_requested)
{
int rc;
struct smb_fattr fattr = {{0}};
@ -1683,6 +1703,14 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
rc = parse_sec_desc(idmap, pntsd, ntsd_len, &fattr);
if (rc)
goto out;
if (fattr.cf_posix_mode && !dacl_requested) {
fattr.cf_posix_mode = false;
fattr.cf_mode = inode->i_mode;
}
if (fattr.cf_posix_mode && !posix_open) {
rc = -EOPNOTSUPP;
goto out;
}
newattrs.ia_valid = ATTR_CTIME;
if (!uid_eq(fattr.cf_uid, INVALID_UID)) {
@ -1694,7 +1722,12 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
newattrs.ia_gid = fattr.cf_gid;
}
newattrs.ia_valid |= ATTR_MODE;
newattrs.ia_mode = (inode->i_mode & ~0777) | (fattr.cf_mode & 0777);
if (fattr.cf_posix_mode)
newattrs.ia_mode = (inode->i_mode & ~07777) |
(fattr.cf_mode & 07777);
else
newattrs.ia_mode = (inode->i_mode & ~0777) |
(fattr.cf_mode & 0777);
ksmbd_vfs_remove_acl_xattrs(idmap, path);
/* Update posix acls */

View File

@ -43,6 +43,7 @@ struct smb_fattr {
kuid_t cf_uid;
kgid_t cf_gid;
umode_t cf_mode;
bool cf_posix_mode;
__le32 daccess;
struct posix_acl *cf_acls;
struct posix_acl *cf_dacls;
@ -99,7 +100,8 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
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);
bool type_check, bool get_write, bool posix_open,
bool dacl_requested);
void id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid);
void ksmbd_init_domain(u32 *sub_auth);
size_t smb_acl_sec_desc_scratch_len(struct smb_fattr *fattr,

View File

@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2026 Hang Nan <nanx95726@gmail.com>
config SMB_SERVER_KUNIT_TESTS
tristate "KUnit tests for SMB3 server helpers" if !KUNIT_ALL_TESTS
depends on SMB_SERVER && SMB_KUNIT_TESTS && TMPFS_XATTR
default SMB_KUNIT_TESTS
help
This builds the KUnit tests for ksmbd server helpers. The tests
exercise internal server functionality and help detect regressions
in server-side behavior. They are intended for kernel developers
and are not suitable for production systems.
For more information on KUnit and unit tests in the kernel,
please read Documentation/dev-tools/kunit/index.rst.

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2026 Hang Nan <nanx95726@gmail.com>
obj-$(CONFIG_SMB_SERVER_KUNIT_TESTS) += smbacl_kunit.o

View File

@ -0,0 +1,301 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* KUnit tests for ksmbd security descriptor (DACL) handling.
*
* Copyright (C) 2026 Hang Nan <nanx95726@gmail.com>
*
* The tests pin the DACL declared-size boundary in smb_check_perm_dacl():
*
* - ksmbd_dacl_walk_must_stop_at_declared_size: a pure semantic harness
* that models the ACE walk. Walking to the end of the enclosing
* security descriptor (the pre-fix behaviour) selects an ACE that
* sits beyond struct smb_acl::size; stopping at the declared DACL
* size (the fixed behaviour) rejects it.
*
* - ksmbd_smb_check_perm_dacl_boundary and
* ksmbd_smb_check_perm_dacl_maximal_boundary: drive the real
* smb_check_perm_dacl() with a descriptor stored through ksmbd's own
* NTACL xattr path on a tmpfs file, and assert that a post-boundary
* ACE is not selected for either a regular or maximal access check.
*/
#include <kunit/test.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/shmem_fs.h>
#include <linux/slab.h>
#include "../smbacl.h"
#include "../smb_common.h"
#include "../vfs.h"
struct ksmbd_acl_walk_result {
bool found;
bool allowed;
const struct smb_ace *selected;
};
static const struct smb_sid test_nonmatching_sid = {
1, 5, {0, 0, 0, 0, 0, 5},
{ cpu_to_le32(21), cpu_to_le32(1), cpu_to_le32(2),
cpu_to_le32(3), cpu_to_le32(9999) }
};
/*
* S-1-22-1-0: the SID id_to_sid(0, SIDUNIX_USER) resolves to, i.e. what
* smb_check_perm_dacl() looks for when called with uid == 0.
*/
static const struct smb_sid test_owner_sid = {
1, 2, {0, 0, 0, 0, 0, 22},
{ cpu_to_le32(1), cpu_to_le32(0) }
};
static int test_compare_sids(const struct smb_sid *a, const struct smb_sid *b)
{
int i;
if (a->revision != b->revision || a->num_subauth != b->num_subauth)
return 1;
for (i = 0; i < NUM_AUTHS; i++) {
if (a->authority[i] != b->authority[i])
return 1;
}
for (i = 0; i < a->num_subauth; i++) {
if (a->sub_auth[i] != b->sub_auth[i])
return 1;
}
return 0;
}
static u16 test_ace_size(const struct smb_sid *sid)
{
return offsetof(struct smb_ace, sid) + CIFS_SID_BASE_SIZE +
sid->num_subauth * sizeof(__le32);
}
static u16 fill_test_ace(struct smb_ace *ace, const struct smb_sid *sid,
u32 access_req)
{
u16 size = test_ace_size(sid);
ace->type = ACCESS_ALLOWED_ACE_TYPE;
ace->flags = 0;
ace->size = cpu_to_le16(size);
ace->access_req = cpu_to_le32(access_req);
memcpy(&ace->sid, sid, size - offsetof(struct smb_ace, sid));
return size;
}
static struct ksmbd_acl_walk_result test_walk_dacl(struct smb_acl *pdacl,
int walk_boundary,
const struct smb_sid *target,
u32 requested)
{
struct ksmbd_acl_walk_result result = {};
struct smb_ace *ace;
int aces_size;
int i;
ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
aces_size = walk_boundary - sizeof(struct smb_acl);
for (i = 0; i < le16_to_cpu(pdacl->num_aces); i++) {
u16 ace_size;
if (aces_size < offsetof(struct smb_ace, sid) + CIFS_SID_BASE_SIZE)
break;
ace_size = le16_to_cpu(ace->size);
if (ace_size > aces_size ||
ace_size < offsetof(struct smb_ace, sid) + CIFS_SID_BASE_SIZE)
break;
aces_size -= ace_size;
if (ace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
ace_size < offsetof(struct smb_ace, sid) + CIFS_SID_BASE_SIZE +
sizeof(__le32) * ace->sid.num_subauth)
break;
if (!test_compare_sids(target, &ace->sid)) {
result.found = true;
result.selected = ace;
result.allowed = !(requested & ~le32_to_cpu(ace->access_req));
return result;
}
ace = (struct smb_ace *)((char *)ace + ace_size);
}
return result;
}
static void ksmbd_dacl_walk_must_stop_at_declared_size(struct kunit *test)
{
struct ksmbd_acl_walk_result declared, enclosing;
struct smb_acl *acl;
struct smb_ace *ace1, *fake;
u16 ace1_size, fake_size;
u16 pdacl_size;
u16 acl_size;
acl = kunit_kzalloc(test, 128, GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, acl);
acl->revision = cpu_to_le16(2);
acl->num_aces = cpu_to_le16(2);
ace1 = (struct smb_ace *)((char *)acl + sizeof(*acl));
ace1_size = fill_test_ace(ace1, &test_nonmatching_sid, 0);
fake = (struct smb_ace *)((char *)ace1 + ace1_size);
fake_size = fill_test_ace(fake, &test_owner_sid, FILE_READ_DATA);
pdacl_size = sizeof(*acl) + ace1_size;
acl_size = pdacl_size + fake_size;
acl->size = cpu_to_le16(pdacl_size);
declared = test_walk_dacl(acl, pdacl_size, &test_owner_sid,
FILE_READ_DATA);
enclosing = test_walk_dacl(acl, acl_size, &test_owner_sid,
FILE_READ_DATA);
KUNIT_EXPECT_FALSE(test, declared.found);
KUNIT_EXPECT_FALSE(test, declared.allowed);
/* Demonstrates that the buggy acl_size boundary selects fake ACE #2. */
KUNIT_EXPECT_TRUE(test, enclosing.found);
KUNIT_EXPECT_TRUE(test, enclosing.allowed);
}
/*
* Build an NTSD whose DACL declares one ACE (pdacl->size) but actually
* contains two: the second ACE sits beyond the declared DACL boundary
* yet inside the enclosing security descriptor. The trailing ACE applies
* to S-1-22-1-0, which smb_check_perm_dacl() looks for when uid is zero.
*/
static struct smb_ntsd *build_boundary_ntsd(struct kunit *test,
const struct smb_sid *first_sid,
u32 first_access,
u32 trailing_access,
int *ntsd_size)
{
struct smb_ntsd *pntsd;
struct smb_acl *pdacl;
struct smb_ace *ace;
u16 first_size = test_ace_size(first_sid);
u16 trailing_size = test_ace_size(&test_owner_sid);
*ntsd_size = sizeof(struct smb_ntsd) + sizeof(struct smb_acl) +
first_size + trailing_size;
pntsd = kunit_kzalloc(test, *ntsd_size, GFP_KERNEL);
if (!pntsd)
return NULL;
pntsd->revision = cpu_to_le16(SD_REVISION);
pntsd->type = cpu_to_le16(DACL_PRESENT);
pntsd->dacloffset = cpu_to_le32(sizeof(struct smb_ntsd));
pdacl = (struct smb_acl *)((char *)pntsd + sizeof(struct smb_ntsd));
pdacl->revision = cpu_to_le16(2);
pdacl->num_aces = cpu_to_le16(2);
pdacl->size = cpu_to_le16(sizeof(struct smb_acl) + first_size);
ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
fill_test_ace(ace, first_sid, first_access);
ace = (struct smb_ace *)((char *)ace + first_size);
fill_test_ace(ace, &test_owner_sid, trailing_access);
return pntsd;
}
static void ksmbd_smb_check_perm_dacl_boundary_test(struct kunit *test)
{
struct file *file;
struct smb_ntsd *pntsd;
__le32 daccess = cpu_to_le32(FILE_READ_DATA);
int ntsd_size, rc;
pntsd = build_boundary_ntsd(test, &test_nonmatching_sid, 0,
FILE_READ_DATA, &ntsd_size);
KUNIT_ASSERT_NOT_NULL(test, pntsd);
file = shmem_file_setup("ksmbd-kunit-dacl", 0,
mk_vma_flags(VMA_NORESERVE_BIT));
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, file);
rc = ksmbd_vfs_set_sd_xattr(NULL, mnt_idmap(file->f_path.mnt),
&file->f_path, pntsd, ntsd_size,
false);
KUNIT_EXPECT_EQ(test, 0, rc);
if (rc)
goto out;
rc = smb_check_perm_dacl(NULL, &file->f_path, &daccess,
cpu_to_le32(FILE_READ_DATA), 0, false);
/*
* The post-boundary ACE (ACE #2, beyond pdacl->size) grants
* FILE_READ_DATA to the caller's SID, but it must not be
* selected: the walk stops at the declared DACL size and access
* is denied. Before the fix the walk used the enclosing
* descriptor length, selected ACE #2 and returned 0.
*/
KUNIT_EXPECT_EQ(test, -EACCES, rc);
out:
fput(file);
}
static void
ksmbd_smb_check_perm_dacl_maximal_boundary_test(struct kunit *test)
{
struct file *file;
struct smb_ntsd *pntsd;
__le32 daccess = FILE_MAXIMAL_ACCESS_LE;
int ntsd_size, rc;
/*
* The in-boundary ACE grants read access. The trailing ACE grants
* write access, which must not be included in the maximal access mask.
*/
pntsd = build_boundary_ntsd(test, &test_owner_sid, FILE_READ_DATA,
FILE_WRITE_DATA, &ntsd_size);
KUNIT_ASSERT_NOT_NULL(test, pntsd);
file = shmem_file_setup("ksmbd-kunit-dacl-maximal", 0,
mk_vma_flags(VMA_NORESERVE_BIT));
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, file);
rc = ksmbd_vfs_set_sd_xattr(NULL, mnt_idmap(file->f_path.mnt),
&file->f_path, pntsd, ntsd_size,
false);
KUNIT_EXPECT_EQ(test, 0, rc);
if (rc)
goto out;
rc = smb_check_perm_dacl(NULL, &file->f_path, &daccess,
FILE_MAXIMAL_ACCESS_LE, 0, false);
KUNIT_EXPECT_EQ(test, 0, rc);
if (rc)
goto out;
KUNIT_EXPECT_TRUE(test, le32_to_cpu(daccess) & FILE_READ_DATA);
KUNIT_EXPECT_FALSE(test, le32_to_cpu(daccess) & FILE_WRITE_DATA);
out:
fput(file);
}
static struct kunit_case ksmbd_smbacl_test_cases[] = {
KUNIT_CASE(ksmbd_dacl_walk_must_stop_at_declared_size),
KUNIT_CASE(ksmbd_smb_check_perm_dacl_boundary_test),
KUNIT_CASE(ksmbd_smb_check_perm_dacl_maximal_boundary_test),
{}
};
static struct kunit_suite ksmbd_smbacl_test_suite = {
.name = "ksmbd-smbacl",
.test_cases = ksmbd_smbacl_test_cases,
};
kunit_test_suite(ksmbd_smbacl_test_suite);
MODULE_DESCRIPTION("KUnit tests for ksmbd smbacl helpers");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");

View File

@ -353,6 +353,13 @@ static int ipc_server_config_on_startup(struct ksmbd_startup_request *req)
if (req->max_ip_connections)
server_conf.max_ip_connections = req->max_ip_connections;
if (req->max_sessions_per_connection)
server_conf.max_sessions_per_connection =
req->max_sessions_per_connection;
else
server_conf.max_sessions_per_connection =
KSMBD_DEFAULT_MAX_SESSIONS_PER_CONNECTION;
ret = ksmbd_set_netbios_name(req->netbios_name);
ret |= ksmbd_set_server_string(req->server_string);
ret |= ksmbd_set_work_group(req->work_group);
@ -532,11 +539,25 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
if (entry->msg_sz < sizeof(struct ksmbd_share_config_response))
return -EINVAL;
/*
* ksmbd-tools releases before 3.5.2 leave payload_sz zero.
* Derive it from the size of the message actually received so
* the checks below bound the payload for those releases too.
*/
if (!resp->payload_sz)
resp->payload_sz = entry->msg_sz -
sizeof(struct ksmbd_share_config_response);
if (strnlen(resp->share_name, sizeof(resp->share_name)) ==
sizeof(resp->share_name))
return -EINVAL;
if (resp->veto_list_sz > resp->payload_sz)
/*
* Pipe shares carry no veto list, but ksmbd-tools before 3.5.3
* copy a global "veto files" setting into the IPC$ response.
*/
if (!(resp->flags & KSMBD_SHARE_FLAG_PIPE) &&
resp->veto_list_sz > resp->payload_sz)
return -EINVAL;
if (resp->flags != KSMBD_SHARE_FLAG_INVALID &&

View File

@ -5,6 +5,7 @@
*/
#include <linux/freezer.h>
#include <linux/uio.h>
#include "smb_common.h"
#include "server.h"
@ -29,11 +30,30 @@ static LIST_HEAD(iface_list);
static int bind_additional_ifaces;
struct tcp_tx {
struct list_head list;
size_t len;
size_t offset;
unsigned long deadline;
int msg_flags;
u8 data[];
};
struct tcp_transport {
struct ksmbd_transport transport;
struct socket *sock;
struct kvec *iov;
unsigned int nr_iov;
/* Serializes queue access and socket writes. Never taken by callbacks. */
struct mutex tx_lock;
struct list_head tx_queue;
struct delayed_work tx_work;
size_t tx_queued;
/* TCP byte positions for accepted PDUs and bytes passed to the socket. */
u64 tx_end;
u64 tx_sent;
bool tx_stopped;
void (*write_space)(struct sock *sk);
};
static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops;
@ -42,6 +62,10 @@ static void tcp_stop_kthread(struct task_struct *kthread);
static void ksmbd_tcp_stop_listener(struct interface *iface);
static struct interface *alloc_iface(char *ifname);
static void ksmbd_tcp_disconnect(struct ksmbd_transport *t);
static void ksmbd_tcp_shutdown(struct ksmbd_transport *t);
static void ksmbd_tcp_tx_work(struct work_struct *work);
static void ksmbd_tcp_write_space(struct sock *sk);
static void ksmbd_tcp_stop_tx(struct tcp_transport *t);
#define KSMBD_TRANS(t) (&(t)->transport)
#define TCP_TRANS(t) ((struct tcp_transport *)container_of(t, \
@ -66,6 +90,9 @@ static struct tcp_transport *alloc_transport(struct socket *client_sk)
if (!t)
return NULL;
t->sock = client_sk;
mutex_init(&t->tx_lock);
INIT_LIST_HEAD(&t->tx_queue);
INIT_DELAYED_WORK(&t->tx_work, ksmbd_tcp_tx_work);
conn = ksmbd_conn_alloc();
if (!conn) {
@ -77,6 +104,7 @@ static struct tcp_transport *alloc_transport(struct socket *client_sk)
if (client_sk->sk->sk_family == AF_INET6) {
memcpy(&conn->inet6_addr, &client_sk->sk->sk_v6_daddr, 16);
conn->inet_hash = ipv6_addr_hash(&client_sk->sk->sk_v6_daddr);
conn->is_ipv6 = true;
} else {
conn->inet_addr = inet_sk(client_sk->sk)->inet_daddr;
conn->inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
@ -85,13 +113,26 @@ static struct tcp_transport *alloc_transport(struct socket *client_sk)
conn->inet_addr = inet_sk(client_sk->sk)->inet_daddr;
conn->inet_hash = ipv4_addr_hash(inet_sk(client_sk->sk)->inet_daddr);
#endif
down_write(&conn_list_lock);
hash_add(conn_list, &conn->hlist, conn->inet_hash);
up_write(&conn_list_lock);
conn->transport = KSMBD_TRANS(t);
KSMBD_TRANS(t)->conn = conn;
KSMBD_TRANS(t)->ops = &ksmbd_tcp_transport_ops;
lock_sock(client_sk->sk);
t->tx_end = tcp_sk(client_sk->sk)->bytes_acked +
(u32)(tcp_sk(client_sk->sk)->write_seq -
tcp_sk(client_sk->sk)->snd_una);
t->tx_sent = t->tx_end;
release_sock(client_sk->sk);
write_lock_bh(&client_sk->sk->sk_callback_lock);
t->write_space = client_sk->sk->sk_write_space;
client_sk->sk->sk_user_data = t;
client_sk->sk->sk_write_space = ksmbd_tcp_write_space;
write_unlock_bh(&client_sk->sk->sk_callback_lock);
down_write(&conn_list_lock);
hash_add(conn_list, &conn->hlist, conn->inet_hash);
up_write(&conn_list_lock);
return t;
}
@ -99,6 +140,7 @@ static void ksmbd_tcp_free_transport(struct ksmbd_transport *kt)
{
struct tcp_transport *t = TCP_TRANS(kt);
ksmbd_tcp_stop_tx(t);
sock_release(t->sock);
kfree(t->iov);
kfree(t);
@ -106,7 +148,8 @@ static void ksmbd_tcp_free_transport(struct ksmbd_transport *kt)
static void free_transport(struct tcp_transport *t)
{
kernel_sock_shutdown(t->sock, SHUT_RDWR);
ksmbd_tcp_shutdown(KSMBD_TRANS(t));
ksmbd_tcp_stop_tx(t);
ksmbd_conn_free(KSMBD_TRANS(t)->conn);
}
@ -431,15 +474,251 @@ 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 bool ksmbd_tcp_tx_has_space(struct tcp_transport *t, size_t size)
{
size_t limit = max_t(size_t, READ_ONCE(t->sock->sk->sk_sndbuf), size);
/* Allow one large response, but bound the retained response buffers. */
return READ_ONCE(t->tx_queued) <= limit - size;
}
static struct tcp_tx *ksmbd_tcp_alloc_tx(const struct ksmbd_transport_write *tx,
size_t offset)
{
struct tcp_tx *msg;
struct iov_iter iter;
size_t remaining = tx->size - offset;
msg = kvmalloc(struct_size(msg, data, remaining), KSMBD_DEFAULT_GFP);
if (!msg)
return NULL;
iov_iter_kvec(&iter, ITER_SOURCE, tx->iov, tx->iov_cnt, tx->size);
iov_iter_advance(&iter, offset);
if (!copy_from_iter_full(msg->data, remaining, &iter)) {
kvfree(msg);
return NULL;
}
msg->len = remaining;
msg->offset = 0;
msg->msg_flags = tx->msg_flags;
return msg;
}
static void ksmbd_tcp_purge_tx(struct tcp_transport *t)
{
struct tcp_tx *msg, *next;
list_for_each_entry_safe(msg, next, &t->tx_queue, list) {
list_del(&msg->list);
kvfree(msg);
}
WRITE_ONCE(t->tx_queued, 0);
}
static bool ksmbd_tcp_send_retry(int ret)
{
return ret == -EAGAIN || ret == -EINTR || ret == -ERESTARTSYS ||
ret == -ENOMEM;
}
static void ksmbd_tcp_tx_work(struct work_struct *work)
{
struct tcp_transport *t = container_of(to_delayed_work(work),
struct tcp_transport, tx_work);
unsigned long delay = 0;
unsigned int budget = 16;
bool failed = false;
int ret;
mutex_lock(&t->tx_lock);
while (!READ_ONCE(t->tx_stopped) && !list_empty(&t->tx_queue) && budget--) {
struct tcp_tx *msg = list_first_entry(&t->tx_queue, struct tcp_tx, list);
struct msghdr hdr = {
.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT | msg->msg_flags,
};
struct kvec iov = {
.iov_base = msg->data + msg->offset,
.iov_len = msg->len - msg->offset,
};
/* MSG_DONTWAIT bypasses sk_sndtimeo, including for retained PDUs. */
if (time_after_eq(jiffies, msg->deadline)) {
WRITE_ONCE(t->tx_stopped, true);
failed = true;
break;
}
ret = kernel_sendmsg(t->sock, &hdr, &iov, 1, iov.iov_len);
if (ret > 0) {
msg->offset += ret;
t->tx_sent += ret;
if (msg->offset == msg->len) {
WRITE_ONCE(t->tx_queued, t->tx_queued - msg->len);
list_del(&msg->list);
kvfree(msg);
continue;
}
} else if (!ksmbd_tcp_send_retry(ret)) {
WRITE_ONCE(t->tx_stopped, true);
failed = true;
break;
}
/*
* Write-space notifications normally resume the head of the queue.
* Also retry after memory pressure, which may produce no notification.
*/
delay = max_t(unsigned long, 1, HZ / 10);
break;
}
if (READ_ONCE(t->tx_stopped))
ksmbd_tcp_purge_tx(t);
else if (!list_empty(&t->tx_queue))
queue_delayed_work(system_dfl_wq, &t->tx_work, delay);
mutex_unlock(&t->tx_lock);
wake_up_all(&t->transport.conn->req_running_q);
if (failed)
ksmbd_conn_abort(t->transport.conn);
}
static void ksmbd_tcp_write_space(struct sock *sk)
{
struct tcp_transport *t;
read_lock_bh(&sk->sk_callback_lock);
t = sk->sk_user_data;
if (t) {
t->write_space(sk);
if (!READ_ONCE(t->tx_stopped) && sk_stream_is_writeable(sk))
mod_delayed_work(system_dfl_wq, &t->tx_work, 0);
}
read_unlock_bh(&sk->sk_callback_lock);
}
static void ksmbd_tcp_stop_tx(struct tcp_transport *t)
{
struct sock *sk = t->sock->sk;
WRITE_ONCE(t->tx_stopped, true);
write_lock_bh(&sk->sk_callback_lock);
if (sk->sk_user_data == t) {
sk->sk_write_space = t->write_space;
sk->sk_user_data = NULL;
}
write_unlock_bh(&sk->sk_callback_lock);
cancel_delayed_work_sync(&t->tx_work);
mutex_lock(&t->tx_lock);
ksmbd_tcp_purge_tx(t);
mutex_unlock(&t->tx_lock);
wake_up_all(&t->transport.conn->req_running_q);
}
static int ksmbd_tcp_writev(struct ksmbd_transport *t,
const struct ksmbd_transport_write *tx)
{
struct msghdr smb_msg = {
.msg_flags = MSG_NOSIGNAL | tx->msg_flags,
struct tcp_transport *tt = TCP_TRANS(t);
struct sock *sk = tt->sock->sk;
struct msghdr hdr = {
.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT | tx->msg_flags,
};
struct tcp_tx *msg;
unsigned long deadline = jiffies + KSMBD_TCP_SEND_TIMEOUT;
long timeout;
int sent = 0;
return kernel_sendmsg(TCP_TRANS(t)->sock, &smb_msg, tx->iov,
tx->iov_cnt, tx->size);
for (;;) {
if (tx->msg_flags & MSG_DONTWAIT) {
if (!mutex_trylock(&tt->tx_lock))
return -EAGAIN;
} else {
mutex_lock(&tt->tx_lock);
}
if (READ_ONCE(tt->tx_stopped) || ksmbd_conn_exiting(t->conn) ||
ksmbd_conn_releasing(t->conn)) {
mutex_unlock(&tt->tx_lock);
return -EPIPE;
}
if (ksmbd_tcp_tx_has_space(tt, tx->size))
break;
mutex_unlock(&tt->tx_lock);
if (tx->msg_flags & MSG_DONTWAIT)
return -EAGAIN;
timeout = deadline - jiffies;
if (time_after_eq(jiffies, deadline) ||
!wait_event_timeout(t->conn->req_running_q,
READ_ONCE(tt->tx_stopped) ||
ksmbd_conn_exiting(t->conn) ||
ksmbd_conn_releasing(t->conn) ||
ksmbd_tcp_tx_has_space(tt, tx->size),
timeout)) {
/* A queued PDU may already have a prefix in the socket. */
ksmbd_conn_abort(t->conn);
return -EAGAIN;
}
}
/* Avoid an extra copy when the socket accepts the whole PDU at once. */
if (list_empty(&tt->tx_queue)) {
sent = kernel_sendmsg(tt->sock, &hdr, tx->iov, tx->iov_cnt, tx->size);
if (sent > 0) {
tt->tx_sent += sent;
if (sent == tx->size)
goto accepted;
} else if (ksmbd_tcp_send_retry(sent)) {
sent = 0;
} else {
WRITE_ONCE(tt->tx_stopped, true);
mutex_unlock(&tt->tx_lock);
ksmbd_conn_abort(t->conn);
return sent ?: -EPIPE;
}
}
/* The request buffers can be freed as soon as writev returns. */
msg = ksmbd_tcp_alloc_tx(tx, sent);
if (!msg) {
if (sent)
WRITE_ONCE(tt->tx_stopped, true);
mutex_unlock(&tt->tx_lock);
if (sent)
ksmbd_conn_abort(t->conn);
return -ENOMEM;
}
msg->deadline = deadline;
list_add_tail(&msg->list, &tt->tx_queue);
WRITE_ONCE(tt->tx_queued, tt->tx_queued + msg->len);
queue_delayed_work(system_dfl_wq, &tt->tx_work, 0);
accepted:
tt->tx_end += tx->size;
if (tx->tracking) {
/* Include retained data, without retaining the caller's tracking object. */
tx->tracking->end = tt->tx_end;
lock_sock(sk);
tx->tracking->retry_after =
clamp_t(unsigned long, 6UL * inet_csk(sk)->icsk_rto,
msecs_to_jiffies(1200), 6 * HZ);
release_sock(sk);
tx->tracking->valid = true;
}
mutex_unlock(&tt->tx_lock);
return tx->size;
}
static bool ksmbd_tcp_is_acked(struct ksmbd_transport *t,
const struct ksmbd_tx_tracking *tracking)
{
struct tcp_transport *tt = TCP_TRANS(t);
struct sock *sk = tt->sock->sk;
bool acked;
mutex_lock(&tt->tx_lock);
lock_sock(sk);
acked = tt->tx_sent >= tracking->end &&
tcp_sk(sk)->bytes_acked >= tracking->end;
release_sock(sk);
mutex_unlock(&tt->tx_lock);
return acked;
}
static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
@ -451,7 +730,9 @@ static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
static void ksmbd_tcp_shutdown(struct ksmbd_transport *t)
{
WRITE_ONCE(TCP_TRANS(t)->tx_stopped, true);
kernel_sock_shutdown(TCP_TRANS(t)->sock, SHUT_RDWR);
wake_up_all(&t->conn->req_running_q);
}
static void tcp_destroy_socket(struct socket *ksmbd_socket)
@ -656,10 +937,15 @@ static void ksmbd_tcp_stop_listener(struct interface *iface)
void ksmbd_tcp_destroy(void)
{
struct interface *iface, *tmp;
LIST_HEAD(iface_list_to_free);
unregister_netdevice_notifier(&ksmbd_netdev_notifier);
list_for_each_entry_safe(iface, tmp, &iface_list, entry) {
rtnl_lock();
list_splice_init(&iface_list, &iface_list_to_free);
rtnl_unlock();
list_for_each_entry_safe(iface, tmp, &iface_list_to_free, entry) {
ksmbd_tcp_stop_listener(iface);
list_del(&iface->entry);
kfree(iface->name);
@ -715,6 +1001,7 @@ int ksmbd_tcp_set_interfaces(char *ifc_list, int ifc_list_sz)
static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops = {
.read = ksmbd_tcp_read,
.writev = ksmbd_tcp_writev,
.is_acked = ksmbd_tcp_is_acked,
.disconnect = ksmbd_tcp_disconnect,
.shutdown = ksmbd_tcp_shutdown,
.free_transport = ksmbd_tcp_free_transport,

View File

@ -5,6 +5,7 @@
*/
#include <crypto/sha2.h>
#include <kunit/visibility.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/fs_struct.h>
@ -61,6 +62,10 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
struct dentry *d;
if (pathname[0] == '\0') {
/* The share root must never be removed. */
if (for_remove)
return -EINVAL;
pathname = share_conf->path;
root_share_path = NULL;
} else {
@ -118,10 +123,11 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
void ksmbd_vfs_query_maximal_access(const struct mnt_idmap *idmap,
struct dentry *dentry, __le32 *daccess)
{
*daccess = cpu_to_le32(FILE_READ_ATTRIBUTES | READ_CONTROL);
*daccess = cpu_to_le32(FILE_READ_ATTRIBUTES | READ_CONTROL |
SYNCHRONIZE);
if (!inode_permission(idmap, d_inode(dentry), MAY_OPEN | MAY_WRITE))
*daccess |= cpu_to_le32(WRITE_DAC | WRITE_OWNER | SYNCHRONIZE |
*daccess |= cpu_to_le32(WRITE_DAC | WRITE_OWNER |
FILE_WRITE_DATA | FILE_APPEND_DATA |
FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES |
FILE_DELETE_CHILD);
@ -216,9 +222,9 @@ int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode)
return err;
}
static ssize_t ksmbd_vfs_getcasexattr(const struct mnt_idmap *idmap,
struct dentry *dentry, char *attr_name,
int attr_name_len, char **attr_value)
ssize_t ksmbd_vfs_getcasexattr(const struct mnt_idmap *idmap,
struct dentry *dentry, char *attr_name,
int attr_name_len, char **attr_value)
{
char *name, *xattr_list = NULL;
ssize_t value_len = -ENOENT, xattr_list_len;
@ -268,7 +274,7 @@ static int ksmbd_vfs_stream_read(struct ksmbd_file *fp, char *buf, loff_t *pos,
return (int)v_len;
if (v_len <= *pos) {
count = -EINVAL;
count = 0;
goto free_buf;
}
@ -360,7 +366,7 @@ int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
if (ksmbd_stream_fd(fp))
return ksmbd_vfs_stream_read(fp, rbuf, pos, count);
if (!work->tcon->posix_extensions) {
if (!fp->is_posix_ctxt) {
int ret;
ret = check_lock_range(filp, *pos, *pos + count - 1, READ);
@ -387,23 +393,21 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
const struct cred *saved_cred;
char *stream_buf = NULL, *wbuf;
const struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
size_t size;
size_t size, write_end;
ssize_t v_len;
int err = 0;
ksmbd_debug(VFS, "write stream data pos : %llu, count : %zd\n",
*pos, count);
if (*pos >= XATTR_SIZE_MAX) {
if (*pos < 0 || *pos >= XATTR_SIZE_MAX) {
pr_err("stream write position %lld is out of bounds\n", *pos);
return -EINVAL;
}
size = *pos + count;
if (size > XATTR_SIZE_MAX) {
size = XATTR_SIZE_MAX;
count = XATTR_SIZE_MAX - *pos;
}
if (count > XATTR_SIZE_MAX - *pos)
return -EFBIG;
write_end = *pos + count;
saved_cred = override_creds(fp->filp->f_cred);
v_len = ksmbd_vfs_getcasexattr(idmap,
@ -416,6 +420,8 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
err = v_len;
goto out_revert;
}
/* Preserve the tail of an existing stream on an in-place write. */
size = max_t(size_t, v_len, write_end);
if (v_len < size) {
wbuf = kvzalloc(size, KSMBD_DEFAULT_GFP);
@ -444,13 +450,69 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
if (err < 0)
goto out;
else
fp->stream.pos = size;
fp->stream.pos = write_end;
err = 0;
out:
kvfree(stream_buf);
return err;
}
/**
* ksmbd_vfs_stream_truncate() - change the length of a named stream
* @fp: stream file handle
* @newsize: new stream length
*
* Resize the stream xattr without changing the base inode size.
*
* Return: 0 on success, otherwise a negative error code
*/
int ksmbd_vfs_stream_truncate(struct ksmbd_file *fp, loff_t newsize)
{
const struct cred *saved_cred;
const struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
char *stream_buf = NULL, *new_buf = NULL;
ssize_t v_len;
int err;
if (newsize < 0)
return -EINVAL;
if (newsize > XATTR_SIZE_MAX)
return -EFBIG;
saved_cred = override_creds(fp->filp->f_cred);
v_len = ksmbd_vfs_getcasexattr(idmap, fp->filp->f_path.dentry,
fp->stream.name, fp->stream.size,
&stream_buf);
if (v_len < 0) {
err = v_len;
goto out;
}
if (v_len == newsize) {
err = 0;
goto out;
}
new_buf = stream_buf;
if (newsize > v_len) {
new_buf = kvzalloc(newsize, KSMBD_DEFAULT_GFP);
if (!new_buf) {
err = -ENOMEM;
goto out;
}
if (v_len)
memcpy(new_buf, stream_buf, v_len);
}
err = ksmbd_vfs_setxattr(idmap, &fp->filp->f_path, fp->stream.name,
new_buf, newsize, 0, true);
if (new_buf != stream_buf)
kvfree(new_buf);
out:
kvfree(stream_buf);
revert_creds(saved_cred);
return err;
}
/**
* ksmbd_vfs_write() - vfs helper for smb file write
* @work: work
@ -468,7 +530,6 @@ int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp,
ssize_t *written)
{
struct file *filp;
loff_t offset = *pos;
int err = 0;
if (work->conn->connection_type) {
@ -490,7 +551,7 @@ int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp,
goto out;
}
if (!work->tcon->posix_extensions) {
if (!fp->is_posix_ctxt) {
err = check_lock_range(filp, *pos, *pos + count - 1, WRITE);
if (err) {
pr_err("unable to write due to lock\n");
@ -515,7 +576,7 @@ int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp,
*written = err;
err = 0;
if (sync) {
err = vfs_fsync_range(filp, offset, offset + *written, 0);
err = vfs_fsync_range(filp, *pos - *written, *pos, 0);
if (err < 0)
pr_err("fsync failed for filename = %pD, err = %d\n",
fp->filp, err);
@ -841,8 +902,8 @@ ssize_t ksmbd_vfs_listxattr(struct dentry *dentry, char **list)
return size;
}
static ssize_t ksmbd_vfs_xattr_len(const struct mnt_idmap *idmap,
struct dentry *dentry, char *xattr_name)
ssize_t ksmbd_vfs_xattr_len(const struct mnt_idmap *idmap,
struct dentry *dentry, char *xattr_name)
{
return vfs_getxattr(idmap, dentry, xattr_name, NULL, 0);
}
@ -955,6 +1016,15 @@ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
char *zero_buf = NULL;
int err;
/*
* A directory handle can hold FILE_WRITE_DATA (the add-file right),
* so the caller access check does not exclude it. Writing zeroes to a
* directory makes no sense and kernel_write() would warn on the
* directory file (no FMODE_WRITE), so reject it here.
*/
if (S_ISDIR(file_inode(fp->filp)->i_mode))
return -EINVAL;
smb_break_all_levII_oplock(work, fp, 1);
if (!work->tcon->posix_extensions) {
loff_t size = i_size_read(file_inode(fp->filp));
@ -1671,6 +1741,7 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
kfree(def_smb_acl);
return rc;
}
EXPORT_SYMBOL_IF_KUNIT(ksmbd_vfs_set_sd_xattr);
int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
const struct mnt_idmap *idmap,
@ -1897,13 +1968,17 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
ssize_t ksmbd_vfs_casexattr_len(const struct mnt_idmap *idmap,
struct dentry *dentry, char *attr_name,
int attr_name_len)
int attr_name_len, char *actual_name)
{
char *name, *xattr_list = NULL;
ssize_t value_len = -ENOENT, xattr_list_len;
xattr_list_len = ksmbd_vfs_listxattr(dentry, &xattr_list);
if (xattr_list_len <= 0)
if (xattr_list_len < 0) {
value_len = xattr_list_len;
goto out;
}
if (!xattr_list_len)
goto out;
for (name = xattr_list; name - xattr_list < xattr_list_len;
@ -1911,8 +1986,13 @@ ssize_t ksmbd_vfs_casexattr_len(const struct mnt_idmap *idmap,
ksmbd_debug(VFS, "%s, len %zd\n", name, strlen(name));
if (strncasecmp(attr_name, name, attr_name_len))
continue;
if (actual_name && strlen(name) + 1 != attr_name_len)
continue;
value_len = ksmbd_vfs_xattr_len(idmap, dentry, name);
/* The caller provides attr_name_len bytes for the actual name. */
if (value_len >= 0 && actual_name)
memcpy(actual_name, name, attr_name_len);
break;
}
@ -2067,7 +2147,7 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
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);
src_fp->stream.name, src_fp->stream.size, NULL);
revert_creds(saved_cred);
if (src_file_size < 0)
return src_file_size;

View File

@ -83,6 +83,7 @@ int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp,
char *buf, size_t count, loff_t *pos, bool sync,
ssize_t *written);
int ksmbd_vfs_stream_truncate(struct ksmbd_file *fp, loff_t newsize);
int ksmbd_vfs_fsync(struct ksmbd_work *work, u64 fid, u64 p_id);
int ksmbd_vfs_remove_file(struct ksmbd_work *work, const struct path *path);
int ksmbd_vfs_link(struct ksmbd_work *work,
@ -108,9 +109,14 @@ ssize_t ksmbd_vfs_getxattr(const struct mnt_idmap *idmap,
struct dentry *dentry,
char *xattr_name,
char **xattr_buf);
ssize_t ksmbd_vfs_xattr_len(const struct mnt_idmap *idmap,
struct dentry *dentry, char *xattr_name);
ssize_t ksmbd_vfs_getcasexattr(const struct mnt_idmap *idmap,
struct dentry *dentry, char *attr_name,
int attr_name_len, char **attr_value);
ssize_t ksmbd_vfs_casexattr_len(const struct mnt_idmap *idmap,
struct dentry *dentry, char *attr_name,
int attr_name_len);
int attr_name_len, char *actual_name);
int ksmbd_vfs_setxattr(const struct mnt_idmap *idmap,
const struct path *path, const char *attr_name,
void *attr_value, size_t attr_size, int flags,

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
#include <linux/rwsem.h>
#include <linux/spinlock.h>
#include <linux/idr.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include "vfs.h"
@ -34,20 +35,31 @@
struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_file;
struct ksmbd_lock {
struct file_lock *fl;
struct ksmbd_conn *conn;
struct list_head clist;
struct hlist_node hlist;
struct list_head flist;
struct list_head llist;
unsigned int flags;
int cmd;
int zero_len;
bool virtual_lock;
bool vfs_locked;
bool pending;
unsigned long long start;
unsigned long long end;
};
int ksmbd_file_lock_check(struct ksmbd_lock *lock);
void ksmbd_file_lock_add(struct ksmbd_file *fp, struct list_head *locks);
bool ksmbd_file_lock_conflict(struct ksmbd_lock *lock);
int ksmbd_file_lock_reserve(struct ksmbd_file *fp, struct ksmbd_lock *lock);
void ksmbd_file_lock_remove(struct ksmbd_lock *lock);
int ksmbd_file_lock_unlock(struct ksmbd_lock *lock);
extern wait_queue_head_t ksmbd_lock_wait;
struct stream {
char *name;
ssize_t size;
@ -102,6 +114,8 @@ struct ksmbd_file {
struct oplock_info __rcu *f_opinfo;
struct ksmbd_conn *conn;
struct ksmbd_tree_connect *tcon;
/* Own a share reference independently of tcon; rebind under f_lock. */
struct ksmbd_share_config *share_conf;
atomic_t refcount;
__le32 daccess;
@ -157,17 +171,12 @@ struct ksmbd_file {
bool durable_replay_consumed;
bool is_posix_ctxt;
bool posix_append;
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,
@ -213,8 +222,12 @@ 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);
bool ksmbd_app_instance_id_matches(struct ksmbd_work *work,
struct ksmbd_file *fp);
struct ksmbd_file *
ksmbd_lookup_fd_app_instance_id_next(char *app_instance_id, int *cursor);
int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,
struct ksmbd_file *fp, bool *closed);
struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
bool ksmbd_has_other_nonposix_open(struct dentry *dentry);
@ -230,6 +243,7 @@ void ksmbd_wake_session_blocked_works(struct ksmbd_session *sess);
int ksmbd_close_inode_fds(struct ksmbd_work *work, struct inode *inode);
int ksmbd_init_global_file_table(void);
void ksmbd_free_global_file_table(void);
void ksmbd_schedule_oplock_break_timer(unsigned long timeout);
void ksmbd_set_fd_limit(unsigned long limit);
int ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,
unsigned int state);
@ -257,6 +271,7 @@ void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp,
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_rebind_replay_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);
int ksmbd_init_file_cache(void);

View File

@ -19,15 +19,33 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
const struct rdma_conn_param *param)
{
struct smbdirect_socket_parameters *sp = &sc->parameters;
struct smbdirect_recv_io *recv_io;
struct smbdirect_recv_io *recv_io = NULL;
u8 peer_initiator_depth;
u8 peer_responder_resources;
struct rdma_conn_param conn_param;
__be32 ird_ord_hdr[2];
int ret;
if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_CREATED))
return -EINVAL;
/*
* Install the real event handler (and the event we expect next)
* before anything that can fail. On an early error the caller
* (smbdirect_listen_connect_request()) only schedules our deferred
* cleanup and returns 0 to the rdma_cm core, which keeps the cm_id, so
* the cm_id must already carry smbdirect_accept_rdma_event_handler():
* it turns any unexpected event (e.g. RDMA_CM_EVENT_DEVICE_REMOVAL)
* into a clean smbdirect_socket_schedule_cleanup(). Otherwise the
* placeholder smbdirect_socket_rdma_event_handler() installed by
* smbdirect_socket_init_accepting() stays in place and returns -ESTALE,
* which makes the rdma_cm core destroy the cm_id behind the back of our
* pending teardown.
*/
sc->rdma.expected_event = RDMA_CM_EVENT_ESTABLISHED;
sc->rdma.cm_id->event_handler = smbdirect_accept_rdma_event_handler;
if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_CREATED)) {
ret = -EINVAL;
goto cleanup;
}
/*
* First set what the we as server are able to support
@ -48,7 +66,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
"smbdirect_accept_init_params() failed %1pe\n",
SMBDIRECT_DEBUG_ERR_PTR(ret));
goto init_params_failed;
goto cleanup;
}
ret = smbdirect_connection_create_qp(sc);
@ -56,7 +74,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
"smbdirect_connection_create_qp() failed %1pe\n",
SMBDIRECT_DEBUG_ERR_PTR(ret));
goto create_qp_failed;
goto cleanup;
}
ret = smbdirect_connection_create_mem_pools(sc);
@ -64,7 +82,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
"smbdirect_connection_create_mem_pools() failed %1pe\n",
SMBDIRECT_DEBUG_ERR_PTR(ret));
goto create_mem_failed;
goto cleanup;
}
recv_io = smbdirect_connection_get_recv_io(sc);
@ -73,7 +91,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
"smbdirect_connection_get_recv_io() failed %1pe\n",
SMBDIRECT_DEBUG_ERR_PTR(ret));
goto get_recv_io_failed;
goto cleanup;
}
recv_io->cqe.done = smbdirect_accept_negotiate_recv_done;
@ -87,7 +105,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
"smbdirect_connection_post_recv_io() failed %1pe\n",
SMBDIRECT_DEBUG_ERR_PTR(ret));
goto post_recv_io_failed;
goto cleanup;
}
/*
* From here recv_io is known to the RDMA QP and needs ib_drain_qp and
@ -123,14 +141,12 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
/* explicitly set above */
WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED);
sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING;
sc->rdma.expected_event = RDMA_CM_EVENT_ESTABLISHED;
sc->rdma.cm_id->event_handler = smbdirect_accept_rdma_event_handler;
ret = rdma_accept(sc->rdma.cm_id, &conn_param);
if (ret) {
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
"rdma_accept() failed %1pe\n",
SMBDIRECT_DEBUG_ERR_PTR(ret));
goto rdma_accept_failed;
goto cleanup;
}
/*
@ -144,31 +160,25 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
return 0;
rdma_accept_failed:
cleanup:
/*
* 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).
* No RDMA state is torn down here, and in particular sc is not
* released: the caller (smbdirect_listen_connect_request()) owns that
* decision and schedules sc's teardown via
* smbdirect_socket_schedule_cleanup() on a non-zero return. All of the
* RDMA state - the QP, the memory pools and the cm_id itself - is then
* torn down asynchronously by smbdirect_socket_cleanup_work() ->
* smbdirect_socket_release() -> smbdirect_socket_destroy(), which
* drains the QP (returning any posted recv_io to the pool) before
* destroying the memory pools and destroys the cm_id with
* rdma_destroy_id(), all in the correct order.
*
* The only resource not reachable by that teardown is a recv_io that
* was taken from the pool but not yet posted to the QP (recv_io is set
* to NULL after a successful post), so hand it back here.
*/
smbdirect_connection_destroy_qp(sc);
smbdirect_connection_destroy_mem_pools(sc);
return ret;
post_recv_io_failed:
/* post failed: recv_io was not accepted by the QP, still in hand */
if (recv_io)
smbdirect_connection_put_recv_io(recv_io);
get_recv_io_failed:
smbdirect_connection_destroy_mem_pools(sc);
create_mem_failed:
smbdirect_connection_destroy_qp(sc);
create_qp_failed:
init_params_failed:
return ret;
}
@ -224,7 +234,7 @@ static void smbdirect_accept_negotiate_recv_done(struct ib_cq *cq, struct ib_wc
struct smbdirect_socket *sc = recv_io->socket;
unsigned long flags;
if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) {
if (unlikely(wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV)) {
if (wc->status != IB_WC_WR_FLUSH_ERR)
smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR,
"wc->status=%s (%d) wc->opcode=%d\n",
@ -302,6 +312,7 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work)
struct smbdirect_socket *sc =
container_of(work, struct smbdirect_socket, connect.work);
struct smbdirect_socket_parameters *sp = &sc->parameters;
struct smbdirect_socket *lsc;
struct smbdirect_recv_io *recv_io;
struct smbdirect_negotiate_req *nreq;
unsigned long flags;
@ -464,29 +475,55 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work)
*/
sp->max_fragmented_send_size = max_fragmented_size;
if (sc->accept.listener) {
struct smbdirect_socket *lsc = sc->accept.listener;
unsigned long flags;
spin_lock_irqsave(&lsc->listen.lock, flags);
list_del(&sc->accept.list);
list_add_tail(&sc->accept.list, &lsc->listen.ready);
wake_up(&lsc->listen.wait_queue);
spin_unlock_irqrestore(&lsc->listen.lock, flags);
/*
* smbdirect_socket_accept() will call
* smbdirect_accept_negotiate_finish(nsc, 0);
*
* So that we don't send the negotiation
* response that grants credits to the peer
* before the socket is accepted by the
* application.
*/
/*
* Every accepting socket was created by
* smbdirect_listen_connect_request() and has a
* listener. If it's already cleared, the listener
* (or its purge_orphaned_work) is responsible for
* releasing us, so we must not send a negotiate
* response.
*
* The memory of the listener is freed via kfree_rcu(),
* see smbdirect_listen_orphan_socket().
*/
rcu_read_lock();
lsc = READ_ONCE(sc->accept.listener);
if (!lsc) {
rcu_read_unlock();
return;
}
ntstatus = le32_to_cpu(STATUS_SUCCESS);
spin_lock_irqsave(&lsc->listen.lock, flags);
/*
* The listener (or its purge_orphaned_work)
* may have cleared sc->accept.listener in the
* meantime and is responsible for releasing us.
*
* If we already failed, we're either
* already on the orphaned list or
* smbdirect_socket_cleanup_work() will
* move us there.
*
* In both cases we must not move us
* to the ready list.
*/
if (sc->accept.listener == lsc && !READ_ONCE(sc->first_error)) {
list_move_tail(&sc->accept.list, &lsc->listen.ready);
wake_up(&lsc->listen.wait_queue);
}
spin_unlock_irqrestore(&lsc->listen.lock, flags);
rcu_read_unlock();
/*
* smbdirect_socket_accept() will call
* smbdirect_accept_negotiate_finish(nsc, 0);
*
* So that we don't send the negotiation
* response that grants credits to the peer
* before the socket is accepted by the
* application.
*/
return;
not_supported:
smbdirect_accept_negotiate_finish(sc, ntstatus);
@ -799,7 +836,11 @@ static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long
if (ret < 0)
return ret;
return 0;
/*
* Return the remaining timeout, so the caller can carry it
* over to the next smbdirect_socket_wait_for_accept() call.
*/
return ret;
}
struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
@ -809,6 +850,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
struct smbdirect_socket *nsc;
unsigned long flags;
again:
if (lsc->status != SMBDIRECT_SOCKET_LISTENING) {
arg->err = -EINVAL;
return NULL;
@ -820,7 +862,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
}
if (list_empty_careful(&lsc->listen.ready)) {
int ret;
long ret;
if (timeo == 0) {
arg->err = -EAGAIN;
@ -828,25 +870,78 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
}
ret = smbdirect_socket_wait_for_accept(lsc, timeo);
if (ret) {
if (ret < 0) {
arg->err = ret;
return NULL;
}
/*
* Carry the remaining timeout over, so that a stream of
* failed connections that we orphan and skip (goto again)
* can't reset the caller's timeout and wait forever.
*/
timeo = ret;
}
spin_lock_irqsave(&lsc->listen.lock, flags);
nsc = list_first_entry_or_null(&lsc->listen.ready,
struct smbdirect_socket,
accept.list);
/*
* Recheck under the lock.
*/
if (lsc->status != SMBDIRECT_SOCKET_LISTENING) {
arg->err = -EINVAL;
spin_unlock_irqrestore(&lsc->listen.lock, flags);
return NULL;
}
if (lsc->first_error) {
arg->err = lsc->first_error;
spin_unlock_irqrestore(&lsc->listen.lock, flags);
return NULL;
}
while ((nsc = list_first_entry_or_null(&lsc->listen.ready,
struct smbdirect_socket,
accept.list))) {
/*
* nsc may have failed after it was moved
* to the ready list, e.g. RDMA_CM_EVENT_DISCONNECTED
* already moved it to SMBDIRECT_SOCKET_DISCONNECTED.
* We must not overwrite that with
* SMBDIRECT_SOCKET_CONNECTED and hand
* out an already disconnected socket.
*
* Doing this under listen.lock means nsc
* still belongs to us, so we can just move
* a failed socket to the orphaned list,
* like smbdirect_listen_orphan_socket() does,
* and try the next one.
*/
if (!READ_ONCE(nsc->first_error) &&
cmpxchg(&nsc->status,
SMBDIRECT_SOCKET_NEGOTIATE_RUNNING,
SMBDIRECT_SOCKET_CONNECTED) ==
SMBDIRECT_SOCKET_NEGOTIATE_RUNNING)
break;
smbdirect_log_rdma_event(nsc, SMBDIRECT_LOG_INFO,
"orphaning failed socket status=%s first_error=%1pe\n",
smbdirect_socket_status_string(nsc->status),
SMBDIRECT_DEBUG_ERR_PTR(nsc->first_error));
list_move_tail(&nsc->accept.list, &lsc->listen.orphaned);
queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work);
}
if (nsc) {
nsc->accept.listener = NULL;
WRITE_ONCE(nsc->accept.listener, NULL);
list_del_init_careful(&nsc->accept.list);
arg->is_empty = list_empty_careful(&lsc->listen.ready);
}
spin_unlock_irqrestore(&lsc->listen.lock, flags);
if (!nsc) {
arg->err = -EAGAIN;
return NULL;
/*
* If we only found failed sockets or no socket,
* we wait for the next one.
*
* A possible -EAGAIN/-ETIMEOUT is handled above.
*/
goto again;
}
/*
@ -855,12 +950,14 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
* so it didn't grant any credits to us.
*
* The caller expects a connected socket
* now as there are no credits anyway.
* now as there are no credits anyway,
* above we already changed to SMBDIRECT_SOCKET_CONNECTED
* under the lsc->listen.lock and with cmpxchg.
*
* Then we send the negotiation response in
* order to grant credits to the peer.
* Now we send the negotiation response in
* order to grant credits to the peer,
* as the socket is now visible to the application layer.
*/
nsc->status = SMBDIRECT_SOCKET_CONNECTED;
smbdirect_accept_negotiate_finish(nsc, 0);
return nsc;

View File

@ -581,7 +581,7 @@ static void smbdirect_connect_negotiate_recv_done(struct ib_cq *cq, struct ib_wc
struct smbdirect_socket *sc = recv_io->socket;
unsigned long flags;
if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) {
if (unlikely(wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV)) {
if (wc->status != IB_WC_WR_FLUSH_ERR)
smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR,
"wc->status=%s (%d) wc->opcode=%d\n",

View File

@ -83,9 +83,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id,
* smbdirect_socket_schedule_cleanup[_status]() =>
* smbdirect_socket_cleanup_work().
*
* As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING,
* but never ever get RDMA_CM_EVENT_DISCONNECTED and
* never reach SMBDIRECT_SOCKET_DISCONNECTED.
* As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING
* and call rdma_disconnect(), but never ever get
* RDMA_CM_EVENT_DISCONNECTED.
*/
if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
smbdirect_socket_schedule_cleanup_status(sc,
@ -113,9 +113,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id,
* smbdirect_socket_schedule_cleanup_status() =>
* smbdirect_socket_cleanup_work().
*
* As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING,
* but never ever get RDMA_CM_EVENT_DISCONNECTED and
* never reach SMBDIRECT_SOCKET_DISCONNECTED.
* As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING
* and call rdma_disconnect(), but never ever get
* RDMA_CM_EVENT_DISCONNECTED.
*
* This is also a normal disconnect so
* SMBDIRECT_LOG_INFO should be good enough
@ -1529,7 +1529,7 @@ void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc)
u32 data_length;
u32 remaining_data_length;
if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) {
if (unlikely(wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV)) {
if (wc->status != IB_WC_WR_FLUSH_ERR)
smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR,
"wc->status=%s (%d) wc->opcode=%d\n",

View File

@ -74,6 +74,8 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc);
void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc);
int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
enum smbdirect_socket_status expected_status,
int unexpected_errno,

View File

@ -10,6 +10,78 @@
static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *id,
struct rdma_cm_event *event);
/*
* This is called by a socket that failed while it
* is still on the pending or ready list of its
* listener, typically at the end of
* smbdirect_socket_cleanup_work(), when the
* disconnect was already started.
*
* It moves itself to the orphaned list and
* lets smbdirect_listen_purge_orphaned_work()
* release it. Otherwise it would stay on the
* pending or ready list until the listener is
* destroyed and fill up the backlog, so that
* no new connections would be accepted.
*
* It's fine to call this more than once.
*/
void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc)
{
struct smbdirect_socket *lsc;
unsigned long flags;
/*
* The memory of the listener is freed via
* kfree_rcu(), so it's safe to dereference it
* under rcu_read_lock(), even if sc->accept.listener
* is cleared and the listener is released concurrently.
*/
rcu_read_lock();
lsc = READ_ONCE(sc->accept.listener);
if (!lsc) {
rcu_read_unlock();
return;
}
spin_lock_irqsave(&lsc->listen.lock, flags);
if (sc->accept.listener == lsc) {
list_move_tail(&sc->accept.list, &lsc->listen.orphaned);
queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work);
}
spin_unlock_irqrestore(&lsc->listen.lock, flags);
rcu_read_unlock();
}
static void smbdirect_listen_purge_orphaned_work(struct work_struct *work)
{
struct smbdirect_socket *lsc =
container_of(work, struct smbdirect_socket, listen.purge_orphaned_work);
struct smbdirect_socket *psc, *tsc;
LIST_HEAD(orphaned_list);
unsigned long flags;
/*
* Clearing accept.listener under listen.lock
* makes us responsible for releasing them.
*/
spin_lock_irqsave(&lsc->listen.lock, flags);
list_splice_tail_init(&lsc->listen.orphaned, &orphaned_list);
list_for_each_entry(psc, &orphaned_list, accept.list)
WRITE_ONCE(psc->accept.listener, NULL);
spin_unlock_irqrestore(&lsc->listen.lock, flags);
/*
* We don't hold the listener's rdma_lock_handler()
* lock here, see smbdirect_socket_destroy()
* for why that's important.
*/
list_for_each_entry_safe(psc, tsc, &orphaned_list, accept.list) {
list_del_init(&psc->accept.list);
smbdirect_socket_release(psc);
}
}
int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog)
{
int ret;
@ -47,6 +119,9 @@ int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog)
sc->rdma.cm_id->event_handler = smbdirect_listen_rdma_event_handler;
rdma_unlock_handler(sc->rdma.cm_id);
INIT_WORK(&sc->listen.purge_orphaned_work,
smbdirect_listen_purge_orphaned_work);
ret = rdma_listen(sc->rdma.cm_id, backlog);
if (ret) {
sc->first_error = ret;
@ -214,6 +289,7 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
size_t backlog = max_t(size_t, 1, lsc->listen.backlog);
size_t psockets;
size_t rsockets;
size_t osockets;
int ret;
if (!smbdirect_frwr_is_supported(&new_id->device->attrs)) {
@ -254,14 +330,21 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
spin_lock_irqsave(&lsc->listen.lock, flags);
psockets = list_count_nodes(&lsc->listen.pending);
rsockets = list_count_nodes(&lsc->listen.ready);
/*
* Orphaned sockets still count against
* the backlog until they are released by
* smbdirect_listen_purge_orphaned_work().
*/
osockets = list_count_nodes(&lsc->listen.orphaned);
spin_unlock_irqrestore(&lsc->listen.lock, flags);
if (psockets > backlog ||
rsockets > backlog ||
(psockets + rsockets) > backlog) {
osockets > backlog ||
(psockets + rsockets + osockets) > backlog) {
smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
"Backlog[%d][%zu] full pending[%zu] ready[%zu]\n",
lsc->listen.backlog, backlog, psockets, rsockets);
"Backlog[%d][%zu] full pending[%zu] ready[%zu] orphaned[%zu]\n",
lsc->listen.backlog, backlog, psockets, rsockets, osockets);
return -EBUSY;
}
@ -279,22 +362,54 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
if (ret)
goto set_settings_failed;
/*
* Publish nsc on the pending list with accept.listener set before
* smbdirect_accept_connect_request() calls rdma_accept(). Once the
* connection can establish, smbdirect_accept_negotiate_recv_work()
* may run and it must observe accept.listener, otherwise nsc would
* be left stranded on the pending list forever.
*
* The listener's handler_mutex is held while we're called, so
* smbdirect_socket_destroy() of the listener can't reach nsc on the
* pending list before we're done.
*
* From here nsc is published on the pending list: on failure of
* smbdirect_accept_connect_request() below we schedule nsc's teardown,
* which orphans nsc off the listener so
* smbdirect_listen_purge_orphaned_work() -> smbdirect_socket_release()
* releases it.
*/
spin_lock_irqsave(&lsc->listen.lock, flags);
list_add_tail(&nsc->accept.list, &lsc->listen.pending);
nsc->accept.listener = lsc;
WRITE_ONCE(nsc->accept.listener, lsc);
spin_unlock_irqrestore(&lsc->listen.lock, flags);
ret = smbdirect_accept_connect_request(nsc, &event->param.conn);
if (ret)
goto accept_connect_failed;
if (ret) {
/*
* The rdma_cm core holds both the listener's and nsc's
* id_priv->handler_mutex across this CONNECT_REQUEST handler,
* so neither can go away under us here and nsc cannot receive
* any rdma event: it is safe to hand nsc to its teardown.
*
* That teardown must be deferred though: nsc->rdma.cm_id
* (= new_id) has its handler_mutex held by us, so it cannot be
* destroyed synchronously from here.
* smbdirect_socket_schedule_cleanup() only queues work;
* smbdirect_socket_cleanup_work() then orphans nsc off the
* listener and smbdirect_listen_purge_orphaned_work() releases
* it, destroying nsc's cm_id with rdma_destroy_id().
*/
smbdirect_socket_schedule_cleanup(nsc, ret);
}
/*
* Always return 0 so the rdma_cm core keeps new_id: nsc owns it now.
* On success it stays connected; on failure its deferred teardown
* above destroys it.
*/
return 0;
accept_connect_failed:
spin_lock_irqsave(&lsc->listen.lock, flags);
list_del_init(&nsc->accept.list);
nsc->accept.listener = NULL;
spin_unlock_irqrestore(&lsc->listen.lock, flags);
set_settings_failed:
set_params_failed:
/*

View File

@ -319,6 +319,15 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
* (smbdirect_socket_destroy) to reap.
*/
if (sc->listen.backlog != -1) { /* was a listener */
/*
* We don't move them to the orphaned list here,
* each child does that itself at the end of its
* smbdirect_socket_cleanup_work(), see
* smbdirect_listen_orphan_socket(). Only that checks
* accept.listener, which is still NULL for a child
* that smbdirect_listen_connect_request() is still
* setting up and will release itself on failure.
*/
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)
@ -427,6 +436,15 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work)
* instances of one class -- harmless, but lockdep cannot tell).
*/
if (sc->listen.backlog != -1) { /* was a listener */
/*
* We don't move them to the orphaned list here,
* each child does that itself at the end of its
* smbdirect_socket_cleanup_work(), see
* smbdirect_listen_orphan_socket(). Only that checks
* accept.listener, which is still NULL for a child
* that smbdirect_listen_connect_request() is still
* setting up and will release itself on failure.
*/
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)
@ -486,6 +504,18 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work)
* in order to notice the broken connection.
*/
smbdirect_socket_wake_up_all(sc);
/*
* If we're still on the pending or ready list
* of a listener, we started the disconnect
* as far as possible above, so we move ourself
* to the orphaned list of the listener,
* which will release us.
*
* This is a no-op internally if
* sc->accept.listener is NULL.
*/
smbdirect_listen_orphan_socket(sc);
}
static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
@ -511,7 +541,13 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
if (sc->status == SMBDIRECT_SOCKET_DESTROYED)
return;
WARN_ONCE(sc->status != SMBDIRECT_SOCKET_DISCONNECTED,
/*
* smbdirect_socket_destroy_sync() doesn't wait
* for RDMA_CM_EVENT_DISCONNECTED, so we may still
* be in SMBDIRECT_SOCKET_DISCONNECTING
* (or already reached SMBDIRECT_SOCKET_DISCONNECTED)
*/
WARN_ONCE(sc->status < SMBDIRECT_SOCKET_DISCONNECTING,
"status=%s first_error=%1pe",
smbdirect_socket_status_string(sc->status),
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
@ -538,10 +574,37 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
disable_work_sync(&sc->recv_io.posted.refill_work);
disable_work_sync(&sc->idle.immediate_work);
disable_delayed_work_sync(&sc->idle.timer_work);
disable_work_sync(&sc->listen.purge_orphaned_work);
if (sc->rdma.cm_id)
rdma_lock_handler(sc->rdma.cm_id);
/*
* We hold the handler lock, so the rdma event
* handlers can't change the status anymore.
*
* If RDMA_CM_EVENT_DISCONNECTED didn't arrive yet,
* we just stop waiting for it here.
*
* We already disabled disconnect_work above
* and before we call rdma_unlock_handler below
* we call smbdirect_connection_destroy_qp which
* sets sc->ib.qp = NULL.
*
* Between rdma_unlock_handler() and
* rdma_destroy_id() below there's a small
* windows where RDMA_CM_EVENT_DISCONNECTED
* could still arrive.
*
* But smbdirect_connection_rdma_event_handler
* will be a noop when calling smbdirect_socket_schedule_cleanup*
* ib_drain_qp() also won't be called.
*/
if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
smbdirect_socket_wake_up_all(sc);
}
if (sc->ib.qp) {
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"drain qp\n");
@ -575,6 +638,19 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
spin_lock_irqsave(&sc->listen.lock, flags);
list_splice_tail_init(&sc->listen.ready, &pending_list);
list_splice_tail_init(&sc->listen.pending, &pending_list);
/*
* purge_orphaned_work is already disabled above,
* so we also need to release the orphaned sockets.
*
* Clearing accept.listener under listen.lock
* makes us responsible for releasing them and
* prevents them from moving themselves to
* the orphaned list via
* smbdirect_listen_orphan_socket().
*/
list_splice_tail_init(&sc->listen.orphaned, &pending_list);
list_for_each_entry(psc, &pending_list, accept.list)
WRITE_ONCE(psc->accept.listener, NULL);
spin_unlock_irqrestore(&sc->listen.lock, flags);
/* It's not possible for upper layer to get to reassembly */
@ -618,7 +694,6 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
"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 */
@ -679,17 +754,21 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
"destroying rdma session\n");
if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING)
smbdirect_socket_cleanup_work(&sc->disconnect_work);
if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"wait for transport being disconnected\n");
wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"waited for transport being disconnected\n");
}
/*
* Once we reached SMBDIRECT_SOCKET_DISCONNECTED,
* we should call smbdirect_socket_destroy()
* We don't wait for RDMA_CM_EVENT_DISCONNECTED,
* rdma_disconnect() was already called by
* smbdirect_socket_cleanup_work() if needed
* and smbdirect_socket_destroy() drains the qp,
* destroys it and calls rdma_destroy_id(), which
* only waits for a currently running event handler
* and makes sure no further events are delivered.
* The rest of the disconnect protocol is handled
* by the rdma core asynchronously.
*
* Waiting for RDMA_CM_EVENT_DISCONNECTED could
* take very long or forever, e.g. if the peer
* just disappeared.
*/
smbdirect_socket_destroy(sc);
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
@ -741,7 +820,17 @@ static void smbdirect_socket_release_destroy(struct kref *kref)
* in DESTROYED state, before we free the memory.
*/
smbdirect_socket_destroy_sync(sc);
kfree(sc);
/*
* Only a listener (backlog != -1) is ever dereferenced
* via sc->accept.listener under rcu_read_lock(), see
* smbdirect_listen_orphan_socket(). Other sockets can be
* freed immediately.
*/
if (sc->listen.backlog != -1) /* was a listener */
kfree_rcu(sc, refs.rcu);
else
kfree(sc);
}
void smbdirect_socket_release(struct smbdirect_socket *sc)

View File

@ -151,6 +151,12 @@ struct smbdirect_socket {
* the disconnect refcount.
*/
struct kref destroy;
/*
* smbdirect_socket_release_destroy() uses
* kfree_rcu(), see accept.listener.
*/
struct rcu_head rcu;
} refs;
/* RDMA related */
@ -216,6 +222,16 @@ struct smbdirect_socket {
* only be > 0.
*/
int backlog;
/*
* Sockets on pending or ready that failed
* move themselves to orphaned and queue
* purge_orphaned_work, which releases them.
* So that they are freed while the listener
* is still alive. They still count against
* the backlog until they are released.
*/
struct list_head orphaned;
struct work_struct purge_orphaned_work;
} listen;
/*
@ -226,7 +242,30 @@ struct smbdirect_socket {
* connection.
*/
struct {
/*
* This is only set, protected by
* listener->listen.lock, while the socket
* is owned by the listener (on its pending,
* ready or orphaned list). The one who clears
* it is responsible for releasing the socket.
*
* The memory of a struct smbdirect_socket is
* freed via kfree_rcu(), so the listener
* can be dereferenced under rcu_read_lock(),
* even if accept.listener is cleared and
* the listener is released concurrently.
*/
struct smbdirect_socket *listener;
/*
* Linkage on one of the listener's
* listen.{pending,ready,orphaned} lists.
*
* accept.list and accept.listener are set
* together under listener->listen.lock before
* smbdirect_accept_connect_request() is called,
* so whenever the socket is on one of those lists
* accept.listener is that listener (never NULL).
*/
struct list_head list;
} accept;
@ -588,6 +627,9 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
spin_lock_init(&sc->listen.lock);
INIT_LIST_HEAD(&sc->listen.pending);
INIT_LIST_HEAD(&sc->listen.ready);
INIT_LIST_HEAD(&sc->listen.orphaned);
INIT_WORK(&sc->listen.purge_orphaned_work, __smbdirect_socket_disabled_work);
disable_work_sync(&sc->listen.purge_orphaned_work);
sc->listen.backlog = -1; /* not a listener */
init_waitqueue_head(&sc->listen.wait_queue);