Skip to content

Commit 363bcbc

Browse files
authored
Merge branch 'main' into feat/xml/1e9-lolz-api-90949
2 parents bba2678 + 68a1778 commit 363bcbc

File tree

2 files changed

+67
-34
lines changed

2 files changed

+67
-34
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -556,20 +556,19 @@ unittest
556556
xml.parsers.expat
557557
-----------------
558558

559-
* Add :meth:`~xml.parsers.expat.xmlparser.SetBillionLaughsAttackProtectionActivationThreshold`
560-
and :meth:`~xml.parsers.expat.xmlparser.SetBillionLaughsAttackProtectionMaximumAmplification`
561-
to :ref:`xmlparser <xmlparser-objects>` objects to mitigate `billion laughs`_
562-
attacks.
563-
(Contributed by Bénédikt Tran in :gh:`90949`.)
564-
565-
.. _billion laughs: https://en.wikipedia.org/wiki/Billion_laughs_attack
566-
567559
* Add :meth:`~xml.parsers.expat.xmlparser.SetAllocTrackerActivationThreshold`
568560
and :meth:`~xml.parsers.expat.xmlparser.SetAllocTrackerMaximumAmplification`
569561
to :ref:`xmlparser <xmlparser-objects>` objects to prevent use of
570562
disproportional amounts of dynamic memory from within an Expat parser.
571563
(Contributed by Bénédikt Tran in :gh:`90949`.)
572564

565+
* Add :meth:`~xml.parsers.expat.xmlparser.SetBillionLaughsAttackProtectionActivationThreshold`
566+
and :meth:`~xml.parsers.expat.xmlparser.SetBillionLaughsAttackProtectionMaximumAmplification`
567+
to :ref:`xmlparser <xmlparser-objects>` objects to tune `billion laughs`_ attacks protection.
568+
(Contributed by Bénédikt Tran in :gh:`90949`.)
569+
570+
.. _billion laughs: https://en.wikipedia.org/wiki/Billion_laughs_attack
571+
573572

574573
zlib
575574
----

Modules/pyexpat.c

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ CALL_XML_HANDLER_SETTER(const struct HandlerInfo *handler_info,
125125
}
126126

