Open
fix CVE-2026-5917 #64
1AAdd on 4 days ago
fix CVE-2026-5917 #64
Open
Total: 2 files changed+199-1
| @@ -0,0 +1,194 @@ | |||
| 1 | +From 35dcd89e842098e3de88ec80d8b432d46ea6902c Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: litenglong <litenglong@kylinos.cn> | ||
| 3 | +Date: Wed, 4 Nov 2026 15:43:46 +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 uses ssh.c with the older git_buf API and git_buf_text_* namespace. | ||
| 10 | +Applied the same fix manually with adaptation to preserve percent-decoding: | ||
| 11 | +git_buf_decode_percent(request, repo, strlen(repo)) -> | ||
| 12 | +decode to temp buffer, then git_buf_text_puts_escaped(request, decoded, "'!", "'\\", "'"). | ||
| 13 | + | ||
| 14 | +Additional changes: | ||
| 15 | +- Upgrade git_buf_text_puts_escaped() from 4-parameter to 5-parameter | ||
| 16 | + implementation (esc_with split into esc_prefix and esc_suffix, both | ||
| 17 | + NULL-safe) | ||
| 18 | +- Update all existing call sites to pass NULL as esc_suffix: | ||
| 19 | + * src/buf_text.h: git_buf_text_puts_escape_regex() inline wrapper | ||
| 20 | + * tests/core/buffer.c: test_core_buffer__puts_escaped() test cases | ||
| 21 | +- Remove unused `int len` variable from gen_proto() | ||
| 22 | +- Preserve percent-decoding: decode to temp git_buf first, then escape | ||
| 23 | + the decoded result for shell safety | ||
| 24 | +- Add test cases for esc_suffix non-NULL (shell quote escaping) | ||
| 25 | + | ||
| 26 | +Signed-off-by: Stephen Gallagher <sgallagh@redhat.com> | ||
| 27 | +Signed-off-by: litenglong <litenglong@kylinos.cn> | ||
| 28 | +Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> | ||
| 29 | +--- | ||
| 30 | + src/buf_text.c | 24 ++++++++++++++++++------ | ||
| 31 | + src/buf_text.h | 8 +++++--- | ||
| 32 | + src/transports/ssh.c | 17 ++++++++++++----- | ||
| 33 | + tests/core/buffer.c | 14 +++++++++++--- | ||
| 34 | + 4 files changed, 46 insertions(+), 17 deletions(-) | ||
| 35 | + | ||
| 36 | +diff --git a/src/buf_text.c b/src/buf_text.c | ||
| 37 | +index 306980b..824dc38 100644 | ||
| 38 | +--- a/src/buf_text.c | ||
| 39 | ++++ b/src/buf_text.c | ||
| 40 | +@@ -10,10 +10,13 @@ int git_buf_text_puts_escaped( | ||
| 41 | + git_buf *buf, | ||
| 42 | + const char *string, | ||
| 43 | + const char *esc_chars, | ||
| 44 | +- const char *esc_with) | ||
| 45 | ++ const char *esc_prefix, | ||
| 46 | ++ const char *esc_suffix) | ||
| 47 | + { | ||
| 48 | + const char *scan; | ||
| 49 | +- size_t total = 0, esc_len = strlen(esc_with), count, alloclen; | ||
| 50 | ++ size_t total = 0, count, alloclen; | ||
| 51 | ++ size_t esc_prefix_len = esc_prefix ? strlen(esc_prefix) : 0; | ||
| 52 | ++ size_t esc_suffix_len = esc_suffix ? strlen(esc_suffix) : 0; | ||
| 53 | + | ||
| 54 | + if (!string) | ||
| 55 | + return 0; | ||
| 56 | +@@ -25,7 +28,7 @@ int git_buf_text_puts_escaped( | ||
| 57 | + scan += count; | ||
| 58 | + /* count run of escaped characters */ | ||
| 59 | + count = strspn(scan, esc_chars); | ||
| 60 | +- total += count * (esc_len + 1); | ||
| 61 | ++ total += count * (esc_prefix_len + esc_suffix_len + 1); | ||
| 62 | + scan += count; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | +@@ -41,13 +44,22 @@ int git_buf_text_puts_escaped( | ||
| 66 | + buf->size += count; | ||
| 67 | + | ||
| 68 | + for (count = strspn(scan, esc_chars); count > 0; --count) { | ||
| 69 | +- /* copy escape sequence */ | ||
| 70 | +- memmove(buf->ptr + buf->size, esc_with, esc_len); | ||
| 71 | +- buf->size += esc_len; | ||
| 72 | ++ /* copy escape prefix sequence */ | ||
| 73 | ++ if (esc_prefix) { | ||
| 74 | ++ memmove(buf->ptr + buf->size, esc_prefix, esc_prefix_len); | ||
| 75 | ++ buf->size += esc_prefix_len; | ||
| 76 | ++ } | ||
| 77 | ++ | ||
| 78 | + /* copy character to be escaped */ | ||
| 79 | + buf->ptr[buf->size] = *scan; | ||
| 80 | + buf->size++; | ||
| 81 | + scan++; | ||
| 82 | ++ | ||
| 83 | ++ /* copy escape suffix sequence */ | ||
| 84 | ++ if (esc_suffix) { | ||
| 85 | ++ memmove(buf->ptr + buf->size, esc_suffix, esc_suffix_len); | ||
| 86 | ++ buf->size += esc_suffix_len; | ||
| 87 | ++ } | ||
| 88 | + } | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | +diff --git a/src/buf_text.h b/src/buf_text.h | ||
| 92 | +index 726b0ae..e826c0a 100644 | ||
| 93 | +--- a/src/buf_text.h | ||
| 94 | ++++ b/src/buf_text.h | ||
| 95 | +@@ -33,21 +33,23 @@ typedef struct { | ||
| 96 | + * @param buf Buffer to append data to | ||
| 97 | + * @param string String to escape and append | ||
| 98 | + * @param esc_chars Characters to be escaped | ||
| 99 | +- * @param esc_with String to insert in from of each found character | ||
| 100 | ++ * @param esc_prefix String to insert before each found character (or NULL) | ||
| 101 | ++ * @param esc_suffix String to insert after each found character (or NULL) | ||
| 102 | + * @return 0 on success, <0 on failure (probably allocation problem) | ||
| 103 | + */ | ||
| 104 | + extern int git_buf_text_puts_escaped( | ||
| 105 | + git_buf *buf, | ||
| 106 | + const char *string, | ||
| 107 | + const char *esc_chars, | ||
| 108 | +- const char *esc_with); | ||
| 109 | ++ const char *esc_prefix, | ||
| 110 | ++ const char *esc_suffix); | ||
| 111 | + | ||
| 112 | + /** | ||
| 113 | + * Append string escaping characters that are regex special | ||
| 114 | + */ | ||
| 115 | + GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string) | ||
| 116 | + { | ||
| 117 | +- return git_buf_text_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\"); | ||
| 118 | ++ return git_buf_text_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\", NULL); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + /** | ||
| 122 | +diff --git a/src/transports/ssh.c b/src/transports/ssh.c | ||
| 123 | +index 8098dda..3062c12 100644 | ||
| 124 | +--- a/src/transports/ssh.c | ||
| 125 | ++++ b/src/transports/ssh.c | ||
| 126 | +@@ -65,7 +65,6 @@ static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) | ||
| 127 | + static int gen_proto(git_buf *request, const char *cmd, const char *url) | ||
| 128 | + { | ||
| 129 | + const char *repo; | ||
| 130 | +- int len; | ||
| 131 | + size_t i; | ||
| 132 | + | ||
| 133 | + for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) { | ||
| 134 | +@@ -89,12 +88,20 @@ done: | ||
| 135 | + return -1; | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | +- len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1; | ||
| 139 | +- | ||
| 140 | +- git_buf_grow(request, len); | ||
| 141 | + git_buf_puts(request, cmd); | ||
| 142 | + git_buf_puts(request, " '"); | ||
| 143 | +- git_buf_decode_percent(request, repo, strlen(repo)); | ||
| 144 | ++ | ||
| 145 | ++ /* Decode percent-encoding to temp buffer, then escape for shell */ | ||
| 146 | ++ { | ||
| 147 | ++ git_buf decoded = GIT_BUF_INIT; | ||
| 148 | ++ if (git_buf_decode_percent(&decoded, repo, strlen(repo)) < 0) { | ||
| 149 | ++ git_buf_free(&decoded); | ||
| 150 | ++ return -1; | ||
| 151 | ++ } | ||
| 152 | ++ git_buf_text_puts_escaped(request, decoded.ptr, "'!", "'\\", "'"); | ||
| 153 | ++ git_buf_free(&decoded); | ||
| 154 | ++ } | ||
| 155 | ++ | ||
| 156 | + git_buf_puts(request, "'"); | ||
| 157 | + | ||
| 158 | + if (git_buf_oom(request)) | ||
| 159 | +diff --git a/tests/core/buffer.c b/tests/core/buffer.c | ||
| 160 | +index c4308cb..e4555bc 100644 | ||
| 161 | +--- a/tests/core/buffer.c | ||
| 162 | ++++ b/tests/core/buffer.c | ||
| 163 | +@@ -709,17 +709,25 @@ void test_core_buffer__puts_escaped(void) | ||
| 164 | + git_buf a = GIT_BUF_INIT; | ||
| 165 | + | ||
| 166 | + git_buf_clear(&a); | ||
| 167 | +- cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "", "")); | ||
| 168 | ++ cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "", "", NULL)); | ||
| 169 | + cl_assert_equal_s("this is a test", a.ptr); | ||
| 170 | + | ||
| 171 | + git_buf_clear(&a); | ||
| 172 | +- cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "t", "\\")); | ||
| 173 | ++ cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "t", "\\", NULL)); | ||
| 174 | + cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); | ||
| 175 | + | ||
| 176 | + git_buf_clear(&a); | ||
| 177 | +- cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "i ", "__")); | ||
| 178 | ++ cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "i ", "__", NULL)); | ||
| 179 | + cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); | ||
| 180 | + | ||
| 181 | ++ git_buf_clear(&a); | ||
| 182 | ++ cl_git_pass(git_buf_text_puts_escaped(&a, "foo'bar!baz", "'!", "'\\", "'")); | ||
| 183 | ++ cl_assert_equal_s("foo'\\''bar'\\!'baz", a.ptr); | ||
| 184 | ++ | ||
| 185 | ++ git_buf_clear(&a); | ||
| 186 | ++ cl_git_pass(git_buf_text_puts_escaped(&a, "no-match", "'!", "'\\", "'")); | ||
| 187 | ++ cl_assert_equal_s("no-match", a.ptr); | ||
| 188 | ++ | ||
| 189 | + git_buf_clear(&a); | ||
| 190 | + cl_git_pass(git_buf_text_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); | ||
| 191 | + cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); | ||
| 192 | +-- | ||
| 193 | +2.25.1 | ||
| 194 | + | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | Name: libgit2 | 1 | Name: libgit2 |
| 2 | Version: 0.27.8 | 2 | Version: 0.27.8 |
| 3 | -Release: 8 | 3 | +Release: 9 |
| 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 |
| @@ -13,6 +13,7 @@ Patch0004: Remove-error-prone-redundant-test.patch | |||
| 13 | Patch0005: CVE-2023-22742.patch | 13 | Patch0005: CVE-2023-22742.patch |
| 14 | # https://github.com/libgit2/libgit2/commit/eb4c1716cd92bf56f2770653a915d5fc01eab8f3 | 14 | # https://github.com/libgit2/libgit2/commit/eb4c1716cd92bf56f2770653a915d5fc01eab8f3 |
| 15 | Patch0006: CVE-2024-24577.patch | 15 | Patch0006: CVE-2024-24577.patch |
| 16 | +Patch0007: CVE-2026-5917-Escape-repo-path-in-gen_proto.patch | ||
| 16 | 17 | ||
| 17 | BuildRequires: gcc cmake >= 2.8.11 ninja-build http-parser-devel libcurl-devel | 18 | BuildRequires: gcc cmake >= 2.8.11 ninja-build http-parser-devel libcurl-devel |
| 18 | BuildRequires: libssh2-devel openssl-devel python3 zlib-devel | 19 | BuildRequires: libssh2-devel openssl-devel python3 zlib-devel |
| @@ -61,6 +62,9 @@ sed -i '/ADD_TEST(online/s/^/#/' tests/CMakeLists.txt | |||
| 61 | %{_includedir}/git2* | 62 | %{_includedir}/git2* |
| 62 | 63 | ||
| 63 | %changelog | 64 | %changelog |
| 65 | +* Wed Nov 4 2026 litenglong <litenglong@kylinos.cn> - 0.27.8-9 | ||
| 66 | +- Fix CVE-2026-5917 | ||
| 67 | + | ||
| 64 | * Thu Feb 08 2024 yaoxin <yao_xin001@hoperun.com> - 0.27.8-8 | 68 | * Thu Feb 08 2024 yaoxin <yao_xin001@hoperun.com> - 0.27.8-8 |
| 65 | - Fix CVE-2024-24577 | 69 | - Fix CVE-2024-24577 |
| 66 | 70 | ||