diff --git a/fs/smb/client/Makefile b/fs/smb/client/Makefile
index 6e83b52..fc6b9d3 100644
--- a/fs/smb/client/Makefile
+++ b/fs/smb/client/Makefile
@@ -42,7 +42,7 @@ cifs-$(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) += \
 	smb1session.o \
 	smb1transport.o
 
-cifs-$(CONFIG_CIFS_COMPRESSION) += compress.o compress/lz77.o
+cifs-$(CONFIG_CIFS_COMPRESSION) += compress.o
 
 ifneq ($(CONFIG_CIFS_ALLOW_INSECURE_LEGACY),)
 #
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index a462c15..befc5ee 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -789,6 +789,8 @@ struct TCP_Server_Info {
 	struct {
 		bool requested; /* "compress" mount option set*/
 		bool enabled; /* actually negotiated with server */
+		bool chained; /* chained transforms were negotiated */
+		bool pattern; /* Pattern_V1 chained payloads were negotiated */
 		__le16 alg; /* preferred alg negotiated with server */
 	} compression;
 	__u16	signing_algorithm;
diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c
index be9023f..8f08609 100644
--- a/fs/smb/client/compress.c
+++ b/fs/smb/client/compress.c
@@ -22,7 +22,7 @@
 #include "cifsproto.h"
 #include "smb2proto.h"
 
-#include "compress/lz77.h"
+#include "../common/compress/lz77.h"
 #include "compress.h"
 
 /*
@@ -44,6 +44,11 @@ struct bucket {
 	unsigned int count;
 };
 
+static inline size_t pow4(size_t n)
+{
+	return n * n * n * n;
+}
+
 /*
  * has_low_entropy() - Compute Shannon entropy of the sampled data.
  * @bkt:	Bytes counts of the sample.
@@ -65,7 +70,6 @@ static bool has_low_entropy(struct bucket *bkt, size_t slen)
 	const size_t threshold = 65, max_entropy = 8 * ilog2(16);
 	size_t i, p, p2, len, sum = 0;
 
-#define pow4(n) (n * n * n * n)
 	len = ilog2(pow4(slen));
 
 	for (i = 0; i < 256 && bkt[i].count > 0; i++) {
@@ -329,14 +333,14 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_s
 		goto err_free;
 	}
 
-	dlen = lz77_compressed_alloc_size(slen);
+	dlen = smb_lz77_compressed_alloc_size(slen);
 	dst = kvzalloc(dlen, GFP_KERNEL);
 	if (!dst) {
 		ret = -ENOMEM;
 		goto err_free;
 	}
 
-	ret = lz77_compress(src, slen, dst, &dlen);
+	ret = smb_lz77_compress(src, slen, dst, &dlen);
 	if (!ret) {
 		struct smb2_compression_hdr hdr = { 0 };
 		struct smb_rqst comp_rq = { .rq_nvec = 3, };
diff --git a/fs/smb/client/compress.h b/fs/smb/client/compress.h
index 2679bac..e08e6d3 100644
--- a/fs/smb/client/compress.h
+++ b/fs/smb/client/compress.h
@@ -18,6 +18,7 @@
 #include <linux/uio.h>
 #include <linux/kernel.h>
 #include "../common/smb2pdu.h"
+#include "../common/compress/compress.h"
 #include "cifsglob.h"
 
 /* sizeof(smb2_compression_hdr) - sizeof(OriginalPayloadSize) */
@@ -34,29 +35,6 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq,
 		 compress_send_fn send_fn);
 bool should_compress(const struct cifs_tcon *tcon, const struct smb_rqst *rq);
 
-/*
- * smb_compress_alg_valid() - Validate a compression algorithm.
- * @alg: Compression algorithm to check.
- * @valid_none: Conditional check whether NONE algorithm should be
- *		considered valid or not.
- *
- * If @alg is SMB3_COMPRESS_NONE, this function returns @valid_none.
- *
- * Note that 'NONE' (0) compressor type is considered invalid in protocol
- * negotiation, as it's never requested to/returned from the server.
- *
- * Return: true if @alg is valid/supported, false otherwise.
- */
-static __always_inline int smb_compress_alg_valid(__le16 alg, bool valid_none)
-{
-	if (alg == SMB3_COMPRESS_NONE)
-		return valid_none;
-
-	if (alg == SMB3_COMPRESS_LZ77 || alg == SMB3_COMPRESS_PATTERN)
-		return true;
-
-	return false;
-}
 #else /* !CONFIG_CIFS_COMPRESSION */
 static inline int smb_compress(void *unused1, void *unused2, void *unused3)
 {
@@ -68,9 +46,5 @@ static inline bool should_compress(void *unused1, void *unused2)
 	return false;
 }
 
-static inline int smb_compress_alg_valid(__le16 unused1, bool unused2)
-{
-	return -EOPNOTSUPP;
-}
 #endif /* !CONFIG_CIFS_COMPRESSION */
 #endif /* _SMB_COMPRESS_H */
diff --git a/fs/smb/client/smb1pdu.h b/fs/smb/client/smb1pdu.h
index 7584e94..0870949 100644
--- a/fs/smb/client/smb1pdu.h
+++ b/fs/smb/client/smb1pdu.h
@@ -1211,11 +1211,6 @@ typedef struct smb_com_transaction_compr_ioctl_req {
 	__le16 compression_state;  /* See below for valid flags */
 } __packed TRANSACT_COMPR_IOCTL_REQ;
 
-/* compression state flags */
-#define COMPRESSION_FORMAT_NONE		0x0000
-#define COMPRESSION_FORMAT_DEFAULT	0x0001
-#define COMPRESSION_FORMAT_LZNT1	0x0002
-
 typedef struct smb_com_transaction_ioctl_rsp {
 	struct smb_hdr hdr;	/* wct = 19 */
 	__u8 Reserved[3];
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 4972cfe..3c7691b 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -636,10 +636,16 @@ build_compression_ctxt(struct smb2_compression_capabilities_context *pneg_ctxt)
 	pneg_ctxt->DataLength =
 		cpu_to_le16(sizeof(struct smb2_compression_capabilities_context)
 			  - sizeof(struct smb2_neg_context));
-	pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(3);
+	/*
+	 * Pattern_V1 is useful only as part of a chained transform. LZ77 remains
+	 * the preferred general-purpose algorithm selected by this client.
+	 */
+	pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(4);
+	pneg_ctxt->Flags = SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED;
 	pneg_ctxt->CompressionAlgorithms[0] = SMB3_COMPRESS_LZ77;
 	pneg_ctxt->CompressionAlgorithms[1] = SMB3_COMPRESS_LZ77_HUFF;
 	pneg_ctxt->CompressionAlgorithms[2] = SMB3_COMPRESS_LZNT1;
+	pneg_ctxt->CompressionAlgorithms[3] = SMB3_COMPRESS_PATTERN;
 }
 
 static unsigned int
