Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 32f7fad

Browse files
committedDec 20, 2022
[3.11] pythongh-99240: Fix double-free bug in Argument Clinic str_converter generated code (pythonGH-99241)
(cherry picked from commit 8dbe08e) Fix double-free bug mentioned at pythonGH-99240, by moving memory clean up out of "exit" label.
1 parent c42a4ad commit 32f7fad

File tree

6 files changed

+201
-25
lines changed

6 files changed

+201
-25
lines changed
 

‎Lib/test/clinic.test

+11-22
Original file line numberDiff line numberDiff line change
@@ -1740,37 +1740,26 @@ test_str_converter_encoding(PyObject *module, PyObject *const *args, Py_ssize_t
17401740
goto exit;
17411741
}
17421742
return_value = test_str_converter_encoding_impl(module, a, b, c, d, d_length, e, e_length);
1743+
/* Post parse cleanup for a */
1744+
PyMem_FREE(a);
1745+
/* Post parse cleanup for b */
1746+
PyMem_FREE(b);
1747+
/* Post parse cleanup for c */
1748+
PyMem_FREE(c);
1749+
/* Post parse cleanup for d */
1750+
PyMem_FREE(d);
1751+
/* Post parse cleanup for e */
1752+
PyMem_FREE(e);
17431753

17441754
exit:
1745-
/* Cleanup for a */
1746-
if (a) {
1747-
PyMem_FREE(a);
1748-
}
1749-
/* Cleanup for b */
1750-
if (b) {
1751-
PyMem_FREE(b);
1752-
}
1753-
/* Cleanup for c */
1754-
if (c) {
1755-
PyMem_FREE(c);
1756-
}
1757-
/* Cleanup for d */
1758-
if (d) {
1759-
PyMem_FREE(d);
1760-
}
1761-
/* Cleanup for e */
1762-
if (e) {
1763-
PyMem_FREE(e);
1764-
}
1765-
17661755
return return_value;
17671756
}
17681757

17691758
static PyObject *
17701759
test_str_converter_encoding_impl(PyObject *module, char *a, char *b, char *c,
17711760
char *d, Py_ssize_t d_length, char *e,
17721761
Py_ssize_t e_length)
1773-
/*[clinic end generated code: output=8acb886a3843f3bc input=eb4c38e1f898f402]*/
1762+
/*[clinic end generated code: output=999c1deecfa15b0a input=eb4c38e1f898f402]*/
17741763

17751764