127127
static int
128-
set_error_code(PyObject *err, enum XML_Error code)
128+
set_xml_error_attr_code(PyObject *err, enum XML_Error code)
129129
{
130130
PyObject *v = PyLong_FromLong((long)code);
131131
int ok = v != NULL && PyObject_SetAttr(err, &_Py_ID(code), v) != -1;
@@ -137,7 +137,7 @@ set_error_code(PyObject *err, enum XML_Error code)
137137
* false on an exception.
138138
*/
139139
static int
140-
set_error_location(PyObject *err, const char *name, XML_Size value)
140+
set_xml_error_attr_location(PyObject *err, const char *name, XML_Size value)
141141
{
142142
PyObject *v = PyLong_FromSize_t((size_t)value);
143143
int ok = v != NULL && PyObject_SetAttrString(err, name, v) != -1;
@@ -146,42 +146,32 @@ set_error_location(PyObject *err, const char *name, XML_Size value)
146146
}
147147

148148

149-
static PyObject *
150-
format_xml_error(enum XML_Error code, XML_Size lineno, XML_Size column)
151-
{
152-
const char *errmsg = XML_ErrorString(code);
153-
PyUnicodeWriter *writer = PyUnicodeWriter_Create(strlen(errmsg) + 1);
154-
if (writer == NULL) {
155-
return NULL;
156-
}
157-
if (PyUnicodeWriter_Format(writer,
158-
"%s: line %zu, column %zu",
159-
errmsg, (size_t)lineno, (size_t)column) < 0)
160-
{
161-
PyUnicodeWriter_Discard(writer);
162-
return NULL;
163-
}
164-
return PyUnicodeWriter_Finish(writer);
165-
}
166-
167149
static PyObject *
168150
set_xml_error(pyexpat_state *state,
169151
enum XML_Error code, XML_Size lineno, XML_Size column,
170152
const char *errmsg)
171153
{
172-
PyObject *arg = errmsg == NULL
173-
? format_xml_error(code, lineno, column)
174-
: PyUnicode_FromStringAndSize(errmsg, strlen(errmsg));
154+
PyObject *arg;
155+
if (errmsg == NULL) {
156+
arg = PyUnicode_FromFormat(
157+
"%s: line %zu, column %zu",
158+
XML_ErrorString(code),
159+
(size_t)lineno, (size_t)column
160+
);
161+
}
162+
else {
163+
arg = PyUnicode_FromStringAndSize(errmsg, strlen(errmsg));
164+
}
175165
if (arg == NULL) {
176166
return NULL;
177167
}
178168
PyObject *res = PyObject_CallOneArg(state->error, arg);
179169
Py_DECREF(arg);
180170
if (
181171
res != NULL
182-
&& set_error_code(res, code)
183-
&& set_error_location(res, "lineno", lineno)
184-
&& set_error_location(res, "offset", column)
172+
&& set_xml_error_attr_code(res, code)
173+
&& set_xml_error_attr_location(res, "lineno", lineno)
174+
&& set_xml_error_attr_location(res, "offset", column)
185175
) {
186176
PyErr_SetObject(state->error, res);
187177
}
@@ -1294,6 +1284,50 @@ pyexpat_xmlparser_SetBillionLaughsAttackProtectionMaximumAmplification_impl(xmlp
12941284
}
12951285
#endif
12961286

1287+
#if XML_COMBINED_VERSION >= 20702
1288+
static PyObject *
1289+
set_activation_threshold(xmlparseobject *self,
1290+
PyTypeObject *cls,
1291+
unsigned long long threshold,
1292+
XML_Bool (*setter)(XML_Parser, unsigned long long))
1293+
{
1294+
assert(self->itself != NULL);
1295+
if (setter(self->itself, threshold) == XML_TRUE) {
1296+
Py_RETURN_NONE;
1297+
}
1298+
// The setter fails if self->itself is NULL (which is not possible here)
1299+
// or is a non-root parser, which currently only happens for parsers
1300+
// created by ExternalEntityParserCreate().
1301+
pyexpat_state *state = PyType_GetModuleState(cls);
1302+
return set_invalid_arg(state, self, "parser must be a root parser");
1303+
}
1304+
1305+
static PyObject *
1306+
set_maximum_amplification(xmlparseobject *self,
1307+
PyTypeObject *cls,
1308+
float max_factor,
1309+
XML_Bool (*setter)(XML_Parser, float))
1310+
{
1311+
assert(self->itself != NULL);
1312+
if (setter(self->itself, max_factor) == XML_TRUE) {
1313+
Py_RETURN_NONE;
1314+
}
1315+
// The setter fails if self->itself is NULL (which is not possible here),
1316+
// is a non-root parser, which currently only happens for parsers created
1317+
// by ExternalEntityParserCreate(), or if 'max_factor' is NaN or < 1.0.
1318+
pyexpat_state *state = PyType_GetModuleState(cls);
1319+
// Note: Expat has no API to determine whether a parser is a root parser,
1320+
// and since the Expat functions for defining the various maximum allowed
1321+
// amplifcation factors fail when a bad parser or an out-of-range factor
1322+
// is given without specifying which check failed, we check whether the
1323+
// factor is out-of-range to improve the error message. See also gh-90949.
1324+
const char *message = (isnan(max_factor) || max_factor < 1.0f)
1325+
? "'max_factor' must be at least 1.0"
1326+
: "parser must be a root parser";
1327+
return set_invalid_arg(state, self, message);
1328+
}
1329+
#endif
1330+
12971331
#if XML_COMBINED_VERSION >= 20702
12981332
/*[clinic input]
12991333
@permit_long_summary

0 commit comments

Comments
 (0)