mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
ksmbd: negotiate and decode SMB2 compression
Parse the SMB 3.1.1 compression capabilities context and negotiate LZ77 with optional chained Pattern_V1 support. Advertise compression on tree connections and decode compressed requests before normal SMB dispatch. Signed-off-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
parent
17e12b1d14
commit
a08de24c2b
|
|
@ -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
|
||||
|
||||
|
|
|
|||
77
fs/smb/server/compress.c
Normal file
77
fs/smb/server/compress.c
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// 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"
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
15
fs/smb/server/compress.h
Normal file
15
fs/smb/server/compress.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* 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);
|
||||
|
||||
#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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -804,6 +805,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 +890,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 +1006,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 +1106,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 +1152,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 +2152,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)
|
||||
|
|
|
|||
|
|
@ -185,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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user