[PATCH] ksmbd: handle encrypted compressed requests
Namjae Jeon <[email protected]>
| Newsgroups | org.kernel.vger.linux-cifs |
|---|---|
| Message-ID | <[email protected]> |
SMB3 permits a message to be compressed before it is encrypted. After
decrypting such a request, ksmbd must trim the AEAD tag using
OriginalMessageSize, decompress the nested compression transform, and
validate the resulting SMB2 PDU.
Share the decompression helper between the connection receive path and
the post-decryption work path so unencrypted and encrypted compressed
requests follow the same validation.
Fixes: a08de24c2b85 ("ksmbd: negotiate and decode SMB2 compression")
Signed-off-by: Namjae Jeon <[email protected]>
---
fs/smb/server/compress.c | 76 ++++++++++++++++++++++++++++++----------
fs/smb/server/compress.h | 1 +
fs/smb/server/server.c | 22 ++++++++++++
fs/smb/server/smb2pdu.c | 22 ++++++++----
4 files changed, 97 insertions(+), 24 deletions(-)
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;
}
--
2.25.1