extmod/modlwip: Ensure socket is finalisable if error during creation.
JavaScript code lint and formatting with Biome / eslint (push) Has been cancelled
Check code formatting / code-formatting (push) Has been cancelled
Check spelling with codespell / codespell (push) Has been cancelled
Build docs / build (push) Has been cancelled
Check examples / embedding (push) Has been cancelled
Package mpremote / build (push) Has been cancelled
.mpy file format and tools / test (push) Has been cancelled
Build ports metadata / build (push) Has been cancelled
alif port / build_alif (alif_ae3_build) (push) Has been cancelled
cc3200 port / build (push) Has been cancelled
esp32 port / build_idf (esp32_build_c2_c5_c6, v5.5.1) (push) Has been cancelled
esp32 port / build_idf (esp32_build_cmod_spiram_s2, v5.3) (push) Has been cancelled
esp32 port / build_idf (esp32_build_cmod_spiram_s2, v5.5.1) (push) Has been cancelled
esp32 port / build_idf (esp32_build_p4, v5.5.1) (push) Has been cancelled
esp32 port / build_idf (esp32_build_s3_c3, v5.3) (push) Has been cancelled
esp32 port / build_idf (esp32_build_s3_c3, v5.5.1) (push) Has been cancelled
esp8266 port / build (push) Has been cancelled
mimxrt port / build (push) Has been cancelled
nrf port / build (push) Has been cancelled
powerpc port / build (push) Has been cancelled
qemu port / build_and_test_arm (bigendian) (push) Has been cancelled
qemu port / build_and_test_arm (sabrelite) (push) Has been cancelled
qemu port / build_and_test_arm (thumb_hardfp) (push) Has been cancelled
qemu port / build_and_test_arm (thumb_softfp) (push) Has been cancelled
qemu port / build_and_test_rv32 (push) Has been cancelled
qemu port / build_and_test_rv64 (push) Has been cancelled
renesas-ra port / build_renesas_ra_board (push) Has been cancelled
rp2 port / build (push) Has been cancelled
samd port / build (push) Has been cancelled
stm32 port / build_stm32 (stm32_misc_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_nucleo_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_pyb_build) (push) Has been cancelled
unix port / minimal (push) Has been cancelled
unix port / reproducible (push) Has been cancelled
unix port / standard (push) Has been cancelled
unix port / standard_v2 (push) Has been cancelled
unix port / coverage (push) Has been cancelled
unix port / coverage_32bit (push) Has been cancelled
unix port / nanbox (push) Has been cancelled
unix port / longlong (push) Has been cancelled
unix port / float (push) Has been cancelled
unix port / gil_enabled (push) Has been cancelled
unix port / stackless_clang (push) Has been cancelled
unix port / float_clang (push) Has been cancelled
unix port / settrace_stackless (push) Has been cancelled
unix port / repr_b (push) Has been cancelled
unix port / macos (push) Has been cancelled
unix port / qemu_mips (push) Has been cancelled
unix port / qemu_arm (push) Has been cancelled
unix port / qemu_riscv64 (push) Has been cancelled
unix port / sanitize_address (push) Has been cancelled
unix port / sanitize_undefined (push) Has been cancelled
webassembly port / build (push) Has been cancelled
windows port / build-vs (Debug, true, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, true, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Debug, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x64, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, dev) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, standard) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, dev) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, standard) (push) Has been cancelled
windows port / cross-build-on-linux (push) Has been cancelled
zephyr port / build (push) Has been cancelled
Python code lint and formatting with ruff / ruff (push) Has been cancelled

Because socket objects have a finaliser they must be created carefully, in
case an exception is raised during the population of their members, eg
invalid input argument or out-of-memory when allocating additional arrays.

Prior to the fix in this commit, the finaliser would crash due to
`incoming.udp_raw.array` being an invalid pointer in the following cases:
- if a SOCK_RAW was created with a proto argument that was not an integer
- if a SOCK_DGRAM or SOCK_RAW was created where the allocation of
  `lwip_incoming_packet_t` failed
