1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * SMB2 compression support for ksmbd.
4 *
5 * Receive and send SMB 3.1.1 compression transforms using the common helpers.
6 *
7 * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
8 */
9 #include <linux/slab.h>
10
11 #include "compress.h"
12 #include "smb_common.h"
13 #include "../common/compress/lz77.h"
14
15 #define SMB_COMPRESS_MIN_LEN PAGE_SIZE
16
17 /**
18 * ksmbd_decompress_request() - replace a compressed request with its SMB2 PDU
19 * @conn: connection which owns the current RFC1002 request buffer
20 *
21 * Derive the uncompressed size from the transform variant, enforce ksmbd's
22 * normal message limits, and ask the common decoder to validate every payload.
23 * On success, replace conn->request_buf with a regular RFC1002-framed SMB2
24 * message so the rest of the request path needs no compression awareness.
25 *
26 * Return: 0 on success, otherwise a negative errno.
27 */
ksmbd_decompress_request(struct ksmbd_conn * conn)28 int ksmbd_decompress_request(struct ksmbd_conn *conn)
29 {
30 struct smb2_compression_hdr *hdr;
31 unsigned int pdu_size = get_rfc1002_len(conn->request_buf);
32 u32 orig_size, offset, out_size;
33 u32 max_allowed_pdu_size;
34 char *buf, *out;
35 int rc;
36
37 if (pdu_size < sizeof(struct smb2_compression_hdr))
38 return -EINVAL;
39
40 if (conn->dialect != SMB311_PROT_ID ||
41 conn->compress_algorithm == SMB3_COMPRESS_NONE)
42 return -EINVAL;
43
44 hdr = smb_get_msg(conn->request_buf);
45 if (hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID)
46 return -EINVAL;
47
48 orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
49 /*
50 * For chained transforms the top-level header is only eight bytes; the
51 * Flags field overlays the first payload header. Reject unknown Flags
52 * and unnegotiated chained mode before allocating the output buffer.
53 */
54 if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) {
55 if (!conn->compress_chained)
56 return -EINVAL;
57 out_size = orig_size;
58 } else if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)) {
59 offset = le32_to_cpu(hdr->Offset);
60 if (offset > pdu_size - sizeof(*hdr) ||
61 check_add_overflow(orig_size, offset, &out_size))
62 return -EINVAL;
63 } else {
64 return -EINVAL;
65 }
66
67 max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn);
68 if (out_size < sizeof(struct smb2_pdu) ||
69 out_size > max_allowed_pdu_size ||
70 out_size > MAX_STREAM_PROT_LEN)
71 return -EINVAL;
72
73 out = kvmalloc(out_size + 4 + 1, KSMBD_DEFAULT_GFP);
74 if (!out)
75 return -ENOMEM;
76
77 buf = (char *)hdr;
78 *(__be32 *)out = cpu_to_be32(out_size);
79 rc = smb_compression_decompress(conn->compress_algorithm,
80 conn->compress_chained,
81 conn->compress_pattern,
82 buf, pdu_size, out + 4, out_size);
83 if (rc) {
84 kvfree(out);
85 return rc;
86 }
87
88 kvfree(conn->request_buf);
89 conn->request_buf = out;
90 return 0;
91 }
92
93 /**
94 * ksmbd_compress_response() - compress an eligible ksmbd response
95 * @work: request work item containing the response iov
96 *
97 * Compression transforms describe one contiguous SMB2 message, while ksmbd
98 * builds responses from multiple iov entries. Flatten the response first,
99 * produce the negotiated transform, and replace the response iov only when the
100 * result is smaller than the original message.
101 *
102 * Encrypted and compound responses are intentionally left unchanged. The
103 * caller may still continue sending the original response when this function
104 * returns zero.
105 *
106 * Return: 1 if the response was replaced, 0 if compression was skipped, or a
107 * negative errno on failure.
108 */
ksmbd_compress_response(struct ksmbd_work * work)109 int ksmbd_compress_response(struct ksmbd_work *work)
110 {
111 struct smb2_compression_hdr *chdr;
112 struct smb2_hdr *req_hdr;
113 u32 src_len, dst_len, compressed_pdu_len, max_dst_len;
114 u8 *src = NULL, *out = NULL, *p;
115 int i, rc;
116
117 if (!work->compress_response || work->encrypted ||
118 work->conn->compress_algorithm != SMB3_COMPRESS_LZ77)
119 return 0;
120
121 req_hdr = smb_get_msg(work->request_buf);
122 if (req_hdr->NextCommand || work->next_smb2_rcv_hdr_off ||
123 work->next_smb2_rsp_hdr_off)
124 return 0;
125
126 src_len = get_rfc1002_len(work->iov[0].iov_base);
127 if (src_len < SMB_COMPRESS_MIN_LEN)
128 return 0;
129
130 src = kvmalloc(src_len, KSMBD_DEFAULT_GFP);
131 if (!src)
132 return -ENOMEM;
133
134 p = src;
135 /* iov[0] contains only the RFC1002 length; the SMB2 PDU starts at iov[1]. */
136 for (i = 1; i < work->iov_cnt; i++) {
137 if (work->iov[i].iov_len > src + src_len - p) {
138 rc = -EINVAL;
139 goto out;
140 }
141 memcpy(p, work->iov[i].iov_base, work->iov[i].iov_len);
142 p += work->iov[i].iov_len;
143 }
144 if (p != src + src_len) {
145 rc = -EINVAL;
146 goto out;
147 }
148
149 max_dst_len = smb_lz77_compressed_alloc_size(src_len) +
150 sizeof(struct smb2_compression_hdr) +
151 3 * sizeof(struct smb2_compression_payload_hdr) +
152 2 * sizeof(struct smb2_compression_pattern_v1);
153 out = kvzalloc(sizeof(__be32) + max_dst_len,
154 KSMBD_DEFAULT_GFP);
155 if (!out) {
156 rc = -ENOMEM;
157 goto out;
158 }
159
160 if (work->conn->compress_chained) {
161 dst_len = max_dst_len;
162 rc = smb_compression_compress_chained(SMB3_COMPRESS_LZ77,
163 work->conn->compress_pattern,
164 src, src_len,
165 out + sizeof(__be32),
166 &dst_len);
167 if (rc == -EMSGSIZE || dst_len >= src_len) {
168 rc = 0;
169 goto out;
170 }
171 if (rc)
172 goto out;
173 compressed_pdu_len = dst_len;
174 } else {
175 /*
176 * Peers which did not negotiate chained compression still use
177 * the original 16-byte unchained transform format.
178 */
179 dst_len = smb_lz77_compressed_alloc_size(src_len);
180 rc = smb_lz77_compress(src, src_len,
181 out + sizeof(__be32) + sizeof(*chdr),
182 &dst_len);
183 if (rc == -EMSGSIZE ||
184 dst_len + sizeof(*chdr) >= src_len) {
185 rc = 0;
186 goto out;
187 }
188 if (rc)
189 goto out;
190
191 compressed_pdu_len = sizeof(*chdr) + dst_len;
192 chdr = (struct smb2_compression_hdr *)(out + sizeof(__be32));
193 chdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
194 chdr->OriginalCompressedSegmentSize = cpu_to_le32(src_len);
195 chdr->CompressionAlgorithm = SMB3_COMPRESS_LZ77;
196 chdr->Flags = cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE);
197 chdr->Offset = 0;
198 }
199
200 *(__be32 *)out = cpu_to_be32(compressed_pdu_len);
201
202 /*
203 * Keep the transform in work->compress_buf until send completion.
204 * Existing response iovs can then be replaced without changing their
205 * individual ownership rules.
206 */
207 work->compress_buf = out;
208 work->iov[0].iov_base = out;
209 work->iov[0].iov_len = sizeof(__be32);
210 work->iov[1].iov_base = out + sizeof(__be32);
211 work->iov[1].iov_len = compressed_pdu_len;
212 work->iov_cnt = 2;
213 work->iov_idx = 1;
214 out = NULL;
215 rc = 1;
216 out:
217 kvfree(out);
218 kvfree(src);
219 return rc;
220 }
221