diff --git a/CVE-2023-26112.patch b/CVE-2023-26112.patch deleted file mode 100644 index fa21013..0000000 --- a/CVE-2023-26112.patch +++ /dev/null @@ -1,48 +0,0 @@ -From a82ea8fb0338f2bd46cf627c4b763094448e6bd7 Mon Sep 17 00:00:00 2001 -From: cdcadman -Date: Wed, 17 May 2023 03:57:08 -0700 -Subject: [PATCH] Address CVE-2023-26112 ReDoS - ---- - src/configobj/validate.py | 2 +- - src/tests/test_validate_errors.py | 10 +++++++++- - 2 files changed, 10 insertions(+), 2 deletions(-) - -diff --git a/src/configobj/validate.py b/src/configobj/validate.py -index 9267a3f..98d879f 100644 ---- a/src/configobj/validate.py -+++ b/src/configobj/validate.py -@@ -541,7 +541,7 @@ class Validator(object): - """ - - # this regex does the initial parsing of the checks -- _func_re = re.compile(r'(.+?)\((.*)\)', re.DOTALL) -+ _func_re = re.compile(r'([^\(\)]+?)\((.*)\)', re.DOTALL) - - # this regex takes apart keyword arguments - _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL) -diff --git a/src/tests/test_validate_errors.py b/src/tests/test_validate_errors.py -index 399daa8..f7d6c27 100644 ---- a/src/tests/test_validate_errors.py -+++ b/src/tests/test_validate_errors.py -@@ -3,7 +3,7 @@ - import pytest - - from configobj import ConfigObj, get_extra_values, ParseError, NestingError --from configobj.validate import Validator -+from configobj.validate import Validator, VdtUnknownCheckError - - @pytest.fixture() - def thisdir(): -@@ -77,3 +77,11 @@ def test_no_parent(tmpdir, specpath): - ini.write('[[haha]]') - with pytest.raises(NestingError): - conf = ConfigObj(str(ini), configspec=specpath, file_error=True) -+ -+ -+def test_re_dos(val): -+ value = "aaa" -+ i = 165100 -+ attack = '\x00'*i + ')' + '('*i -+ with pytest.raises(VdtUnknownCheckError): -+ val.check(attack, value) diff --git a/configobj-5.0.8-gh.tar.gz b/configobj-5.0.8-gh.tar.gz deleted file mode 100644 index 0a7bef2..0000000 --- a/configobj-5.0.8-gh.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:547dc047e31c71d7a8732016336769ed450588f34a7c13077aa7acc7df245eda -size 99071 diff --git a/configobj-5.0.9-gh.tar.gz b/configobj-5.0.9-gh.tar.gz new file mode 100644 index 0000000..b8f68fd --- /dev/null +++ b/configobj-5.0.9-gh.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bd70f9ce7912679c4ba9c80da289877906db0ca6bd02c3ab545d660e9b60d4f +size 98246 diff --git a/python-configobj.changes b/python-configobj.changes index fd426e5..7d09bd9 100644 --- a/python-configobj.changes +++ b/python-configobj.changes @@ -1,3 +1,12 @@ +------------------------------------------------------------------- +Tue Feb 4 08:55:17 UTC 2025 - John Paul Adrian Glaubitz + +- Update to 5.0.9 + * Drop support for Python 2 and <3.7 + * Fix CVE-2023-26112, ReDoS attack +- Drop CVE-2023-26112.patch, merged upstream +- Drop remove_six.patch, fixed upstream + ------------------------------------------------------------------- Wed Sep 11 12:08:59 UTC 2024 - Matej Cepl diff --git a/python-configobj.spec b/python-configobj.spec index e6b01b4..0d11e45 100644 --- a/python-configobj.spec +++ b/python-configobj.spec @@ -1,7 +1,7 @@ # # spec file for package python-configobj # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-configobj -Version: 5.0.8 +Version: 5.0.9 Release: 0 Summary: Config file reading, writing and validation License: BSD-3-Clause @@ -26,11 +26,6 @@ Group: Development/Languages/Python URL: https://github.com/DiffSK/configobj # No tests in PyPI sdist Source: https://github.com/DiffSK/configobj/archive/refs/tags/v%{version}.tar.gz#/configobj-%{version}-gh.tar.gz -# PATCH-FIX-UPSTREAM https://github.com/DiffSK/configobj/pull/236 Address CVE-2023-26112 ReDoS -Patch0: CVE-2023-26112.patch -# PATCH-FIX-UPSTREAM remove_six.patch gh#DiffSK/configobj#239 mcepl@suse.com -# We don't need six anymore -Patch1: remove_six.patch BuildRequires: %{python_module pip} BuildRequires: %{python_module setuptools} BuildRequires: %{python_module wheel} diff --git a/remove_six.patch b/remove_six.patch deleted file mode 100644 index 4a64c53..0000000 --- a/remove_six.patch +++ /dev/null @@ -1,379 +0,0 @@ -https://github.com/DiffSK/configobj/issues/239 - -Index: configobj-5.0.8/setup.py -=================================================================== ---- configobj-5.0.8.orig/setup.py -+++ configobj-5.0.8/setup.py -@@ -41,7 +41,6 @@ DESCRIPTION = 'Config file reading, writ - URL = 'https://github.com/DiffSK/configobj' - - REQUIRES = """ -- six - """ - - VERSION = '' -Index: configobj-5.0.8/src/configobj/__init__.py -=================================================================== ---- configobj-5.0.8.orig/src/configobj/__init__.py -+++ configobj-5.0.8/src/configobj/__init__.py -@@ -19,7 +19,6 @@ import sys - - from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE - --import six - from ._version import __version__ - - # imported lazily to avoid startup performance hit if it isn't used -@@ -553,11 +552,11 @@ class Section(dict): - """Fetch the item and do string interpolation.""" - val = dict.__getitem__(self, key) - if self.main.interpolation: -- if isinstance(val, six.string_types): -+ if isinstance(val, str): - return self._interpolate(key, val) - if isinstance(val, list): - def _check(entry): -- if isinstance(entry, six.string_types): -+ if isinstance(entry, str): - return self._interpolate(key, entry) - return entry - new = [_check(entry) for entry in val] -@@ -580,7 +579,7 @@ class Section(dict): - ``unrepr`` must be set when setting a value to a dictionary, without - creating a new sub-section. - """ -- if not isinstance(key, six.string_types): -+ if not isinstance(key, str): - raise ValueError('The key "%s" is not a string.' % key) - - # add the comment -@@ -614,11 +613,11 @@ class Section(dict): - if key not in self: - self.scalars.append(key) - if not self.main.stringify: -- if isinstance(value, six.string_types): -+ if isinstance(value, str): - pass - elif isinstance(value, (list, tuple)): - for entry in value: -- if not isinstance(entry, six.string_types): -+ if not isinstance(entry, str): - raise TypeError('Value is not a string "%s".' % entry) - else: - raise TypeError('Value is not a string "%s".' % value) -@@ -959,7 +958,7 @@ class Section(dict): - return False - else: - try: -- if not isinstance(val, six.string_types): -+ if not isinstance(val, str): - # TODO: Why do we raise a KeyError here? - raise KeyError() - else: -@@ -1230,7 +1229,7 @@ class ConfigObj(Section): - - - def _load(self, infile, configspec): -- if isinstance(infile, six.string_types): -+ if isinstance(infile, str): - self.filename = infile - if os.path.isfile(infile): - with open(infile, 'rb') as h: -@@ -1298,7 +1297,7 @@ class ConfigObj(Section): - break - break - -- assert all(isinstance(line, six.string_types) for line in content), repr(content) -+ assert all(isinstance(line, str) for line in content), repr(content) - content = [line.rstrip('\r\n') for line in content] - - self._parse(content) -@@ -1403,7 +1402,7 @@ class ConfigObj(Section): - else: - line = infile - -- if isinstance(line, six.text_type): -+ if isinstance(line, str): - # it's already decoded and there's no need to do anything - # else, just use the _decode utility method to handle - # listifying appropriately -@@ -1448,7 +1447,7 @@ class ConfigObj(Section): - - # No encoding specified - so we need to check for UTF8/UTF16 - for BOM, (encoding, final_encoding) in list(BOMS.items()): -- if not isinstance(line, six.binary_type) or not line.startswith(BOM): -+ if not isinstance(line, bytes) or not line.startswith(BOM): - # didn't specify a BOM, or it's not a bytestring - continue - else: -@@ -1464,9 +1463,9 @@ class ConfigObj(Section): - else: - infile = newline - # UTF-8 -- if isinstance(infile, six.text_type): -+ if isinstance(infile, str): - return infile.splitlines(True) -- elif isinstance(infile, six.binary_type): -+ elif isinstance(infile, bytes): - return infile.decode('utf-8').splitlines(True) - else: - return self._decode(infile, 'utf-8') -@@ -1474,12 +1473,8 @@ class ConfigObj(Section): - return self._decode(infile, encoding) - - -- if six.PY2 and isinstance(line, str): -- # don't actually do any decoding, since we're on python 2 and -- # returning a bytestring is fine -- return self._decode(infile, None) - # No BOM discovered and no encoding specified, default to UTF-8 -- if isinstance(infile, six.binary_type): -+ if isinstance(infile, bytes): - return infile.decode('utf-8').splitlines(True) - else: - return self._decode(infile, 'utf-8') -@@ -1487,7 +1482,7 @@ class ConfigObj(Section): - - def _a_to_u(self, aString): - """Decode ASCII strings to unicode if a self.encoding is specified.""" -- if isinstance(aString, six.binary_type) and self.encoding: -+ if isinstance(aString, bytes) and self.encoding: - return aString.decode(self.encoding) - else: - return aString -@@ -1499,9 +1494,9 @@ class ConfigObj(Section): - - if is a string, it also needs converting to a list. - """ -- if isinstance(infile, six.string_types): -+ if isinstance(infile, str): - return infile.splitlines(True) -- if isinstance(infile, six.binary_type): -+ if isinstance(infile, bytes): - # NOTE: Could raise a ``UnicodeDecodeError`` - if encoding: - return infile.decode(encoding).splitlines(True) -@@ -1510,7 +1505,7 @@ class ConfigObj(Section): - - if encoding: - for i, line in enumerate(infile): -- if isinstance(line, six.binary_type): -+ if isinstance(line, bytes): - # NOTE: The isinstance test here handles mixed lists of unicode/string - # NOTE: But the decode will break on any non-string values - # NOTE: Or could raise a ``UnicodeDecodeError`` -@@ -1520,7 +1515,7 @@ class ConfigObj(Section): - - def _decode_element(self, line): - """Decode element to unicode if necessary.""" -- if isinstance(line, six.binary_type) and self.default_encoding: -+ if isinstance(line, bytes) and self.default_encoding: - return line.decode(self.default_encoding) - else: - return line -@@ -1532,7 +1527,7 @@ class ConfigObj(Section): - Used by ``stringify`` within validate, to turn non-string values - into strings. - """ -- if not isinstance(value, six.string_types): -+ if not isinstance(value, str): - # intentially 'str' because it's just whatever the "normal" - # string type is for the python version we're dealing with - return str(value) -@@ -1786,7 +1781,7 @@ class ConfigObj(Section): - return self._quote(value[0], multiline=False) + ',' - return ', '.join([self._quote(val, multiline=False) - for val in value]) -- if not isinstance(value, six.string_types): -+ if not isinstance(value, str): - if self.stringify: - # intentially 'str' because it's just whatever the "normal" - # string type is for the python version we're dealing with -@@ -2111,7 +2106,7 @@ class ConfigObj(Section): - if not output.endswith(newline): - output += newline - -- if isinstance(output, six.binary_type): -+ if isinstance(output, bytes): - output_bytes = output - else: - output_bytes = output.encode(self.encoding or -@@ -2353,7 +2348,7 @@ class ConfigObj(Section): - This method raises a ``ReloadError`` if the ConfigObj doesn't have - a filename attribute pointing to a file. - """ -- if not isinstance(self.filename, six.string_types): -+ if not isinstance(self.filename, str): - raise ReloadError() - - filename = self.filename -Index: configobj-5.0.8/src/tests/test_configobj.py -=================================================================== ---- configobj-5.0.8.orig/src/tests/test_configobj.py -+++ configobj-5.0.8/src/tests/test_configobj.py -@@ -1,5 +1,6 @@ - # coding=utf-8 - from __future__ import unicode_literals -+import io - import os - import re - -@@ -8,7 +9,6 @@ from warnings import catch_warnings - from tempfile import NamedTemporaryFile - - import pytest --import six - - import configobj as co - from configobj import ConfigObj, flatten_errors, ReloadError, DuplicateError, MissingInterpolationOption, InterpolationLoopError, ConfigObjError -@@ -36,13 +36,13 @@ def cfg_lines(config_string_representati - '{!r}'.format(config_string_representation)) - - first_content = lines[line_no_with_content] -- if isinstance(first_content, six.binary_type): -+ if isinstance(first_content, bytes): - first_content = first_content.decode('utf-8') - ws_chars = len(re.search('^(\s*)', first_content).group(1)) - - def yield_stringified_line(): - for line in lines: -- if isinstance(line, six.binary_type): -+ if isinstance(line, bytes): - yield line.decode('utf-8') - else: - yield line -@@ -70,7 +70,7 @@ def cfg_contents(request): - - with NamedTemporaryFile(delete=False, mode='wb') as cfg_file: - for line in lines: -- if isinstance(line, six.binary_type): -+ if isinstance(line, bytes): - cfg_file.write(line + os.linesep.encode('utf-8')) - else: - cfg_file.write((line + os.linesep).encode('utf-8')) -@@ -186,11 +186,7 @@ class TestEncoding(object): - - c = ConfigObj(cfg, encoding='utf8') - -- if six.PY2: -- assert not isinstance(c['test'], str) -- assert isinstance(c['test'], unicode) -- else: -- assert isinstance(c['test'], str) -+ assert isinstance(c['test'], str) - - - #issue #18 -@@ -198,11 +194,7 @@ class TestEncoding(object): - cfg = cfg_contents(b"test = some string") - - c = ConfigObj(cfg) -- if six.PY2: -- assert isinstance(c['test'], str) -- assert not isinstance(c['test'], unicode) -- else: -- assert isinstance(c['test'], str) -+ assert isinstance(c['test'], str) - - #issue #44 - def test_that_encoding_using_list_of_strings(self): -@@ -210,11 +202,7 @@ class TestEncoding(object): - - c = ConfigObj(cfg, encoding='utf8') - -- if six.PY2: -- assert isinstance(c['test'], unicode) -- assert not isinstance(c['test'], str) -- else: -- assert isinstance(c['test'], str) -+ assert isinstance(c['test'], str) - - assert c['test'] == '\U0001f41c' - -@@ -223,7 +211,7 @@ class TestEncoding(object): - c = cfg_contents(ant_cfg) - cfg = ConfigObj(c, encoding='utf-8') - -- assert isinstance(cfg['tags']['bug']['translated'], six.text_type) -+ assert isinstance(cfg['tags']['bug']['translated'], str) - - #issue #44 and #55 - def test_encoding_in_config_files(self, request, ant_cfg): -@@ -233,7 +221,7 @@ class TestEncoding(object): - request.addfinalizer(lambda : os.unlink(cfg_file.name)) - - cfg = ConfigObj(cfg_file.name, encoding='utf-8') -- assert isinstance(cfg['tags']['bug']['translated'], six.text_type) -+ assert isinstance(cfg['tags']['bug']['translated'], str) - cfg.write() - - @pytest.fixture -@@ -500,7 +488,7 @@ def test_unicode_handling(): - 'section': {'test': 'test', 'test2': 'test2'}} - uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1') - assert uc.BOM -- assert isinstance(uc['test1'], six.text_type) -+ assert isinstance(uc['test1'], str) - assert uc.encoding == 'utf_8' - assert uc.newlines == '\n' - assert len(uc.write()) == 13 -@@ -508,14 +496,14 @@ def test_unicode_handling(): - a_list = uc.write() - assert 'latin1' in str(a_list) - assert len(a_list) == 14 -- assert isinstance(a_list[0], six.binary_type) -+ assert isinstance(a_list[0], bytes) - assert a_list[0].startswith(BOM_UTF8) - - u = u_base.replace('\n', '\r\n').encode('utf-8').splitlines(True) - uc = ConfigObj(u) - assert uc.newlines == '\r\n' - uc.newlines = '\r' -- file_like = six.BytesIO() -+ file_like = io.BytesIO() - uc.write(file_like) - file_like.seek(0) - uc2 = ConfigObj(file_like) -@@ -723,7 +711,7 @@ class TestSectionBehavior(object): - val = section[key] - newkey = key.replace('XXXX', 'CLIENT1') - section.rename(key, newkey) -- if isinstance(val, six.string_types): -+ if isinstance(val, str): - val = val.replace('XXXX', 'CLIENT1') - section[newkey] = val - -@@ -811,7 +799,7 @@ class TestReloading(object): - return content - - def test_handle_no_filename(self): -- for bad_args in ([six.BytesIO()], [], [[]]): -+ for bad_args in ([io.BytesIO()], [], [[]]): - cfg = ConfigObj(*bad_args) - with pytest.raises(ReloadError) as excinfo: - cfg.reload() -@@ -1264,21 +1252,21 @@ class TestEdgeCasesWhenWritingOut(object - def test_newline_terminated(self, empty_cfg): - empty_cfg.newlines = '\n' - empty_cfg['a'] = 'b' -- collector = six.BytesIO() -+ collector = io.BytesIO() - empty_cfg.write(collector) - assert collector.getvalue() == b'a = b\n' - - def test_hash_escaping(self, empty_cfg): - empty_cfg.newlines = '\n' - empty_cfg['#a'] = 'b # something' -- collector = six.BytesIO() -+ collector = io.BytesIO() - empty_cfg.write(collector) - assert collector.getvalue() == b'"#a" = "b # something"\n' - - empty_cfg = ConfigObj() - empty_cfg.newlines = '\n' - empty_cfg['a'] = 'b # something', 'c # something' -- collector = six.BytesIO() -+ collector = io.BytesIO() - empty_cfg.write(collector) - assert collector.getvalue() == b'a = "b # something", "c # something"\n' -