mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
31 ksmbd fixes
-----BEGIN PGP SIGNATURE----- iQGzBAABCgAdFiEE6fsu8pdIjtWE/DpLiiy9cAdyT1EFAmox4zkACgkQiiy9cAdy T1GiPAwAkMFnKHWoZ1+th9QyMb3ac3ClmhwHLvsEJJo+Q6281u+y9/fiFll9oq+G hxpxkFRlt0F0WS4X4bo72RjOj9msB1gXADcNAIPTE+ukm3Psi8gKlXqUSlRs5Dd3 Hc7O87UQOstyAtVZyBRlFvCIsOIkijQYdV6dONljKvuWPGGO+ICM/V9Qsqnrd3nM g11O3dZSfwZUANGYMumq1DuZ25FyV1ffbuFKmzIVvZI5/Qip4X/vND0zixOjiE2o W1fbNxZsNXdNDD5vSI26200b36lv0sqpecy7w/W4N5/vOkf5TUkkNVdHxcLKkP8/ Z0wBaF+Gcsb/c/WWEpF4eOB087W2lmmiunTOOwS94/kgABmhMedoKK52wZnvNYFG JjOoXv+KPKbLdlVAj1PlioxBeu7i/aV37FwJ+7m/ynSpDk2bk6GEz2wHAqyPyid4 y4KHc4dN+/nWLLB071tGqbPheecPxL7SwrtppW2rWEemZNqi4Y3vXIfx3pBrhE7W peFaxuDu =cmJl -----END PGP SIGNATURE----- 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 ...
This commit is contained in:
commit
dac3b26eae
|
|
@ -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),)
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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, };
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
399
fs/smb/common/compress/compress.c
Normal file
399
fs/smb/common/compress/compress.c
Normal file
|
|
@ -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);
|
||||
28
fs/smb/common/compress/compress.h
Normal file
28
fs/smb/common/compress/compress.h
Normal file
|
|
@ -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 */
|
||||
|
|
@ -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 @@ noinline int lz77_compress(const void *src, const u32 slen, void *dst, u32 *dlen
|
|||
|
||||
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");
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
209
fs/smb/server/compress.c
Normal file
209
fs/smb/server/compress.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
16
fs/smb/server/compress.h
Normal file
16
fs/smb/server/compress.h
Normal file
|
|
@ -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__ */
|
||||
|
|
@ -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 @@ int ksmbd_conn_handler_loop(void *p)
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -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 @@ int smb2_tree_connect(struct ksmbd_work *work)
|
|||
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 @@ int smb2_query_dir(struct ksmbd_work *work)
|
|||
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 @@ int smb2_query_dir(struct ksmbd_work *work)
|
|||
|
||||
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 @@ int smb2_lock(struct ksmbd_work *work)
|
|||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
rsp->hdr.Status =
|
||||
STATUS_RANGE_NOT_LOCKED;
|
||||
kfree(smb_lock);
|
||||
goto out2;
|
||||
}
|
||||
|
||||
list_del(&smb_lock->llist);
|
||||
release_async_work(work);
|
||||
goto retry;
|
||||
|
||||
if (work->state == KSMBD_WORK_ACTIVE)
|
||||
goto retry;
|
||||
|
||||
locks_free_lock(flock);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) ||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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 @@ static ssize_t ksmbd_vfs_getcasexattr(struct mnt_idmap *idmap,
|
|||
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)
|
||||
{
|
||||
smb_break_all_levII_oplock(work, fp, 1);
|
||||
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);
|
||||
const struct cred *saved_cred;
|
||||
int err;
|
||||
|
||||
return vfs_fallocate(fp->filp,
|
||||
FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
|
||||
off, len);
|
||||
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)
|
||||
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;
|
||||
|
||||
abs_name = convert_to_unix_name(work->tcon->share_conf, name);
|
||||
if (!abs_name)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
/* resolve the name beneath the share root so ".." cannot escape */
|
||||
CLASS(filename_kernel, filename)(name);
|
||||
|
||||
dent = start_creating_path(AT_FDCWD, abs_name, path, flags);
|
||||
kfree(abs_name);
|
||||
err = vfs_path_parent_lookup(filename, flags | LOOKUP_BENEATH,
|
||||
path, &last, &share_conf->vfs_path);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
err = mnt_want_write(path->mnt);
|
||||
if (err) {
|
||||
path_put(path);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user