17761765
/*[clinic input]

‎Lib/test/test_clinic.py

+15
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,17 @@ def test_str_converter(self):
10451045
self.assertEqual(ac_tester.str_converter('a', b'b', b'c'), ('a', 'b', 'c'))
10461046
self.assertEqual(ac_tester.str_converter('a', b'b', 'c\0c'), ('a', 'b', 'c\0c'))
10471047

1048+
def test_str_converter_encoding(self):
1049+
with self.assertRaises(TypeError):
1050+
ac_tester.str_converter_encoding(1)
1051+
self.assertEqual(ac_tester.str_converter_encoding('a', 'b', 'c'), ('a', 'b', 'c'))
1052+
with self.assertRaises(TypeError):
1053+
ac_tester.str_converter_encoding('a', b'b\0b', 'c')
1054+
self.assertEqual(ac_tester.str_converter_encoding('a', b'b', bytearray([ord('c')])), ('a', 'b', 'c'))
1055+
self.assertEqual(ac_tester.str_converter_encoding('a', b'b', bytearray([ord('c'), 0, ord('c')])),
1056+
('a', 'b', 'c\x00c'))
1057+
self.assertEqual(ac_tester.str_converter_encoding('a', b'b', b'c\x00c'), ('a', 'b', 'c\x00c'))
1058+
10481059
def test_py_buffer_converter(self):
10491060
with self.assertRaises(TypeError):
10501061
ac_tester.py_buffer_converter('a', 'b')
@@ -1225,6 +1236,10 @@ def test_gh_99233_refcount(self):
12251236
arg_refcount_after = sys.getrefcount(arg)
12261237
self.assertEqual(arg_refcount_origin, arg_refcount_after)
12271238

1239+
def test_gh_99240_double_free(self):
1240+
expected_error = r'gh_99240_double_free\(\) argument 2 must be encoded string without null bytes, not str'
1241+
with self.assertRaisesRegex(TypeError, expected_error):
1242+
ac_tester.gh_99240_double_free('a', '\0b')
12281243

12291244
if __name__ == "__main__":
12301245
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix double-free bug in Argument Clinic ``str_converter`` by
2+
extracting memory clean up to a new ``post_parsing`` section.

‎Modules/_testclinic.c

+79
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,64 @@ str_converter_impl(PyObject *module, const char *a, const char *b,
551551
}
552552

553553

554+
/*[clinic input]
555+
str_converter_encoding
556+
557+
a: str(encoding="idna")
558+
b: str(encoding="idna", accept={bytes, bytearray, str})
559+
c: str(encoding="idna", accept={bytes, bytearray, str}, zeroes=True)
560+
/
561+
562+
[clinic start generated code]*/
563+
564+
static PyObject *
565+
str_converter_encoding_impl(PyObject *module, char *a, char *b, char *c,
566+
Py_ssize_t c_length)
567+
/*[clinic end generated code: output=af68766049248a1c input=0c5cf5159d0e870d]*/
568+
{
569+
assert(!PyErr_Occurred());
570+
PyObject *out[3] = {NULL,};
571+
int i = 0;
572+
PyObject *arg;
573+
574+
arg = PyUnicode_FromString(a);
575+
assert(arg || PyErr_Occurred());
576+
if (!arg) {
577+
goto error;
578+
}
579+
out[i++] = arg;
580+
581+
arg = PyUnicode_FromString(b);
582+
assert(arg || PyErr_Occurred());
583+
if (!arg) {
584+
goto error;
585+
}
586+
out[i++] = arg;
587+
588+
arg = PyUnicode_FromStringAndSize(c, c_length);
589+
assert(arg || PyErr_Occurred());
590+
if (!arg) {
591+
goto error;
592+
}
593+
out[i++] = arg;
594+
595+
PyObject *tuple = PyTuple_New(3);
596+
if (!tuple) {
597+
goto error;
598+
}
599+
for (int j = 0; j < 3; j++) {
600+
PyTuple_SET_ITEM(tuple, j, out[j]);
601+
}
602+
return tuple;
603+
604+
error:
605+
for (int j = 0; j < i; j++) {
606+
Py_DECREF(out[j]);
607+
}
608+
return NULL;
609+
}
610+
611+
554612
static PyObject *
555613
bytes_from_buffer(Py_buffer *buf)
556614
{
@@ -927,6 +985,25 @@ gh_99233_refcount_impl(PyObject *module, PyObject *args)
927985
}
928986

929987

