mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
- 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:
commit
4e1b4fdf61
|
|
@ -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.
|
||||
============================== =================================================
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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 +
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
15
fs/smb/server/tests/Kconfig
Normal file
15
fs/smb/server/tests/Kconfig
Normal 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.
|
||||
4
fs/smb/server/tests/Makefile
Normal file
4
fs/smb/server/tests/Makefile
Normal 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
|
||||
301
fs/smb/server/tests/smbacl_kunit.c
Normal file
301
fs/smb/server/tests/smbacl_kunit.c
Normal 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");
|
||||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user