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