mem: track fd invalidation of imported memblocks

When a memblock closes its fd, have it emit signal to other memblocks
possibly sharing the fd, so they know it is now invalid.

When a memblock has an invalidated fd, it'll not allow mapping more
memory, nor match against fd searches.

This avoids bugs where kernel fd reuse causes already invalid memblocks
to be used.  Generally, higher level code should make sure memblocks get
freed in right order so that this situation does not occur, but try to
keep mempool in consistent state regardless.
This commit is contained in:
Pauli Virtanen 2024-02-02 19:36:03 +02:00 committed by Wim Taymans
parent 6ab86209f2
commit f1d2d40748

View file

@ -78,6 +78,9 @@ static inline int memfd_create(const char *name, unsigned int flags)
#define pw_mempool_emit_added(p,b) pw_mempool_emit(p, added, 0, b)
#define pw_mempool_emit_removed(p,b) pw_mempool_emit(p, removed, 0, b)
#define memblock_emit(b,m,v,...) spa_hook_list_call(&b->listener_list, struct memblock_events, m, v, ##__VA_ARGS__)
#define memblock_emit_invalidated(b) memblock_emit(b, invalidated, 0)
struct mempool {
struct pw_mempool this;
@ -93,6 +96,15 @@ struct memblock {
struct spa_list link; /* link in mempool */
struct spa_list mappings; /* list of struct mapping */
struct spa_list memmaps; /* list of struct memmap */
struct memblock *owner; /* owner of fd, if another memblock */
struct spa_hook owner_listener; /* listen for fd owner memblock events */
struct spa_hook_list listener_list;
};
struct memblock_events {
#define VERSION_MEMBLOCK_EVENTS 0
uint32_t version;
void (*invalidated) (void *data);
};
/* a mapped region of a block */
@ -285,6 +297,11 @@ static struct mapping * memblock_map(struct memblock *b,
return NULL;
}
if (b->this.fd == -1) {
pw_log_error("%p: block:%p cannot map memory with stale fd", p, b);
errno = EINVAL;
return NULL;
}
ptr = mmap(NULL, size, prot, fl, b->this.fd, offset);
if (ptr == MAP_FAILED) {
@ -347,6 +364,12 @@ struct pw_memmap * pw_memblock_map(struct pw_memblock *block,
struct pw_map_range range;
struct stat sb;
if (b->this.fd == -1) {
pw_log_error("%p: block:%p cannot map memory with stale fd", p, block);
errno = EINVAL;
return NULL;
}
if (fstat(b->this.fd, &sb) != 0)
return NULL;
@ -482,6 +505,7 @@ struct pw_memblock * pw_mempool_alloc(struct pw_mempool *pool, enum pw_memblock_
b->this.size = size;
spa_list_init(&b->mappings);
spa_list_init(&b->memmaps);
spa_hook_list_init(&b->listener_list);
#ifdef HAVE_MEMFD_CREATE
char name[128];
@ -567,6 +591,9 @@ static struct memblock * mempool_find_fd(struct pw_mempool *pool, int fd)
struct memblock *b;
spa_list_for_each(b, &impl->blocks, link) {
if (b->this.fd == -1)
continue;
if (fd == b->this.fd) {
pw_log_debug("%p: found %p id:%u fd:%d ref:%d",
pool, &b->this, b->this.id, fd, b->this.ref);
@ -583,6 +610,12 @@ struct pw_memblock * pw_mempool_import(struct pw_mempool *pool,
struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
struct memblock *b;
if (fd < 0) {
pw_log_error("%p: cannot import invalid fd", pool);
errno = EINVAL;
return NULL;
}
b = mempool_find_fd(pool, fd);
if (b != NULL) {
b->this.ref++;
@ -595,6 +628,7 @@ struct pw_memblock * pw_mempool_import(struct pw_mempool *pool,
spa_list_init(&b->memmaps);
spa_list_init(&b->mappings);
spa_hook_list_init(&b->listener_list);
b->this.ref = 1;
b->this.pool = pool;
@ -613,15 +647,57 @@ struct pw_memblock * pw_mempool_import(struct pw_mempool *pool,
return &b->this;
}
static void memblock_invalidated(void *data)
{
struct memblock *b = data;
if (!b->owner)
return;
pw_log_debug("%p: invalidated block:%p id:%u fd:%d ref:%d owner:%p",
b->this.pool, b, b->this.id, b->this.fd, b->this.ref, b->owner);
spa_hook_remove(&b->owner_listener);
b->owner = NULL;
b->this.fd = -1;
}
static const struct memblock_events memblock_events = {
VERSION_MEMBLOCK_EVENTS,
.invalidated = memblock_invalidated,
};
SPA_EXPORT
struct pw_memblock * pw_mempool_import_block(struct pw_mempool *pool,
struct pw_memblock *mem)
{
struct pw_memblock *block;
struct memblock *b;
pw_log_debug("%p: import block:%p type:%d fd:%d", pool,
mem, mem->type, mem->fd);
return pw_mempool_import(pool,
block = pw_mempool_import(pool,
mem->flags | PW_MEMBLOCK_FLAG_DONT_CLOSE,
mem->type, mem->fd);
if (!block)
return NULL;
b = SPA_CONTAINER_OF(block, struct memblock, this);
if (!b->owner) {
struct memblock *bmem = SPA_CONTAINER_OF(mem, struct memblock, this);
while (bmem->owner)
bmem = bmem->owner;
if (!(bmem->this.flags & PW_MEMBLOCK_FLAG_DONT_CLOSE)) {
b->owner = bmem;
spa_hook_list_append(&bmem->listener_list, &b->owner_listener, &memblock_events, b);
}
}
return block;
}
SPA_EXPORT
@ -725,6 +801,13 @@ void pw_memblock_free(struct pw_memblock *block)
if (!SPA_FLAG_IS_SET(block->flags, PW_MEMBLOCK_FLAG_DONT_NOTIFY))
pw_mempool_emit_removed(impl, block);
if (b->owner) {
spa_hook_remove(&b->owner_listener);
b->owner = NULL;
}
memblock_emit_invalidated(b);
spa_list_consume(mm, &b->memmaps, link)
pw_memmap_free(&mm->this);
@ -737,6 +820,9 @@ void pw_memblock_free(struct pw_memblock *block)
pw_log_debug("%p: close fd:%d", pool, block->fd);
close(block->fd);
}
spa_hook_list_clean(&b->listener_list);
free(b);
}