diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index 8fc07151a..7a5e5c057 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -2570,7 +2570,10 @@ static void *v4l2_mmap(void *addr, size_t length, int prot, buf = &file->buffers[id]; data = &buf->buf->buffer->datas[0]; - pw_map_range_init(&range, data->mapoffset, data->maxsize, 1024); + if (pw_map_range_init(&range, data->mapoffset, data->maxsize, 1024) < 0) { + res = MAP_FAILED; + goto error_unlock; + } if (!SPA_FLAG_IS_SET(data->flags, SPA_DATA_FLAG_READABLE)) prot &= ~PROT_READ; diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 1b8297935..e88a5db0a 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -692,7 +692,10 @@ static int map_data(struct filter *impl, struct spa_data *data, int prot) void *ptr; struct pw_map_range range; - pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); + if (pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize) < 0) { + pw_log_error("%p: invalid buffer map range", impl); + return -EOVERFLOW; + } ptr = mmap(NULL, range.size, prot, MAP_SHARED, data->fd, range.offset); if (ptr == MAP_FAILED) { @@ -721,7 +724,8 @@ static int unmap_data(struct filter *impl, struct spa_data *data) { struct pw_map_range range; - pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); + if (pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize) < 0) + return -EOVERFLOW; if (munmap(SPA_PTROFF(data->data, -range.start, void), range.size) < 0) pw_log_warn("%p: failed to unmap: %m", impl); diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c index e398a8f09..32389ed85 100644 --- a/src/pipewire/mem.c +++ b/src/pipewire/mem.c @@ -421,7 +421,10 @@ struct pw_memmap * pw_memblock_map(struct pw_memblock *block, m = memblock_find_mapping(b, flags, offset, size); if (m == NULL) { struct pw_map_range range; - pw_map_range_init(&range, offset, size, p->pagesize); + if (pw_map_range_init(&range, offset, size, p->pagesize) < 0) { + errno = EOVERFLOW; + return NULL; + } m = memblock_map(b, flags, range.offset, range.size); if (m == NULL) diff --git a/src/pipewire/mem.h b/src/pipewire/mem.h index 52abbd54d..0c3ce73bc 100644 --- a/src/pipewire/mem.h +++ b/src/pipewire/mem.h @@ -178,14 +178,21 @@ struct pw_map_range { #define PW_MAP_RANGE_INIT (struct pw_map_range){ 0, } /** Calculate parameters to mmap() memory into \a range so that - * \a size bytes at \a offset can be mapped with mmap(). */ -PW_API_MEM void pw_map_range_init(struct pw_map_range *range, + * \a size bytes at \a offset can be mapped with mmap(). + * Returns 0 on success, -EOVERFLOW if offset + size overflows. */ +PW_API_MEM int pw_map_range_init(struct pw_map_range *range, uint32_t offset, uint32_t size, uint32_t page_size) { range->offset = SPA_ROUND_DOWN_N(offset, page_size); range->start = offset - range->offset; + if (size > UINT32_MAX - range->start) + return -EOVERFLOW; + /* Check that rounding up to page_size won't overflow */ + if (range->start + size > UINT32_MAX - (page_size - 1)) + return -EOVERFLOW; range->size = SPA_ROUND_UP_N(range->start + size, page_size); + return 0; } /** diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 4ee2138bc..36b63c000 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -807,7 +807,10 @@ static int map_data(struct stream *impl, struct spa_data *data, int prot) void *ptr; struct pw_map_range range; - pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); + if (pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize) < 0) { + pw_log_error("%p: invalid buffer map range", impl); + return -EOVERFLOW; + } ptr = mmap(NULL, range.size, prot, MAP_SHARED, data->fd, range.offset); if (ptr == MAP_FAILED) { @@ -837,7 +840,8 @@ static int unmap_data(struct stream *impl, struct spa_data *data) { struct pw_map_range range; - pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); + if (pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize) < 0) + return -EOVERFLOW; if (munmap(SPA_PTROFF(data->data, -range.start, void), range.size) < 0) pw_log_warn("%p: failed to unmap: %m", impl); diff --git a/test/test-mempool.c b/test/test-mempool.c index 36f69c571..3793b23c4 100644 --- a/test/test-mempool.c +++ b/test/test-mempool.c @@ -41,9 +41,74 @@ PWTEST(mempool_issue4884) return PWTEST_PASS; } -PWTEST_SUITE(pw_mempool) +PWTEST(map_range_overflow) { - pwtest_add(mempool_issue4884, PWTEST_NOARG); + /* + * Test that pw_map_range_init rejects offset + size combinations + * that would overflow uint32_t, which could cause mmap with a + * truncated size and subsequent out-of-bounds access. + */ + struct pw_map_range range; + uint32_t page_size = 4096; + int res; + + /* Normal case: should succeed */ + res = pw_map_range_init(&range, 0, 4096, page_size); + pwtest_int_eq(res, 0); + pwtest_int_eq(range.offset, 0u); + pwtest_int_eq(range.start, 0u); + pwtest_int_eq(range.size, 4096u); + + /* Page-aligned offset: should succeed */ + res = pw_map_range_init(&range, 4096, 4096, page_size); + pwtest_int_eq(res, 0); + pwtest_int_eq(range.offset, 4096u); + pwtest_int_eq(range.start, 0u); + pwtest_int_eq(range.size, 4096u); + + /* Non-aligned offset: start gets the remainder */ + res = pw_map_range_init(&range, 100, 4096, page_size); + pwtest_int_eq(res, 0); + pwtest_int_eq(range.offset, 0u); + pwtest_int_eq(range.start, 100u); + + /* size=0: should succeed */ + res = pw_map_range_init(&range, 0, 0, page_size); + pwtest_int_eq(res, 0); + + /* Overflow: non-aligned offset causes start > 0, then start + size wraps */ + res = pw_map_range_init(&range, 4095, 0xFFFFF002, page_size); + pwtest_int_lt(res, 0); + + /* Overflow: max size with any non-zero start */ + res = pw_map_range_init(&range, 1, UINT32_MAX, page_size); + pwtest_int_lt(res, 0); + + /* Both large but page-aligned: start=0, start+size=0x80000000, + * round-up doesn't overflow, so this should succeed */ + res = pw_map_range_init(&range, 0x80000000, 0x80000000, page_size); + pwtest_int_eq(res, 0); + + /* Non-aligned offset but still fits: start=1, start+size=0x80000001 */ + res = pw_map_range_init(&range, 0x80000001, 0x80000000, page_size); + pwtest_int_eq(res, 0); + + /* Overflow: round-up of start+size would exceed uint32 */ + res = pw_map_range_init(&range, 1, UINT32_MAX - 1, page_size); + pwtest_int_lt(res, 0); + + /* start=0, size=UINT32_MAX: start + size doesn't wrap, but + * SPA_ROUND_UP_N to page_size would overflow, so must fail */ + res = pw_map_range_init(&range, 0, UINT32_MAX, page_size); + pwtest_int_lt(res, 0); + + return PWTEST_PASS; +} + +PWTEST_SUITE(pw_mempool) +{ + pwtest_add(mempool_issue4884, PWTEST_NOARG); + pwtest_add(map_range_overflow, PWTEST_NOARG); return PWTEST_PASS; }