Hello,

Bookworm is on libgit2 version 1.5.1+ds-1+deb12u1. Four of the six CVEs that 
Trixie fixed in DSA-6453-1 hit that version, so I've backported the Trixie 
patches into 1.5.1+ds-1+deb12u2. The debdiff is attached, and I've pushed the 
changes to a Salsa fork here: 
https://salsa.debian.org/tsimonq2/libgit2/-/tree/debian/bookworm?ref_type=heads

What was fixed:

- CVE-2026-53584: submodule paths from .gitmodules weren't checked for "..", so 
a malicious repo could push submodule lookup/add/init/update outside the work tree. Those 
paths now return GIT_EINVALID.
- CVE-2026-53585: git_delta_apply would allocate whatever size a delta header 
claimed, so a tiny pack could force a huge malloc. Delta results are capped at 
2 GiB by default (GIT_OPT_SET_PACK_MAX_OBJECT_SIZE can raise it). The two new 
GIT_OPT_*_PACK_MAX_OBJECT_SIZE values are stuck on the end of git_libgit2_opt_t 
so existing values don't shift.
- CVE-2026-53586: after an offsite redirect on the first HTTP request, the 
credential callback still saw the original URL, so a helper could hand the 
first site's credentials to the new host. It now gets the host it's actually 
talking to.
- CVE-2026-5917: the repository path went into single quotes unescaped in the 
SSH command, so a ' in the path could run commands on the server. It's escaped 
now.

CVE-2026-5917 also closes #1144465 (already closed in unstable by 1.9.7+ds-1).

CVE-2026-53583 and CVE-2026-53587 are not an issue on Bookworm. I marked them 
not-affected in security-tracker commit 
e5fda4f0c6a330341f8b559c26c84f0665769e22.

CVE-2026-53583 lives in the OpenSSL stream backend. Bookworm builds with 
-DUSE_HTTPS=mbedTLS, openssl.c is behind #ifdef GIT_OPENSSL, and the shipped 
.so links mbedTLS, not libssl. CVE-2026-53587 is in the object-format 
capability parsing that landed in 1.6.1. 1.5.1 simply doesn't have that code.

Built with sbuild using a Bookworm schroot. dh_auto_test is fine for static and 
shared, patches apply cleanly, and the only Lintian warning is the same 
pkg-config one we already had. Offline clar suite: 2804/0/46 on deb12u1, 
2807/0/46 on this package (the three new ones are upstream's submodule escape 
tests). Invasive and util suites match. 936 exported symbols, identical; 
gensymbols and abidiff are clean. autopkgtest passed.

I also tested each fix against deb12u1 and then this package:

- CVE-2026-53584: upstream's submodule::escape tests fail 3/5 on deb12u1, and a 
submodule with path ../escape-target still shows up. All 5 pass here.
- CVE-2026-53585: indexing upstream's 150-byte delta_100mb.pack (claims a 100 
MiB object) sat around 107 MiB RSS on deb12u1. Here, with the cap at 42 MiB, it 
refuses it as too large at ~8 MiB RSS. A pack claiming 3 GiB made deb12u1 try a 
3 GiB malloc; this package refuses it with the default cap before allocating.
- CVE-2026-53586: upstream's redirect_authentication_is_current_host test 
against a localhost redirect from :7101 to :7102. Callback got :7101 on 
deb12u1, :7102 on this package.
- CVE-2026-5917: no upstream test, so I used a local sshd. A path ending in '; 
touch /tmp/pwned_5917; ' created the file on deb12u1. Here the path went out 
escaped, no file, and the server just said it wasn't a git repository.

I rebuilt the reverse dependencies against both deb12u1 and this package in 
Bookworm schroots, and found no regressions. If you would like more details on 
this, or if I should test something specific, just let me know.

Salsa pipeline: https://salsa.debian.org/tsimonq2/libgit2/-/pipelines/1186327

Debusine work request: 
https://debusine.debian.net/debian/developers/work-request/1434734/

This is my first LTS NMU; feedback is appreciated. If nothing showstopping 
comes up, I'll upload this and issue the DLA on Monday morning (October 12th, 
2026).