988+
/*[clinic input]
989+
gh_99240_double_free
990+
991+
a: str(encoding="idna")
992+
b: str(encoding="idna")
993+
/
994+
995+
Proof-of-concept of GH-99240 double-free bug.
996+
997+
[clinic start generated code]*/
998+
999+
static PyObject *
1000+
gh_99240_double_free_impl(PyObject *module, char *a, char *b)
1001+
/*[clinic end generated code: output=586dc714992fe2ed input=23db44aa91870fc7]*/
1002+
{
1003+
Py_RETURN_NONE;
1004+
}
1005+
1006+
9301007
static PyMethodDef tester_methods[] = {
9311008
TEST_EMPTY_FUNCTION_METHODDEF
9321009
OBJECTS_CONVERTER_METHODDEF
@@ -951,6 +1028,7 @@ static PyMethodDef tester_methods[] = {
9511028
DOUBLE_CONVERTER_METHODDEF
9521029
PY_COMPLEX_CONVERTER_METHODDEF
9531030
STR_CONVERTER_METHODDEF
1031+
STR_CONVERTER_ENCODING_METHODDEF
9541032
PY_BUFFER_CONVERTER_METHODDEF
9551033
KEYWORDS_METHODDEF
9561034
KEYWORDS_KWONLY_METHODDEF
@@ -970,6 +1048,7 @@ static PyMethodDef tester_methods[] = {
9701048
KEYWORD_ONLY_PARAMETER_METHODDEF
9711049
VARARG_AND_POSONLY_METHODDEF
9721050
GH_99233_REFCOUNT_METHODDEF
1051+
GH_99240_DOUBLE_FREE_METHODDEF
9731052
{NULL, NULL}
9741053
};
9751054

‎Modules/clinic/_testclinic.c.h

+71-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Tools/clinic/clinic.py

+23-2
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,12 @@ def __init__(self):
349349
# "goto exit" if there are any.
350350
self.return_conversion = []
351351

352+
# The C statements required to do some operations
353+
# after the end of parsing but before cleaning up.
354+
# These operations may be, for example, memory deallocations which
355+
# can only be done without any error happening during argument parsing.
356+
self.post_parsing = []
357+
352358
# The C statements required to clean up after the impl call.
353359
self.cleanup = []
354360

@@ -761,6 +767,7 @@ def parser_body(prototype, *fields, declarations=''):
761767
{modifications}
762768
{return_value} = {c_basename}_impl({impl_arguments});
763769
{return_conversion}
770+
{post_parsing}
764771
765772
{exit_label}
766773
{cleanup}
@@ -1405,6 +1412,7 @@ def render_function(self, clinic, f):
14051412
template_dict['impl_parameters'] = ", ".join(data.impl_parameters)
14061413
template_dict['impl_arguments'] = ", ".join(data.impl_arguments)
14071414
template_dict['return_conversion'] = format_escape("".join(data.return_conversion).rstrip())
1415+
template_dict['post_parsing'] = format_escape("".join(data.post_parsing).rstrip())
14081416
template_dict['cleanup'] = format_escape("".join(data.cleanup))
14091417
template_dict['return_value'] = data.return_value
14101418

@@ -1429,6 +1437,7 @@ def render_function(self, clinic, f):
14291437
return_conversion=template_dict['return_conversion'],
14301438
initializers=template_dict['initializers'],
14311439
modifications=template_dict['modifications'],
1440+
post_parsing=template_dict['post_parsing'],
14321441
cleanup=template_dict['cleanup'],
14331442
)
14341443

@@ -2660,6 +2669,10 @@ def _render_non_self(self, parameter, data):
26602669
# parse_arguments
26612670
self.parse_argument(data.parse_arguments)
26622671

2672+
# post_parsing
2673+
if post_parsing := self.post_parsing():
2674+
data.post_parsing.append('/* Post parse cleanup for ' + name + ' */\n' + post_parsing.rstrip() + '\n')
2675+
26632676
# cleanup
26642677
cleanup = self.cleanup()
26652678
if cleanup:
@@ -2755,6 +2768,14 @@ def modify(self):
27552768
"""
27562769
return ""
27572770

2771+
def post_parsing(self):
2772+
"""
2773+
The C statements required to do some operations after the end of parsing but before cleaning up.
2774+
Return a string containing this code indented at column 0.
2775+
If no operation is necessary, return an empty string.
2776+
"""
2777+
return ""
2778+
27582779
def cleanup(self):
27592780
"""
27602781
The C statements required to clean up after this variable.
@@ -3351,10 +3372,10 @@ def converter_init(self, *, accept={str}, encoding=None, zeroes=False):
33513372
if NoneType in accept and self.c_default == "Py_None":
33523373
self.c_default = "NULL"
33533374

3354-
def cleanup(self):
3375+
def post_parsing(self):
33553376
if self.encoding:
33563377
name = self.name
3357-
return "".join(["if (", name, ") {\n PyMem_FREE(", name, ");\n}\n"])
3378+
return f"PyMem_FREE({name});\n"
33583379

33593380
def parse_arg(self, argname, displayname):
33603381
if self.format_unit == 's':

0 commit comments

Comments
 (0)
Please sign in to comment.