@@ -827,9 +833,12 @@ static void decode_compress_ctx(struct TCP_Server_Info *server,
 			 struct smb2_compression_capabilities_context *ctxt)
 {
 	unsigned int len = le16_to_cpu(ctxt->DataLength);
-	__le16 alg;
+	unsigned int count, i;
 
 	server->compression.enabled = false;
+	server->compression.chained = false;
+	server->compression.pattern = false;
+	server->compression.alg = SMB3_COMPRESS_NONE;
 
 	/*
 	 * Caller checked that DataLength remains within SMB boundary. We still
@@ -841,20 +850,37 @@ static void decode_compress_ctx(struct TCP_Server_Info *server,
 		return;
 	}
 
-	if (le16_to_cpu(ctxt->CompressionAlgorithmCount) != 1) {
+	count = le16_to_cpu(ctxt->CompressionAlgorithmCount);
+	if (!count || count > ARRAY_SIZE(ctxt->CompressionAlgorithms) ||
+	    len < 8 + count * sizeof(__le16)) {
 		pr_warn_once("invalid SMB3 compress algorithm count\n");
 		return;
 	}
 
-	alg = ctxt->CompressionAlgorithms[0];
-
-	/* 'NONE' (0) compressor type is never negotiated */
-	if (alg == 0 || le16_to_cpu(alg) > 3) {
-		pr_warn_once("invalid compression algorithm '%u'\n", alg);
+	if (ctxt->Flags != SMB2_COMPRESSION_CAPABILITIES_FLAG_NONE &&
+	    ctxt->Flags != SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED) {
+		pr_warn_once("invalid SMB3 compression flags\n");
 		return;
 	}
 
-	server->compression.alg = alg;
+	for (i = 0; i < count; i++) {
+		/* Record the intersection supported by the shared SMB codec. */
+		if (ctxt->CompressionAlgorithms[i] == SMB3_COMPRESS_LZ77)
+			server->compression.alg = SMB3_COMPRESS_LZ77;
+		else if (ctxt->CompressionAlgorithms[i] == SMB3_COMPRESS_PATTERN)
+			server->compression.pattern = true;
+	}
+	if (server->compression.alg != SMB3_COMPRESS_LZ77)
+		return;
+
+	/*
+	 * Pattern_V1 cannot appear in an unchained transform even if a broken
+	 * peer lists it in the algorithm array.
+	 */
+	server->compression.chained =
+		ctxt->Flags == SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED;
+	if (!server->compression.chained)
+		server->compression.pattern = false;
 	server->compression.enabled = true;
 }
 
diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h
index 30d7009..b9bf2fa 100644
--- a/fs/smb/client/smb2pdu.h
+++ b/fs/smb/client/smb2pdu.h
@@ -195,10 +195,6 @@ struct network_resiliency_req {
 
 #define NO_FILE_ID 0xFFFFFFFFFFFFFFFFULL /* general ioctls to srv not to file */
 
-struct compress_ioctl {
-	__le16 CompressionState; /* See cifspdu.h for possible flag values */
-} __packed;
-
 /*
  * Maximum number of iovs we need for an ioctl request.
  * [0] : struct smb2_ioctl_req
diff --git a/fs/smb/common/Makefile b/fs/smb/common/Makefile
index 9e0730a..f2c6e09 100644
--- a/fs/smb/common/Makefile
+++ b/fs/smb/common/Makefile
@@ -4,3 +4,6 @@
 #
 
 obj-$(CONFIG_SMBFS) += cifs_md4.o
+obj-$(CONFIG_SMBFS) += smb_compress.o
+
+smb_compress-y := compress/compress.o compress/lz77.o
diff --git a/fs/smb/common/compress/compress.c b/fs/smb/common/compress/compress.c
new file mode 100644
index 0000000..b07a317
--- /dev/null
+++ b/fs/smb/common/compress/compress.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SMB2 compression transform helpers.
+ *
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ */
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#include "compress.h"
+#include "lz77.h"
+
+#define SMB2_COMPRESSION_CHAINED_HDR_LEN \
+	offsetof(struct smb2_compression_hdr, CompressionAlgorithm)
+#define SMB2_COMPRESSION_PAYLOAD_BASE_LEN \
+	(sizeof(struct smb2_compression_payload_hdr) - sizeof(__le32))
+
+/*
+ * A NONE payload carries bytes verbatim. Keep both cursors and remaining
+ * lengths together so every chained payload handler applies identical bounds
+ * accounting.
+ */
+static int smb_decompress_none(const u8 **src, u32 *slen, u8 **dst, u32 *dlen,
+			       u32 len)
+{
+	if (len > *slen || len > *dlen)
+		return -EINVAL;
+
+	memcpy(*dst, *src, len);
+	*src += len;
+	*slen -= len;
+	*dst += len;
+	*dlen -= len;
+	return 0;
+}
+
+/*
+ * Pattern_V1 represents a run of one byte. Its wire payload is always the
+ * fixed-size smb2_compression_pattern_v1 structure.
+ */
+static int smb_decompress_pattern(const u8 **src, u32 *slen, u8 **dst,
+				  u32 *dlen, u32 len)
+{
+	const struct smb2_compression_pattern_v1 *pattern;
+	u32 repetitions;
+
+	if (len != sizeof(*pattern) || len > *slen)
+		return -EINVAL;
+
+	pattern = (const struct smb2_compression_pattern_v1 *)*src;
+	repetitions = le32_to_cpu(pattern->Repetitions);
+	if (repetitions > *dlen)
+		return -EINVAL;
+
+	memset(*dst, pattern->Pattern, repetitions);
+	*src += len;
+	*slen -= len;
+	*dst += repetitions;
+	*dlen -= repetitions;
+	return 0;
+}
+
+/*
+ * LZ77 payload Length includes the four-byte OriginalPayloadSize field.
+ * Consume that field before passing the compressed stream to the raw codec.
+ */
+static int smb_decompress_lz77_payload(const u8 **src, u32 *slen, u8 **dst,
+				       u32 *dlen, u32 len)
+{
+	u32 orig_size;
+	int rc;
+
+	if (len < sizeof(__le32) || len > *slen)
+		return -EINVAL;
+
+	orig_size = get_unaligned_le32(*src);
+	if (orig_size > *dlen)
+		return -EINVAL;
+
+	*src += sizeof(__le32);
+	*slen -= sizeof(__le32);
+	len -= sizeof(__le32);
+
+	rc = smb_lz77_decompress(*src, len, *dst, orig_size);
+	if (rc)
+		return rc;
+
+	*src += len;
+	*slen -= len;
+	*dst += orig_size;
+	*dlen -= orig_size;
+	return 0;
+}
+
+static int smb_decompress_chained(__le16 alg, bool allow_chained,
+				  const struct smb2_compression_hdr *hdr,
+				  u32 slen, void *dst, u32 dlen)
+{
+	const struct smb2_compression_payload_hdr *payload;
+	const u8 *src = (const u8 *)hdr + SMB2_COMPRESSION_CHAINED_HDR_LEN;
+	u32 orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+	u32 remaining = slen - SMB2_COMPRESSION_CHAINED_HDR_LEN;
+	u8 *out = dst;
+	u32 out_remaining = dlen;
+	bool first = true;
+	int rc;
+
+	if (!allow_chained || orig_size != dlen)
+		return -EINVAL;
+
+	/*
+	 * The chained transform has an eight-byte top-level header. The next
+	 * bytes are a sequence of payload headers whose Length fields account
+	 * for payload data, including OriginalPayloadSize where applicable.
+	 */
+	while (remaining) {
+		__le16 payload_alg;
+		__le16 flags;
+		u32 len;
+
+		if (remaining < SMB2_COMPRESSION_PAYLOAD_BASE_LEN)
+			return -EINVAL;
+
+		payload = (const struct smb2_compression_payload_hdr *)src;
+		payload_alg = payload->CompressionAlgorithm;
+		flags = payload->Flags;
+		len = le32_to_cpu(payload->Length);
+
+		/*
+		 * CHAINED marks only the first payload. Requiring NONE on every
+		 * later payload rejects ambiguous or independently chained data.
+		 */
+		if ((first && flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) ||
+		    (!first && flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)))
+			return -EINVAL;
+
+		src += SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
+		remaining -= SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
+
+		if (payload_alg == SMB3_COMPRESS_NONE) {
+			rc = smb_decompress_none(&src, &remaining, &out,
+						 &out_remaining, len);
+		} else if (payload_alg == SMB3_COMPRESS_PATTERN) {
+			rc = smb_decompress_pattern(&src, &remaining, &out,
+						    &out_remaining, len);
+		} else if (payload_alg == alg && alg == SMB3_COMPRESS_LZ77) {
+			rc = smb_decompress_lz77_payload(&src, &remaining, &out,
+							 &out_remaining, len);
+		} else {
+			return -EINVAL;
+		}
+		if (rc)
+			return rc;
+		first = false;
+	}
+
+	return out_remaining ? -EINVAL : 0;
+}
+
+static int smb_decompress_unchained(__le16 alg,
+				    const struct smb2_compression_hdr *hdr,
+				    u32 slen, void *dst, u32 dlen)
+{
+	u32 orig_size, offset, comp_size;
+
+	if (hdr->CompressionAlgorithm != alg ||
+	    !smb_compress_alg_valid(hdr->CompressionAlgorithm, false))
+		return -EINVAL;
+
+	orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+	offset = le32_to_cpu(hdr->Offset);
+	if (offset > slen - sizeof(*hdr) || offset > dlen ||
+	    orig_size > dlen - offset || orig_size + offset != dlen)
+		return -EINVAL;
+
+	memcpy(dst, (const u8 *)hdr + sizeof(*hdr), offset);
+	comp_size = slen - sizeof(*hdr) - offset;
+	return smb_lz77_decompress((const u8 *)hdr + sizeof(*hdr) + offset,
+				   comp_size, (u8 *)dst + offset, orig_size);
+}
+
+/**
+ * smb_compression_decompress() - decode an SMB2 compression transform
+ * @alg: negotiated general-purpose compression algorithm
+ * @allow_chained: whether chained transforms were negotiated
+ * @src: transform header followed by compressed payload data
+ * @slen: total number of bytes available at @src
+ * @dst: output buffer for the reconstructed SMB2 message
+ * @dlen: exact expected size of the reconstructed SMB2 message
+ *
+ * Validate the transform type and negotiated capabilities before dispatching
+ * to the chained or unchained decoder. The caller supplies the expected output
+ * size after applying its transport-specific message size limits.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int smb_compression_decompress(__le16 alg, bool allow_chained,
+			       const void *src, u32 slen, void *dst, u32 dlen)
+{
+	const struct smb2_compression_hdr *hdr = src;
+
+	if (!src || !dst || slen < sizeof(*hdr) ||
+	    hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID ||
+	    alg == SMB3_COMPRESS_NONE)
+		return -EINVAL;
+
+	if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED))
+		return smb_decompress_chained(alg, allow_chained, hdr, slen,
+					      dst, dlen);
+
+	if (hdr->Flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE))
+		return -EINVAL;
+
+	return smb_decompress_unchained(alg, hdr, slen, dst, dlen);
+}
+EXPORT_SYMBOL_GPL(smb_compression_decompress);
+
+struct smb_compression_builder {
+	u8 *pos;
+	u32 remaining;
+	bool first;
+};
+
+/*
+ * Reserve one chained payload header and initialize its common fields.
+ * OriginalPayloadSize is present only for LZNT1/LZ77/LZ77+Huffman payloads.
+ */
+static struct smb2_compression_payload_hdr *
+smb_compression_add_payload(struct smb_compression_builder *builder,
+			    __le16 alg, u32 payload_len, bool orig_size)
+{
+	struct smb2_compression_payload_hdr *payload;
+	u32 hdr_len = SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
+	u32 total_len;
+
+	if (orig_size)
+		hdr_len += sizeof(payload->OriginalPayloadSize);
+	if (check_add_overflow(hdr_len, payload_len, &total_len) ||
+	    total_len > builder->remaining)
+		return NULL;
+
+	payload = (struct smb2_compression_payload_hdr *)builder->pos;
+	payload->CompressionAlgorithm = alg;
+	payload->Flags = cpu_to_le16(builder->first ?
+		SMB2_COMPRESSION_FLAG_CHAINED : SMB2_COMPRESSION_FLAG_NONE);
+	payload->Length = cpu_to_le32(payload_len +
+		(orig_size ? sizeof(payload->OriginalPayloadSize) : 0));
+
+	builder->pos += hdr_len;
+	builder->remaining -= hdr_len;
+	builder->first = false;
+	return payload;
+}
+
+static int smb_compression_add_pattern(struct smb_compression_builder *builder,
+				       u8 pattern, u32 repetitions)
+{
+	struct smb2_compression_pattern_v1 *payload;
+
+	if (!smb_compression_add_payload(builder, SMB3_COMPRESS_PATTERN,
+					 sizeof(*payload), false))
+		return -ENOSPC;
+
+	payload = (struct smb2_compression_pattern_v1 *)builder->pos;
+	payload->Pattern = pattern;
+	payload->Reserved1 = 0;
+	payload->Reserved2 = 0;
+	payload->Repetitions = cpu_to_le32(repetitions);
+	builder->pos += sizeof(*payload);
+	builder->remaining -= sizeof(*payload);
+	return 0;
+}
+
+static int smb_compression_add_none(struct smb_compression_builder *builder,
+				    const u8 *src, u32 len)
+{
+	if (!smb_compression_add_payload(builder, SMB3_COMPRESS_NONE, len, false))
+		return -ENOSPC;
+
+	memcpy(builder->pos, src, len);
+	builder->pos += len;
+	builder->remaining -= len;
+	return 0;
+}
+
+static int smb_compression_add_lz77(struct smb_compression_builder *builder,
+				    const u8 *src, u32 len)
+{
+	struct smb2_compression_payload_hdr *payload;
+	u32 comp_len;
+	int rc;
+
+	if (builder->remaining <= sizeof(*payload))
+		return -ENOSPC;
+
+	comp_len = builder->remaining - sizeof(*payload);
+	payload = smb_compression_add_payload(builder, SMB3_COMPRESS_LZ77,
+					      comp_len, true);
+	if (!payload)
+		return -ENOSPC;
+
+	rc = smb_lz77_compress(src, len, builder->pos, &comp_len);
+	if (rc)
+		return rc;
+
+	payload->Length = cpu_to_le32(comp_len +
+				      sizeof(payload->OriginalPayloadSize));
+	payload->OriginalPayloadSize = cpu_to_le32(len);
+	builder->pos += comp_len;
+	builder->remaining -= comp_len;
+	return 0;
+}
+
+/**
+ * smb_compression_compress_chained() - build a chained SMB2 transform
+ * @alg: negotiated general-purpose compression algorithm
+ * @allow_pattern: whether Pattern_V1 was negotiated
+ * @src: complete uncompressed SMB2 message
+ * @slen: size of @src
+ * @dst: output buffer for the transform
+ * @dlen: input capacity of @dst and output transform size
+ *
+ * Following the algorithm in [MS-SMB2] 3.1.4.4, encode sufficiently long
+ * repeated runs at the front and back as Pattern_V1 payloads. Compress a
+ * middle region larger than 1 KiB with LZ77; smaller middle regions are
+ * represented by a chained NONE payload.
+ *
+ * This helper does not decide whether the final transform is smaller than the
+ * original message. The transport caller owns that policy decision.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int smb_compression_compress_chained(__le16 alg, bool allow_pattern,
+				     const void *src, u32 slen,
+				     void *dst, u32 *dlen)
+{
+	struct smb2_compression_hdr *hdr = dst;
+	struct smb_compression_builder builder;
+	const u8 *input = src;
+	u32 forward = 0, backward = 0, middle_len;
+	int rc;
+
+	if (!src || !dst || !dlen || alg != SMB3_COMPRESS_LZ77 ||
+	    *dlen <= SMB2_COMPRESSION_CHAINED_HDR_LEN || !slen)
+		return -EINVAL;
+
+	hdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
+	hdr->OriginalCompressedSegmentSize = cpu_to_le32(slen);
+	builder.pos = (u8 *)dst + SMB2_COMPRESSION_CHAINED_HDR_LEN;
+	builder.remaining = *dlen - SMB2_COMPRESSION_CHAINED_HDR_LEN;
+	builder.first = true;
+
+	if (allow_pattern && slen > 32) {
+		for (forward = 1; forward < slen; forward++) {
+			if (input[forward] != input[0])
+				break;
+		}
+		if (forward <= 32)
+			forward = 0;
+
+		for (backward = 1; backward < slen - forward; backward++) {
+			if (input[slen - backward - 1] != input[slen - 1])
+				break;
+		}
+		if (backward <= 32)
+			backward = 0;
+	}
+
+	if (forward) {
+		rc = smb_compression_add_pattern(&builder, input[0], forward);
+		if (rc)
+			return rc;
+	}
+
+	middle_len = slen - forward - backward;
+	if (middle_len > 1024)
+		rc = smb_compression_add_lz77(&builder, input + forward,
+					      middle_len);
+	else if (middle_len)
+		rc = smb_compression_add_none(&builder,
+					      input + forward, middle_len);
+	else
+		rc = 0;
+	if (rc)
+		return rc;
+
+	if (backward) {
+		rc = smb_compression_add_pattern(&builder, input[slen - 1],
+						 backward);
+		if (rc)
+			return rc;
+	}
+
+	*dlen = builder.pos - (u8 *)dst;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(smb_compression_compress_chained);
diff --git a/fs/smb/common/compress/compress.h b/fs/smb/common/compress/compress.h
new file mode 100644
index 0000000..7ace3bf
--- /dev/null
+++ b/fs/smb/common/compress/compress.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ */
+#ifndef _COMMON_SMB_COMPRESS_H
+#define _COMMON_SMB_COMPRESS_H
+
+#include "../smb2pdu.h"
+
+/*
+ * SMB3_COMPRESS_NONE is valid only in chained payload headers. It is never
+ * negotiated as a compression algorithm.
+ */
+static __always_inline bool smb_compress_alg_valid(__le16 alg, bool valid_none)
+{
+	if (alg == SMB3_COMPRESS_NONE)
+		return valid_none;
+
+	return alg == SMB3_COMPRESS_LZ77 || alg == SMB3_COMPRESS_PATTERN;
+}
+
+int smb_compression_decompress(__le16 alg, bool allow_chained,
+			       const void *src, u32 slen, void *dst, u32 dlen);
+int smb_compression_compress_chained(__le16 alg, bool allow_pattern,
+				     const void *src, u32 slen,
+				     void *dst, u32 *dlen);
+
+#endif /* _COMMON_SMB_COMPRESS_H */
diff --git a/fs/smb/client/compress/lz77.c b/fs/smb/common/compress/lz77.c
similarity index 78%
rename from fs/smb/client/compress/lz77.c
rename to fs/smb/common/compress/lz77.c
index 7365d0f..9216d97 100644
--- a/fs/smb/client/compress/lz77.c
+++ b/fs/smb/common/compress/lz77.c
@@ -1,8 +1,10 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
  * Copyright (C) 2024-2026, SUSE LLC
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
  *
  * Authors: Enzo Matsumiya <ematsumiya@suse.de>
+ *          Namjae Jeon <linkinjeon@kernel.org>
  *
  * Implementation of the LZ77 "plain" compression algorithm, as per MS-XCA spec.
  */
@@ -10,6 +12,8 @@
 #include <linux/sizes.h>
 #include <linux/count_zeros.h>
 #include <linux/unaligned.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
 
 #include "lz77.h"
 
@@ -32,7 +36,7 @@
  */
 #define LZ77_MATCH_MAX_DIST	SZ_8K
 #define LZ77_HASH_LOG		15
-#define LZ77_HASH_SIZE		(1 << LZ77_HASH_LOG)
+#define LZ77_HASH_SIZE		BIT(LZ77_HASH_LOG)
 #define LZ77_RSTEP_SIZE		sizeof(u32)
 #define LZ77_MSTEP_SIZE		sizeof(u64)
 #define LZ77_SKIP_TRIGGER	4
@@ -215,7 +219,8 @@ static __always_inline u32 lz77_hash(const u32 v)
 	return ((v ^ 0x9E3779B9) * 0x85EBCA6B) >> (32 - LZ77_HASH_LOG);
 }
 
