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:
Linus Torvalds 2026-06-18 09:21:50 -07:00
commit dac3b26eae
35 changed files with 1348 additions and 277 deletions

View File

@ -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),)
#

View File

@ -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;

View File

@ -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, };

View File

@ -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 */

View File

@ -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];

View File

@ -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;
}

View File

@ -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

View File

@ -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

View 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);

View 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 */

View File

@ -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");

View File

@ -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 */

View File

@ -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;

View File

@ -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;
/*

View File

@ -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))

View File

@ -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
View 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
View 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__ */

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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) ||

View File

@ -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 */

View File

@ -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;
}

View File

@ -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__ */

View File

@ -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;

View File

@ -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;