Open
fix CVE-2026-5917 #65
1AAdd on 4 days ago
fix CVE-2026-5917 #65
Open
Total: 2 files changed+203-1
| @@ -0,0 +1,198 @@ | |||
| 1 | +From 7f7f0263e943c1d1af81dd52863b3ef92a5e79bc Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: litenglong <litenglong@kylinos.cn> | ||
| 3 | +Date: Wed, 4 Nov 2026 14:35:04 +0800 | ||
| 4 | +Subject: [PATCH] CVE-2026-5917: Escape repo path in gen_proto() | ||
| 5 | + | ||
| 6 | +Takes inspiration from 346f28b89d6aae0c3ea0b9f0b0bc0f0afa177fab | ||
| 7 | + | ||
| 8 | +Conflict: upstream modifies ssh_libssh2.c and uses git_str API, but this | ||
| 9 | +branch still uses ssh.c for the libssh2 backend with the older git_buf API. | ||
| 10 | +Applied the same fix manually with additional adaptation to preserve | ||
| 11 | +percent-decoding behavior: | ||
| 12 | +git_buf_decode_percent(request, repo, strlen(repo)) -> | ||
| 13 | +decode to temp buffer, then git_buf_puts_escaped(request, decoded, "'!", "'\\", "'"). | ||
| 14 | + | ||
| 15 | +Additional changes: | ||
| 16 | +- Upgrade git_buf_puts_escaped() from 4-parameter to 5-parameter | ||
| 17 | + implementation to match upstream API (esc_with split into esc_prefix | ||
| 18 | + and esc_suffix, both NULL-safe) | ||
| 19 | +- Update all existing call sites to pass NULL as esc_suffix: | ||
| 20 | + * src/buffer.h: git_buf_puts_escape_regex() inline wrapper | ||
| 21 | + * tests/core/buffer.c: test_core_buffer__puts_escaped() test cases | ||
| 22 | +- src/transports/ssh.c uses 5-parameter form with prefix+char+suffix | ||
| 23 | + shell escaping pattern | ||
| 24 | +- Remove unused `int len` variable from gen_proto() | ||
| 25 | +- Preserve percent-decoding: decode to temp git_buf first, then escape | ||
| 26 | + the decoded result for shell safety. This maintains backward | ||
| 27 | + compatibility for URLs containing %XX-encoded characters. | ||
| 28 | +- Add test cases for esc_suffix non-NULL (shell quote escaping) | ||
| 29 | + | ||
| 30 | +Signed-off-by: Stephen Gallagher <sgallagh@redhat.com> | ||
| 31 | +Signed-off-by: litenglong <litenglong@kylinos.cn> | ||
| 32 | +Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> | ||
| 33 | +--- | ||
| 34 | + src/buffer.c | 24 ++++++++++++++++++------ | ||
| 35 | + src/buffer.h | 8 +++++--- | ||
| 36 | + src/transports/ssh.c | 17 ++++++++++++----- | ||
| 37 | + tests/core/buffer.c | 14 +++++++++++--- | ||
| 38 | + 4 files changed, 46 insertions(+), 17 deletions(-) | ||
| 39 | + | ||
| 40 | +diff --git a/src/buffer.c b/src/buffer.c | ||
| 41 | +index fe087ea..033766b 100644 | ||
| 42 | +--- a/src/buffer.c | ||
| 43 | ++++ b/src/buffer.c | ||
| 44 | +@@ -1059,10 +1059,13 @@ int git_buf_puts_escaped( | ||
| 45 | + git_buf *buf, | ||
| 46 | + const char *string, | ||
| 47 | + const char *esc_chars, | ||
| 48 | +- const char *esc_with) | ||
| 49 | ++ const char *esc_prefix, | ||
| 50 | ++ const char *esc_suffix) | ||
| 51 | + { | ||
| 52 | + const char *scan; | ||
| 53 | +- size_t total = 0, esc_len = strlen(esc_with), count, alloclen; | ||
| 54 | ++ size_t total = 0, count, alloclen; | ||
| 55 | ++ size_t esc_prefix_len = esc_prefix ? strlen(esc_prefix) : 0; | ||
| 56 | ++ size_t esc_suffix_len = esc_suffix ? strlen(esc_suffix) : 0; | ||
| 57 | + | ||
| 58 | + if (!string) | ||
| 59 | + return 0; | ||
| 60 | +@@ -1074,7 +1077,7 @@ int git_buf_puts_escaped( | ||
| 61 | + scan += count; | ||
| 62 | + /* count run of escaped characters */ | ||
| 63 | + count = strspn(scan, esc_chars); | ||
| 64 | +- total += count * (esc_len + 1); | ||
| 65 | ++ total += count * (esc_prefix_len + esc_suffix_len + 1); | ||
| 66 | + scan += count; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | +@@ -1090,13 +1093,22 @@ int git_buf_puts_escaped( | ||
| 70 | + buf->size += count; | ||
| 71 | + | ||
| 72 | + for (count = strspn(scan, esc_chars); count > 0; --count) { | ||
| 73 | +- /* copy escape sequence */ | ||
| 74 | +- memmove(buf->ptr + buf->size, esc_with, esc_len); | ||
| 75 | +- buf->size += esc_len; | ||
| 76 | ++ /* copy escape prefix sequence */ | ||
| 77 | ++ if (esc_prefix) { | ||
| 78 | ++ memmove(buf->ptr + buf->size, esc_prefix, esc_prefix_len); | ||
| 79 | ++ buf->size += esc_prefix_len; | ||
| 80 | ++ } | ||
| 81 | ++ | ||
| 82 | + /* copy character to be escaped */ | ||
| 83 | + buf->ptr[buf->size] = *scan; | ||
| 84 | + buf->size++; | ||
| 85 | + scan++; | ||
| 86 | ++ | ||
| 87 | ++ /* copy escape suffix sequence */ | ||
| 88 | ++ if (esc_suffix) { | ||
| 89 | ++ memmove(buf->ptr + buf->size, esc_suffix, esc_suffix_len); | ||
| 90 | ++ buf->size += esc_suffix_len; | ||
| 91 | ++ } | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | +diff --git a/src/buffer.h b/src/buffer.h | ||
| 96 | +index 75930e2..6fd8985 100644 | ||
| 97 | +--- a/src/buffer.h | ||
| 98 | ++++ b/src/buffer.h | ||
| 99 | +@@ -281,21 +281,23 @@ int git_buf_splice( | ||
| 100 | + * @param buf Buffer to append data to | ||
| 101 | + * @param string String to escape and append | ||
| 102 | + * @param esc_chars Characters to be escaped | ||
| 103 | +- * @param esc_with String to insert in from of each found character | ||
| 104 | ++ * @param esc_prefix String to insert before each found character (or NULL) | ||
| 105 | ++ * @param esc_suffix String to insert after each found character (or NULL) | ||
| 106 | + * @return 0 on success, <0 on failure (probably allocation problem) | ||
| 107 | + */ | ||
| 108 | + extern int git_buf_puts_escaped( | ||
| 109 | + git_buf *buf, | ||
| 110 | + const char *string, | ||
| 111 | + const char *esc_chars, | ||
| 112 | +- const char *esc_with); | ||
| 113 | ++ const char *esc_prefix, | ||
| 114 | ++ const char *esc_suffix); | ||
| 115 | + | ||
| 116 | + /** | ||
| 117 | + * Append string escaping characters that are regex special | ||
| 118 | + */ | ||
| 119 | + GIT_INLINE(int) git_buf_puts_escape_regex(git_buf *buf, const char *string) | ||
| 120 | + { | ||
| 121 | +- return git_buf_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\"); | ||
| 122 | ++ return git_buf_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\", NULL); | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + /** | ||
| 126 | +diff --git a/src/transports/ssh.c b/src/transports/ssh.c | ||
| 127 | +index 86cfcc9..dea8af8 100644 | ||
| 128 | +--- a/src/transports/ssh.c | ||
| 129 | ++++ b/src/transports/ssh.c | ||
| 130 | +@@ -68,7 +68,6 @@ static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) | ||
| 131 | + static int gen_proto(git_buf *request, const char *cmd, const char *url) | ||
| 132 | + { | ||
| 133 | + const char *repo; | ||
| 134 | +- int len; | ||
| 135 | + size_t i; | ||
| 136 | + | ||
| 137 | + for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) { | ||
| 138 | +@@ -92,12 +91,20 @@ done: | ||
| 139 | + return -1; | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | +- len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1; | ||
| 143 | +- | ||
| 144 | +- git_buf_grow(request, len); | ||
| 145 | + git_buf_puts(request, cmd); | ||
| 146 | + git_buf_puts(request, " '"); | ||
| 147 | +- git_buf_decode_percent(request, repo, strlen(repo)); | ||
| 148 | ++ | ||
| 149 | ++ /* Decode percent-encoding to temp buffer, then escape for shell */ | ||
| 150 | ++ { | ||
| 151 | ++ git_buf decoded = GIT_BUF_INIT; | ||
| 152 | ++ if (git_buf_decode_percent(&decoded, repo, strlen(repo)) < 0) { | ||
| 153 | ++ git_buf_dispose(&decoded); | ||
| 154 | ++ return -1; | ||
| 155 | ++ } | ||
| 156 | ++ git_buf_puts_escaped(request, decoded.ptr, "'!", "'\\", "'"); | ||
| 157 | ++ git_buf_dispose(&decoded); | ||
| 158 | ++ } | ||
| 159 | ++ | ||
| 160 | + git_buf_puts(request, "'"); | ||
| 161 | + | ||
| 162 | + if (git_buf_oom(request)) | ||
| 163 | +diff --git a/tests/core/buffer.c b/tests/core/buffer.c | ||
| 164 | +index 2af4a87..d3ec2d2 100644 | ||
| 165 | +--- a/tests/core/buffer.c | ||
| 166 | ++++ b/tests/core/buffer.c | ||
| 167 | +@@ -693,17 +693,25 @@ void test_core_buffer__puts_escaped(void) | ||
| 168 | + git_buf a = GIT_BUF_INIT; | ||
| 169 | + | ||
| 170 | + git_buf_clear(&a); | ||
| 171 | +- cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "", "")); | ||
| 172 | ++ cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "", "", NULL)); | ||
| 173 | + cl_assert_equal_s("this is a test", a.ptr); | ||
| 174 | + | ||
| 175 | + git_buf_clear(&a); | ||
| 176 | +- cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "t", "\\")); | ||
| 177 | ++ cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "t", "\\", NULL)); | ||
| 178 | + cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); | ||
| 179 | + | ||
| 180 | + git_buf_clear(&a); | ||
| 181 | +- cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "i ", "__")); | ||
| 182 | ++ cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "i ", "__", NULL)); | ||
| 183 | + cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); | ||
| 184 | + | ||
| 185 | ++ git_buf_clear(&a); | ||
| 186 | ++ cl_git_pass(git_buf_puts_escaped(&a, "foo'bar!baz", "'!", "'\\", "'")); | ||
| 187 | ++ cl_assert_equal_s("foo'\\''bar'\\!'baz", a.ptr); | ||
| 188 | ++ | ||
| 189 | ++ git_buf_clear(&a); | ||
| 190 | ++ cl_git_pass(git_buf_puts_escaped(&a, "no-match", "'!", "'\\", "'")); | ||
| 191 | ++ cl_assert_equal_s("no-match", a.ptr); | ||
| 192 | ++ | ||
| 193 | + git_buf_clear(&a); | ||
| 194 | + cl_git_pass(git_buf_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); | ||
| 195 | + cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); | ||
| 196 | +-- | ||
| 197 | +2.25.1 | ||
| 198 | + | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | Name: libgit2 | 1 | Name: libgit2 |
| 2 | Version: 1.3.2 | 2 | Version: 1.3.2 |
| 3 | -Release: 3 | 3 | +Release: 4 |
| 4 | Summary: portable, pure C implementation of the Git core methods | 4 | Summary: portable, pure C implementation of the Git core methods |
| 5 | License: GPLv2 with exceptions | 5 | License: GPLv2 with exceptions |
| 6 | URL: https://libgit2.org | 6 | URL: https://libgit2.org |
| @@ -8,6 +8,7 @@ Source0: https://github.com/libgit2/libgit2/archive/v%{version}.tar.gz | |||
| 8 | Patch1: CVE-2023-22742.patch | 8 | Patch1: CVE-2023-22742.patch |
| 9 | # https://github.com/libgit2/libgit2/commit/eb4c1716cd92bf56f2770653a915d5fc01eab8f3 | 9 | # https://github.com/libgit2/libgit2/commit/eb4c1716cd92bf56f2770653a915d5fc01eab8f3 |
| 10 | Patch2: CVE-2024-24577.patch | 10 | Patch2: CVE-2024-24577.patch |
| 11 | +Patch3: CVE-2026-5917-Escape-repo-path-in-gen_proto.patch | ||
| 11 | 12 | ||
| 12 | BuildRequires: gcc cmake >= 2.8.11 ninja-build http-parser-devel libcurl-devel | 13 | BuildRequires: gcc cmake >= 2.8.11 ninja-build http-parser-devel libcurl-devel |
| 13 | BuildRequires: libssh2-devel openssl-devel python3 zlib-devel | 14 | BuildRequires: libssh2-devel openssl-devel python3 zlib-devel |
| @@ -59,6 +60,9 @@ rm -vr deps | |||
| 59 | %{_includedir}/git2* | 60 | %{_includedir}/git2* |
| 60 | 61 | ||
| 61 | %changelog | 62 | %changelog |
| 63 | +* Wed Nov 4 2026 litenglong <litenglong@kylinos.cn> - 1.3.2-4 | ||
| 64 | +- Fix CVE-2026-5917 | ||
| 65 | + | ||
| 62 | * Wed Feb 07 2024 yaoxin <yao_xin001@hoperun.com> - 1.3.2-3 | 66 | * Wed Feb 07 2024 yaoxin <yao_xin001@hoperun.com> - 1.3.2-3 |
| 63 | - Fix CVE-2024-24577 | 67 | - Fix CVE-2024-24577 |
| 64 | 68 | ||