-noinline int lz77_compress(const void *src, const u32 slen, void *dst, u32 *dlen)
+noinline int smb_lz77_compress(const void *src, const u32 slen,
+			       void *dst, u32 *dlen)
 {
 	const void *srcp, *rlim, *end, *anchor;
 	u32 *htable, hash, flag_count = 0;
@@ -223,10 +228,11 @@ noinline int lz77_compress(const void *src, const u32 slen, void *dst, u32 *dlen
 	long flag = 0;
 
 	/* This is probably a bug, so throw a warning. */
-	if (WARN_ON_ONCE(*dlen < lz77_compressed_alloc_size(slen)))
+	if (WARN_ON_ONCE(*dlen < smb_lz77_compressed_alloc_size(slen)))
 		return -EINVAL;
 
-	srcp = anchor = src;
+	srcp = src;
+	anchor = src;
 	end = srcp + slen; /* absolute end */
 	rlim = end - LZ77_MSTEP_SIZE; /* read limit (for lz77_match_len()) */
 	dstp = dst;
@@ -251,7 +257,8 @@ noinline int lz77_compress(const void *src, const u32 slen, void *dst, u32 *dlen
 	/*
 	 * Main loop.
 	 *
-	 * @dlen is >= lz77_compressed_alloc_size(), so run without bound-checking @dstp.
+	 * @dlen is >= smb_lz77_compressed_alloc_size(), so run without
+	 * bound-checking @dstp.
 	 *
 	 * This code was crafted in a way to best utilise fetch-decode-execute CPU flow.
 	 * Any attempt to optimize it, or even organize it, can lead to huge performance loss.
@@ -333,3 +340,122 @@ out:
 
 	return -EMSGSIZE;
 }
+EXPORT_SYMBOL_GPL(smb_lz77_compress);
+
+static int lz77_decode_match_len(const u8 **src, const u8 *end, u16 token,
+				 u8 *nibble, bool *have_nibble, u32 *len)
+{
+	u8 extra;
+
+	*len = (token & 0x7) + 3;
+	if ((token & 0x7) != 0x7)
+		return 0;
+
+	if (!*have_nibble) {
+		if (*src >= end)
+			return -EINVAL;
+		*nibble = *(*src)++;
+		extra = *nibble & 0xf;
+		*have_nibble = true;
+	} else {
+		extra = *nibble >> 4;
+		*have_nibble = false;
+	}
+
+	*len += extra;
+	if (extra == 0xf) {
+		u8 b;
+
+		if (*src >= end)
+			return -EINVAL;
+		b = *(*src)++;
+		if (b != 0xff) {
+			*len += b;
+		} else {
+			u16 w;
+
+			if (end - *src < 2)
+				return -EINVAL;
+			w = get_unaligned_le16(*src);
+			*src += 2;
+			if (w) {
+				*len = w + 3;
+			} else {
+				u32 long_len;
+
+				if (end - *src < 4)
+					return -EINVAL;
+				long_len = get_unaligned_le32(*src);
+				*src += 4;
+				if (check_add_overflow(long_len, 3, len))
+					return -EINVAL;
+			}
+		}
+	}
+
+	return 0;
+}
+
+int smb_lz77_decompress(const void *src, const u32 slen, void *dst,
+			const u32 dlen)
+{
+	const u8 *sp = src, *send = sp + slen;
+	u8 *dp = dst, *dend = dp + dlen;
+	u32 flags = 0;
+	int flag_count = 0;
+	u8 nibble = 0;
+	bool have_nibble = false;
+
+	while (dp < dend) {
+		u32 len, dist;
+		u16 token;
+
+		if (!flag_count) {
+			if (send - sp < 4)
+				return -EINVAL;
+			flags = get_unaligned_le32(sp);
+			sp += 4;
+			flag_count = 32;
+		}
+
+		if (!(flags & 0x80000000)) {
+			if (sp >= send)
+				return -EINVAL;
+			*dp++ = *sp++;
+			flags <<= 1;
+			flag_count--;
+			continue;
+		}
+
+		flags <<= 1;
+		flag_count--;
+
+		if (send - sp < 2)
+			return -EINVAL;
+
+		token = get_unaligned_le16(sp);
+		sp += 2;
+
+		dist = (token >> 3) + 1;
+		if (dist > dp - (u8 *)dst)
+			return -EINVAL;
+
+		if (lz77_decode_match_len(&sp, send, token, &nibble,
+					  &have_nibble, &len))
+			return -EINVAL;
+
+		if (len > dend - dp)
+			return -EINVAL;
+
+		while (len--) {
+			*dp = *(dp - dist);
+			dp++;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(smb_lz77_decompress);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SMB plain LZ77 compression");
diff --git a/fs/smb/client/compress/lz77.h b/fs/smb/common/compress/lz77.h
similarity index 77%
rename from fs/smb/client/compress/lz77.h
rename to fs/smb/common/compress/lz77.h
index 4e57084..e032c0f 100644
--- a/fs/smb/client/compress/lz77.h
+++ b/fs/smb/common/compress/lz77.h
@@ -12,7 +12,7 @@
 #include <linux/kernel.h>
 
 /**
- * lz77_compressed_alloc_size() - Compute compressed buffer size.
+ * smb_lz77_compressed_alloc_size() - Compute compressed buffer size.
  * @size:	uncompressed (src) size
  *
  * Compute allocation size for the compressed buffer based on uncompressed size.
@@ -26,18 +26,21 @@
  * Worst case scenario is an all-literal compression, which means:
  * metadata bytes = 4 + ((@size / 32) * 4) + 4, or, simplified, (@size >> 3) + 8
  *
- * The worst case scenario rarely happens, but such overprovisioning also allows lz77_compress()
- * main loop to run without ever bound checking dst, which is a huge perf improvement, while also
- * being safe when compression goes bad.
+ * The worst case scenario rarely happens, but such overprovisioning also
+ * allows smb_lz77_compress() main loop to run without ever bound checking dst,
+ * which is a huge perf improvement, while also being safe when compression goes
+ * bad.
  *
  * Return: required (*) allocation size for compressed buffer.
  *
- * (*) checked once in the beginning of lz77_compress()
+ * (*) checked once in the beginning of smb_lz77_compress()
  */
-static __always_inline u32 lz77_compressed_alloc_size(const u32 size)
+static __always_inline u32 smb_lz77_compressed_alloc_size(const u32 size)
 {
 	return size + (size >> 3) + 8;
 }
 
-int lz77_compress(const void *src, const u32 slen, void *dst, u32 *dlen);
+int smb_lz77_compress(const void *src, const u32 slen, void *dst, u32 *dlen);
+int smb_lz77_decompress(const void *src, const u32 slen, void *dst,
+			const u32 dlen);
 #endif /* _SMB_COMPRESS_LZ77_H */
diff --git a/fs/smb/common/fscc.h b/fs/smb/common/fscc.h
index bc3012c..859849a 100644
--- a/fs/smb/common/fscc.h
+++ b/fs/smb/common/fscc.h
@@ -100,6 +100,24 @@ struct duplicate_extents_to_file_ex {
 	__le32 Reserved;
 } __packed;
 
+/*
+ * compression state flags
+ * See MS-FSCC 2.3.18
+ *     MS-FSCC 2.3.67
+ *     MS-FSCC 2.4.9
+ */
+#define COMPRESSION_FORMAT_NONE	0x0000
+#define COMPRESSION_FORMAT_DEFAULT	0x0001
+#define COMPRESSION_FORMAT_LZNT1	0x0002
+
+/*
+ * See MS-FSCC 2.3.18
+ *     MS-FSCC 2.3.67
+ */
+struct compress_ioctl {
+	__le16 CompressionState;
+} __packed;
+
 /* See MS-FSCC 2.3.20 */
 struct fsctl_get_integrity_information_rsp {
 	__le16	ChecksumAlgorithm;
diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h
index aeb0a24..325ff83 100644
--- a/fs/smb/common/smb2pdu.h
+++ b/fs/smb/common/smb2pdu.h
@@ -218,10 +218,9 @@ struct smb2_transform_hdr {
  * These are simplified versions from the spec, as we don't need a fully fledged
  * form of both unchained and chained structs.
  *
- * Moreover, even in chained compressed payloads, the initial compression header
- * has the form of the unchained one -- i.e. it never has the
- * OriginalPayloadSize field and ::Offset field always represent an offset
- * (instead of a length, as it is in the chained header).
+ * For chained payloads, only the first 8 bytes belong to the transform header.
+ * CompressionAlgorithm, Flags and Offset below overlay the first chained
+ * payload header, where Offset represents Length.
  *
  * See MS-SMB2 2.2.42 for more details.
  */
@@ -524,9 +523,7 @@ struct smb2_compression_capabilities_context {
 	__le16	CompressionAlgorithmCount;
 	__le16	Padding;
 	__le32	Flags;
-	__le16	CompressionAlgorithms[3];
-	__u16	Pad;  /* Some servers require pad to DataLen multiple of 8 */
-	/* Check if pad needed */
+	__le16	CompressionAlgorithms[4];
 } __packed;
 
 /*
diff --git a/fs/smb/common/smbfsctl.h b/fs/smb/common/smbfsctl.h
index 3253a18..d1fcb46 100644
--- a/fs/smb/common/smbfsctl.h
+++ b/fs/smb/common/smbfsctl.h
@@ -61,8 +61,8 @@
 #define FSCTL_LOCK_VOLUME            0x00090018
 #define FSCTL_UNLOCK_VOLUME          0x0009001C
 #define FSCTL_IS_PATHNAME_VALID      0x0009002C /* BB add struct */
-#define FSCTL_GET_COMPRESSION        0x0009003C /* BB add struct */
-#define FSCTL_SET_COMPRESSION        0x0009C040 /* BB add struct */
+#define FSCTL_GET_COMPRESSION        0x0009003C
+#define FSCTL_SET_COMPRESSION        0x0009C040
 #define FSCTL_QUERY_FAT_BPB          0x00090058 /* BB add struct */
 /* Verify the next FSCTL number, we had it as 0x00090090 before */
 #define FSCTL_FILESYSTEM_GET_STATS   0x00090060 /* BB add struct */
@@ -159,6 +159,12 @@
 #define IO_REPARSE_TAG_LX_CHR	     0x80000025
 #define IO_REPARSE_TAG_LX_BLK	     0x80000026
 
+#define IO_REPARSE_TAG_LX_SYMLINK_LE   cpu_to_le32(IO_REPARSE_TAG_LX_SYMLINK)
+#define IO_REPARSE_TAG_AF_UNIX_LE      cpu_to_le32(IO_REPARSE_TAG_AF_UNIX)
+#define IO_REPARSE_TAG_LX_FIFO_LE      cpu_to_le32(IO_REPARSE_TAG_LX_FIFO)
+#define IO_REPARSE_TAG_LX_CHR_LE       cpu_to_le32(IO_REPARSE_TAG_LX_CHR)
+#define IO_REPARSE_TAG_LX_BLK_LE       cpu_to_le32(IO_REPARSE_TAG_LX_BLK)
+
 /* If Name Surrogate Bit is set, the file or directory represents another named entity in the system. */
 #define IS_REPARSE_TAG_NAME_SURROGATE(tag) (!!((tag) & 0x20000000))
 
diff --git a/fs/smb/server/Makefile b/fs/smb/server/Makefile
index 6407ba6..a3e9306 100644
--- a/fs/smb/server/Makefile
+++ b/fs/smb/server/Makefile
@@ -10,7 +10,7 @@ ksmbd-y :=	unicode.o auth.o vfs.o vfs_cache.o server.o ndr.o \
 		mgmt/tree_connect.o mgmt/user_session.o smb_common.o \
 		transport_tcp.o transport_ipc.o smbacl.o smb2pdu.o \
 		smb2ops.o smb2misc.o ksmbd_spnego_negtokeninit.asn1.o \
-		ksmbd_spnego_negtokentarg.asn1.o asn1.o
+		ksmbd_spnego_negtokentarg.asn1.o asn1.o compress.o
 
 $(obj)/asn1.o: $(obj)/ksmbd_spnego_negtokeninit.asn1.h $(obj)/ksmbd_spnego_negtokentarg.asn1.h
 
diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c
new file mode 100644
index 0000000..f8cf515
--- /dev/null
+++ b/fs/smb/server/compress.c
@@ -0,0 +1,209 @@
+// 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
+
+/**
+ * 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)
+{
+	struct smb2_compression_hdr *hdr;
+	unsigned int pdu_size = get_rfc1002_len(conn->request_buf);
+	u32 orig_size, offset, out_size;
+	u32 max_allowed_pdu_size;
+	char *buf, *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(conn->request_buf);
+	if (hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID)
+		return -EINVAL;
+
+	orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+	if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) {
+		out_size = orig_size;
+	} else {
+		offset = le32_to_cpu(hdr->Offset);
+		if (offset > pdu_size - sizeof(*hdr) ||
+		    check_add_overflow(orig_size, offset, &out_size))
+			return -EINVAL;
+	}
+
+	max_allowed_pdu_size = SMB3_MAX_MSGSIZE + conn->vals->max_write_size;
+	if (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;
+
+	buf = (char *)hdr;
+	*(__be32 *)out = cpu_to_be32(out_size);
+	rc = smb_compression_decompress(conn->compress_algorithm,
+					conn->compress_chained,
+					buf, pdu_size, out + 4, out_size);
+	if (rc) {
+		kvfree(out);
+		return rc;
+	}
+
+	kvfree(conn->request_buf);
+	conn->request_buf = out;
+	return 0;
+}
+
+/**
+ * ksmbd_compress_response() - compress an eligible ksmbd response
+ * @work: request work item containing the response iov
+ *
+ * Compression transforms describe one contiguous SMB2 message, while ksmbd
+ * builds responses from multiple iov entries. Flatten the response first,
+ * produce the negotiated transform, and replace the response iov only when the
+ * result is smaller than the original message.
+ *
+ * Encrypted and compound responses are intentionally left unchanged. The
+ * caller may still continue sending the original response when this function
+ * returns zero.
+ *
+ * Return: 1 if the response was replaced, 0 if compression was skipped, or a
+ * negative errno on failure.
+ */
+int ksmbd_compress_response(struct ksmbd_work *work)
+{
+	struct smb2_compression_hdr *chdr;
+	struct smb2_hdr *req_hdr;
+	u32 src_len, dst_len, compressed_pdu_len, max_dst_len;
+	u8 *src = NULL, *out = NULL, *p;
+	int i, rc;
+
+	if (!work->compress_response || work->encrypted ||
+	    work->conn->compress_algorithm != SMB3_COMPRESS_LZ77)
+		return 0;
+
+	req_hdr = smb_get_msg(work->request_buf);
+	if (req_hdr->NextCommand || work->next_smb2_rcv_hdr_off ||
+	    work->next_smb2_rsp_hdr_off)
+		return 0;
+
+	src_len = get_rfc1002_len(work->iov[0].iov_base);
+	if (src_len < SMB_COMPRESS_MIN_LEN)
+		return 0;
+
+	src = kvmalloc(src_len, KSMBD_DEFAULT_GFP);
+	if (!src)
+		return -ENOMEM;
+
+	p = src;
+	/* iov[0] contains only the RFC1002 length; the SMB2 PDU starts at iov[1]. */
+	for (i = 1; i < work->iov_cnt; i++) {
+		if (work->iov[i].iov_len > src + src_len - p) {
+			rc = -EINVAL;
+			goto out;
+		}
+		memcpy(p, work->iov[i].iov_base, work->iov[i].iov_len);
+		p += work->iov[i].iov_len;
+	}
+	if (p != src + src_len) {
+		rc = -EINVAL;
+		goto out;
+	}
+
+	max_dst_len = smb_lz77_compressed_alloc_size(src_len) +
+		sizeof(struct smb2_compression_hdr) +
+		3 * sizeof(struct smb2_compression_payload_hdr) +
+		2 * sizeof(struct smb2_compression_pattern_v1);
+	out = kvzalloc(sizeof(__be32) + max_dst_len,
+		       KSMBD_DEFAULT_GFP);
+	if (!out) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	if (work->conn->compress_chained) {
+		dst_len = max_dst_len;
+		rc = smb_compression_compress_chained(SMB3_COMPRESS_LZ77,
+						      work->conn->compress_pattern,
+						      src, src_len,
+						      out + sizeof(__be32),
+						      &dst_len);
+		if (rc == -EMSGSIZE || dst_len >= src_len) {
+			rc = 0;
+			goto out;
+		}
+		if (rc)
+			goto out;
+		compressed_pdu_len = dst_len;
+	} else {
+		/*
+		 * Peers which did not negotiate chained compression still use
+		 * the original 16-byte unchained transform format.
+		 */
+		dst_len = smb_lz77_compressed_alloc_size(src_len);
+		rc = smb_lz77_compress(src, src_len,
+				       out + sizeof(__be32) + sizeof(*chdr),
+				       &dst_len);
+		if (rc == -EMSGSIZE ||
+		    dst_len + sizeof(*chdr) >= src_len) {
+			rc = 0;
+			goto out;
+		}
+		if (rc)
+			goto out;
+
+		compressed_pdu_len = sizeof(*chdr) + dst_len;
+		chdr = (struct smb2_compression_hdr *)(out + sizeof(__be32));
+		chdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
+		chdr->OriginalCompressedSegmentSize = cpu_to_le32(src_len);
+		chdr->CompressionAlgorithm = SMB3_COMPRESS_LZ77;
+		chdr->Flags = cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE);
+		chdr->Offset = 0;
+	}
+
+	*(__be32 *)out = cpu_to_be32(compressed_pdu_len);
+
+	/*
+	 * Keep the transform in work->compress_buf until send completion.
+	 * Existing response iovs can then be replaced without changing their
+	 * individual ownership rules.
+	 */
+	work->compress_buf = out;
+	work->iov[0].iov_base = out;
+	work->iov[0].iov_len = sizeof(__be32);
+	work->iov[1].iov_base = out + sizeof(__be32);
+	work->iov[1].iov_len = compressed_pdu_len;
+	work->iov_cnt = 2;
+	work->iov_idx = 1;
+	out = NULL;
+	rc = 1;
+out:
+	kvfree(out);
+	kvfree(src);
+	return rc;
+}
diff --git a/fs/smb/server/compress.h b/fs/smb/server/compress.h
new file mode 100644
index 0000000..663c6f4
--- /dev/null
+++ b/fs/smb/server/compress.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * SMB2 compression support for ksmbd.
+ *
+ * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
+ */
+#ifndef __KSMBD_COMPRESS_H__
+#define __KSMBD_COMPRESS_H__
+
+#include "connection.h"
+#include "../common/compress/compress.h"
+
+int ksmbd_decompress_request(struct ksmbd_conn *conn);
+int ksmbd_compress_response(struct ksmbd_work *work);
+
+#endif /* __KSMBD_COMPRESS_H__ */
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 8347495..9e8fdb3 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -12,6 +12,7 @@
 #include "smb_common.h"
 #include "mgmt/ksmbd_ida.h"
 #include "connection.h"
+#include "compress.h"
 #include "transport_tcp.h"
 #include "transport_rdma.h"
 #include "misc.h"
@@ -531,6 +532,17 @@ recheck:
 			continue;
 		}
 
+		if (((struct smb2_hdr *)smb_get_msg(conn->request_buf))->ProtocolId ==
+		    SMB2_COMPRESSION_TRANSFORM_ID) {
+			/*
+			 * Convert the transform into a normal RFC1002-framed SMB2
+			 * request before protocol validation and work allocation.
+			 */
+			if (ksmbd_decompress_request(conn))
+				break;
+			pdu_size = get_rfc1002_len(conn->request_buf);
+		}
+
 		if (!ksmbd_smb_request(conn))
 			break;
 
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index e074be9..ec75633 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -115,6 +115,9 @@ struct ksmbd_conn {
 
 	__le16				cipher_type;
 	__le16				compress_algorithm;
+	/* Negotiated SMB 3.1.1 compression capabilities. */
+	bool				compress_chained;
+	bool				compress_pattern;
 	bool				posix_ext_supported;
 	bool				signing_negotiated;
 	__le16				signing_algorithm;
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index ab4958d..a5ab679 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -53,6 +53,7 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
 	}
 
 	kfree(work->tr_buf);
+	kvfree(work->compress_buf);
 	kvfree(work->request_buf);
 	kfree(work->iov);
 
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index d36393f..0da8cc0 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -67,12 +67,16 @@ struct ksmbd_work {
 	unsigned int                    response_sz;
 
 	void				*tr_buf;
+	/* Contiguous SMB2 compression transform owned by this work item. */
+	void				*compress_buf;
 
 	unsigned char			state;
 	/* No response for cancelled request */
 	bool                            send_no_response:1;
 	/* Request is encrypted */
 	bool                            encrypted:1;
+	/* READ response should be wrapped in a compression transform. */
+	bool                            compress_response:1;
 	/* Is this SYNC or ASYNC ksmbd_work */
 	bool                            asynchronous:1;
 	bool                            need_invalidate_rkey:1;
diff --git a/fs/smb/server/misc.c b/fs/smb/server/misc.c
index a543ec9..966004c 100644
--- a/fs/smb/server/misc.c
+++ b/fs/smb/server/misc.c
@@ -283,39 +283,6 @@ char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename)
 	return ksmbd_casefold_sharename(um, name);
 }
 
-/**
- * convert_to_unix_name() - convert windows name to unix format
- * @share:	ksmbd_share_config pointer
- * @name:	file name that is relative to share
- *
- * Return:	converted name on success, otherwise NULL
- */
-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name)
-{
-	int no_slash = 0, name_len, path_len;
-	char *new_name;
-
-	if (name[0] == '/')
-		name++;
-
-	path_len = share->path_sz;
-	name_len = strlen(name);
-	new_name = kmalloc(path_len + name_len + 2, KSMBD_DEFAULT_GFP);
-	if (!new_name)
-		return new_name;
-
-	memcpy(new_name, share->path, path_len);
-	if (new_name[path_len - 1] != '/') {
-		new_name[path_len] = '/';
-		no_slash = 1;
-	}
-
-	memcpy(new_name + path_len + no_slash, name, name_len);
-	path_len += name_len + no_slash;
-	new_name[path_len] = 0x00;
-	return new_name;
-}
-
 char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info,
 				  const struct nls_table *local_nls,
 				  int *conv_len)
