Skip to content

Commit b049a13

Browse files
authored
Merge pull request #2137 from gitpython-developers/fix-config-injection
reject control chars in written values in configuration
2 parents 5a15361 + 8e24503 commit b049a13

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

git/config.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,24 @@ def _value_to_string(self, value: Union[str, bytes, int, float, bool]) -> str:
882882
return str(value)
883883
return force_text(value)
884884

885+
def _value_to_string_safe(self, value: Union[str, bytes, int, float, bool]) -> str:
886+
value_str = self._value_to_string(value)
887+
if re.search(r"[\r\n\x00]", value_str):
888+
raise ValueError("Git config values must not contain CR, LF, or NUL")
889+
return value_str
890+
891+
@needs_values
892+
@set_dirty_and_flush_changes
893+
def set(
894+
self,
895+
section: str,
896+
option: str,
897+
value: Union[str, bytes, int, float, bool, None] = None,
898+
) -> None:
899+
if value is not None:
900+
value = self._value_to_string_safe(value)
901+
return super().set(section, option, value)
902+
885903
@needs_values
886904
@set_dirty_and_flush_changes
887905
def set_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> "GitConfigParser":
@@ -902,9 +920,10 @@ def set_value(self, section: str, option: str, value: Union[str, bytes, int, flo
902920
:return:
903921
This instance
904922
"""
923+
value_str = self._value_to_string_safe(value)
905924
if not self.has_section(section):
906925
self.add_section(section)
907-
self.set(section, option, self._value_to_string(value))
926+
super().set(section, option, value_str)
908927
return self
909928

910929
@needs_values
@@ -929,9 +948,10 @@ def add_value(self, section: str, option: str, value: Union[str, bytes, int, flo
929948
:return:
930949
This instance
931950
"""
951+
value_str = self._value_to_string_safe(value)
932952
if not self.has_section(section):
933953
self.add_section(section)
934-
self._sections[section].add(option, self._value_to_string(value))
954+
self._sections[section].add(option, value_str)
935955
return self
936956

937957
def rename_section(self, section: str, new_name: str) -> "GitConfigParser":

test/test_config.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,39 @@ 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_set_value_rejects_config_injection(self, rw_dir):
155+
config_path = osp.join(rw_dir, "config")
156+
payload = "foo\n[core]\nhooksPath=/tmp/hooks"
157+
158+
with GitConfigParser(config_path, read_only=False) as git_config:
159+
with pytest.raises(ValueError, match="CR, LF, or NUL"):
160+
git_config.set_value("user", "name", payload)
161+
162+
with GitConfigParser(config_path, read_only=True) as git_config:
163+
self.assertFalse(git_config.has_section("user"))
164+
self.assertFalse(git_config.has_section("core"))
165+
166+
@with_rw_directory
167+
def test_set_and_add_value_reject_unsafe_value_characters(self, rw_dir):
168+
config_path = osp.join(rw_dir, "config")
169+
bad_values = ("foo\rbar", "foo\nbar", "foo\x00bar", b"foo\nbar")
170+
171+
with GitConfigParser(config_path, read_only=False) as git_config:
172+
git_config.add_section("user")
173+
for bad_value in bad_values:
174+
with pytest.raises(ValueError, match="CR, LF, or NUL"):
175+
git_config.set("user", "name", bad_value)
176+
with pytest.raises(ValueError, match="CR, LF, or NUL"):
177+
git_config.set_value("user", "name", bad_value)
178+
with pytest.raises(ValueError, match="CR, LF, or NUL"):
179+
git_config.add_value("user", "name", bad_value)
180+
181+
git_config.set_value("user", "name", "safe")
182+
183+
with GitConfigParser(config_path, read_only=True) as git_config:
184+
self.assertEqual(git_config.get_value("user", "name"), "safe")
185+
153186
def test_base(self):
154187
path_repo = fixture_path("git_config")
155188
path_global = fixture_path("git_config_global")

0 commit comments

Comments
 (0)