CVE-2023-7152 (Python, published 2023-12-29T05:15Z)
Description: value: A vulnerability, which was classified as critical, has been found in MicroPython 1.21.0/1.22.0-preview. Affected by this issue is the function poll_set_add_fd of the file extmod/modselect.c. The manipulation leads to use after free. The exploit has been disclosed to the public and may be used. The patch is identified as 8b24aa36ba978eafc6114b6798b47b7bfecdca26. It is recommended to apply a patch to fix this issue. VDB-249158 is the identifier assigned to this vulnerability.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-416 \u2014 Use After Free
The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
Repository: https://github.com/jimmo/micropython
Files changed:
extmod/modselect.c
tests/extmod/select_poll_fd.py
Commit message: extmod/modselect: Handle growing the pollfds allocation correctly.

The poll_obj_t instances have their pollfd field point into this
allocation.  So if re-allocating results in a move, we need to update the
existing poll_obj_t's.

Update the test to cover this case.

Fixes issue #12887.

This work was funded through GitHub Sponsors.

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>


Vulnerable code:
            poll_set->pollfds = m_renew(struct pollfd, poll_set->pollfds, poll_set->alloc, poll_set->alloc + 4);
            poll_set->alloc += 4;
# Test registering a very large number of file descriptors.

Fixed code:
#include <string.h>
// How much (in pollfds) to grow the allocation for poll_set->pollfds by.
#define POLL_SET_ALLOC_INCREMENT (4)

            size_t new_alloc = poll_set->alloc + POLL_SET_ALLOC_INCREMENT;
            // Try to grow in-place.
            struct pollfd *new_fds = m_renew_maybe(struct pollfd, poll_set->pollfds, poll_set->alloc, new_alloc, false);
            if (!new_fds) {
                // Failed to grow in-place. Do a new allocation and copy over the pollfd values.
                new_fds = m_new(struct pollfd, new_alloc);
                memcpy(new_fds, poll_set->pollfds, sizeof(struct pollfd) * poll_set->alloc);

                // Update existing poll_obj_t to update their pollfd field to
                // point to the same offset inside the new allocation.
                for (mp_uint_t i = 0; i < poll_set->map.alloc; ++i) {
                    if (!mp_map_slot_is_filled(&poll_set->map, i)) {
                        continue;
                    }

                    poll_obj_t *poll_obj = MP_OBJ_TO_PTR(poll_set->map.table[i].value);
                    if (!poll_obj) {
                        // This is the one we're currently adding,
                        // poll_set_add_obj doesn't assign elem->value until
                        // afterwards.
                        continue;
                    }

                    poll_obj->pollfd = new_fds + (poll_obj->pollfd - poll_set->pollfds);
                }

                // Delete the old allocation.
                m_del(struct pollfd, poll_set->pollfds, poll_set->alloc);
            }

            poll_set->pollfds = new_fds;
            poll_set->alloc = new_alloc;
# Test registering a very large number of file descriptors (will trigger
# EINVAL due to more than OPEN_MAX fds).
    assert False

# Register stdout/stderr, plus many extra ones to trigger the fd vector
# resizing. Then unregister the excess ones and verify poll still works.
poller = select.poll()
for fd in range(1, 1000):
    poller.register(fd)
for i in range(3, 1000):
    poller.unregister(i)
print(sorted(poller.poll()))