diff --git a/fs/smb/server/misc.h b/fs/smb/server/misc.h
index 1342369..3909104 100644
--- a/fs/smb/server/misc.h
+++ b/fs/smb/server/misc.h
@@ -25,7 +25,6 @@ void ksmbd_strip_last_slash(char *path);
 void ksmbd_conv_path_to_windows(char *path);
 char *ksmbd_casefold_sharename(struct unicode_map *um, const char *name);
 char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename);
-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name);
 
 #define KSMBD_DIR_INFO_ALIGNMENT	8
 struct ksmbd_dir_info;
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index b193dde..60e7e82 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -528,7 +528,12 @@ static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci,
 
 		ret = compare_guid_key(opinfo, client_guid, lctx->lease_key);
 		if (ret) {
+			if (!atomic_inc_not_zero(&opinfo->refcount))
+				continue;
+			if (m_opinfo)
+				opinfo_put(m_opinfo);
 			m_opinfo = opinfo;
+
 			/* skip upgrading lease about breaking lease */
 			if (atomic_read(&opinfo->breaking_cnt))
 				continue;
@@ -1246,6 +1251,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 			if (atomic_read(&m_opinfo->breaking_cnt))
 				opinfo->o_lease->flags =
 					SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE;
+			opinfo_put(m_opinfo);
 			goto out;
 		}
 	}
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 5d799b2..36feda7 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -22,6 +22,7 @@
 #include "crypto_ctx.h"
 #include "auth.h"
 #include "stats.h"
