Skip to content

Commit ee9fbd0

Browse files
committedMar 22, 2024··
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 ee9fbd0

File tree

4 files changed

+131
-10
lines changed

4 files changed

+131
-10
lines changed
 

‎src/lxml.c

+95
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,102 @@
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 is loaded. If there is a version mismatch, we could run into segfaults.
53+
54+
if (PyXmlSec_GetLibXmlVersionMajor() != PyXmlSec_GetLibXmlCompiledVersionMajor() ||
55+
PyXmlSec_GetLibXmlVersionMinor() != PyXmlSec_GetLibXmlCompiledVersionMinor()) {
56+
return -1;
57+
}
58+
59+
return 0;
60+
}
61+
62+
static int PyXmlSec_CheckLxmlLibraryVersion(void) {
63+
// Make sure that the version of libxml2 lxml is using is the same as the one we are using. Because
64+
// we pass trees between the two libraries, we need to make sure that they are using the same version
65+
// of libxml2, or we could run into difficult to debug segfaults.
66+
// See: https://github.com/xmlsec/python-xmlsec/issues/283
67+
68+
PyObject* lxml = NULL;
69+
PyObject* version = NULL;
70+
71+
// Default to failure
72+
int result = -1;
73+
74+
lxml = PyImport_ImportModule("lxml.etree");
75+
if (lxml == NULL) {
76+
goto FINALIZE;
77+
}
78+
version = PyObject_GetAttrString(lxml, "LIBXML_VERSION");
79+
if (version == NULL) {
80+
goto FINALIZE;
81+
}
82+
if (!PyTuple_Check(version) || PyTuple_Size(version) != 3) {
83+
goto FINALIZE;
84+
}
85+
86+
PyObject* major = PyTuple_GetItem(version, 0);
87+
PyObject* minor = PyTuple_GetItem(version, 1);
88+
89+
if (!PyLong_Check(major) || !PyLong_Check(minor)) {
90+
goto FINALIZE;
91+
}
92+
93+
if (PyLong_AsLong(major) != PyXmlSec_GetLibXmlVersionMajor() || PyLong_AsLong(minor) != PyXmlSec_GetLibXmlVersionMinor()) {
94+
goto FINALIZE;
95+
}
96+
97+
result = 0;
98+
99+
FINALIZE:
100+
// Cleanup our references, and return the result
101+
Py_XDECREF(lxml);
102+
Py_XDECREF(version);
103+
return result;
104+
}
20105

21106
int PyXmlSec_InitLxmlModule(void) {
107+
if (PyXmlSec_CheckLibXmlLibraryVersion() < 0) {
108+
PyXmlSec_SetLastError("xmlsec libxml2 library compiled version vs runtime version mismatch");
109+
return -1;
110+
}
111+
112+
if (PyXmlSec_CheckLxmlLibraryVersion() < 0) {
113+
PyXmlSec_SetLastError("lxml & xmlsec libxml2 library version mismatch");
114+
return -1;
115+
}
116+
22117
return import_lxml__etree();
23118
}
24119

‎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 loaded
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__

‎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,

‎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)
Please sign in to comment.