]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Feature] Add v3 request validation and use safe UCL parser flags
authorVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 7 Feb 2026 12:22:22 +0000 (12:22 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 7 Feb 2026 13:18:02 +0000 (13:18 +0000)
Enforce max 2 parts (metadata + message) in /checkv3 multipart requests,
returning HTTP 400 for malformed requests with extra parts. Switch UCL
parser to UCL_PARSER_SAFE_FLAGS to disable macros/includes in untrusted
metadata input.

src/libserver/protocol.c

index 560282b41e41bf44d0affe65e29347e65dae6b22..42b1158c5ddb8d035880677456159712bde8cd07 100644 (file)
@@ -2429,6 +2429,15 @@ rspamd_protocol_handle_v3_request(struct rspamd_task *task,
                                                                  (rspamd_mempool_destruct_t) rspamd_multipart_form_free,
                                                                  form);
 
+       /* Enforce single message per request: expect at most 2 parts (metadata + message) */
+       gsize nparts = rspamd_multipart_form_nparts(form);
+       if (nparts > 2) {
+               g_set_error(&task->err, rspamd_protocol_quark(), 400,
+                                       "v3 request must contain at most 2 parts (metadata + message), got %lu",
+                                       (unsigned long) nparts);
+               return FALSE;
+       }
+
        /* Find metadata part */
        const struct rspamd_multipart_entry_c *metadata_part =
                rspamd_multipart_form_find(form, "metadata", sizeof("metadata") - 1);
@@ -2450,7 +2459,7 @@ rspamd_protocol_handle_v3_request(struct rspamd_task *task,
                                                                                 "msgpack",
                                                                                 sizeof("msgpack") - 1) != -1) {
                is_msgpack = TRUE;
-               parser = ucl_parser_new(UCL_PARSER_DEFAULT | UCL_PARSER_NO_FILEVARS);
+               parser = ucl_parser_new(UCL_PARSER_SAFE_FLAGS);
                ucl_parser_add_chunk_full(parser, (const unsigned char *) metadata_part->data,
                                                                  metadata_part->data_len,
                                                                  ucl_parser_get_default_priority(parser),
@@ -2458,7 +2467,8 @@ rspamd_protocol_handle_v3_request(struct rspamd_task *task,
                                                                  UCL_PARSE_MSGPACK);
        }
        else {
-               parser = ucl_parser_new(UCL_PARSER_DEFAULT | UCL_PARSER_NO_FILEVARS);
+               /* Strict mode: disable UCL macros/includes, treat as plain JSON */
+               parser = ucl_parser_new(UCL_PARSER_SAFE_FLAGS);
                ucl_parser_add_chunk(parser, (const unsigned char *) metadata_part->data,
                                                         metadata_part->data_len);
        }