summaryrefslogtreecommitdiffstats
path: root/fs/smb/server/compress.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
commit4352b8aee98005853aa63f57d6377282de17a33f (patch)
tree27614d7a9ef05c32d089004e104364ce42324837 /fs/smb/server/compress.c
downloadlinux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz
linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ...
Diffstat (limited to 'fs/smb/server/compress.c')
-rw-r--r--fs/smb/server/compress.c260
1 files changed, 260 insertions, 0 deletions
diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c
new file mode 100644
index 000000000..5162fb84c
--- /dev/null
+++ b/fs/smb/server/compress.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SMB2 compression support for ksmbd.
+ *
+ * Receive and send SMB 3.1.1 compression transforms using the common helpers.
+ *
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ */
+#include <linux/slab.h>
+
+#include "compress.h"
+#include "smb_common.h"
+#include "../common/compress/lz77.h"
+
+#define SMB_COMPRESS_MIN_LEN PAGE_SIZE
+
+static int __ksmbd_decompress_request(struct ksmbd_conn *conn,
+ void *request_buf, void **out_buf)
+{
+ struct smb2_compression_hdr *hdr;
+ unsigned int pdu_size = get_rfc1002_len(request_buf);
+ u32 orig_size, offset, out_size;
+ u32 max_allowed_pdu_size;
+ char *out;
+ int rc;
+
+ if (pdu_size < sizeof(struct smb2_compression_hdr))
+ return -EINVAL;
+
+ if (conn->dialect != SMB311_PROT_ID ||
+ conn->compress_algorithm == SMB3_COMPRESS_NONE)
+ return -EINVAL;
+
+ hdr = smb_get_msg(request_buf);
+ if (hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID)
+ return -EINVAL;
+
+ orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+ /*
+ * For chained transforms the top-level header is only eight bytes; the
+ * Flags field overlays the first payload header. Reject unknown Flags
+ * and unnegotiated chained mode before allocating the output buffer.
+ */
+ if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) {
+ if (!conn->compress_chained)
+ return -EINVAL;
+ out_size = orig_size;
+ } else if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)) {
+ offset = le32_to_cpu(hdr->Offset);
+ if (offset > pdu_size - sizeof(*hdr) ||
+ check_add_overflow(orig_size, offset, &out_size))
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+
+ max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn);
+ if (out_size < sizeof(struct smb2_pdu) ||
+ out_size > max_allowed_pdu_size ||
+ out_size > MAX_STREAM_PROT_LEN)
+ return -EINVAL;
+
+ out = kvmalloc(out_size + 4 + 1, KSMBD_DEFAULT_GFP);
+ if (!out)
+ return -ENOMEM;
+
+ *(__be32 *)out = cpu_to_be32(out_size);
+ rc = smb_compression_decompress(conn->compress_algorithm,
+ conn->compress_chained,
+ conn->compress_pattern,
+ (char *)hdr, pdu_size, out + 4, out_size);
+ if (rc) {
+ kvfree(out);
+ return rc;
+ }
+
+ *out_buf = out;
+ return 0;
+}
+
+/**
+ * ksmbd_decompress_request() - replace a compressed request with its SMB2 PDU
+ * @conn: connection which owns the current RFC1002 request buffer
+ *
+ * Derive the uncompressed size from the transform variant, enforce ksmbd's
+ * normal message limits, and ask the common decoder to validate every payload.
+ * On success, replace conn->request_buf with a regular RFC1002-framed SMB2
+ * message so the rest of the request path needs no compression awareness.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int ksmbd_decompress_request(struct ksmbd_conn *conn)
+{
+ void *out_buf;
+ int rc;
+
+ rc = __ksmbd_decompress_request(conn, conn->request_buf, &out_buf);
+ if (rc)
+ return rc;
+
+ kvfree(conn->request_buf);
+ conn->request_buf = out_buf;
+ return 0;
+}
+
+/**
+ * ksmbd_decompress_work_request() - decompress an encrypted work request
+ * @work: work item whose request buffer contains a compression transform
+ *
+ * SMB3 encrypts a compressed message by applying compression first and
+ * encryption second. The receive loop can therefore only decode the
+ * compression transform before work allocation for an unencrypted request;
+ * an encrypted request must be decompressed after its encryption layer has
+ * been removed.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int ksmbd_decompress_work_request(struct ksmbd_work *work)
+{
+ void *out_buf;
+ int rc;
+
+ rc = __ksmbd_decompress_request(work->conn, work->request_buf,
+ &out_buf);
+ if (rc)
+ return rc;
+
+ kvfree(work->request_buf);
+ work->request_buf = out_buf;
+ return 0;
+}
+
+/**
+ * ksmbd_compress_response() - compress an eligible ksmbd response
+ * @work: request work item containing the response iov
+ *
+ * Compression transforms describe one contiguous SMB2 message, while ksmbd
+ * builds responses from multiple iov entries. Flatten the response first,
+ * produce the negotiated transform, and replace the response iov only when the
+ * result is smaller than the original message.
+ *
+ * Encrypted and compound responses are intentionally left unchanged. The
+ * caller may still continue sending the original response when this function
+ * returns zero.
+ *
+ * Return: 1 if the response was replaced, 0 if compression was skipped, or a
+ * negative errno on failure.
+ */
+int ksmbd_compress_response(struct ksmbd_work *work)
+{
+ struct smb2_compression_hdr *chdr;
+ struct smb2_hdr *req_hdr;
+ u32 src_len, dst_len, compressed_pdu_len, max_dst_len;
+ u8 *src = NULL, *out = NULL, *p;
+ int i, rc;
+
+ if (!work->compress_response || work->encrypted ||
+ work->conn->compress_algorithm != SMB3_COMPRESS_LZ77)
+ return 0;
+
+ req_hdr = smb_get_msg(work->request_buf);
+ if (req_hdr->NextCommand || work->next_smb2_rcv_hdr_off ||
+ work->next_smb2_rsp_hdr_off)
+ return 0;
+
+ src_len = get_rfc1002_len(work->iov[0].iov_base);
+ if (src_len < SMB_COMPRESS_MIN_LEN)
+ return 0;
+
+ src = kvmalloc(src_len, KSMBD_DEFAULT_GFP);
+ if (!src)
+ return -ENOMEM;
+
+ p = src;
+ /* iov[0] contains only the RFC1002 length; the SMB2 PDU starts at iov[1]. */
+ for (i = 1; i < work->iov_cnt; i++) {
+ if (work->iov[i].iov_len > src + src_len - p) {
+ rc = -EINVAL;
+ goto out;
+ }
+ memcpy(p, work->iov[i].iov_base, work->iov[i].iov_len);
+ p += work->iov[i].iov_len;
+ }
+ if (p != src + src_len) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ max_dst_len = smb_lz77_compressed_alloc_size(src_len) +
+ sizeof(struct smb2_compression_hdr) +
+ 3 * sizeof(struct smb2_compression_payload_hdr) +
+ 2 * sizeof(struct smb2_compression_pattern_v1);
+ out = kvzalloc(sizeof(__be32) + max_dst_len,
+ KSMBD_DEFAULT_GFP);
+ if (!out) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ if (work->conn->compress_chained) {
+ dst_len = max_dst_len;
+ rc = smb_compression_compress_chained(SMB3_COMPRESS_LZ77,
+ work->conn->compress_pattern,
+ src, src_len,
+ out + sizeof(__be32),
+ &dst_len);
+ if (rc == -EMSGSIZE || dst_len >= src_len) {
+ rc = 0;
+ goto out;
+ }
+ if (rc)
+ goto out;
+ compressed_pdu_len = dst_len;
+ } else {
+ /*
+ * Peers which did not negotiate chained compression still use
+ * the original 16-byte unchained transform format.
+ */
+ dst_len = smb_lz77_compressed_alloc_size(src_len);
+ rc = smb_lz77_compress(src, src_len,
+ out + sizeof(__be32) + sizeof(*chdr),
+ &dst_len);
+ if (rc == -EMSGSIZE ||
+ dst_len + sizeof(*chdr) >= src_len) {
+ rc = 0;
+ goto out;
+ }
+ if (rc)
+ goto out;
+
+ compressed_pdu_len = sizeof(*chdr) + dst_len;
+ chdr = (struct smb2_compression_hdr *)(out + sizeof(__be32));
+ chdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
+ chdr->OriginalCompressedSegmentSize = cpu_to_le32(src_len);
+ chdr->CompressionAlgorithm = SMB3_COMPRESS_LZ77;
+ chdr->Flags = cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE);
+ chdr->Offset = 0;
+ }
+
+ *(__be32 *)out = cpu_to_be32(compressed_pdu_len);
+
+ /*
+ * Keep the transform in work->compress_buf until send completion.
+ * Existing response iovs can then be replaced without changing their
+ * individual ownership rules.
+ */
+ work->compress_buf = out;
+ work->iov[0].iov_base = out;
+ work->iov[0].iov_len = sizeof(__be32);
+ work->iov[1].iov_base = out + sizeof(__be32);
+ work->iov[1].iov_len = compressed_pdu_len;
+ work->iov_cnt = 2;
+ work->iov_idx = 1;
+ out = NULL;
+ rc = 1;
+out:
+ kvfree(out);
+ kvfree(src);
+ return rc;
+}