+#include "compress.h"
 
 int ksmbd_debug_types;
 
@@ -244,6 +245,15 @@ send:
 	if (work->tcon)
 		ksmbd_tree_connect_put(work->tcon);
 	smb3_preauth_hash_rsp(work);
+	/*
+	 * Preauthentication hashes cover the original SMB2 response. Apply the
+	 * transport compression wrapper only after updating the hash.
+	 */
+	if (work->compress_response) {
+		rc = ksmbd_compress_response(work);
+		if (rc < 0)
+			ksmbd_debug(CONN, "Failed to compress response: %d\n", rc);
+	}
 	if (work->sess && work->sess->enc && work->encrypted &&
 	    conn->ops->encrypt_resp) {
 		rc = conn->ops->encrypt_resp(work);
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 00e63de..c1c6c1a 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -17,7 +17,7 @@
 #include <linux/fileattr.h>
 
 #include "glob.h"
-#include "smbfsctl.h"
+#include "../common/smbfsctl.h"
 #include "oplock.h"
 #include "smbacl.h"
 
@@ -42,6 +42,7 @@
 #include "ndr.h"
 #include "stats.h"
 #include "transport_tcp.h"
+#include "compress.h"
 
 static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
 {
@@ -614,6 +615,11 @@ int smb2_check_user_session(struct ksmbd_work *work)
 					sess_id, work->sess->id);
 			return -EINVAL;
 		}
+		if (work->sess->state != SMB2_SESSION_VALID) {
+			pr_err("compound request on a non-valid session (state %d)\n",
+					work->sess->state);
+			return -EINVAL;
+		}
 		return 1;
 	}
 
@@ -804,6 +810,30 @@ static void build_encrypt_ctxt(struct smb2_encryption_neg_context *pneg_ctxt,
 	pneg_ctxt->Ciphers[0] = cipher_type;
 }
 
+static void build_compress_ctxt(struct smb2_compression_capabilities_context *pneg_ctxt,
+				__le16 compress_algorithm, bool compress_chained,
+				bool compress_pattern)
+{
+	/*
+	 * Return only algorithms implemented by ksmbd. Pattern_V1 is advertised
+	 * as a second ID when the client also enabled chained transforms.
+	 */
+	pneg_ctxt->ContextType = SMB2_COMPRESSION_CAPABILITIES;
+	pneg_ctxt->DataLength = cpu_to_le16(compress_pattern ? 12 : 10);
+	pneg_ctxt->Reserved = cpu_to_le32(0);
+	pneg_ctxt->CompressionAlgorithmCount =
+		cpu_to_le16(compress_pattern ? 2 : 1);
+	pneg_ctxt->Padding = cpu_to_le16(0);
+	pneg_ctxt->Flags = compress_chained ?
+		SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED :
+		SMB2_COMPRESSION_CAPABILITIES_FLAG_NONE;
+	pneg_ctxt->CompressionAlgorithms[0] = compress_algorithm;
+	pneg_ctxt->CompressionAlgorithms[1] = compress_pattern ?
+		SMB3_COMPRESS_PATTERN : 0;
+	pneg_ctxt->CompressionAlgorithms[2] = 0;
+	pneg_ctxt->CompressionAlgorithms[3] = 0;
+}
+
 static void build_sign_cap_ctxt(struct smb2_signing_capabilities *pneg_ctxt,
 				__le16 sign_algo)
 {
@@ -865,8 +895,19 @@ static unsigned int assemble_neg_contexts(struct ksmbd_conn *conn,
 		ctxt_size += sizeof(struct smb2_encryption_neg_context) + 2;
 	}
 
-	/* compression context not yet supported */
-	WARN_ON(conn->compress_algorithm != SMB3_COMPRESS_NONE);
+	if (conn->compress_algorithm != SMB3_COMPRESS_NONE) {
+		ctxt_size = round_up(ctxt_size, 8);
+		ksmbd_debug(SMB,
+			    "assemble SMB2_COMPRESSION_CAPABILITIES context\n");
+		build_compress_ctxt((struct smb2_compression_capabilities_context *)
+				    (pneg_ctxt + ctxt_size),
+				    conn->compress_algorithm,
+				    conn->compress_chained,
+				    conn->compress_pattern);
+		neg_ctxt_cnt++;
+		ctxt_size += sizeof(struct smb2_neg_context) +
+			(conn->compress_pattern ? 12 : 10);
+	}
 
 	if (conn->posix_ext_supported) {
 		ctxt_size = round_up(ctxt_size, 8);
@@ -970,10 +1011,59 @@ bool smb3_encryption_negotiated(struct ksmbd_conn *conn)
 	    conn->cipher_type;
 }
 
-static void decode_compress_ctxt(struct ksmbd_conn *conn,
-				 struct smb2_compression_capabilities_context *pneg_ctxt)
+static __le32 decode_compress_ctxt(struct ksmbd_conn *conn,
+				   struct smb2_compression_capabilities_context *pneg_ctxt,
+				   int ctxt_len)
 {
+	int alg_cnt, algs_size, i;
+
+	if (sizeof(struct smb2_neg_context) + 10 > ctxt_len) {
+		pr_err("Invalid SMB2_COMPRESSION_CAPABILITIES context length\n");
+		return STATUS_INVALID_PARAMETER;
+	}
+
 	conn->compress_algorithm = SMB3_COMPRESS_NONE;
+	conn->compress_chained = false;
+	conn->compress_pattern = false;
+
+	alg_cnt = le16_to_cpu(pneg_ctxt->CompressionAlgorithmCount);
+	if (!alg_cnt)
+		return STATUS_INVALID_PARAMETER;
+
+	if (pneg_ctxt->Flags != SMB2_COMPRESSION_CAPABILITIES_FLAG_NONE &&
+	    pneg_ctxt->Flags != SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED)
+		return STATUS_INVALID_PARAMETER;
+
+	algs_size = alg_cnt * sizeof(__le16);
+	if (sizeof(struct smb2_neg_context) + 8 + algs_size > ctxt_len) {
+		pr_err("Invalid compression algorithm count(%d)\n", alg_cnt);
+		return STATUS_INVALID_PARAMETER;
+	}
+
+	for (i = 0; i < alg_cnt; i++) {
+		__le16 alg = pneg_ctxt->CompressionAlgorithms[i];
+
+		/*
+		 * LZ77 is the required general-purpose codec. Pattern_V1 is an
+		 * optional chained payload type and cannot stand alone.
+		 */
+		if (alg == SMB3_COMPRESS_LZ77) {
+			conn->compress_algorithm = alg;
+			conn->compress_chained =
+				pneg_ctxt->Flags ==
+				SMB2_COMPRESSION_CAPABILITIES_FLAG_CHAINED;
+			ksmbd_debug(SMB, "Compression Algorithm ID = 0x%x\n",
+				    le16_to_cpu(alg));
+		} else if (alg == SMB3_COMPRESS_PATTERN) {
+			conn->compress_pattern = true;
+		}
+	}
+
+	if (conn->compress_algorithm == SMB3_COMPRESS_NONE ||
+	    !conn->compress_chained)
+		conn->compress_pattern = false;
+
+	return STATUS_SUCCESS;
 }
 
 static void decode_sign_cap_ctxt(struct ksmbd_conn *conn,
@@ -1021,6 +1111,7 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
 	unsigned int offset = le32_to_cpu(req->NegotiateContextOffset);
 	unsigned int neg_ctxt_cnt = le16_to_cpu(req->NegotiateContextCount);
 	__le32 status = STATUS_INVALID_PARAMETER;
+	int compress_ctxt_cnt = 0;
 
 	ksmbd_debug(SMB, "decoding %d negotiate contexts\n", neg_ctxt_cnt);
 	if (len_of_smb <= offset) {
@@ -1066,11 +1157,16 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
 		} else if (pctx->ContextType == SMB2_COMPRESSION_CAPABILITIES) {
 			ksmbd_debug(SMB,
 				    "deassemble SMB2_COMPRESSION_CAPABILITIES context\n");
-			if (conn->compress_algorithm)
+			if (compress_ctxt_cnt++) {
+				status = STATUS_INVALID_PARAMETER;
 				break;
+			}
 
-			decode_compress_ctxt(conn,
-					     (struct smb2_compression_capabilities_context *)pctx);
+			status = decode_compress_ctxt(conn,
+				(struct smb2_compression_capabilities_context *)
+				pctx, ctxt_len);
+			if (status != STATUS_SUCCESS)
+				break;
 		} else if (pctx->ContextType == SMB2_NETNAME_NEGOTIATE_CONTEXT_ID) {
 			ksmbd_debug(SMB,
 				    "deassemble SMB2_NETNAME_NEGOTIATE_CONTEXT_ID context\n");
@@ -2061,6 +2157,10 @@ out_err1:
 	rsp->Reserved = 0;
 	/* default manual caching */
 	rsp->ShareFlags = SMB2_SHAREFLAG_MANUAL_CACHING;
+	/* Tell the client that READ requests may request compressed responses. */
+	if (conn->dialect == SMB311_PROT_ID &&
+	    conn->compress_algorithm != SMB3_COMPRESS_NONE)
+		rsp->ShareFlags |= cpu_to_le32(SMB2_SHAREFLAG_COMPRESS_DATA);
 
 	rc = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_tree_connect_rsp));
 	if (rc)
@@ -3604,7 +3704,10 @@ int smb2_open(struct ksmbd_work *work)
 
 	if (!created)
 		smb2_update_xattrs(tcon, &path, fp);
-	else
+
+	ksmbd_vfs_update_compressed_fattr(path.dentry, &fp->f_ci->m_fattr);
+
+	if (created)
 		smb2_new_xattrs(tcon, &path, fp);
 
 	memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
@@ -4466,6 +4569,8 @@ int smb2_query_dir(struct ksmbd_work *work)
 		ksmbd_debug(SMB, "Search pattern is %s\n", srch_ptr);
 	}
 
