xref: /linux/fs/smb/server/compress.c (revision c27e360545373b7aee9862a5beef3b9fb3df0c25)
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  */
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 	if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) {
50 		out_size = orig_size;
51 	} else {
52 		offset = le32_to_cpu(hdr->Offset);
53 		if (offset > pdu_size - sizeof(*hdr) ||
54 		    check_add_overflow(orig_size, offset, &out_size))
55 			return -EINVAL;
56 	}
57 
58 	max_allowed_pdu_size = SMB3_MAX_MSGSIZE + conn->vals->max_write_size;
59 	if (out_size < sizeof(struct smb2_pdu) ||
60 	    out_size > max_allowed_pdu_size ||
61 	    out_size > MAX_STREAM_PROT_LEN)
62 		return -EINVAL;
63 
64 	out = kvmalloc(out_size + 4 + 1, KSMBD_DEFAULT_GFP);
65 	if (!out)
66 		return -ENOMEM;
67 
68 	buf = (char *)hdr;
69 	*(__be32 *)out = cpu_to_be32(out_size);
70 	rc = smb_compression_decompress(conn->compress_algorithm,
71 					conn->compress_chained,
72 					buf, pdu_size, out + 4, out_size);
73 	if (rc) {
74 		kvfree(out);
75 		return rc;
76 	}
77 
78 	kvfree(conn->request_buf);
79 	conn->request_buf = out;
80 	return 0;
81 }
82 
83 /**
84  * ksmbd_compress_response() - compress an eligible ksmbd response
85  * @work: request work item containing the response iov
86  *
87  * Compression transforms describe one contiguous SMB2 message, while ksmbd
88  * builds responses from multiple iov entries. Flatten the response first,
89  * produce the negotiated transform, and replace the response iov only when the
90  * result is smaller than the original message.
91  *
92  * Encrypted and compound responses are intentionally left unchanged. The
93  * caller may still continue sending the original response when this function
94  * returns zero.
95  *
96  * Return: 1 if the response was replaced, 0 if compression was skipped, or a
97  * negative errno on failure.
98  */
99 int ksmbd_compress_response(struct ksmbd_work *work)
100 {
101 	struct smb2_compression_hdr *chdr;
102 	struct smb2_hdr *req_hdr;
103 	u32 src_len, dst_len, compressed_pdu_len, max_dst_len;
104 	u8 *src = NULL, *out = NULL, *p;
105 	int i, rc;
106 
107 	if (!work->compress_response || work->encrypted ||
108 	    work->conn->compress_algorithm != SMB3_COMPRESS_LZ77)
109 		return 0;
110 
111 	req_hdr = smb_get_msg(work->request_buf);
112 	if (req_hdr->NextCommand || work->next_smb2_rcv_hdr_off ||
113 	    work->next_smb2_rsp_hdr_off)
114 		return 0;
115 
116 	src_len = get_rfc1002_len(work->iov[0].iov_base);
117 	if (src_len < SMB_COMPRESS_MIN_LEN)
118 		return 0;
119 
120 	src = kvmalloc(src_len, KSMBD_DEFAULT_GFP);
121 	if (!src)
122 		return -ENOMEM;
123 
124 	p = src;
125 	/* iov[0] contains only the RFC1002 length; the SMB2 PDU starts at iov[1]. */
126 	for (i = 1; i < work->iov_cnt; i++) {
127 		if (work->iov[i].iov_len > src + src_len - p) {
128 			rc = -EINVAL;
129 			goto out;
130 		}
131 		memcpy(p, work->iov[i].iov_base, work->iov[i].iov_len);
132 		p += work->iov[i].iov_len;
133 	}
134 	if (p != src + src_len) {
135 		rc = -EINVAL;
136 		goto out;
137 	}
138 
139 	max_dst_len = smb_lz77_compressed_alloc_size(src_len) +
140 		sizeof(struct smb2_compression_hdr) +
141 		3 * sizeof(struct smb2_compression_payload_hdr) +
142 		2 * sizeof(struct smb2_compression_pattern_v1);
143 	out = kvzalloc(sizeof(__be32) + max_dst_len,
144 		       KSMBD_DEFAULT_GFP);
145 	if (!out) {
146 		rc = -ENOMEM;
147 		goto out;
148 	}
149 
150 	if (work->conn->compress_chained) {
151 		dst_len = max_dst_len;
152 		rc = smb_compression_compress_chained(SMB3_COMPRESS_LZ77,
153 						      work->conn->compress_pattern,
154 						      src, src_len,
155 						      out + sizeof(__be32),
156 						      &dst_len);
157 		if (rc == -EMSGSIZE || dst_len >= src_len) {
158 			rc = 0;
159 			goto out;
160 		}
161 		if (rc)
162 			goto out;
163 		compressed_pdu_len = dst_len;
164 	} else {
165 		/*
166 		 * Peers which did not negotiate chained compression still use
167 		 * the original 16-byte unchained transform format.
168 		 */
169 		dst_len = smb_lz77_compressed_alloc_size(src_len);
170 		rc = smb_lz77_compress(src, src_len,
171 				       out + sizeof(__be32) + sizeof(*chdr),
172 				       &dst_len);
173 		if (rc == -EMSGSIZE ||
174 		    dst_len + sizeof(*chdr) >= src_len) {
175 			rc = 0;
176 			goto out;
177 		}
178 		if (rc)
179 			goto out;
180 
181 		compressed_pdu_len = sizeof(*chdr) + dst_len;
182 		chdr = (struct smb2_compression_hdr *)(out + sizeof(__be32));
183 		chdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
184 		chdr->OriginalCompressedSegmentSize = cpu_to_le32(src_len);
185 		chdr->CompressionAlgorithm = SMB3_COMPRESS_LZ77;
186 		chdr->Flags = cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE);
187 		chdr->Offset = 0;
188 	}
189 
190 	*(__be32 *)out = cpu_to_be32(compressed_pdu_len);
191 
192 	/*
193 	 * Keep the transform in work->compress_buf until send completion.
194 	 * Existing response iovs can then be replaced without changing their
195 	 * individual ownership rules.
196 	 */
197 	work->compress_buf = out;
198 	work->iov[0].iov_base = out;
199 	work->iov[0].iov_len = sizeof(__be32);
200 	work->iov[1].iov_base = out + sizeof(__be32);
201 	work->iov[1].iov_len = compressed_pdu_len;
202 	work->iov_cnt = 2;
203 	work->iov_idx = 1;
204 	out = NULL;
205 	rc = 1;
206 out:
207 	kvfree(out);
208 	kvfree(src);
209 	return rc;
210 }
211