summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-06-18 09:21:50 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-06-18 09:21:50 -0700
commitdac3b26eae7bee261fa05f20c3fcc24988a7c233 (patch)
tree7329c2f9a86be2db9e627293078ab643e01a8bde
parente753a63f2ac8599182a5b6899c158a745188551d (diff)
parent1c8951963d8ed357f70f59e0ad4ddce2199d2016 (diff)
Merge tag 'v7.2-rc-part1-ksmbd-fixes' of git://git.samba.org/ksmbd
Pull smb server updates from Steve French: - Use after free fixes - Out of bounds read fix - Add SMB compression support both at rest and over the wire: support decompression of compressed SMB2 requests, initially allow compressed SMB2 READ responses, and implement get/set compression operations for per-file compression state. - Credentials fixes: for various FSCTLs, setinfo, delete on close and for alternate data streams - Fix access checks and permission checks in DUPLICAT_EXTENTS and SET_ZERO_DATA fsctls, find_file_posix_info, FILE_LINK_INFORMATION and smb2_set_info_sec - Reject non valid session in compound request - Serialize QUERY_DIRECTORY - Prevent path traversal bypass by restricting caseless retry - Path lookup fix - Two minor cleanup fixes * tag 'v7.2-rc-part1-ksmbd-fixes' of git://git.samba.org/ksmbd: (31 commits) ksmbd: fix path resolution in ksmbd_vfs_kern_path_create ksmbd: use opener credentials for FSCTL mutations ksmbd: use opener credentials for ADS I/O ksmbd: require source read access for duplicate extents ksmbd: run set info with opener credentials ksmbd: use opener credentials for delete-on-close ksmbd: serialize QUERY_DIRECTORY requests per file ksmbd: add permission checks for FSCTL_DUPLICATE_EXTENTS_TO_FILE ksmbd: enforce FILE_READ_ATTRIBUTES on SMB_FIND_FILE_POSIX_INFORMATION ksmbd: reject non-VALID session in compound request branch ksmbd: compress SMB2 READ responses ksmbd: negotiate and decode SMB2 compression cifs: negotiate chained SMB2 compression capabilities smb: add common SMB2 compression transform helpers smb: move LZ77 compression into common code ksmbd: add per-handle permission check to FILE_LINK_INFORMATION ksmbd: add a permission check for FSCTL_SET_ZERO_DATA ksmbd: add a WRITE_DAC/WRITE_OWNER check to SMB2 SET_INFO SECURITY ksmbd: fix use-after-free of a deferred file_lock on SMB2_CLOSE then SMB2_CANCEL smb: server: remove code guarded by nonexistent config option ...
-rw-r--r--fs/smb/client/Makefile2
-rw-r--r--fs/smb/client/cifsglob.h2
-rw-r--r--fs/smb/client/compress.c12
-rw-r--r--fs/smb/client/compress.h28
-rw-r--r--fs/smb/client/smb1pdu.h5
-rw-r--r--fs/smb/client/smb2pdu.c44
-rw-r--r--fs/smb/client/smb2pdu.h4
-rw-r--r--fs/smb/common/Makefile3
-rw-r--r--fs/smb/common/compress/compress.c399
-rw-r--r--fs/smb/common/compress/compress.h28
-rw-r--r--fs/smb/common/compress/lz77.c (renamed from fs/smb/client/compress/lz77.c)136
-rw-r--r--fs/smb/common/compress/lz77.h (renamed from fs/smb/client/compress/lz77.h)17
-rw-r--r--fs/smb/common/fscc.h18
-rw-r--r--fs/smb/common/smb2pdu.h11
-rw-r--r--fs/smb/common/smbfsctl.h10
-rw-r--r--fs/smb/server/Makefile2
-rw-r--r--fs/smb/server/compress.c209
-rw-r--r--fs/smb/server/compress.h16
-rw-r--r--fs/smb/server/connection.c12
-rw-r--r--fs/smb/server/connection.h3
-rw-r--r--fs/smb/server/ksmbd_work.c1
-rw-r--r--fs/smb/server/ksmbd_work.h4
-rw-r--r--fs/smb/server/misc.c33
-rw-r--r--fs/smb/server/misc.h1
-rw-r--r--fs/smb/server/oplock.c6
-rw-r--r--fs/smb/server/server.c10
-rw-r--r--fs/smb/server/smb2pdu.c303
-rw-r--r--fs/smb/server/smb2pdu.h3
-rw-r--r--fs/smb/server/smb_common.c8
-rw-r--r--fs/smb/server/smbacl.c4
-rw-r--r--fs/smb/server/smbfsctl.h91
-rw-r--r--fs/smb/server/vfs.c182
-rw-r--r--fs/smb/server/vfs.h3
-rw-r--r--fs/smb/server/vfs_cache.c5
-rw-r--r--fs/smb/server/vfs_cache.h2
35 files changed, 1344 insertions, 273 deletions
diff --git a/fs/smb/client/Makefile b/fs/smb/client/Makefile
index 6e83b5204699..fc6b9d35c962 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 a462c1590a9e..befc5eecb55c 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 be9023f841e6..8f0860970741 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 2679baca129b..e08e6d339d21 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 7584e94d9b2b..0870949144ab 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 4972cfe249f6..3c7691b39377 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 30d70097fe2f..b9bf2fa989d5 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 9e0730a385fb..f2c6e09d4e77 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 000000000000..b07a317597a4
--- /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 000000000000..7ace3bf4b664
--- /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
index 7365d0f97396..9216d973d876 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
index 4e570846aefa..e032c0f1b48d 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 bc3012cc295d..859849a42fec 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 aeb0a245c532..325ff83b12fe 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 3253a18ecb5c..d1fcb46a7cde 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 6407ba6b9340..a3e9306055e8 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 000000000000..f8cf515b9c30
--- /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 000000000000..663c6f44f09b
--- /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 8347495dbc62..9e8fdb39e5a2 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 e074be942582..ec75633b7da0 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 ab4958dc3eb0..a5ab6799a65c 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 d36393ff8310..0da8cc0972d6 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 a543ec9d3581..966004c414a8 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 13423696ae8c..3909104e18ad 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 b193dde4810d..60e7e821c245 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 5d799b2d4c62..36feda7e0942 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 00e63debcbe9..c1c6c1a64f60 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 e7cf573e59f0..3bed676bb5ad 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 741aabdfcef5..7de73223189a 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 664b1b4a3233..340ea98fa494 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 ecdf8f6e0df4..000000000000
--- 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 cd1dbca0cffb..74b0307cb100 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 16ca29ee16e5..7b3d2f4fd985 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 ba3355a6057a..8c556e46cc10 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 e6871266a94b..7d547e1a74f7 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;