Skip to content

Commit cca84ee

Browse files
committed
Version check when setting up lxml
- Expose both the compiled version and the linked version of libxml2 - Do a check that the versions match when initializing the module. Otherwise raise an exception. This seems like a better result then a difficult to diagnose segfault.
1 parent abccdbe commit cca84ee

File tree

4 files changed

+132
-10
lines changed

4 files changed

+132
-10
lines changed

Diff for: src/lxml.c

+96
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "common.h"
1111
#include "lxml.h"
12+
#include "exception.h"
1213

1314
#include <etree_defs.h>
1415
#include <etree_api.h>
@@ -17,8 +18,103 @@
1718
#include <libxml/parser.h>
1819
#include <libxml/dict.h>
1920

21+
#define XMLSEC_EXTRACT_VERSION(x, y) ((x / (y)) % 100)
22+
23+
#define XMLSEC_EXTRACT_MAJOR(x) XMLSEC_EXTRACT_VERSION(x, 100 * 100)
24+
#define XMLSEC_EXTRACT_MINOR(x) XMLSEC_EXTRACT_VERSION(x, 100)
25+
#define XMLSEC_EXTRACT_PATCH(x) XMLSEC_EXTRACT_VERSION(x, 1)
26+
27+
static long PyXmlSec_GetLibXmlVersionLong() {
28+
return PyOS_strtol(xmlParserVersion, NULL, 10);
29+
}
30+
long PyXmlSec_GetLibXmlVersionMajor() {
31+
return XMLSEC_EXTRACT_MAJOR(PyXmlSec_GetLibXmlVersionLong());
32+
}
33+
long PyXmlSec_GetLibXmlVersionMinor() {
34+
return XMLSEC_EXTRACT_MINOR(PyXmlSec_GetLibXmlVersionLong());
35+
}
36+
long PyXmlSec_GetLibXmlVersionPatch() {
37+
return XMLSEC_EXTRACT_PATCH(PyXmlSec_GetLibXmlVersionLong());
38+
}
39+
40+
long PyXmlSec_GetLibXmlCompiledVersionMajor() {
41+
return XMLSEC_EXTRACT_MAJOR(LIBXML_VERSION);
42+
}
43+
long PyXmlSec_GetLibXmlCompiledVersionMinor() {
44+
return XMLSEC_EXTRACT_MINOR(LIBXML_VERSION);
45+
}
46+
long PyXmlSec_GetLibXmlCompiledVersionPatch() {
47+
return XMLSEC_EXTRACT_PATCH(LIBXML_VERSION);
48+
}
49+
50+
static int PyXmlSec_CheckLibXmlLibraryVersion(void) {
51+
// Make sure that the version of libxml2 that we were compiled against is the same as the one
52+
// that we are using. If the versions we are using are not internally consistent, we could run
53+
// into segfaults.
54+
55+
if (PyXmlSec_GetLibXmlVersionMajor() != PyXmlSec_GetLibXmlCompiledVersionMajor() ||
56+
PyXmlSec_GetLibXmlVersionMinor() != PyXmlSec_GetLibXmlCompiledVersionMinor()) {
57+
return -1;
58+
}
59+
60+
return 0;
61+
}
62+
63+
static int PyXmlSec_CheckLxmlLibraryVersion(void) {
64+
// Make sure that the version of libxml2 that lxml was compiled against is the same as the one
65+
// that we are using. Since we pass trees between the two libraries, if there is a version mismatch
66+
// we could run into segfaults.
67+
// See: https://github.com/xmlsec/python-xmlsec/issues/283
68+
69+
PyObject* lxml = NULL;
70+
PyObject* version = NULL;
71+
72+
// Default to failure
73+
int result = -1;
74+
75+
lxml = PyImport_ImportModule("lxml.etree");
76+
if (lxml == NULL) {
77+
goto FINALIZE;
78+
}
79+
version = PyObject_GetAttrString(lxml, "LIBXML_VERSION");
80+
if (version == NULL) {
81+
goto FINALIZE;
82+
}
83+
if (!PyTuple_Check(version) || PyTuple_Size(version) != 3) {
84+
goto FINALIZE;
85+
}
86+
87+
PyObject* major = PyTuple_GetItem(version, 0);
88+
PyObject* minor = PyTuple_GetItem(version, 1);
89+
90+
if (!PyLong_Check(major) || !PyLong_Check(minor)) {
91+
goto FINALIZE;
92+
}
93+
94+
if (PyLong_AsLong(major) != PyXmlSec_GetLibXmlVersionMajor() || PyLong_AsLong(minor) != PyXmlSec_GetLibXmlVersionMinor()) {
95+
goto FINALIZE;
96+
}
97+
98+
result = 0;
99+
100+
FINALIZE:
101+
// Cleanup our references, and return the result
102+
Py_XDECREF(lxml);
103+
Py_XDECREF(version);
104+
return result;
105+
}
20106

21107
int PyXmlSec_InitLxmlModule(void) {
108+
if (PyXmlSec_CheckLibXmlLibraryVersion() < 0) {
109+
PyXmlSec_SetLastError("xmlsec libxml2 library compiled version vs runtime version mismatch");
110+
return -1;
111+
}
112+
113+
if (PyXmlSec_CheckLxmlLibraryVersion() < 0) {
114+
PyXmlSec_SetLastError("lxml & xmlsec libxml2 library version mismatch");
115+
return -1;
116+
}
117+
22118
return import_lxml__etree();
23119
}
24120

