Skip to content

Unhandled IndexError when calling .read() on a malformed config file #1887

Closed
@DaveLak

Description

@DaveLak

Hi, I noticed the fuzzing tests that OSS-Fuzz runs on this project are broken and while I was working on fixing them I believe I came across a minor bug:

The Bug

An Uncaught Python exception: IndexError: string index out of range can be triggered if when trying to call .read() on GitConfigParser if it was initialized with a malformed config file.

Current Behavior

It's easiest to demonstrate, so please consider this example:

import io
from git.config import GitConfigParser


def reproduce_issue():

    malformed_config_content_bytestring = b'[-]\nk:"v\n"'

    problematic_config_file = io.BytesIO(malformed_config_content_bytestring)

    # problematic_config_file looks now like this:
    """
    [-]
    k:"v
    "
    """

    # We have to name the file otherwise we'll trigger
    # `AttributeError: '_io.BytesIO' object has no attribute 'name'` here:
    # https://github.com/gitpython-developers/GitPython/blob/c7675d2cedcd737f20359a4a786e213510452413/git/config.py#L623
    problematic_config_file.name = "fuzzedconfig.config"

    # This is fine
    git_config = GitConfigParser(problematic_config_file)
    
    # The next line raised an unhandled `IndexError: string index out of range`
    git_config.read()


if __name__ == "__main__":
    reproduce_issue()

Assuming that code is in /path/to/example/config_indexerror_reproduction.py then

python config_indexerror_reproduction.py

produces something akin to:

Traceback (most recent call last):
  File "/path/to/example/config_indexerror_reproduction.py", line 30, in <module>
    reproduce_issue()
  File "/path/to/example/config_indexerror_reproduction.py", line 26, in reproduce_issue
    git_config.read()
  File "/path/to/example/.venv/lib/python3.12/site-packages/git/config.py", line 607, in read
    self._read(file_path, file_path.name)
  File "/path/to/example/.venv/lib/python3.12/site-packages/git/config.py", line 514, in _read
    cursect.setlast(optname, optval + string_decode(line))
                                      ^^^^^^^^^^^^^^^^^^^
  File "/path/to/example/.venv/lib/python3.12/site-packages/git/config.py", line 441, in string_decode
    if v[-1] == "\\":
       ~^^^^
IndexError: string index out of range
My Reproduction Environment Details

The reproduction code above was tested on:

Python Version: 3.12.1 (main, Feb  5 2024, 16:23:00) [Clang 15.0.0 (clang-1500.1.0.2.5)]
OS Information: macOS-14.4.1-x86_64-i386-64bit
Installed Packages:
Package   Version
--------- -------
gitdb     4.0.11
GitPython 3.1.42
pip       24.0
smmap     5.0.1

And the fuzzer environment was:

Python Version: 3.8.3 (default, Mar 17 2024, 03:21:27)
[Clang 15.0.0 (https://github.com/llvm/llvm-project.git bf7f8d6fa6f460bf0a16ffe
OS Information: Linux-6.6.16-linuxkit-x86_64-with-glibc2.2.5
Installed Packages:
Package                   Version
------------------------- -------
altgraph                  0.17.4
atheris                   2.3.0
coverage                  6.3.2
importlib_metadata        7.0.2
gitdb                     4.0.11
GitPython                 3.1.42
pip                       24.0
smmap                     5.0.1
packaging                 24.0
pyinstaller               5.0.1
setuptools                41.0.1
six                       1.15.0
zipp                      3.18.1

So, if I'm reading the source correct, it seems like the combination of some header section ([-] above) followed by a key/value assignment that has a value consisting of a double quoted string with a new line inside it confuses the check here which strips the " on the new line:

GitPython/git/config.py

Lines 521 to 528 in c7675d2

else:
line = line.rstrip()
if line.endswith('"'):
is_multi_line = False
line = line[:-1]
# END handle quotations
optval = cursect.getlast(optname)
cursect.setlast(optname, optval + string_decode(line))

and passes an empty string to string_decode which isn't expecting that when it indexes into it's arg:

GitPython/git/config.py

Lines 454 to 455 in c7675d2

def string_decode(v: str) -> str:
if v[-1] == "\\":

Expected Behavior

I'd expect an explicitly raised ParsingError similar to how it's handled a little further up:

GitPython/git/config.py

Lines 514 to 518 in c7675d2

else:
# Check if it's an option with no value - it's just ignored by git.
if not self.OPTVALUEONLY.match(line):
if not e:
e = cp.ParsingError(fpname)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions