new file mode 100644
@@ -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 */
@@ -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"
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> --- .../dovecot/dovecot/CVE-2021-29157.patch | 152 ++++++++++++++++++ .../recipes-support/dovecot/dovecot_2.3.14.bb | 1 + 2 files changed, 153 insertions(+) create mode 100644 meta-networking/recipes-support/dovecot/dovecot/CVE-2021-29157.patch