- if an integer was passed in for the socket type but it was not one of
  SOCK_STREAM, SOCK_DGRAM or SOCK_RAW

Furthermore, if the allocation of `lwip_incoming_packet_t` failed then it
may have led to corruption within lwIP when freeing `socket->pcb.raw`
because that PCB was not fully set up with its callbacks.

This commit fixes all of these issues by ensuring:
- `pcb.tcp` and `incoming.udp_raw.array` are initialised to NULL early on
- the proto argument is parsed before allocating the PCB
- the allocation of `lwip_incoming_packet_t` occurs befor allocating the
  PCB
- `incoming.udp_raw.array` is checked for NULL in the finaliser code

The corresponding test (which already checked most of these causes of
failure) has been updated to include a previously-uncovered scenario.

Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Damien George
2026-02-07 23:11:16 +11:00
parent b3d88cf210
commit 406356ec8b
2 changed files with 19 additions and 4 deletions
+14 -4
View File
@@ -384,7 +384,7 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket, bool free_queue
pbuf_free(socket->incoming.tcp.pbuf);
socket->incoming.tcp.pbuf = NULL;
}
} else {
} else if (socket->incoming.udp_raw.array != NULL) {
for (size_t i = 0; i < LWIP_INCOMING_PACKET_QUEUE_LEN; ++i) {
lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[i];
if (slot->pbuf != NULL) {
@@ -938,7 +938,12 @@ static void lwip_socket_print(const mp_print_t *print, mp_obj_t self_in, mp_prin
static mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 0, 4, false);
// Once the socket is allocated it must be in a valid state to be finalised:
// - `incoming.udp_raw.array` is NULL or a valid heap pointer
// - `pcb` is NULL or a valid lwIP PCB that has been fully initialised
lwip_socket_obj_t *socket = mp_obj_malloc_with_finaliser(lwip_socket_obj_t, &lwip_socket_type);
socket->pcb.tcp = NULL;
socket->incoming.udp_raw.array = NULL;
socket->timeout = -1;
socket->recv_offset = 0;
socket->domain = MOD_NETWORK_AF_INET;
@@ -946,10 +951,16 @@ static mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, size_t n_args, s
socket->callback = MP_OBJ_NULL;
socket->state = STATE_NEW;
// Parse given arguments.
uint8_t socket_proto = 0;
(void)socket_proto;
if (n_args >= 1) {
socket->domain = mp_obj_get_int(args[0]);
if (n_args >= 2) {
socket->type = mp_obj_get_int(args[1]);
if (n_args >= 3) {
socket_proto = mp_obj_get_int(args[2]);
}
}
}
@@ -963,18 +974,17 @@ static mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, size_t n_args, s
#if MICROPY_PY_LWIP_SOCK_RAW
case MOD_NETWORK_SOCK_RAW:
#endif
socket->incoming.udp_raw.array = m_new0(lwip_incoming_packet_t, LWIP_INCOMING_PACKET_QUEUE_LEN);
if (socket->type == MOD_NETWORK_SOCK_DGRAM) {
socket->pcb.udp = udp_new();
}
#if MICROPY_PY_LWIP_SOCK_RAW
else {
mp_int_t proto = n_args <= 2 ? 0 : mp_obj_get_int(args[2]);
socket->pcb.raw = raw_new(proto);
socket->pcb.raw = raw_new(socket_proto);
}
#endif
socket->incoming.udp_raw.iget = 0;
socket->incoming.udp_raw.iput = 0;
socket->incoming.udp_raw.array = m_new0(lwip_incoming_packet_t, LWIP_INCOMING_PACKET_QUEUE_LEN);
break;
default:
mp_raise_OSError(MP_EINVAL);
+5
View File
@@ -16,6 +16,11 @@ try:
except TypeError:
print("TypeError")
try:
s = socket.socket(socket.AF_INET, 123456)
except OSError:
print("OSError")
try:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, None)
except TypeError: