Skip to content

Commit a5e047d

Browse files
authored
Merge pull request #2211 from gitpython-developers/config-sanitize-more
fix: harden config parsing boundaries
2 parents b473abb + ef7568e commit a5e047d

4 files changed

Lines changed: 63 additions & 3 deletions

File tree

git/config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,11 @@ def write_section(name: str, section_dict: _OMD) -> None:
705705
continue
706706

707707
for v in values:
708-
fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace("\n", "\n\t"))).encode(defenc))
708+
value = self._value_to_string(v)
709+
if any(char in value for char in '\n\t\b\\"'):
710+
value = value.replace("\\", "\\\\").replace('"', '\\"')
711+
value = '"%s\\\n"' % value.replace("\n", "\\n").replace("\t", "\\t").replace("\b", "\\b")
712+
fp.write(("\t%s = %s\n" % (key, value)).encode(defenc))
709713
# END if key is not __name__
710714

711715
# END section writing

git/objects/submodule/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ def _config_parser(
271271
raise ValueError("Cannot write blobs of 'historical' submodule configurations")
272272
# END handle writes of historical submodules
273273

274-
return SubmoduleConfigParser(fp_module, read_only=read_only)
274+
return SubmoduleConfigParser(fp_module, read_only=read_only, merge_includes=False)
275275

276276
def _clear_cache(self) -> None:
277277
"""Clear the possibly changed values."""

test/test_config.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io
88
import os
99
import os.path as osp
10+
import subprocess
1011
import sys
1112
from unittest import mock
1213

@@ -15,7 +16,6 @@
1516
from git import GitConfigParser
1617
from git.config import _OMD, cp
1718
from git.util import cwd, rmfile
18-
1919
from test.lib import SkipTest, TestCase, fixture_path, with_rw_directory
2020

2121
_tc_lock_fpaths = osp.join(osp.dirname(__file__), "fixtures/*.lock")
@@ -150,6 +150,46 @@ def test_config_value_with_trailing_new_line(self):
150150
git_config = GitConfigParser(config_file)
151151
git_config.read() # This should not throw an exception
152152

153+
@with_rw_directory
154+
def test_rewriting_multiline_value_does_not_create_option(self, rw_dir):
155+
config_path = osp.join(rw_dir, "config")
156+
with open(config_path, "wb") as config_file:
157+
config_file.write(b'[core]\n\tzzz = "A\\nhooksPath = ../evil-hooks\\\n"\n')
158+
159+
with GitConfigParser(config_path, read_only=False) as git_config:
160+
self.assertEqual(git_config.get_value("core", "zzz"), "A\nhooksPath = ../evil-hooks")
161+
git_config.set_value("user", "name", "Test User")
162+
163+
with GitConfigParser(config_path, read_only=True) as git_config:
164+
self.assertEqual(git_config.get_value("core", "zzz"), "A\nhooksPath = ../evil-hooks")
165+
self.assertFalse(git_config.has_option("core", "hooksPath"))
166+
self.assertEqual(
167+
subprocess.run(["git", "config", "--file", config_path, "--get", "core.hooksPath"]).returncode, 1
168+
)
169+
170+
@with_rw_directory
171+
def test_writer_escapes_special_characters_without_newline(self, rw_dir):
172+
config_path = osp.join(rw_dir, "config")
173+
values = {"tab": "\tvalue\t", "backspace": "a\bb", "quote": 'a"b', "backslash": "a\\qb"}
174+
175+
with GitConfigParser(config_path, read_only=False) as git_config:
176+
for key, value in values.items():
177+
git_config.set_value("section", key, value)
178+
179+
with GitConfigParser(config_path, read_only=True) as git_config:
180+
for key, value in values.items():
181+
self.assertEqual(git_config.get_value("section", key), value)
182+
self.assertEqual(
183+
subprocess.run(
184+
["git", "config", "--file", config_path, "--get", "section.%s" % key],
185+
stdout=subprocess.PIPE,
186+
check=True,
187+
).stdout,
188+
value.encode() + b"\n",
189+
)
190+
with open(config_path, "rb") as config_file:
191+
self.assertNotIn(b"\x08", config_file.read())
192+
153193
@with_rw_directory
154194
def test_set_value_rejects_config_injection(self, rw_dir):
155195
config_path = osp.join(rw_dir, "config")

test/test_submodule.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,22 @@ def test_ignore_non_submodule_file(self, rwdir):
12081208

12091209
assert len(parent.submodules) == 0
12101210

1211+
@with_rw_directory
1212+
def test_gitmodules_does_not_merge_includes(self, rwdir):
1213+
parent = git.Repo.init(rwdir)
1214+
secret_path = osp.join(rwdir, "secret")
1215+
with open(secret_path, "w", encoding="utf-8") as secret:
1216+
secret.write("not git config\n")
1217+
with open(osp.join(rwdir, ".gitmodules"), "w", encoding="utf-8") as modules:
1218+
modules.write('[submodule "module"]\n')
1219+
modules.write("\tpath = module\n")
1220+
modules.write("\turl = https://example.com/module.git\n")
1221+
modules.write("[include]\n")
1222+
modules.write("\tpath = %s\n" % secret_path)
1223+
1224+
parser = Submodule._config_parser(parent, None, read_only=True)
1225+
self.assertEqual(parser.get_value('submodule "module"', "path"), "module")
1226+
12111227
@with_rw_directory
12121228
def test_remove_norefs(self, rwdir):
12131229
parent = git.Repo.init(osp.join(rwdir, "parent"))

0 commit comments

Comments
 (0)