+	mutex_lock(&dir_fp->readdir_lock);
+
 	if (srch_flag & SMB2_REOPEN || srch_flag & SMB2_RESTART_SCANS) {
 		ksmbd_debug(SMB, "Restart directory scan\n");
 		generic_file_llseek(dir_fp->filp, 0, SEEK_SET);
@@ -4570,6 +4675,7 @@ no_buf_len:
 			goto err_out;
 	}
 
+	mutex_unlock(&dir_fp->readdir_lock);
 	kfree(srch_ptr);
 	ksmbd_fd_put(work, dir_fp);
 	ksmbd_revert_fsids(work);
@@ -4577,6 +4683,7 @@ no_buf_len:
 
 err_out:
 	pr_err("error while processing smb2 query dir rc = %d\n", rc);
+	mutex_unlock(&dir_fp->readdir_lock);
 	kfree(srch_ptr);
 
 err_out2:
@@ -5276,6 +5383,7 @@ static int get_file_compression_info(struct smb2_query_info_rsp *rsp,
 {
 	struct smb2_file_comp_info *file_info;
 	struct kstat stat;
+	u16 fmt;
 	int ret;
 
 	ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
@@ -5283,9 +5391,13 @@ static int get_file_compression_info(struct smb2_query_info_rsp *rsp,
 	if (ret)
 		return ret;
 
+	ret = ksmbd_vfs_get_compression(fp, &fmt);
+	if (ret)
+		return ret;
+
 	file_info = (struct smb2_file_comp_info *)rsp->Buffer;
-	file_info->CompressedFileSize = cpu_to_le64(stat.blocks << 9);
-	file_info->CompressionFormat = COMPRESSION_FORMAT_NONE;
+	file_info->CompressedFileSize = cpu_to_le64(min_t(u64, stat.blocks << 9, stat.size));
+	file_info->CompressionFormat = cpu_to_le16(fmt);
 	file_info->CompressionUnitShift = 0;
 	file_info->ChunkShift = 0;
 	file_info->ClusterShift = 0;
@@ -5329,6 +5441,12 @@ static int find_file_posix_info(struct smb2_query_info_rsp *rsp,
 	int out_buf_len = sizeof(struct smb311_posix_qinfo) + 32;
 	int ret;
 
+	if (!(fp->daccess & FILE_READ_ATTRIBUTES_LE)) {
+		pr_err("no right to read the attributes : 0x%x\n",
+		       fp->daccess);
+		return -EACCES;
+	}
+
 	ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
 			  AT_STATX_SYNC_AS_STAT);
 	if (ret)
@@ -6567,6 +6685,11 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
 	}
 	case FILE_LINK_INFORMATION:
 	{
+		if (!(fp->daccess & FILE_DELETE_LE)) {
+			pr_err("no right to delete : 0x%x\n", fp->daccess);
+			return -EACCES;
+		}
+
 		if (buf_len < sizeof(struct smb2_file_link_info))
 			return -EMSGSIZE;
 
@@ -6624,6 +6747,9 @@ static int smb2_set_info_sec(struct ksmbd_file *fp, int addition_info,
 
 	fp->saccess |= FILE_SHARE_DELETE_LE;
 
+	if (!(fp->daccess & (FILE_WRITE_DAC_LE | FILE_WRITE_OWNER_LE)))
+		return -EACCES;
+
 	return set_info_sec(fp->conn, fp->tcon, &fp->filp->f_path, pntsd,
 			buf_len, false, true);
 }
@@ -6636,6 +6762,7 @@ static int smb2_set_info_sec(struct ksmbd_file *fp, int addition_info,
  */
 int smb2_set_info(struct ksmbd_work *work)
 {
+	const struct cred *saved_cred;
 	struct smb2_set_info_req *req;
 	struct smb2_set_info_rsp *rsp;
 	struct ksmbd_file *fp = NULL;
@@ -6677,6 +6804,7 @@ int smb2_set_info(struct ksmbd_work *work)
 		goto err_out;
 	}
 
+	saved_cred = override_creds(fp->filp->f_cred);
 	switch (req->InfoType) {
 	case SMB2_O_INFO_FILE:
 		ksmbd_debug(SMB, "GOT SMB2_O_INFO_FILE\n");
@@ -6684,19 +6812,15 @@ int smb2_set_info(struct ksmbd_work *work)
 		break;
 	case SMB2_O_INFO_SECURITY:
 		ksmbd_debug(SMB, "GOT SMB2_O_INFO_SECURITY\n");
-		if (ksmbd_override_fsids(work)) {
-			rc = -ENOMEM;
-			goto err_out;
-		}
 		rc = smb2_set_info_sec(fp,
 				       le32_to_cpu(req->AdditionalInformation),
 				       (char *)req + le16_to_cpu(req->BufferOffset),
 				       le32_to_cpu(req->BufferLength));
-		ksmbd_revert_fsids(work);
 		break;
 	default:
 		rc = -EOPNOTSUPP;
 	}
+	revert_creds(saved_cred);
 
 	if (rc < 0)
 		goto err_out;
@@ -7004,6 +7128,15 @@ int smb2_read(struct ksmbd_work *work)
 		kvfree(aux_payload_buf);
 		goto out;
 	}
+	/*
+	 * RDMA responses are transferred through channel buffers and encrypted
+	 * responses use the encryption transform, so only normal SMB transport
+	 * responses are candidates for compression.
+	 */
+	if (!is_rdma_channel && nbytes &&
+	    (req->Flags & SMB2_READFLAG_REQUEST_COMPRESSED) &&
+	    conn->compress_algorithm != SMB3_COMPRESS_NONE)
+		work->compress_response = true;
 	ksmbd_fd_put(work, fp);
 	return 0;
 
@@ -7341,14 +7474,14 @@ int smb2_cancel(struct ksmbd_work *work)
 				continue;
 
 			/*
-			 * A cancelled deferred byte-range lock frees its
-			 * file_lock and takes the smb2_lock() early-exit that
-			 * skips release_async_work(), so the work stays on
-			 * conn->async_requests with a live cancel_fn pointing
-			 * at the freed file_lock.  Re-firing it on a second
-			 * SMB2_CANCEL is a use-after-free.
+			 * Only an ACTIVE deferred work may have its cancel_fn
+			 * fired.  A CANCELLED or CLOSED work already took the
+			 * smb2_lock() non-ACTIVE early-exit that frees the
+			 * file_lock and skips release_async_work(), so it is
+			 * still on conn->async_requests with a live cancel_fn
+			 * pointing at the freed file_lock.
 			 */
-			if (iter->state == KSMBD_WORK_CANCELLED)
+			if (iter->state != KSMBD_WORK_ACTIVE)
 				break;
 
 			ksmbd_debug(SMB,
@@ -7755,29 +7888,27 @@ skip:
 				list_del(&work->fp_entry);
 				spin_unlock(&fp->f_lock);
 
-				if (work->state != KSMBD_WORK_ACTIVE) {
-					list_del(&smb_lock->llist);
-					locks_free_lock(flock);
+				list_del(&smb_lock->llist);
+				release_async_work(work);
 
-					if (work->state == KSMBD_WORK_CANCELLED) {
-						rsp->hdr.Status =
-							STATUS_CANCELLED;
-						kfree(smb_lock);
-						smb2_send_interim_resp(work,
-								       STATUS_CANCELLED);
-						work->send_no_response = 1;
-						goto out;
-					}
+				if (work->state == KSMBD_WORK_ACTIVE)
+					goto retry;
 
-					rsp->hdr.Status =
-						STATUS_RANGE_NOT_LOCKED;
+				locks_free_lock(flock);
+
+				if (work->state == KSMBD_WORK_CANCELLED) {
+					rsp->hdr.Status = STATUS_CANCELLED;
 					kfree(smb_lock);
-					goto out2;
+					smb2_send_interim_resp(work,
+							STATUS_CANCELLED);
+					work->send_no_response = 1;
+					goto out;
 				}
 
-				list_del(&smb_lock->llist);
-				release_async_work(work);
-				goto retry;
+				rsp->hdr.Status =
+					STATUS_RANGE_NOT_LOCKED;
+				kfree(smb_lock);
+				goto out2;
 			} else if (!rc) {
 				list_add(&smb_lock->llist, &rollback_list);
 				spin_lock(&work->conn->llist_lock);
@@ -7934,9 +8065,9 @@ static int fsctl_copychunk(struct ksmbd_work *work,
 
 	/*
 	 * FILE_READ_DATA should only be included in
-	 * the FSCTL_COPYCHUNK case
+	 * the FSCTL_SRV_COPYCHUNK case
 	 */
-	if (cnt_code == FSCTL_COPYCHUNK &&
+	if (cnt_code == FSCTL_SRV_COPYCHUNK &&
 	    !(dst_fp->daccess & (FILE_READ_DATA_LE | FILE_GENERIC_READ_LE))) {
 		rsp->hdr.Status = STATUS_ACCESS_DENIED;
 		goto out;
@@ -8256,6 +8387,7 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
 	if (fp->f_ci->m_fattr != old_fattr &&
 	    test_share_config_flag(work->tcon->share_conf,
 				   KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) {
+		const struct cred *saved_cred;
 		struct xattr_dos_attrib da;
 
 		ret = ksmbd_vfs_get_dos_attrib_xattr(idmap,
@@ -8264,9 +8396,11 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
 			goto out;
 
 		da.attr = le32_to_cpu(fp->f_ci->m_fattr);
+		saved_cred = override_creds(fp->filp->f_cred);
 		ret = ksmbd_vfs_set_dos_attrib_xattr(idmap,
 						     &fp->filp->f_path,
 						     &da, true);
+		revert_creds(saved_cred);
 		if (ret)
 			fp->f_ci->m_fattr = old_fattr;
 	}
@@ -8353,6 +8487,62 @@ int smb2_ioctl(struct ksmbd_work *work)
 		ret = -EOPNOTSUPP;
 		rsp->hdr.Status = STATUS_FS_DRIVER_REQUIRED;
 		goto out2;
+	case FSCTL_GET_COMPRESSION: {
+		struct compress_ioctl *cmpr_rsp;
+		struct ksmbd_file *fp;
+		u16 fmt;
+
+		if (out_buf_len < sizeof(struct compress_ioctl)) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		fp = ksmbd_lookup_fd_fast(work, id);
+		if (!fp) {
+			ret = -ENOENT;
+			goto out;
+		}
+
+		ret = ksmbd_vfs_get_compression(fp, &fmt);
+		ksmbd_fd_put(work, fp);
+		if (ret < 0)
+			goto out;
+
+		cmpr_rsp = (struct compress_ioctl *)&rsp->Buffer[0];
+		cmpr_rsp->CompressionState = cpu_to_le16(fmt);
+		nbytes = sizeof(struct compress_ioctl);
+		rsp->PersistentFileId = req->PersistentFileId;
+		rsp->VolatileFileId = req->VolatileFileId;
+		break;
+	}
+	case FSCTL_SET_COMPRESSION: {
+		struct compress_ioctl *cmpr_req;
+		struct ksmbd_file *fp;
+
+		if (in_buf_len < sizeof(struct compress_ioctl)) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+			ksmbd_debug(SMB, "User does not have write permission\n");
+			ret = -EACCES;
+			goto out;
+		}
+
+		cmpr_req = (struct compress_ioctl *)buffer;
+		fp = ksmbd_lookup_fd_fast(work, id);
+		if (!fp) {
+			ret = -ENOENT;
+			goto out;
+		}
+
+		ret = ksmbd_vfs_set_compression(work, fp, le16_to_cpu(cmpr_req->CompressionState));
+		ksmbd_fd_put(work, fp);
+		if (ret)
+			goto out;
+		break;
+	}
 	case FSCTL_CREATE_OR_GET_OBJECT_ID:
 	{
 		struct file_object_buf_type1_ioctl_rsp *obj_buf;
@@ -8410,7 +8600,7 @@ int smb2_ioctl(struct ksmbd_work *work)
 			goto out;
 		nbytes = ret;
 		break;
-	case FSCTL_REQUEST_RESUME_KEY:
+	case FSCTL_SRV_REQUEST_RESUME_KEY:
 		if (out_buf_len < sizeof(struct resume_key_ioctl_rsp)) {
 			ret = -EINVAL;
 			goto out;
@@ -8424,8 +8614,8 @@ int smb2_ioctl(struct ksmbd_work *work)
 		rsp->VolatileFileId = req->VolatileFileId;
 		nbytes = sizeof(struct resume_key_ioctl_rsp);
 		break;
-	case FSCTL_COPYCHUNK:
-	case FSCTL_COPYCHUNK_WRITE:
+	case FSCTL_SRV_COPYCHUNK:
+	case FSCTL_SRV_COPYCHUNK_WRITE:
 		if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
 			ksmbd_debug(SMB,
 				    "User does not have write permission\n");
@@ -8500,6 +8690,12 @@ int smb2_ioctl(struct ksmbd_work *work)
 				goto out;
 			}
 
+			if (!(fp->daccess & FILE_WRITE_DATA_LE)) {
+				ksmbd_fd_put(work, fp);
+				ret = -EACCES;
+				goto out;
+			}
+
 			ret = ksmbd_vfs_zero_data(work, fp, off, len);
 			ksmbd_fd_put(work, fp);
 			if (ret < 0)
@@ -8575,6 +8771,21 @@ int smb2_ioctl(struct ksmbd_work *work)
 			goto dup_ext_out;
 		}
 
+		if (!test_tree_conn_flag(work->tcon,
+					 KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+			ret = -EACCES;
+			goto dup_ext_out;
+		}
+
+		if (!(fp_out->daccess & FILE_WRITE_DATA_LE)) {
+			ret = -EACCES;
+			goto dup_ext_out;
+		}
+		if (!(fp_in->daccess & FILE_READ_DATA_LE)) {
+			ret = -EACCES;
+			goto dup_ext_out;
+		}
+
 		src_off = le64_to_cpu(dup_ext->SourceFileOffset);
 		dst_off = le64_to_cpu(dup_ext->TargetFileOffset);
 		length = le64_to_cpu(dup_ext->ByteCount);
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index e7cf573..3bed676 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -230,9 +230,6 @@ struct smb2_file_mode_info {
 	__le32 Mode;
 } __packed;
 
-#define COMPRESSION_FORMAT_NONE 0x0000
-#define COMPRESSION_FORMAT_LZNT1 0x0002
-
 struct smb2_file_comp_info {
 	__le64 CompressedFileSize;
 	__le16 CompressionFormat;
diff --git a/fs/smb/server/smb_common.c b/fs/smb/server/smb_common.c
index 741aabd..7de7322 100644
--- a/fs/smb/server/smb_common.c
+++ b/fs/smb/server/smb_common.c
@@ -102,9 +102,6 @@ static const struct {
 	int version;
 	const char *string;
 } version_strings[] = {
-#ifdef CONFIG_SMB_INSECURE_SERVER
-	{SMB1_PROT, SMB1_VERSION_STRING},
-#endif
 	{SMB2_PROT, SMB20_VERSION_STRING},
 	{SMB21_PROT, SMB21_VERSION_STRING},
 	{SMB30_PROT, SMB30_VERSION_STRING},
@@ -188,11 +185,6 @@ bool ksmbd_smb_request(struct ksmbd_conn *conn)
 		return false;
 
 	proto = (__le32 *)smb_get_msg(conn->request_buf);
-	if (*proto == SMB2_COMPRESSION_TRANSFORM_ID) {
-		pr_err_ratelimited("smb2 compression not support yet");
-		return false;
-	}
-
 	if (*proto != SMB1_PROTO_NUMBER &&
 	    *proto != SMB2_PROTO_NUMBER &&
 	    *proto != SMB2_TRANSFORM_PROTO_NUM)
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index 664b1b4..340ea98 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1477,7 +1477,9 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
 			break;
 		aces_size -= ace_size;
 
-		if (ace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES)
+		if (ace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
+		    ace_size < offsetof(struct smb_ace, sid) + CIFS_SID_BASE_SIZE +
+			      sizeof(__le32) * ace->sid.num_subauth)
 			break;
 
 		if (!compare_sids(&sid, &ace->sid) ||
diff --git a/fs/smb/server/smbfsctl.h b/fs/smb/server/smbfsctl.h
deleted file mode 100644
index ecdf8f6..0000000
--- a/fs/smb/server/smbfsctl.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-/*
- *   fs/smb/server/smbfsctl.h: SMB, CIFS, SMB2 FSCTL definitions
- *
- *   Copyright (c) International Business Machines  Corp., 2002,2009
- *   Author(s): Steve French (sfrench@us.ibm.com)
- */
-
-/* IOCTL information */
-/*
- * List of ioctl/fsctl function codes that are or could be useful in the
- * future to remote clients like cifs or SMB2 client.  There is probably
- * a slightly larger set of fsctls that NTFS local filesystem could handle,
- * including the seven below that we do not have struct definitions for.
- * Even with protocol definitions for most of these now available, we still
- * need to do some experimentation to identify which are practical to do
- * remotely.  Some of the following, such as the encryption/compression ones
- * could be invoked from tools via a specialized hook into the VFS rather
- * than via the standard vfs entry points
- */
-
-#ifndef __KSMBD_SMBFSCTL_H
-#define __KSMBD_SMBFSCTL_H
-
-#define FSCTL_DFS_GET_REFERRALS      0x00060194
-#define FSCTL_DFS_GET_REFERRALS_EX   0x000601B0
-#define FSCTL_REQUEST_OPLOCK_LEVEL_1 0x00090000
-#define FSCTL_REQUEST_OPLOCK_LEVEL_2 0x00090004
-#define FSCTL_REQUEST_BATCH_OPLOCK   0x00090008
-#define FSCTL_LOCK_VOLUME            0x00090018
-#define FSCTL_UNLOCK_VOLUME          0x0009001C
-#define FSCTL_IS_PATHNAME_VALID      0x0009002C /* BB add struct */
-#define FSCTL_GET_COMPRESSION        0x0009003C /* BB add struct */
-#define FSCTL_SET_COMPRESSION        0x0009C040 /* BB add struct */
-#define FSCTL_QUERY_FAT_BPB          0x00090058 /* BB add struct */
-/* Verify the next FSCTL number, we had it as 0x00090090 before */
-#define FSCTL_FILESYSTEM_GET_STATS   0x00090060 /* BB add struct */
-#define FSCTL_GET_NTFS_VOLUME_DATA   0x00090064 /* BB add struct */
-#define FSCTL_GET_RETRIEVAL_POINTERS 0x00090073 /* BB add struct */
-#define FSCTL_IS_VOLUME_DIRTY        0x00090078 /* BB add struct */
-#define FSCTL_ALLOW_EXTENDED_DASD_IO 0x00090083 /* BB add struct */
-#define FSCTL_REQUEST_FILTER_OPLOCK  0x0009008C
-#define FSCTL_FIND_FILES_BY_SID      0x0009008F /* BB add struct */
-#define FSCTL_SET_OBJECT_ID          0x00090098 /* BB add struct */
-#define FSCTL_GET_OBJECT_ID          0x0009009C /* BB add struct */
-#define FSCTL_DELETE_OBJECT_ID       0x000900A0 /* BB add struct */
-#define FSCTL_SET_REPARSE_POINT      0x000900A4 /* BB add struct */
-#define FSCTL_GET_REPARSE_POINT      0x000900A8 /* BB add struct */
-#define FSCTL_DELETE_REPARSE_POINT   0x000900AC /* BB add struct */
-#define FSCTL_SET_OBJECT_ID_EXTENDED 0x000900BC /* BB add struct */
-#define FSCTL_CREATE_OR_GET_OBJECT_ID 0x000900C0 /* BB add struct */
-#define FSCTL_SET_SPARSE             0x000900C4 /* BB add struct */
-#define FSCTL_SET_ZERO_DATA          0x000980C8 /* BB add struct */
-#define FSCTL_SET_ENCRYPTION         0x000900D7 /* BB add struct */
-#define FSCTL_ENCRYPTION_FSCTL_IO    0x000900DB /* BB add struct */
-#define FSCTL_WRITE_RAW_ENCRYPTED    0x000900DF /* BB add struct */
-#define FSCTL_READ_RAW_ENCRYPTED     0x000900E3 /* BB add struct */
-#define FSCTL_READ_FILE_USN_DATA     0x000900EB /* BB add struct */
-#define FSCTL_WRITE_USN_CLOSE_RECORD 0x000900EF /* BB add struct */
-#define FSCTL_SIS_COPYFILE           0x00090100 /* BB add struct */
-#define FSCTL_RECALL_FILE            0x00090117 /* BB add struct */
-#define FSCTL_QUERY_SPARING_INFO     0x00090138 /* BB add struct */
-#define FSCTL_SET_ZERO_ON_DEALLOC    0x00090194 /* BB add struct */
-#define FSCTL_SET_SHORT_NAME_BEHAVIOR 0x000901B4 /* BB add struct */
-#define FSCTL_QUERY_ALLOCATED_RANGES 0x000940CF /* BB add struct */
-#define FSCTL_SET_DEFECT_MANAGEMENT  0x00098134 /* BB add struct */
-#define FSCTL_DUPLICATE_EXTENTS_TO_FILE 0x00098344
-#define FSCTL_SIS_LINK_FILES         0x0009C104
-#define FSCTL_PIPE_PEEK              0x0011400C /* BB add struct */
-#define FSCTL_PIPE_TRANSCEIVE        0x0011C017 /* BB add struct */
-/* strange that the number for this op is not sequential with previous op */
-#define FSCTL_PIPE_WAIT              0x00110018 /* BB add struct */
-#define FSCTL_REQUEST_RESUME_KEY     0x00140078
-#define FSCTL_LMR_GET_LINK_TRACK_INF 0x001400E8 /* BB add struct */
-#define FSCTL_LMR_SET_LINK_TRACK_INF 0x001400EC /* BB add struct */
-#define FSCTL_VALIDATE_NEGOTIATE_INFO 0x00140204
-#define FSCTL_QUERY_NETWORK_INTERFACE_INFO 0x001401FC
-#define FSCTL_COPYCHUNK              0x001440F2
-#define FSCTL_COPYCHUNK_WRITE        0x001480F2
-
-#define IO_REPARSE_TAG_MOUNT_POINT   0xA0000003
-#define IO_REPARSE_TAG_HSM           0xC0000004
-#define IO_REPARSE_TAG_SIS           0x80000007
-
-/* WSL reparse tags */
-#define IO_REPARSE_TAG_LX_SYMLINK_LE	cpu_to_le32(0xA000001D)
-#define IO_REPARSE_TAG_AF_UNIX_LE	cpu_to_le32(0x80000023)
-#define IO_REPARSE_TAG_LX_FIFO_LE	cpu_to_le32(0x80000024)
-#define IO_REPARSE_TAG_LX_CHR_LE	cpu_to_le32(0x80000025)
-#define IO_REPARSE_TAG_LX_BLK_LE	cpu_to_le32(0x80000026)
-#endif /* __KSMBD_SMBFSCTL_H */
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index cd1dbca..74b0307 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -20,6 +20,7 @@
 #include <linux/sched/xacct.h>
 #include <linux/crc32c.h>
 #include <linux/splice.h>
+#include <linux/fileattr.h>
 
 #include "glob.h"
 #include "oplock.h"
@@ -247,17 +248,20 @@ out:
 static int ksmbd_vfs_stream_read(struct ksmbd_file *fp, char *buf, loff_t *pos,
 				 size_t count)
 {
+	const struct cred *saved_cred;
 	ssize_t v_len;
 	char *stream_buf = NULL;
 
 	ksmbd_debug(VFS, "read stream data pos : %llu, count : %zd\n",
 		    *pos, count);
 
+	saved_cred = override_creds(fp->filp->f_cred);
 	v_len = ksmbd_vfs_getcasexattr(file_mnt_idmap(fp->filp),
 				       fp->filp->f_path.dentry,
 				       fp->stream.name,
 				       fp->stream.size,
 				       &stream_buf);
+	revert_creds(saved_cred);
 	if ((int)v_len <= 0)
 		return (int)v_len;
 
@@ -381,6 +385,7 @@ int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
 static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
 				  size_t count)
 {
+	const struct cred *saved_cred;
 	char *stream_buf = NULL, *wbuf;
 	struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
 	size_t size;
@@ -401,6 +406,7 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
 		count = XATTR_SIZE_MAX - *pos;
 	}
 
+	saved_cred = override_creds(fp->filp->f_cred);
 	v_len = ksmbd_vfs_getcasexattr(idmap,
 				       fp->filp->f_path.dentry,
 				       fp->stream.name,
@@ -409,14 +415,14 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
 	if (v_len < 0) {
 		pr_err("not found stream in xattr : %zd\n", v_len);
 		err = v_len;
-		goto out;
+		goto out_revert;
 	}
 
 	if (v_len < size) {
 		wbuf = kvzalloc(size, KSMBD_DEFAULT_GFP);
 		if (!wbuf) {
 			err = -ENOMEM;
-			goto out;
+			goto out_revert;
 		}
 
 		if (v_len > 0)
@@ -434,6 +440,8 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
 				 size,
 				 0,
 				 true);
+out_revert:
+	revert_creds(saved_cred);
 	if (err < 0)
 		goto out;
 	else
@@ -910,15 +918,21 @@ void ksmbd_vfs_set_fadvise(struct file *filp, __le32 option)
 int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
 			loff_t off, loff_t len)
 {
+	const struct cred *saved_cred;
+	int err;
+
 	smb_break_all_levII_oplock(work, fp, 1);
+	saved_cred = override_creds(fp->filp->f_cred);
 	if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE)
-		return vfs_fallocate(fp->filp,
-				     FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
-				     off, len);
-
-	return vfs_fallocate(fp->filp,
-			     FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
-			     off, len);
+		err = vfs_fallocate(fp->filp,
+				    FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+				    off, len);
+	else
+		err = vfs_fallocate(fp->filp,
+				    FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
+				    off, len);
+	revert_creds(saved_cred);
+	return err;
 }
 
 int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
@@ -1000,13 +1014,15 @@ int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
 
 int ksmbd_vfs_unlink(struct file *filp)
 {
+	const struct cred *saved_cred;
 	int err = 0;
 	struct dentry *dir, *dentry = filp->f_path.dentry;
 	struct mnt_idmap *idmap = file_mnt_idmap(filp);
 
+	saved_cred = override_creds(filp->f_cred);
 	err = mnt_want_write(filp->f_path.mnt);
 	if (err)
-		return err;
+		goto out_revert;
 
 	dir = dget_parent(dentry);
 	dentry = start_removing_dentry(dir, dentry);
@@ -1025,7 +1041,8 @@ int ksmbd_vfs_unlink(struct file *filp)
 out:
 	dput(dir);
 	mnt_drop_write(filp->f_path.mnt);
-
+out_revert:
+	revert_creds(saved_cred);
 	return err;
 }
 
@@ -1140,7 +1157,7 @@ int __ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath,
 
 retry:
 	err = ksmbd_vfs_path_lookup(share_conf, filepath, flags, path, for_remove);
-	if (!err || !caseless)
+	if (!err || err != -ENOENT || !caseless)
 		return err;
 
 	path_len = strlen(filepath);
@@ -1242,15 +1259,30 @@ struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
 					  unsigned int flags,
 					  struct path *path)
 {
-	char *abs_name;
+	struct ksmbd_share_config *share_conf = work->tcon->share_conf;
+	struct qstr last;
 	struct dentry *dent;
+	int err;
+
+	/* resolve the name beneath the share root so ".." cannot escape */
+	CLASS(filename_kernel, filename)(name);
+
+	err = vfs_path_parent_lookup(filename, flags | LOOKUP_BENEATH,
+				     path, &last, &share_conf->vfs_path);
+	if (err)
+		return ERR_PTR(err);
 
-	abs_name = convert_to_unix_name(work->tcon->share_conf, name);
-	if (!abs_name)
-		return ERR_PTR(-ENOMEM);
+	err = mnt_want_write(path->mnt);
+	if (err) {
+		path_put(path);
+		return ERR_PTR(err);
+	}
 
-	dent = start_creating_path(AT_FDCWD, abs_name, path, flags);
-	kfree(abs_name);
+	dent = start_creating_noperm(path->dentry, &last);
+	if (IS_ERR(dent)) {
+		mnt_drop_write(path->mnt);
+		path_put(path);
+	}
 	return dent;
 }
 
@@ -1881,3 +1913,117 @@ int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap,
 	posix_acl_release(acls);
 	return rc;
 }
+
+void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr)
+{
+	int rc;
+	struct file_kattr fa = { .flags_valid = true };
+
+	rc = vfs_fileattr_get(dentry, &fa);
+	if (rc == -ENOIOCTLCMD)
+		*fattr &= ~FILE_ATTRIBUTE_COMPRESSED_LE;
+	if (rc)
+		return;
+
+	if (fa.flags & FS_COMPR_FL)
+		*fattr |= FILE_ATTRIBUTE_COMPRESSED_LE;
+	else
+		*fattr &= ~FILE_ATTRIBUTE_COMPRESSED_LE;
+}
+
+int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt)
+{
+	struct file_kattr fa = { .flags_valid = true };
+	int rc;
+
+	rc = vfs_fileattr_get(fp->filp->f_path.dentry, &fa);
+	if (rc == -ENOIOCTLCMD) {
+		*fmt = COMPRESSION_FORMAT_NONE;
+		rc = 0;
+		goto out;
+	}
+	if (rc)
+		goto out;
+
+	if (fa.flags & FS_COMPR_FL)
+		*fmt = COMPRESSION_FORMAT_LZNT1;
+	else
+		*fmt = COMPRESSION_FORMAT_NONE;
+
+out:
+	return rc;
+}
+
+int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u16 fmt)
+{
+	const struct cred *saved_cred = NULL;
+	struct file_kattr fa;
+	struct dentry *dentry = fp->filp->f_path.dentry;
+	struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
+	u32 flags;
+	__le32 old_fattr;
+	int rc;
+
+	if (!(fp->daccess & FILE_WRITE_DATA_LE)) {
+		rc = -EACCES;
+		goto out;
+	}
+
+	saved_cred = override_creds(fp->filp->f_cred);
+	rc = vfs_fileattr_get(dentry, &fa);
+	if (rc)
+		goto out;
+
+	flags = fa.flags;
+	if (fmt == COMPRESSION_FORMAT_NONE) {
+		flags &= ~FS_COMPR_FL;
+	} else if (fmt == COMPRESSION_FORMAT_DEFAULT ||
+		   fmt == COMPRESSION_FORMAT_LZNT1) {
+		flags |= FS_COMPR_FL;
+	} else {
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (flags != fa.flags) {
+		fileattr_fill_flags(&fa, flags);
+		rc = mnt_want_write_file(fp->filp);
+		if (rc)
+			goto out;
+
+		rc = vfs_fileattr_set(idmap, dentry, &fa);
+		mnt_drop_write_file(fp->filp);
+		if (rc)
+			goto out;
+	}
+
+	old_fattr = fp->f_ci->m_fattr;
+	if (fmt == COMPRESSION_FORMAT_NONE)
+		fp->f_ci->m_fattr &= ~FILE_ATTRIBUTE_COMPRESSED_LE;
+	else
+		fp->f_ci->m_fattr |= FILE_ATTRIBUTE_COMPRESSED_LE;
+
+	if (fp->f_ci->m_fattr != old_fattr &&
+	    test_share_config_flag(work->tcon->share_conf,
+				   KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) {
+		struct xattr_dos_attrib da;
+
+		rc = ksmbd_vfs_get_dos_attrib_xattr(idmap, dentry, &da);
+		if (rc <= 0) {
+			rc = 0;
+			goto out;
+		}
+
+		da.attr = le32_to_cpu(fp->f_ci->m_fattr);
+		rc = ksmbd_vfs_set_dos_attrib_xattr(idmap,
+						    &fp->filp->f_path,
+						    &da, true);
+		if (rc)
+			rc = 0;
+	}
+
+out:
+	if (saved_cred)
+		revert_creds(saved_cred);
+	return rc;
+}
diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h
index 16ca29e..7b3d2f4 100644
--- a/fs/smb/server/vfs.h
+++ b/fs/smb/server/vfs.h
@@ -168,4 +168,7 @@ int ksmbd_vfs_set_init_posix_acl(struct mnt_idmap *idmap,
 int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap,
 				const struct path *path,
 				struct inode *parent_inode);
+void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr);
+int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt);
+int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u16 fmt);
 #endif /* __KSMBD_VFS_H__ */
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index ba3355a..8c556e4 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -385,10 +385,14 @@ static void __ksmbd_inode_close(struct ksmbd_file *fp)
 		up_write(&ci->m_lock);
 
 		if (remove_stream_xattr) {
+			const struct cred *saved_cred;
+
+			saved_cred = override_creds(filp->f_cred);
 			err = ksmbd_vfs_remove_xattr(file_mnt_idmap(filp),
 						     &filp->f_path,
 						     fp->stream.name,
 						     true);
+			revert_creds(saved_cred);
 			if (err)
 				pr_err("remove xattr failed : %s\n",
 				       fp->stream.name);
@@ -790,6 +794,7 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp)
 	INIT_LIST_HEAD(&fp->node);
 	INIT_LIST_HEAD(&fp->lock_list);
 	spin_lock_init(&fp->f_lock);
+	mutex_init(&fp->readdir_lock);
 	atomic_set(&fp->refcount, 1);
 
 	fp->filp		= filp;
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index e687126..7d547e1 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -8,6 +8,7 @@
 
 #include <linux/file.h>
 #include <linux/fs.h>
+#include <linux/mutex.h>
 #include <linux/rwsem.h>
 #include <linux/spinlock.h>
 #include <linux/idr.h>
@@ -113,6 +114,7 @@ struct ksmbd_file {
 
 	/* if ls is happening on directory, below is valid*/
 	struct ksmbd_readdir_data	readdir_data;
+	struct mutex			readdir_lock;
 	int				dot_dotdot[2];
 	unsigned int			f_state;
 	bool				reserve_lease_break;
