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