8000 Core: invalidated shared memory addresses after unmapping. by bavshin-f5 · Pull Request #737 · nginx/nginx · GitHub
[go: up one dir, main page]

Skip to content

Core: invalidated shared memory addresses after unmapping. #737

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

bavshin-f5
Copy link
Member

As we unmap the unused shared zones before destroying the cycle pool, it was possible to access already unmapped address from a cleanup handler. The change gives such handlers a way to determine that the memory is unavailable and any processing should be skipped.

I encountered the issue while attempting to discard unused data from a shared zone. The most suitable location for such code is a cleanup handler on the cycle pool: it's a bit early with the old cycle workers still running and potentially using the data, but we already know that the new cycle is going live and its shared data is initialized. And we don't have anything reliable beyond that point.
Currently, this pattern is extremely dangerous, as the handler may accidentally touch an unmapped address and crash the master process. There are a few indirect ways of testing if an address is mapped, but most of those are non-portable and worse than the offered patch.

In the end, I had to go back to a proven solution with non-reusable zones. But it would be nice to figure out an alternative to these, even if it takes several small changes like the current one.

Comment on lines 16 to 20
#if (NGX_HAVE_MAP_ANON || NGX_HAVE_MAP_DEVZERO)
#define NGX_INVALID_SHM MAP_FAILED
#elif (NGX_HAVE_SYSVSHM)
#define NGX_INVALID_SHM (void *) -1
#endif
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NGX_INVALID_SHM value here is chosen for consistency with the state after a failed ngx_shm_alloc(). Alloc may leave the address set to MAP_FAILED, thus we have to check for this value anyways.

Coincidentally, the error path in ngx_init_cycle() is not ready to handle MAP_FAILED left by the allocation failure and will attempt to ngx_shm_free() it. Seems like a good reason to consistently reset the addr to NULL.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last point is indeed true. Failed allocations end up with followup unmap errors.

limit_req_zone 1 zone=foo:100000000m rate=1r/s;                              

Log:

2025/07/15 20:26:19 [alert] 3588934#0: mmap(MAP_ANON|MAP_SHARED, 104857600000000) failed (12: Cannot allocate memory)
2025/07/15 20:26:19 [alert] 3588934#0: munmap(FFFFFFFFFFFFFFFF, 104857600000000) failed (22: Invalid argument)

This needs to be fixed for sure by resetting shm->addr to NULL.

@ac000
Copy link
Member
ac000 commented Jul 15, 2025

Hi @bavshin-f5

Rather than introducing a new macro, you could just reuse MAP_FAILED.

If you do want to introduce a new macro, I'd alias it to MAP_FAILED
rather than NULL.

While unlikely, it is possible for mmap(2) to return address 0. E.g.
if asked for with MAP_FIXED or if the mmap_min_addr sysctl is set to
0.

Same reason mmap(2) doesn't return NULL on failure...

@bavshin-f5
Copy link
Member Author

There are two configurations where we consider MAP_FAILED unavailable, NGX_HAVE_SYSVSHM fallback and Windows. The latter even returns NULL when MapViewOfFile fails.

There's also at least one location where we considered NULL an invalid mapping marker and attempted to unmap MAP_FAILED — the error path of ngx_init_cycle. It is now updated to consistently use the new macro.

I don't have a strong opinion on the value though. That's why there are two commits; the first one does exactly what you suggest and the second one can be discarded.

@arut
Copy link
Contributor
arut commented Jul 15, 2025

Hi @bavshin-f5

Rather than introducing a new macro, you could just reuse MAP_FAILED.

If you do want to introduce a new macro, I'd alias it to MAP_FAILED rather than NULL.

While unlikely, it is possible for mmap(2) to return address 0. E.g. if asked for with MAP_FIXED or if the mmap_min_addr sysctl is set to 0.

Same reason mmap(2) doesn't return NULL on failure...

POSIX says this about non-fixed allocations:

When the implementation selects a value for pa, it never places a mapping at address 0

Also, POSIX does not say anything about MAP_FIXED with 0 actually returning a mapping at zero address. Linux man mmap however promises that:

If the MAP_FIXED flag is specified, and addr is 0 (NULL), then the mapped address will be 0 (NULL).

Anyway, it looks like using NULL is completely safe. I cannot imagine a situation when nginx decides to forcefully map memory at address 0. I suggest avoiding adding a new macro and using NULL instead, just like it's already done in ngx_init_cycle().

@arut
Copy link
Contributor
arut commented Jul 15, 2025

Regarding the cleanup handlers which may access the freed memory and require an additional reset in ngx_shm_free(), @bavshin-f5 could you please elaborate on these handlers?

@bavshin-f5
Copy link
Member Author

Regarding the cleanup handlers which may access the freed memory and require a additional reset in ngx_shm_free(), @bavshin-f5 could you please elaborate on these handlers?

There are no such handlers in the nginx code at the moment. I attempted to add one as noted in the PR description and encountered the problem while removing the shared zone from the configuration.

Ideally, I'd also love to have a deinit for the shared memory, but this change was slightly more important.

Anyway, it looks like using NULL is completely safe. I cannot imagine a situation when nginx decides to forcefully map memory at address 0. I suggest avoiding adding a new macro and using NULL instead, just like it's already done in ngx_init_cycle().

So NULL instead of NGX_INVALID_SHM? Done in another fixup commit. If that looks good to you, I'll squash the commits.

@arut
Copy link
Contributor
arut commented Jul 15, 2025

Regarding the cleanup handlers which may access the freed memory and require a additional reset in ngx_shm_free(), @bavshin-f5 could you please elaborate on these handlers?

There are no such handlers in the nginx code at the moment. I attempted to add one as noted in the PR description and encountered the problem while removing the shared zone from the configuration.

Ideally, I'd also love to have a deinit for the shared memory, but this change was slightly more important.

Anyway, it looks like using NULL is completely safe. I cannot imagine a situation when nginx decides to forcefully map memory at address 0. I suggest avoiding adding a new macro and using NULL instead, just like it's already done in ngx_init_cycle().

So NULL instead of NGX_INVALID_SHM? Done in another fixup commit. If that looks good to you, I'll squash the commits.

Yes, NULL definitely.

Regarding the handler, the case looks unusual, hence the question. Typically a module allocates shared memory and then uses slab allocator to allocate data structures within the shared memory. The pointers are typically named sh. And they will keep referencing the memory after unmapping anyway. Accessing shared memory from a cycle cleanup handler is just not a good idea.

As we unmap the unused shared zones before destroying the cycle pool, it
was possible to access already unmapped address from a cleanup handler.
The change gives such handlers a way to determine that the memory is
unavailable and any processing should be skipped.
@ac000
Copy link
Member
ac000 commented Jul 17, 2025

Linux man mmap however promises that:

If the MAP_FIXED flag is specified, and addr is 0 (NULL), then the
mapped address will be 0 (NULL).

It also requires the mmap_min_addr sysctl to be set to 0. Otherwise
you get; EPERM (Operation not permitted)

Even with mmap_min_addr set to 0 and letting the kernel pick the
address, it seems to avoid using 0.

Anyway, it looks like using NULL is completely safe. I cannot imagine

More or less, as it stands from what I can tell we never call mmap(2)
with MAP_FIXED and it would require mmap_min_addr to be set to 0
which these days nothing should be requiring that.

Still, nginx does support quite a lot of legacy type stuff...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0