dovecot: patch CVE-2021-29157

Details: https://nvd.nist.gov/vuln/detail/CVE-2021-29157

Backport the patch that it used by Debian[1] to fix this CVE.

[1]: https://sources.debian.org/src/dovecot/1%3A2.3.13%2Bdfsg1-2%2Bdeb11u1/debian/patches
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
This commit is contained in:
Gyorgy Sarvari 2026-02-27 16:40:31 +01:00
parent 7fd8cd657f
commit 331ff3f94b
2 changed files with 153 additions and 0 deletions

View File

@ -0,0 +1,152 @@
From 1ee6540ef6ffa8cefade0161f4dcd47d82a1d10b Mon Sep 17 00:00:00 2001
From: Gyorgy Sarvari <skandigraun@gmail.com>
Date: Fri, 27 Feb 2026 16:10:35 +0100
Subject: [PATCH] Fix CVE-2021-29157
CVE: CVE-2021-29157
Upstream-Status: Backport [the patch was taken from Debian: https://sources.debian.org/src/dovecot/1%3A2.3.13%2Bdfsg1-2%2Bdeb11u1/debian/patches/CVE-2021-29157.patch]
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
src/lib-dict-extra/dict-fs.c | 29 ++++++++++++++++
src/lib-oauth2/oauth2-jwt.c | 58 ++++++++++++++++++--------------
src/lib-oauth2/test-oauth2-jwt.c | 2 +-
3 files changed, 62 insertions(+), 27 deletions(-)
diff --git a/src/lib-dict-extra/dict-fs.c b/src/lib-dict-extra/dict-fs.c
index 31af578..f39c86c 100644
--- a/src/lib-dict-extra/dict-fs.c
+++ b/src/lib-dict-extra/dict-fs.c
@@ -68,8 +68,37 @@ static void fs_dict_deinit(struct dict *_dict)
i_free(dict);
}
+/* Remove unsafe paths */
+static const char *fs_dict_escape_key(const char *key)
+{
+ const char *ptr;
+ string_t *new_key = NULL;
+ /* we take the slow path always if we see potential
+ need for escaping */
+ while ((ptr = strstr(key, "/.")) != NULL) {
+ /* move to the first dot */
+ const char *ptr2 = ptr + 1;
+ /* find position of non-dot */
+ while (*ptr2 == '.') ptr2++;
+ if (new_key == NULL)
+ new_key = t_str_new(strlen(key));
+ str_append_data(new_key, key, ptr - key);
+ /* if ptr2 is / or end of string, escape */
+ if (*ptr2 == '/' || *ptr2 == '\0')
+ str_append(new_key, "/...");
+ else
+ str_append(new_key, "/.");
+ key = ptr + 2;
+ }
+ if (new_key == NULL)
+ return key;
+ str_append(new_key, key);
+ return str_c(new_key);
+}
+
static const char *fs_dict_get_full_key(struct fs_dict *dict, const char *key)
{
+ key = fs_dict_escape_key(key);
if (str_begins(key, DICT_PATH_SHARED))
return key + strlen(DICT_PATH_SHARED);
else if (str_begins(key, DICT_PATH_PRIVATE)) {
diff --git a/src/lib-oauth2/oauth2-jwt.c b/src/lib-oauth2/oauth2-jwt.c
index 83b241c..8e43cf9 100644
--- a/src/lib-oauth2/oauth2-jwt.c
+++ b/src/lib-oauth2/oauth2-jwt.c
@@ -277,6 +277,34 @@ oauth2_jwt_copy_fields(ARRAY_TYPE(oauth2_field) *fields, struct json_tree *tree)
}
}
+/* Escapes '/' and '%' in identifier to %hex */
+static const char *escape_identifier(const char *identifier)
+{
+ size_t pos = strcspn(identifier, "/%");
+ /* nothing to escape */
+ if (identifier[pos] == '\0')
+ return identifier;
+
+ size_t len = strlen(identifier);
+ string_t *new_id = t_str_new(len);
+ str_append_data(new_id, identifier, pos);
+
+ for (size_t i = pos; i < len; i++) {
+ switch (identifier[i]) {
+ case '/':
+ str_append(new_id, "%2f");
+ break;
+ case '%':
+ str_append(new_id, "%25");
+ break;
+ default:
+ str_append_c(new_id, identifier[i]);
+ break;
+ }
+ }
+ return str_c(new_id);
+}
+
static int
oauth2_jwt_header_process(struct json_tree *tree, const char **alg_r,
const char **kid_r, const char **error_r)
@@ -377,6 +405,8 @@ oauth2_jwt_body_process(const struct oauth2_settings *set, const char *alg,
const char *azp = get_field(tree, "azp");
if (azp == NULL)
azp = "default";
+ else
+ azp = escape_identifier(azp);
if (oauth2_validate_signature(set, azp, alg, kid, blobs, error_r) < 0)
return -1;
@@ -429,32 +459,8 @@ int oauth2_try_parse_jwt(const struct oauth2_settings *set,
else if (*kid == '\0') {
*error_r = "'kid' field is empty";
return -1;
- }
-
- size_t pos = strcspn(kid, "./%");
- if (pos < strlen(kid)) {
- /* sanitize kid, cannot allow dots or / in it, so we encode them
- */
- string_t *new_kid = t_str_new(strlen(kid));
- /* put initial data */
- str_append_data(new_kid, kid, pos);
- for (const char *c = kid+pos; *c != '\0'; c++) {
- switch (*c) {
- case '.':
- str_append(new_kid, "%2e");
- break;
- case '/':
- str_append(new_kid, "%2f");
- break;
- case '%':
- str_append(new_kid, "%25");
- break;
- default:
- str_append_c(new_kid, *c);
- break;
- }
- }
- kid = str_c(new_kid);
+ } else {
+ kid = escape_identifier(kid);
}
/* parse body */
diff --git a/src/lib-oauth2/test-oauth2-jwt.c b/src/lib-oauth2/test-oauth2-jwt.c
index 4cfba64..1706a96 100644
--- a/src/lib-oauth2/test-oauth2-jwt.c
+++ b/src/lib-oauth2/test-oauth2-jwt.c
@@ -577,7 +577,7 @@ static void test_jwt_kid_escape(void)
random_fill(ptr, 32);
buffer_t *b64_key = t_base64_encode(0, SIZE_MAX,
secret->data, secret->used);
- save_key_to("HS256", "hello%2eworld%2f%25", str_c(b64_key));
+ save_key_to("HS256", "hello.world%2f%25", str_c(b64_key));
/* make a token */
buffer_t *tokenbuf = create_jwt_token_kid("HS256", "hello.world/%");
/* sign it */

View File

@ -13,6 +13,7 @@ SRC_URI = "http://dovecot.org/releases/2.3/dovecot-${PV}.tar.gz \
file://0001-m4-Check-for-libunwind-instead-of-libunwind-generic.patch \
file://0001-auth-Fix-handling-passdbs-with-identical-driver-args.patch \
file://0001-lib-smtp-smtp-server-connection-Fix-STARTTLS-command.patch \
file://CVE-2021-29157.patch \
"
SRC_URI[md5sum] = "2f03532cec3280ae45a101a7a55ccef5"