diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c index 01d1771ff663..5162fb84c755 100644 --- a/fs/smb/server/compress.c +++ b/fs/smb/server/compress.c @@ -14,24 +14,14 @@ #define SMB_COMPRESS_MIN_LEN PAGE_SIZE -/** - * 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) +static int __ksmbd_decompress_request(struct ksmbd_conn *conn, + void *request_buf, void **out_buf) { struct smb2_compression_hdr *hdr; - unsigned int pdu_size = get_rfc1002_len(conn->request_buf); + unsigned int pdu_size = get_rfc1002_len(request_buf); u32 orig_size, offset, out_size; u32 max_allowed_pdu_size; - char *buf, *out; + char *out; int rc; if (pdu_size < sizeof(struct smb2_compression_hdr)) @@ -41,7 +31,7 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn) conn->compress_algorithm == SMB3_COMPRESS_NONE) return -EINVAL; - hdr = smb_get_msg(conn->request_buf); + hdr = smb_get_msg(request_buf); if (hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID) return -EINVAL; @@ -74,19 +64,69 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn) if (!out) return -ENOMEM; - buf = (char *)hdr; *(__be32 *)out = cpu_to_be32(out_size); rc = smb_compression_decompress(conn->compress_algorithm, conn->compress_chained, conn->compress_pattern, - buf, pdu_size, out + 4, out_size); + (char *)hdr, pdu_size, out + 4, out_size); if (rc) { kvfree(out); return rc; } + *out_buf = out; + return 0; +} + +/** + * 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) +{ + void *out_buf; + int rc; + + rc = __ksmbd_decompress_request(conn, conn->request_buf, &out_buf); + if (rc) + return rc; + kvfree(conn->request_buf); - conn->request_buf = out; + conn->request_buf = out_buf; + return 0; +} + +/** + * ksmbd_decompress_work_request() - decompress an encrypted work request + * @work: work item whose request buffer contains a compression transform + * + * SMB3 encrypts a compressed message by applying compression first and + * encryption second. The receive loop can therefore only decode the + * compression transform before work allocation for an unencrypted request; + * an encrypted request must be decompressed after its encryption layer has + * been removed. + * + * Return: 0 on success, otherwise a negative errno. + */ +int ksmbd_decompress_work_request(struct ksmbd_work *work) +{ + void *out_buf; + int rc; + + rc = __ksmbd_decompress_request(work->conn, work->request_buf, + &out_buf); + if (rc) + return rc; + + kvfree(work->request_buf); + work->request_buf = out_buf; return 0; } diff --git a/fs/smb/server/compress.h b/fs/smb/server/compress.h index 663c6f44f09b..13df2eb221e8 100644 --- a/fs/smb/server/compress.h +++ b/fs/smb/server/compress.h @@ -11,6 +11,7 @@ #include "../common/compress/compress.h" int ksmbd_decompress_request(struct ksmbd_conn *conn); +int ksmbd_decompress_work_request(struct ksmbd_work *work); int ksmbd_compress_response(struct ksmbd_work *work); #endif /* __KSMBD_COMPRESS_H__ */ diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c index ba44bea9ddc3..6cfe8148da85 100644 --- a/fs/smb/server/server.c +++ b/fs/smb/server/server.c @@ -193,6 +193,28 @@ static void __handle_ksmbd_work(struct ksmbd_work *work, return; } work->encrypted = true; + + /* + * SMB3 applies compression before encryption. The receive loop + * handles a plain compression transform before allocating work, but + * an encrypted request exposes that transform only after decryption. + */ + if (((struct smb2_hdr *)smb_get_msg(work->request_buf))->ProtocolId == + SMB2_COMPRESSION_TRANSFORM_ID) { + rc = ksmbd_decompress_work_request(work); + if (rc < 0) { + ksmbd_conn_abort(conn); + return; + } + } + + /* The decrypted payload must now be a complete SMB2 request. */ + if (((struct smb2_hdr *)smb_get_msg(work->request_buf))->ProtocolId != + SMB2_PROTO_NUMBER || + get_rfc1002_len(work->request_buf) < sizeof(struct smb2_pdu)) { + ksmbd_conn_abort(conn); + return; + } } if (conn->ops->allocate_rsp_buf(work)) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 8d06c934f24f..a564535132e5 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -11817,18 +11817,27 @@ int smb3_decrypt_req(struct ksmbd_work *work) char *buf = work->request_buf; unsigned int pdu_length = get_rfc1002_len(buf); struct kvec iov[2]; - int buf_data_size = pdu_length - sizeof(struct smb2_transform_hdr); + unsigned int buf_data_size; struct smb2_transform_hdr *tr_hdr = smb_get_msg(buf); + unsigned int original_msg_size; int rc = 0; - if (pdu_length < sizeof(struct smb2_transform_hdr) || - buf_data_size < sizeof(struct smb2_hdr)) { + if (pdu_length < sizeof(struct smb2_transform_hdr)) { pr_err("Transform message is too small (%u)\n", pdu_length); return -ECONNABORTED; } - if (buf_data_size < le32_to_cpu(tr_hdr->OriginalMessageSize)) { + buf_data_size = pdu_length - sizeof(struct smb2_transform_hdr); + original_msg_size = le32_to_cpu(tr_hdr->OriginalMessageSize); + if (buf_data_size < sizeof(struct smb2_compression_hdr) || + original_msg_size < sizeof(struct smb2_compression_hdr)) { + pr_err("Transform message is too small (%u)\n", + pdu_length); + return -ECONNABORTED; + } + + if (buf_data_size < original_msg_size) { pr_err("Transform message is broken\n"); return -ECONNABORTED; } @@ -11841,8 +11850,9 @@ int smb3_decrypt_req(struct ksmbd_work *work) if (rc) return rc; - memmove(buf + 4, iov[1].iov_base, buf_data_size); - *(__be32 *)buf = cpu_to_be32(buf_data_size); + /* Drop the AEAD authentication tag from the inner RFC1002 frame. */ + memmove(buf + 4, iov[1].iov_base, original_msg_size); + *(__be32 *)buf = cpu_to_be32(original_msg_size); return rc; }