Best regards,
Simon Quigley
[email protected]
diff -Nru libgit2-1.5.1+ds/debian/changelog libgit2-1.5.1+ds/debian/changelog
--- libgit2-1.5.1+ds/debian/changelog   2024-02-08 05:31:43.000000000 -0600
+++ libgit2-1.5.1+ds/debian/changelog   2026-10-08 12:29:11.000000000 -0500
@@ -1,3 +1,17 @@
+libgit2 (1.5.1+ds-1+deb12u2) bookworm-security; urgency=high
+
+  * Non-maintainer upload by the LTS Team.
+  * CVE-2026-53584: Reject path traversal in submodule paths read from
+    .gitmodules.
+  * CVE-2026-53585: Limit the size of objects produced by delta application
+    (2 GiB by default) to prevent memory exhaustion.
+  * CVE-2026-53586: Pass the current host to the credential callback after an
+    HTTP redirect.
+  * CVE-2026-5917: Escape the repository path in the SSH remote command to
+    prevent shell command injection. (Closes: #1144465)
+
+ -- Simon Quigley <[email protected]>  Thu, 08 Oct 2026 12:29:11 -0500
+
 libgit2 (1.5.1+ds-1+deb12u1) bookworm-security; urgency=high
 
   * Team upload.
diff -Nru libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53584.patch 
libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53584.patch
--- libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53584.patch      
1969-12-31 18:00:00.000000000 -0600
+++ libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53584.patch      
2026-10-08 12:15:02.000000000 -0500
@@ -0,0 +1,348 @@
+Description: submodule: reject path traversal in submodule paths 
(CVE-2026-53584)
+ Submodule paths loaded from .gitmodules were not checked for ".."
+ traversal components, so a crafted repository could make submodule
+ lookup, add, init and update operate on directories outside the
+ repository working tree. Validate the path as upstream does and reject
+ invalid entries with GIT_EINVALID.
+ .
+ Backported from the trixie patch (1.9.0+ds-2+deb13u1, DSA-6453-1); the
+ context was adjusted for 1.5 (git_strmap instead of
+ git_submodule_namemap). Upstream's submodule::escape tests are included.
+Author: Edward Thomson <[email protected]>
+Origin: backport, 
https://github.com/libgit2/libgit2/commit/ec7371da9f359cd8293e9108e7a0b1c1b61b67c4
+Bug: https://github.com/libgit2/libgit2/security/advisories/GHSA-cw77-j82w-mchm
+Applied-Upstream: 1.9.5, 
https://github.com/libgit2/libgit2/commit/ec7371da9f359cd8293e9108e7a0b1c1b61b67c4
+Forwarded: not-needed
+Last-Update: 2026-10-08
+---
+diff --git a/src/libgit2/submodule.c b/src/libgit2/submodule.c
+index 0f4f072..9cadeac 100644
+--- a/src/libgit2/submodule.c
++++ b/src/libgit2/submodule.c
+@@ -77,6 +77,7 @@ static void submodule_get_index_status(unsigned int *, 
git_submodule *);
+ static void submodule_get_wd_status(unsigned int *, git_submodule *, 
git_repository *, git_submodule_ignore_t);
+ static void submodule_update_from_index_entry(git_submodule *sm, const 
git_index_entry *ie);
+ static void submodule_update_from_head_data(git_submodule *sm, mode_t mode, 
const git_oid *id);
++static int path_is_valid(git_repository *repo, const char *path);
+ 
+ static int submodule_cmp(const void *a, const void *b)
+ {
+@@ -213,6 +214,7 @@ static int load_submodule_names(git_strmap **out, 
git_repository *repo, git_conf
+ 
+       while ((error = git_config_next(&entry, iter)) == 0) {
+               const char *fdot, *ldot;
++
+               fdot = strchr(entry->name, '.');
+               ldot = strrchr(entry->name, '.');
+ 
+@@ -225,6 +227,7 @@ static int load_submodule_names(git_strmap **out, 
git_repository *repo, git_conf
+ 
+               git_str_clear(&buf);
+               git_str_put(&buf, fdot + 1, ldot - fdot - 1);
++
+               isvalid = git_submodule_name_is_valid(repo, buf.ptr, 0);
+               if (isvalid < 0) {
+                       error = isvalid;
+@@ -233,6 +236,14 @@ static int load_submodule_names(git_strmap **out, 
git_repository *repo, git_conf
+               if (!isvalid)
+                       continue;
+ 
++              isvalid = path_is_valid(repo, entry->value);
++              if (isvalid < 0) {
++                      error = isvalid;
++                      goto out;
++              }
++              if (!isvalid)
++                      continue;
++
+               if ((error = git_strmap_set(names, git__strdup(entry->value), 
git_str_detach(&buf))) < 0) {
+                       git_error_set(GIT_ERROR_NOMEMORY, "error inserting 
submodule into hash table");
+                       error = -1;
+@@ -294,9 +305,9 @@ int git_submodule__lookup_with_cache(
+       const char *name,    /* trailing slash is allowed */
+       git_strmap *cache)
+ {
+-      int error;
+-      unsigned int location;
+       git_submodule *sm;
++      unsigned int location;
++      int error;
+ 
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
+@@ -319,7 +330,17 @@ int git_submodule__lookup_with_cache(
+       if ((error = submodule_alloc(&sm, repo, name)) < 0)
+               return error;
+ 
+-      if ((error = git_submodule_reload(sm, false)) < 0) {
++      /*
++       * Only try to reload if they gave us a valid _name_; if this is a
++       * path, we'll do some lookups then try to reload to populate.
++       */
++      if (git_submodule_name_is_valid(sm->repo, name, 0) <= 0) {
++              error = GIT_ENOTFOUND;
++      } else {
++              error = git_submodule_reload(sm, false);
++      }
++
++      if (error < 0 && error != GIT_ENOTFOUND) {
+               git_submodule_free(sm);
+               return error;
+       }
+@@ -378,6 +399,7 @@ int git_submodule__lookup_with_cache(
+       /* If we still haven't found it, do the WD check */
+       if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) {
+               git_submodule_free(sm);
++              git_error_set(GIT_ERROR_SUBMODULE, "invalid submodule name: 
'%s'", name);
+               error = GIT_ENOTFOUND;
+ 
+               /* If it's not configured, we still check if there's a repo at 
the path */
+@@ -429,6 +451,27 @@ int git_submodule_name_is_valid(git_repository *repo, 
const char *name, int flag
+       return isvalid;
+ }
+ 
++static int path_is_valid(git_repository *repo, const char *path)
++{
++      git_str buf = GIT_STR_INIT;
++      int flags = GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS &
++                  ~GIT_FS_PATH_REJECT_EMPTY_COMPONENT;
++      int error, isvalid;
++
++      /* Avoid allocating a new string if we can avoid it */
++      if (strchr(path, '\\') != NULL) {
++              if ((error = git_fs_path_normalize_slashes(&buf, path)) < 0)
++                      return error;
++      } else {
++              git_str_attach_notowned(&buf, path, strlen(path));
++      }
++
++      isvalid = git_path_is_valid(repo, buf.ptr, 0, flags);
++      git_str_dispose(&buf);
++
++      return isvalid;
++}
++
+ static void submodule_free_dup(void *sm)
+ {
+       git_submodule_free(sm);
+@@ -1667,18 +1710,34 @@ static int submodule_update_head(git_submodule 
*submodule)
+       return 0;
+ }
+ 
++static int submodule_name_error(const char *name)
++{
++      git_error_set(GIT_ERROR_SUBMODULE,
++              "invalid value for submodule name: '%s'", name);
++      return GIT_EINVALID;
++}
++
++static int submodule_config_error(const char *property, const char *value)
++{
++      git_error_set(GIT_ERROR_SUBMODULE,
++              "invalid value for submodule '%s' property: '%s'", property, 
value);
++      return GIT_EINVALID;
++}
++
+ int git_submodule_reload(git_submodule *sm, int force)
+ {
+       git_config *mods = NULL;
+-      int error;
++      int valid, error = 0;
+ 
+       GIT_UNUSED(force);
+ 
+       GIT_ASSERT_ARG(sm);
+ 
+-      if ((error = git_submodule_name_is_valid(sm->repo, sm->name, 0)) <= 0)
++      if ((valid = git_submodule_name_is_valid(sm->repo, sm->name, 0)) <= 0) {
+               /* This should come with a warning, but we've no API for that */
++              error = valid ? valid : submodule_name_error(sm->name);
+               goto out;
++      }
+ 
+       if (git_repository_is_bare(sm->repo))
+               goto out;
+@@ -1701,6 +1760,12 @@ int git_submodule_reload(git_submodule *sm, int force)
+           (error = submodule_update_head(sm)) < 0)
+               goto out;
+ 
++      if ((valid = path_is_valid(sm->repo, sm->path)) <= 0) {
++              /* This should come with a warning, but we've no API for that */
++              error = valid ? valid : submodule_config_error("path", 
sm->path);
++              goto out;
++      }
++
+ out:
+       git_config_free(mods);
+       return error;
+@@ -1884,13 +1949,6 @@ void git_submodule_free(git_submodule *sm)
+       GIT_REFCOUNT_DEC(sm, submodule_release);
+ }
+ 
+-static int submodule_config_error(const char *property, const char *value)
+-{
+-      git_error_set(GIT_ERROR_INVALID,
+-              "invalid value for submodule '%s' property: '%s'", property, 
value);
+-      return -1;
+-}
+-
+ int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value)
+ {
+       int val;
+@@ -2089,6 +2147,13 @@ static int submodule_load_each(const git_config_entry 
*entry, void *payload)
+               goto done;
+       }
+ 
++      isvalid = path_is_valid(data->repo, sm->path);
++      if (isvalid <= 0) {
++              git_submodule_free(sm);
++              error = isvalid;
++              goto done;
++      }
++
+       if ((error = git_strmap_set(map, sm->name, sm)) < 0)
+               goto done;
+ 
+diff --git a/src/util/fs_path.h b/src/util/fs_path.h
+index e5ca673..31da593 100644
+--- a/src/util/fs_path.h
++++ b/src/util/fs_path.h
+@@ -609,18 +609,18 @@ extern int git_fs_path_from_url_or_path(git_str 
*local_path_out, const char *url
+  */
+ #ifdef GIT_WIN32
+ # define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \
+-      GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \
+-      GIT_FS_PATH_REJECT_TRAVERSAL | \
+-      GIT_FS_PATH_REJECT_BACKSLASH | \
+-      GIT_FS_PATH_REJECT_TRAILING_DOT | \
+-      GIT_FS_PATH_REJECT_TRAILING_SPACE | \
+-      GIT_FS_PATH_REJECT_TRAILING_COLON | \
+-      GIT_FS_PATH_REJECT_DOS_PATHS | \
+-      GIT_FS_PATH_REJECT_NT_CHARS
++      (GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \
++       GIT_FS_PATH_REJECT_TRAVERSAL | \
++       GIT_FS_PATH_REJECT_BACKSLASH | \
++       GIT_FS_PATH_REJECT_TRAILING_DOT | \
++       GIT_FS_PATH_REJECT_TRAILING_SPACE | \
++       GIT_FS_PATH_REJECT_TRAILING_COLON | \
++       GIT_FS_PATH_REJECT_DOS_PATHS | \
++       GIT_FS_PATH_REJECT_NT_CHARS)
+ #else
+ # define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \
+-      GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \
+-      GIT_FS_PATH_REJECT_TRAVERSAL
++      (GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \
++       GIT_FS_PATH_REJECT_TRAVERSAL)
+ #endif
+ 
+ /**
+diff --git a/tests/libgit2/submodule/escape.c 
b/tests/libgit2/submodule/escape.c
+index bcd52b5..e644f83 100644
+--- a/tests/libgit2/submodule/escape.c
++++ b/tests/libgit2/submodule/escape.c
+@@ -16,6 +16,9 @@ void test_submodule_escape__cleanup(void)
+ #define EVIL_SM_NAME_WINDOWS "..\\\\..\\\\modules\\\\evil"
+ #define EVIL_SM_NAME_WINDOWS_UNESC "..\\..\\modules\\evil"
+ 
++#define ESCAPE_SM_NAME "escape"
++#define ESCAPE_SM_PATH "../escape-target"
++
+ static int find_evil(git_submodule *sm, const char *name, void *payload)
+ {
+       int *foundit = (int *) payload;
+@@ -23,7 +26,8 @@ static int find_evil(git_submodule *sm, const char *name, 
void *payload)
+       GIT_UNUSED(sm);
+ 
+       if (!git__strcmp(EVIL_SM_NAME, name) ||
+-          !git__strcmp(EVIL_SM_NAME_WINDOWS_UNESC, name))
++          !git__strcmp(EVIL_SM_NAME_WINDOWS_UNESC, name) ||
++          !git__strcmp(ESCAPE_SM_NAME, name))
+               *foundit = true;
+ 
+       return 0;
+@@ -49,7 +53,9 @@ void test_submodule_escape__from_gitdir(void)
+       foundit = 0;
+       cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit));
+       cl_assert_equal_i(0, foundit);
++
+       cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, 
EVIL_SM_NAME));
++
+       /*
+        * We do know about this as it's in the index and HEAD, but the data is
+        * incomplete as there is no configured data for it (we pretend it
+@@ -83,7 +89,9 @@ void test_submodule_escape__from_gitdir_windows(void)
+       foundit = 0;
+       cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit));
+       cl_assert_equal_i(0, foundit);
++
+       cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, 
EVIL_SM_NAME_WINDOWS_UNESC));
++
+       /*
+        * We do know about this as it's in the index and HEAD, but the data is
+        * incomplete as there is no configured data for it (we pretend it
+@@ -96,3 +104,53 @@ void test_submodule_escape__from_gitdir_windows(void)
+       cl_assert_equal_i(GIT_SUBMODULE_STATUS_IN_INDEX | 
GIT_SUBMODULE_STATUS_IN_HEAD, sm_location);
+       git_submodule_free(sm);
+ }
++
++void test_submodule_escape__from_path_cannot_be_enumerated(void)
++{
++      git_str buf = GIT_STR_INIT;
++      int foundit;
++
++      g_repo = setup_fixture_submodule_simple();
++
++      cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), 
".gitmodules"));
++      cl_git_rewritefile(buf.ptr,
++                         "[submodule \"" ESCAPE_SM_NAME "\"]\n"
++                         "    path = " ESCAPE_SM_PATH "\n"
++                         "    url = 
https://github.com/libgit2/TestGitRepository.git\n";);
++      git_str_dispose(&buf);
++
++      foundit = 0;
++      cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit));
++      cl_assert_equal_i(0, foundit);
++}
++
++void test_submodule_escape__from_path_cannot_be_lookedup(void)
++{
++      git_str buf = GIT_STR_INIT;
++      git_submodule *sm;
++
++      g_repo = setup_fixture_submodule_simple();
++
++      cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), 
".gitmodules"));
++      cl_git_rewritefile(buf.ptr,
++                         "[submodule \"" ESCAPE_SM_NAME "\"]\n"
++                         "    path = " ESCAPE_SM_PATH "\n"
++                         "    url = 
https://github.com/libgit2/TestGitRepository.git\n";);
++      git_str_dispose(&buf);
++
++      cl_git_fail_with(GIT_EINVALID, git_submodule_lookup(&sm, g_repo, 
ESCAPE_SM_NAME));
++      cl_git_fail_with(GIT_EINVALID, git_submodule_lookup(&sm, g_repo, 
ESCAPE_SM_PATH));
++}
++
++void test_submodule_escape__during_add(void)
++{
++      git_submodule *sm;
++
++      g_repo = setup_fixture_submodule_simple();
++
++      cl_git_fail_with(GIT_EINVALID, git_submodule_add_setup(
++              &sm, g_repo,
++              "https://github.com/libgit2/TestGitRepository.git";,
++              ESCAPE_SM_PATH,
++              1));
++}
+diff --git a/tests/libgit2/submodule/submodule_helpers.c 
b/tests/libgit2/submodule/submodule_helpers.c
+index b8fc9f6..d62c734 100644
+--- a/tests/libgit2/submodule/submodule_helpers.c
++++ b/tests/libgit2/submodule/submodule_helpers.c
+@@ -4,6 +4,7 @@
+ #include "posix.h"
+ #include "submodule_helpers.h"
+ #include "git2/sys/repository.h"
++#include "submodule.h"
+ 
+ /* rewrite gitmodules -> .gitmodules
+  * rewrite the empty or relative urls inside each module
diff -Nru libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53585.patch 
libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53585.patch
--- libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53585.patch      
1969-12-31 18:00:00.000000000 -0600
+++ libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53585.patch      
2026-10-08 12:15:02.000000000 -0500
@@ -0,0 +1,122 @@
+Description: delta: limit the size of objects produced by delta application 
(CVE-2026-53585)
+ git_delta_apply() trusted the result size declared in a delta header and
+ allocated that much memory before validating the delta, so a very small
+ crafted pack could force huge allocations. Refuse delta results larger
+ than a configurable maximum (2 GiB by default) and add
+ GIT_OPT_GET_PACK_MAX_OBJECT_SIZE / GIT_OPT_SET_PACK_MAX_OBJECT_SIZE to
+ adjust it. The new option values are appended to the enum, so existing
+ values and the ABI are unchanged.
+ .
+ Hand-ported to 1.5.1: the option handling lives in src/libgit2/libgit2.c
+ (there is no settings.c in 1.5) and the include/git2/common.h enum
+ context differs. Upstream's test and its binary fixture are not included.
+Author: Edward Thomson <[email protected]>
+Origin: backport, 
https://github.com/libgit2/libgit2/commit/0cdfdd5fa8f8514c82413025e1e0808866cf7c30
+Bug: https://github.com/libgit2/libgit2/security/advisories/GHSA-27m5-gxxh-x79j
+Applied-Upstream: 1.9.5, 
https://github.com/libgit2/libgit2/commit/0cdfdd5fa8f8514c82413025e1e0808866cf7c30
+Forwarded: not-needed
+Last-Update: 2026-10-08
+---
+diff --git a/include/git2/common.h b/include/git2/common.h
+index c3e3e7b..495e0bc 100644
+--- a/include/git2/common.h
++++ b/include/git2/common.h
+@@ -227,7 +227,9 @@ typedef enum {
+       GIT_OPT_GET_EXTENSIONS,
+       GIT_OPT_SET_EXTENSIONS,
+       GIT_OPT_GET_OWNER_VALIDATION,
+-      GIT_OPT_SET_OWNER_VALIDATION
++      GIT_OPT_SET_OWNER_VALIDATION,
++      GIT_OPT_GET_PACK_MAX_OBJECT_SIZE,
++      GIT_OPT_SET_PACK_MAX_OBJECT_SIZE
+ } git_libgit2_opt_t;
+ 
+ /**
+@@ -473,6 +475,16 @@ typedef enum {
+  *      > Set that repository directories should be owned by the current
+  *      > user. The default is to validate ownership.
+  *
++ *   opts(GIT_OPT_GET_PACK_MAX_OBJECT_SIZE, size_t *out)
++ *      > Gets the maximum size of a declared packfile object. This can
++ *      > be used to limit maximum memory usage when fetching from a
++ *      > remote.
++ *
++ *   opts(GIT_OPT_SET_PACK_MAX_OBJECT_SIZE, size_t object_size)
++ *      > Set the maximum size of an object that libgit2 will allow in
++ *      > a pack file when downloading a pack file from a remote.
++ *      > The default is 2 GiB.
++ *
+  * @param option Option key
+  * @param ... value to set the option
+  * @return 0 on success, <0 on failure
+diff --git a/src/libgit2/delta.c b/src/libgit2/delta.c
+index 2d2c5fa..8f8833a 100644
+--- a/src/libgit2/delta.c
++++ b/src/libgit2/delta.c
+@@ -13,6 +13,8 @@
+ #define RABIN_SHIFT 23
+ #define RABIN_WINDOW 16
+ 
++extern size_t git_indexer__max_object_size;
++
+ static const unsigned int T[256] = {
+       0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,
+       0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,
+@@ -561,6 +563,12 @@ int git_delta_apply(
+               return -1;
+       }
+ 
++      if (res_sz > git_indexer__max_object_size) {
++              git_error_set(GIT_ERROR_INVALID,
++                      "failed to apply delta: overly large object");
++              return -1;
++      }
++
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1);
+       res_dp = git__malloc(alloc_sz);
+       GIT_ERROR_CHECK_ALLOC(res_dp);
+diff --git a/src/libgit2/indexer.c b/src/libgit2/indexer.c
+index afb9ce8..c910f18 100644
+--- a/src/libgit2/indexer.c
++++ b/src/libgit2/indexer.c
+@@ -24,10 +24,11 @@
+ #include "zstream.h"
+ #include "object.h"
+ 
+-size_t git_indexer__max_objects = UINT32_MAX;
+-
+ #define UINT31_MAX (0x7FFFFFFF)
+ 
++size_t git_indexer__max_objects = UINT32_MAX;
++size_t git_indexer__max_object_size = UINT31_MAX;
++
+ struct entry {
+       git_oid oid;
+       uint32_t crc;
+diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c
+index 2fda072..c400f83 100644
+--- a/src/libgit2/libgit2.c
++++ b/src/libgit2/libgit2.c
+@@ -43,6 +43,7 @@ extern size_t git_mwindow__window_size;
+ extern size_t git_mwindow__mapped_limit;
+ extern size_t git_mwindow__file_limit;
+ extern size_t git_indexer__max_objects;
++extern size_t git_indexer__max_object_size;
+ extern bool git_disable_pack_keep_file_checks;
+ extern int git_odb__packed_priority;
+ extern int git_odb__loose_priority;
+@@ -368,6 +369,14 @@ int git_libgit2_opts(int key, ...)
+               *(va_arg(ap, size_t *)) = git_indexer__max_objects;
+               break;
+ 
++      case GIT_OPT_SET_PACK_MAX_OBJECT_SIZE:
++              git_indexer__max_object_size = va_arg(ap, size_t);
++              break;
++
++      case GIT_OPT_GET_PACK_MAX_OBJECT_SIZE:
++              *(va_arg(ap, size_t *)) = git_indexer__max_object_size;
++              break;
++
+       case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS:
+               git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0);
+               break;
diff -Nru libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53586.patch 
libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53586.patch
--- libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53586.patch      
1969-12-31 18:00:00.000000000 -0600
+++ libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-53586.patch      
2026-10-08 12:15:02.000000000 -0500
@@ -0,0 +1,68 @@
+Description: http: give the credential callback the current host after a 
redirect (CVE-2026-53586)
+ After following an offsite initial redirect, the HTTP transport passed
+ the original remote (or proxy) URL to the credential callback instead of
+ the redirect target, so a callback that scopes credentials to the
+ original host could hand them to the redirected host. Format the current
+ server URL and pass that to the callback, as upstream does.
+ .
+ Backported from the trixie patch (applies with offsets only). Upstream's
+ ci/test.sh change for the online tests does not apply and is left out.
+Author: Edward Thomson <[email protected]>
+Origin: backport, 
https://github.com/libgit2/libgit2/commit/af2b29ad0a74d5bac9751376879ddaf848136d3b
+Bug: https://github.com/libgit2/libgit2/security/advisories/GHSA-2889-x8f6-mc4x
+Applied-Upstream: 1.9.5, 
https://github.com/libgit2/libgit2/commit/af2b29ad0a74d5bac9751376879ddaf848136d3b
+Forwarded: not-needed
+Last-Update: 2026-10-08
+---
+diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c
+index 7db5582..ff1c32b 100644
+--- a/src/libgit2/transports/http.c
++++ b/src/libgit2/transports/http.c
+@@ -132,12 +132,12 @@ GIT_INLINE(void) free_cred(git_credential **cred)
+ static int handle_auth(
+       http_server *server,
+       const char *server_type,
+-      const char *url,
+       unsigned int allowed_schemetypes,
+       unsigned int allowed_credtypes,
+       git_credential_acquire_cb callback,
+       void *callback_payload)
+ {
++      git_str server_url = GIT_STR_INIT;
+       int error = 1;
+ 
+       if (server->cred)
+@@ -156,7 +156,8 @@ static int handle_auth(
+       }
+ 
+       if (error > 0 && callback) {
+-              error = callback(&server->cred, url, server->url.username, 
allowed_credtypes, callback_payload);
++              if ((error = git_net_url_fmt(&server_url, &server->url)) == 0)
++                      error = callback(&server->cred, server_url.ptr, 
server->url.username, allowed_credtypes, callback_payload);
+ 
+               /* treat GIT_PASSTHROUGH as if callback isn't set */
+               if (error == GIT_PASSTHROUGH)
+@@ -171,6 +172,7 @@ static int handle_auth(
+       if (!error)
+               server->auth_schemetypes = allowed_schemetypes;
+ 
++      git_str_dispose(&server_url);
+       return error;
+ }
+ 
+@@ -190,7 +192,6 @@ GIT_INLINE(int) handle_remote_auth(
+       return handle_auth(
+               &transport->server,
+               SERVER_TYPE_REMOTE,
+-              transport->owner->url,
+               response->server_auth_schemetypes,
+               response->server_auth_credtypes,
+               connect_opts->callbacks.credentials,
+@@ -213,7 +214,6 @@ GIT_INLINE(int) handle_proxy_auth(
+       return handle_auth(
+               &transport->proxy,
+               SERVER_TYPE_PROXY,
+-              connect_opts->proxy_opts.url,
+               response->server_auth_schemetypes,
+               response->proxy_auth_credtypes,
+               connect_opts->proxy_opts.credentials,
diff -Nru libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-5917.patch 
libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-5917.patch
--- libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-5917.patch       
1969-12-31 18:00:00.000000000 -0600
+++ libgit2-1.5.1+ds/debian/patches/backports/CVE-2026-5917.patch       
2026-10-08 12:15:02.000000000 -0500
@@ -0,0 +1,100 @@
+Description: ssh: escape the repository path in the remote command 
(CVE-2026-5917)
+ gen_proto() inserted the repository path unescaped inside single quotes
+ in the command sent with libssh2_channel_exec(), so a path containing a
+ single quote could inject shell commands that run on the SSH server.
+ Escape ' and ! in the path as upstream does.
+ .
+ 1.5's git_str_puts_escaped() has no suffix argument, so this uses the
+ self-contained backported_git_str_puts_escaped() helper from the trixie
+ patch. The file is src/libgit2/transports/ssh.c in 1.5 (ssh_libssh2.c
+ upstream).
+Author: Stephen Gallagher <[email protected]>
+Origin: backport, 
https://github.com/libgit2/libgit2/commit/b2105b8e60798cb28086d4c648b1cb4854eadccb
+Bug-Debian: https://bugs.debian.org/1144465
+Applied-Upstream: 1.9.7, 
https://github.com/libgit2/libgit2/commit/b2105b8e60798cb28086d4c648b1cb4854eadccb
+Forwarded: not-needed
+Last-Update: 2026-10-08
+---
+diff --git a/src/libgit2/transports/ssh.c b/src/libgit2/transports/ssh.c
+index 85e7797..6ab762c 100644
+--- a/src/libgit2/transports/ssh.c
++++ b/src/libgit2/transports/ssh.c
+@@ -56,6 +56,69 @@ static void ssh_error(LIBSSH2_SESSION *session, const char 
*errmsg)
+       git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr);
+ }
+ 
++/* Backported to support not only escape prefix, but also escape suffix */
++static int backported_git_str_puts_escaped(
++      git_str *buf,
++      const char *string,
++      const char *esc_chars,
++      const char *esc_prefix,
++      const char *esc_suffix)
++{
++      const char *scan;
++      size_t total = 0, count, alloclen;
++      size_t esc_prefix_len = esc_prefix ? strlen(esc_prefix) : 0;
++      size_t esc_suffix_len = esc_suffix ? strlen(esc_suffix) : 0;
++
++      if (!string)
++              return 0;
++
++      for (scan = string; *scan; ) {
++              /* count run of non-escaped characters */
++              count = strcspn(scan, esc_chars);
++              total += count;
++              scan += count;
++              /* count run of escaped characters */
++              count = strspn(scan, esc_chars);
++              total += count * (esc_prefix_len + esc_suffix_len + 1);
++              scan += count;
++      }
++
++      GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1);
++      if (git_str_grow_by(buf, alloclen) < 0)
++              return -1;
++
++      for (scan = string; *scan; ) {
++              count = strcspn(scan, esc_chars);
++
++              memmove(buf->ptr + buf->size, scan, count);
++              scan += count;
++              buf->size += count;
++
++              for (count = strspn(scan, esc_chars); count > 0; --count) {
++                      /* copy escape prefix sequence */
++                      if (esc_prefix) {
++                              memmove(buf->ptr + buf->size, esc_prefix, 
esc_prefix_len);
++                              buf->size += esc_prefix_len;
++                      }
++
++                      /* copy character to be escaped */
++                      buf->ptr[buf->size] = *scan;
++                      buf->size++;
++                      scan++;
++
++                      /* copy escape suffix sequence */
++                      if (esc_suffix) {
++                              memmove(buf->ptr + buf->size, esc_suffix, 
esc_suffix_len);
++                              buf->size += esc_suffix_len;
++                      }
++              }
++      }
++
++      buf->ptr[buf->size] = '\0';
++
++      return 0;
++}
++
+ /*
+  * Create a git protocol request.
+  *
+@@ -77,7 +140,7 @@ static int gen_proto(git_str *request, const char *cmd, 
git_net_url *url)
+ 
+       git_str_puts(request, cmd);
+       git_str_puts(request, " '");
+-      git_str_puts(request, repo);
++      backported_git_str_puts_escaped(request, repo, "'!", "'\\", "'");
+       git_str_puts(request, "'");
+ 
+       if (git_str_oom(request))
diff -Nru libgit2-1.5.1+ds/debian/patches/series 
libgit2-1.5.1+ds/debian/patches/series
--- libgit2-1.5.1+ds/debian/patches/series      2024-02-08 05:31:43.000000000 
-0600
+++ libgit2-1.5.1+ds/debian/patches/series      2026-10-08 12:27:57.000000000 
-0500
@@ -4,3 +4,7 @@
 disable-flaky-stat-tests.patch
 backports/CVE-2024-24575.patch
 backports/CVE-2024-24577.patch
+backports/CVE-2026-5917.patch
+backports/CVE-2026-53584.patch
+backports/CVE-2026-53585.patch
+backports/CVE-2026-53586.patch

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to