-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
gh-143198: fix some crashes in sqlite3.executemany with concurrent mutations
#143210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…nt parameter iterator
Misc/NEWS.d/next/Library/2025-12-27-10-36-18.gh-issue-143198.DdIHyC.rst
Outdated
Show resolved
Hide resolved
sqlite3.execute[many] with re-entrant parameter iteratorsqlite3.execute[many] with concurrent mutations
sqlite3.execute[many] with concurrent mutationssqlite3.executemany with concurrent mutations
|
There were more UAFs that I could find... |
sqlite3.executemany with concurrent mutationssqlite3.executemany with concurrent mutations
15f7823 to
6056bb2
Compare
6056bb2 to
75b2a0c
Compare
|
Ok, let's avoid force-pushing more. I force pushed because I wanted a single commit for the follow-up PR |
This is not always possible and it's more prone to errors in the future. The connection may be closed but we would check it way later. I tried to only add checks when the code in question never uses the sqlite3 API (e.g., I can't make the diff smaller though. It's the minimal diff on the C code I can do. |
| const char* binding_name; | ||
| int i; | ||
| int rc; | ||
| int rc; /* sqlite3 error code */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a reason why I added this comment. I was myself confused when I used the same variable (one for internal C API and one for sqlite API call). Later I will rename that variable when I will refactor the functions.
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for minimizing the diff, @picnixz.
It seems that most new checks are not covered by tests. At least tests were passed when I commented out them one by one. Maybe more tests are needed?
It seems that neither bind_param(), nor bind_parameters() use self->connection->db, so the check can be added only after the call of bind_parameters(). Maybe much later -- immediately before sqlite3_changes() and sqlite3_last_insert_rowid().
I wonder, what happens when we close the connection in one of numerous callbacks? Then self->connection->db can be set NULL after calling some SQLite C API, not only after calling some Python C API.
Misc/NEWS.d/next/Library/2025-12-27-10-36-18.gh-issue-143198.DdIHyC.rst
Outdated
Show resolved
Hide resolved
Modules/_sqlite/cursor.c
Outdated
| num_params = PySequence_Size(parameters); | ||
| if (num_params == -1) { | ||
| return; | ||
| if (num_params == -1 && PyErr_Occurred()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't PyErr_Occurred redundant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I thought but, in release mode the check is not performed. You can have a __len__ that returns -1 without setting an exception:
PySequenceMethods *m = Py_TYPE(s)->tp_as_sequence;
if (m && m->sq_length) {
Py_ssize_t len = m->sq_length(s);
assert(_Py_CheckSlotResult(s, "__len__", len >= 0));
return len;
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, you're right. I thought we directly called __len__ but it's warpped as follows:
PyObject *res = vectorcall_method(&_Py_ID(__len__), stack, 1);
Py_ssize_t len;
if (res == NULL)
return -1;
Py_SETREF(res, _PyNumber_Index(res));
if (res == NULL)
return -1;So returning -1 always implies that there is an exception set here. I'll remove that check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can still get -1 for the extension class. But this is a bug in the extension.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want me to re-add the check though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because on DEBUG builds there's some assert that would be crashing if the extension class does something bad
|
@serhiy-storchaka I've extensively added test cases for the adpater's part. I didn't check all |
FTR, the reason why
executeis actually not affected is that the iterator we are taking is built from a list we create ourselves so it's not possible to have "bad" side effects._sqlitecursor cache via re-entrant parameter iterator #143198