Skip to content

Never run plugins on exit()#1007

Open
funderscore1 wants to merge 2 commits into
OpenRC:masterfrom
funderscore1:musl-openrc-plugins
Open

Never run plugins on exit()#1007
funderscore1 wants to merge 2 commits into
OpenRC:masterfrom
funderscore1:musl-openrc-plugins

Conversation

@funderscore1
Copy link
Copy Markdown

The commits should be self-explanatory, but I'll recap anyway:

OpenRC has been running plugins in cleanup() on premature exit, to make sure that all plugin hooks are ran. cleanup() on openrc/openrc-run is an atexit function, as in, it will run on exit(). Running plugins means a fork(), then letting the plugins do their job, and finally exit(). At this point this is the second exit() that has been ran.

The problem is that running exit() more than once is undefined behavior. While glibc did not complain about it for more than 20 years (this bug was around since 2008 in rc: 4ae5aeb), musl gets angry at it. This will make the child process (the plugin) helpfully hang as soon as it's running exit(), to indicate that this should not happen. A side effect of this is OpenRC(-run) itself also hanging, as it is waiting for the plugin to exit.

However the hang only happens if a service fails. If a service succeeds, then no amount of running plugin hooks will be done in cleanup() (as this kind of thing is only done on failure).

This can make a system unbootable or not able to shutdown anymore if a service fails on boot/shutdown.

I've been told that this plugin system will be eventually replaced by a more robust messaging system, but for now, we can stop doing that, and instead just manually call OUT hooks before exit(). This is a slight change in behavior but it shouldn't really matter given the small number of OpenRC plugins.

This issue was found in Alpine edge, initially running plymouth-openrc-plugin, but any plugin will cause this bug to happen.

Ferass El Hafidi added 2 commits May 13, 2026 21:00
On exit, openrc's cleanup function, which is ran on exit(), checks if there
are remaining hooks to run (by checking hook_out) and runs plugins. Running
plugins means fork()ing and afterwards exit(). Calling exit() more than once
like OpenRC does is undefined behavior. glibc seems to not care about
this, but musl keeps track of if exit() is getting called >1 times
(exit() being called in forks do count), and if it is, it will helpfully
hang, as this should not happen.

In practise this means, if a plugin (such as plymouth-openrc-plugin) is installed
in a musl-based system, a failing `service <...> start` will cause a
hang of that command.

Remove all the handling of plugin hooks on exit, and instead explicitely
run the OUT hooks on most failures, but *before* exit().
Given this plugin system will be replaced in the future anyway, and there
aren't many users of it, this slight change of behavior should be fine.

Signed-off-by: Ferass El Hafidi <funderscore@postmarketos.org>
Just like openrc-run, the cleanup function in rc.c checks if there
are remaining hooks to run (by checking hook_out) and runs plugins.
Calling exit() more than once like OpenRC does is undefined behavior.
For more details, see the previous commit message.

In practise this means, if a plugin (such as plymouth-openrc-plugin) is installed
in a musl-based system, 2 scenarios may occur:

* A service fails on boot: OpenRC is sort of stuck. If a GUI is running
  already, it may not look like much has happened, but things like
  shutting down won't work (would need to use `reboot -f` without going
  through openrc)
* A service fails on shutdown: shutdown just hangs, have to forcibly
  poweroff.

Remove all the handling of plugin hooks on exit, and instead explicitely
run the OUT hooks on most failures, but *before* exit().

Signed-off-by: Ferass El Hafidi <funderscore@postmarketos.org>
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.

1 participant