-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
Fix NULL pointer dereference in mp_obj_get_type #17944 #17948
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: master
Are you sure you want to change the base?
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #17948 +/- ##
=======================================
Coverage 98.38% 98.39%
=======================================
Files 171 171
Lines 22297 22304 +7
=======================================
+ Hits 21938 21945 +7
Misses 359 359 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code size report:
|
py/obj.c
Outdated
@@ -57,6 +57,9 @@ const mp_obj_type_t *MICROPY_WRAP_MP_OBJ_GET_TYPE(mp_obj_get_type)(mp_const_obj_ | |||
#if MICROPY_OBJ_IMMEDIATE_OBJS && MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A | |||
|
|||
if (mp_obj_is_obj(o_in)) { | |||
if (o_in == MP_OBJ_NULL) { |
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.
I believe this should also be applied to the other instance of mp_obj_is_obj
later in the function, and to the final ->type
dereference at the end of the function.
If I understood this correctly, this only fixes the issue for MICROPY_OBJ_REPR_A
, doesn't it?.
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.
Thanks for the review — you’re right on both points.
My original guard lived only in the MICROPY_OBJ_REPR_A
/mp_obj_is_obj()
branch, so it would miss other branches and other representations. I’ve moved the MP_OBJ_NULL
check to the top of mp_obj_get_type()
, before any branching.
To address the crash at its source without changing list layout, I implemented a mutation check in objlist.c
. Pass this check to mp_quicksort
and check after any call that can run Python code (key function, comparator) and before swaps. If the pointer or length changed, raise ValueError("list modified during sort")
.
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.
I see. Considering that your change is affecting all ports you may want to move the test from tests/ports/unix
to tests/basics
instead. If you don't have a board for each port involved to test this just ask, folks here can help you out :)
Also, there's a non-trivial size increase on all ports caused by this change, see the comment for py/objlist.c
below to see whether things can be made shorter.
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 check should not be needed, there should be no way for MP_OBJ_NULL to get here (at least, if the mutate-while-sorting issue is solved).
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 so much for reviewing the code. There're indeed some duplicate checks and I've removed them. Please help me test it on different boards. Currently, I don't have an extra board with me.
62b1794
to
cc02711
Compare
py/objlist.c
Outdated
@@ -41,6 +41,11 @@ static mp_obj_t list_pop(size_t n_args, const mp_obj_t *args); | |||
|
|||
/******************************************************************************/ | |||
/* list */ | |||
static inline void list_sort_mutation_check(mp_obj_list_t *self, mp_obj_t *orig_items, size_t orig_len){ | |||
if (self->items != orig_items || self->len != orig_len){ | |||
mp_raise_ValueError(MP_ERROR_TEXT("list modified during sort.")); |
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.
Error messages shouldn't have a trailing period (I've got bitten by this as well in the past).
py/objlist.c
Outdated
mp_cstack_check(); | ||
while (head < tail) { | ||
mp_obj_t *h = head - 1; | ||
mp_obj_t *t = tail; | ||
mp_obj_t v = key_fn == MP_OBJ_NULL ? tail[0] : mp_call_function_1(key_fn, tail[0]); // get pivot using key_fn | ||
list_sort_mutation_check(self, orig_items, orig_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.
This can probably be removed, as I think the execution flow should always pass through the call to list_sort_mutation_check
at line 296.
py/objlist.c
Outdated
mp_obj_t x = h[0]; | ||
h[0] = t[0]; | ||
t[0] = x; | ||
} | ||
list_sort_mutation_check(self, orig_items, orig_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.
This can probably be removed too, at this point the list should have been checked by the call to list_sort_mutation_check
on line 306, I think.
py/objlist.c
Outdated
} while (h < t && mp_binary_op(MP_BINARY_OP_LESS, key_fn == MP_OBJ_NULL ? h[0] : mp_call_function_1(key_fn, h[0]), v) == binop_less_result); | ||
do {--t; | ||
list_sort_mutation_check(self, orig_items, orig_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.
This can probably be removed too, as I think execution should pass through line 301 anyway.
Thanks for the contribution. For this to be considered for merging, it needs to be as minimal as possible in terms of code size increase (see above "Code size report", which will change as you push to this PR). |
@MaksimFeng For the To work around that, relevant module inclusions that may fail can be wrapped like this: try:
import gc
except ImportError:
print("SKIP")
raise SystemExit Existing documentation on writing tests can be a bit scarce, but there's #17301 that aims to flesh that out. You may want to scan those changes as they may clarify a few things. Edit: the same thing makes the Zephyr test fail too? I thought that had the garbage collector enabled, go figure. |
Closes: micropython#17941 Signed-off-by: Kai <maksimfengk@outlook.com>
@agatti, thank you for the reminder and the supplementary document. It was very helpful. It appears the Zephyr test failure was caused by the gc import.... |
Summary
py/obj:Against null during list.sort,add testcase.
If a list's comparison function calls array.clear() and gc.collect() during sort, an element slot can
become MP_OBJ_NULL. Guard mp_obj_get_type() to avoid a NULL dereference.
Closes: #17941
Testing
Testing on unix port.