Diff for: src/lxml.h

+9
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@ PyXmlSec_LxmlElementPtr PyXmlSec_elementFactory(PyXmlSec_LxmlDocumentPtr doc, xm
2929
// converts o to PyObject, None object is not allowed, does not increment ref_counts
3030
int PyXmlSec_LxmlElementConverter(PyObject* o, PyXmlSec_LxmlElementPtr* p);
3131

32+
// get version numbers for libxml2 both compiled and linked
33+
long PyXmlSec_GetLibXmlVersionMajor();
34+
long PyXmlSec_GetLibXmlVersionMinor();
35+
long PyXmlSec_GetLibXmlVersionPatch();
36+
37+
long PyXmlSec_GetLibXmlCompiledVersionMajor();
38+
long PyXmlSec_GetLibXmlCompiledVersionMinor();
39+
long PyXmlSec_GetLibXmlCompiledVersionPatch();
40+
3241
#endif // __PYXMLSEC_LXML_H__

Diff for: src/main.c

+27-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "common.h"
1111
#include "platform.h"
1212
#include "exception.h"
13+
#include "lxml.h"
1314

1415
#include <xmlsec/xmlsec.h>
1516
#include <xmlsec/crypto.h>
@@ -127,10 +128,27 @@ static PyObject* PyXmlSec_GetLibXmlSecVersion() {
127128
}
128129

129130
static char PyXmlSec_GetLibXmlVersion__doc__[] = \
130-
"get_libxml_version() -> tuple\n"
131-
"Returns Version tuple of wrapped libxml library.";
131+
"get_libxml_version() -> tuple[int, int, int]\n"
132+
"Returns version tuple of libxml2 library xmlsec is using.";
132133
static PyObject* PyXmlSec_GetLibXmlVersion() {
133-
return Py_BuildValue("(iii)", XMLSEC_LIBXML_VERSION_MAJOR, XMLSEC_LIBXML_VERSION_MINOR, XMLSEC_LIBXML_VERSION_PATCH);
134+
return Py_BuildValue(
135+
"(iii)",
136+
PyXmlSec_GetLibXmlVersionMajor(),
137+
PyXmlSec_GetLibXmlVersionMinor(),
138+
PyXmlSec_GetLibXmlVersionPatch()
139+
);
140+
}
141+
142+
static char PyXmlSec_GetLibXmlCompiledVersion__doc__[] = \
143+
"get_libxml_compiled_version() -> tuple[int, int, int]\n"
144+
"Returns version tuple of libxml2 library xmlsec was compiled with.";
145+
static PyObject* PyXmlSec_GetLibXmlCompiledVersion() {
146+
return Py_BuildValue(
147+
"(iii)",
148+
PyXmlSec_GetLibXmlCompiledVersionMajor(),
149+
PyXmlSec_GetLibXmlCompiledVersionMinor(),
150+
PyXmlSec_GetLibXmlCompiledVersionPatch()
151+
);
134152
}
135153

136154
static char PyXmlSec_PyEnableDebugOutput__doc__[] = \
@@ -412,6 +430,12 @@ static PyMethodDef PyXmlSec_MainMethods[] = {
412430
METH_NOARGS,
413431
PyXmlSec_GetLibXmlVersion__doc__
414432
},
433+
{
434+
"get_libxml_compiled_version",
435+
(PyCFunction)PyXmlSec_GetLibXmlCompiledVersion,
436+
METH_NOARGS,
437+
PyXmlSec_GetLibXmlCompiledVersion__doc__
438+
},
415439
{
416440
"enable_debug_trace",
417441
(PyCFunction)PyXmlSec_PyEnableDebugOutput,

Diff for: src/platform.h

-7
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,13 @@
1212

1313
#define PY_SSIZE_T_CLEAN 1
1414

15-
#include <libxml/xmlversion.h>
1615
#include <xmlsec/version.h>
1716
#include <Python.h>
1817

1918
#ifdef MS_WIN32
2019
#include <windows.h>
2120
#endif /* MS_WIN32 */
2221

23-
#define XMLSEC_EXTRACT_VERSION(x, y) ((x / (y)) % 100)
24-
25-
#define XMLSEC_LIBXML_VERSION_MAJOR XMLSEC_EXTRACT_VERSION(LIBXML_VERSION, 100 * 100)
26-
#define XMLSEC_LIBXML_VERSION_MINOR XMLSEC_EXTRACT_VERSION(LIBXML_VERSION, 100)
27-
#define XMLSEC_LIBXML_VERSION_PATCH XMLSEC_EXTRACT_VERSION(LIBXML_VERSION, 1)
28-
2922
#define XMLSEC_VERSION_HEX ((XMLSEC_VERSION_MAJOR << 16) | (XMLSEC_VERSION_MINOR << 8) | (XMLSEC_VERSION_SUBMINOR))
3023

3124
// XKMS support was removed in version 1.2.21

0 commit comments

Comments
 (0)