-
Notifications
You must be signed in to change notification settings - Fork 566
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrite decimal parsing to eliminate buffer overflow
This is a fix for GitHub security advisory GHSA-pm6v-h62r-rwx8. The old code had a hardcoded buffer of 100 bytes (and a comment asking why it was hardcoded!) and fetching a decimal greater than 100 digits would cause a buffer overflow. Author arturxedex128 supplied a very simple code to reproduce the error which was put into the 3 PostgreSQL unit tests as test_large_decimal. (Thank you arturxedex128!) Unfortunately the strategy is still that we have to parse decimals, but now Python strings / Unicode objects are used so there is no arbitrary limit.
- Loading branch information
1 parent
e7fefd8
commit 6b107a2
Showing
9 changed files
with
197 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
|
||
#include "pyodbc.h" | ||
#include "wrapper.h" | ||
#include "textenc.h" | ||
#include "decimal.h" | ||
|
||
static PyObject* decimal = 0; | ||
// The Decimal constructor. | ||
|
||
static PyObject* re_sub = 0; | ||
static PyObject* re_compile = 0; | ||
static PyObject* re_escape = 0; | ||
|
||
// In Python 2.7, the 3 strings below are bytes objects. In 3.x they are Unicode objects. | ||
|
||
|
||
static PyObject* pDecimalPoint = 0; | ||
// A "." object which we replace pLocaleDecimal with if they are not the same. | ||
|
||
static PyObject* pLocaleDecimal = 0; | ||
// The decimal character used by the locale. This can be overridden by the user. | ||
// | ||
// In 2.7 this is a bytes object, otherwise unicode. | ||
|
||
static PyObject* pLocaleDecimalEscaped = 0; | ||
// A version of pLocaleDecimal escaped to be used in a regular expression. (The character | ||
// could be something special in regular expressions.) This is zero when pLocaleDecimal is | ||
// ".", indicating no replacement is necessary. | ||
|
||
static PyObject* pRegExpRemove = 0; | ||
// A regular expression that matches characters we want to remove before parsing. | ||
|
||
|
||
bool InitializeDecimal() { | ||
// This is called when the module is initialized and creates globals. | ||
|
||
Object d(PyImport_ImportModule("decimal")); | ||
decimal = PyObject_GetAttrString(d, "Decimal"); | ||
if (!decimal) | ||
return 0; | ||
Object re(PyImport_ImportModule("re")); | ||
re_sub = PyObject_GetAttrString(re, "sub"); | ||
re_escape = PyObject_GetAttrString(re, "escape"); | ||
re_compile = PyObject_GetAttrString(re, "compile"); | ||
|
||
Object module(PyImport_ImportModule("locale")); | ||
Object ldict(PyObject_CallMethod(module, "localeconv", 0)); | ||
Object point(PyDict_GetItemString(ldict, "decimal_point")); | ||
|
||
if (!point) | ||
return false; | ||
|
||
#if PY_MAJOR_VERSION >= 3 | ||
pDecimalPoint = PyUnicode_FromString("."); | ||
#else | ||
pDecimalPoint = PyBytes_FromString("."); | ||
#endif | ||
|
||
if (!pDecimalPoint) | ||
return false; | ||
|
||
#if PY_MAJOR_VERSION >= 3 | ||
if (!SetDecimalPoint(point)) | ||
return false; | ||
#else | ||
// In 2.7, we only support non-Unicode right now. | ||
if (PyBytes_Check(point)) | ||
if (!SetDecimalPoint(point)) | ||
return false; | ||
#endif | ||
|
||
return true; | ||
} | ||
|
||
PyObject* GetDecimalPoint() { | ||
Py_INCREF(pLocaleDecimal); | ||
return pLocaleDecimal; | ||
} | ||
|
||
bool SetDecimalPoint(PyObject* pNew) | ||
{ | ||
if (PyObject_RichCompareBool(pNew, pDecimalPoint, Py_EQ) == 1) | ||
{ | ||
// They are the same. | ||
Py_XDECREF(pLocaleDecimal); | ||
pLocaleDecimal = pDecimalPoint; | ||
Py_INCREF(pLocaleDecimal); | ||
|
||
Py_XDECREF(pLocaleDecimalEscaped); | ||
pLocaleDecimalEscaped = 0; | ||
} | ||
else | ||
{ | ||
// They are different, so we'll need a regular expression to match it so it can be | ||
// replaced in getdata GetDataDecimal. | ||
|
||
Py_XDECREF(pLocaleDecimal); | ||
pLocaleDecimal = pNew; | ||
Py_INCREF(pLocaleDecimal); | ||
|
||
Object e(PyObject_CallFunctionObjArgs(re_escape, pNew, 0)); | ||
if (!e) | ||
return false; | ||
|
||
Py_XDECREF(pLocaleDecimalEscaped); | ||
pLocaleDecimalEscaped = e.Detach(); | ||
} | ||
|
||
#if PY_MAJOR_VERSION >= 3 | ||
Object s(PyUnicode_FromFormat("[^0-9%U-]+", pLocaleDecimal)); | ||
#else | ||
Object s(PyBytes_FromFormat("[^0-9%s-]+", PyString_AsString(pLocaleDecimal))); | ||
#endif | ||
if (!s) | ||
return false; | ||
|
||
Object r(PyObject_CallFunctionObjArgs(re_compile, s.Get(), 0)); | ||
if (!r) | ||
return false; | ||
|
||
Py_XDECREF(pRegExpRemove); | ||
pRegExpRemove = r.Detach(); | ||
|
||
return true; | ||
} | ||
|
||
|
||
PyObject* DecimalFromText(const TextEnc& enc, const byte* pb, Py_ssize_t cb) | ||
{ | ||
// Creates a Decimal object from a text buffer. | ||
|
||
// The Decimal constructor requires the decimal point to be '.', so we need to convert the | ||
// locale's decimal to it. We also need to remove non-decimal characters such as thousands | ||
// separators and currency symbols. | ||
// | ||
// Remember that the thousands separate will often be '.', so have to do this carefully. | ||
// We'll create a regular expression with 0-9 and whatever the thousands separator is. | ||
|
||
Object text(TextBufferToObject(enc, pb, cb)); | ||
if (!text) | ||
return 0; | ||
|
||
Object cleaned = PyObject_CallMethod(pRegExpRemove, "sub", "sO", "", text.Get()); | ||
if (!cleaned) | ||
return 0; | ||
|
||
if (pLocaleDecimalEscaped) | ||
{ | ||
Object c2(PyObject_CallFunctionObjArgs(re_sub, pLocaleDecimalEscaped, pDecimalPoint, 0)); | ||
if (!c2) | ||
return 0; | ||
cleaned.Attach(c2.Detach()); | ||
} | ||
|
||
PyObject* result = PyObject_CallFunctionObjArgs(decimal, cleaned.Get(), 0); | ||
return result; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#pragma once | ||
|
||
bool InitializeDecimal(); | ||
PyObject* GetDecimalPoint(); | ||
bool SetDecimalPoint(PyObject* pNew); | ||
|
||
PyObject* DecimalFromText(const TextEnc& enc, const byte* pb, Py_ssize_t cb); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.