init: Fix a race condition in KillProcessGroup()
Multiple tests in CtsInitTestCases, e.g. RebootTest#StopServicesSIGKILL,
can trigger the following race condition:
* A service is started. This involves calling fork() and also to call
RunService() in the child process. RunService() calls setpgid().
* Service::Stop() is called and calls KillProcessGroup().
KillProcessGroup() calls kill(-pgid, SIGKILL) before the child process
has called setpgid(). pgid is the process ID of the child process. The
kill() call fails because setpgid() has not yet been called.
Fix this race condition by adding a setpgid() call in the parent process
and by waiting from the parent until the child has called setsid() if a
console is attached.
Bug: 213617178
Test: Cuttlefish + atest 'CtsInitTestCases'
Change-Id: I4c55790c2dcde8716b860aecd57708d51a081086
Signed-off-by: Bart Van Assche <bvanassche@google.com>
diff --git a/init/service.cpp b/init/service.cpp
index 331cd88..caa9095 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -507,7 +507,7 @@
// Enters namespaces, sets environment variables, writes PID files and runs the service executable.
void Service::RunService(const std::vector<Descriptor>& descriptors,
- InterprocessFifo cgroups_activated) {
+ InterprocessFifo cgroups_activated, InterprocessFifo setsid_finished) {
if (auto result = EnterNamespaces(namespaces_, name_, mount_namespace_); !result.ok()) {
LOG(FATAL) << "Service '" << name_ << "' failed to set up namespaces: " << result.error();
}
@@ -557,6 +557,12 @@
// priority. Aborts on failure.
SetProcessAttributesAndCaps();
+ // If SetProcessAttributes() called setsid(), report this to the parent.
+ if (!proc_attr_.console.empty()) {
+ setsid_finished.Write(2);
+ }
+ setsid_finished.Close();
+
if (!ExpandArgsAndExecv(args_, sigstop_)) {
PLOG(ERROR) << "cannot execv('" << args_[0]
<< "'). See the 'Debugging init' section of init's README.md for tips";
@@ -598,7 +604,7 @@
return {};
}
- InterprocessFifo cgroups_activated;
+ InterprocessFifo cgroups_activated, setsid_finished;
if (Result<void> result = cgroups_activated.Initialize(); !result.ok()) {
return result;
@@ -608,6 +614,13 @@
return result;
}
+ // Only check proc_attr_.console after the CheckConsole() call.
+ if (!proc_attr_.console.empty()) {
+ if (Result<void> result = setsid_finished.Initialize(); !result.ok()) {
+ return result;
+ }
+ }
+
struct stat sb;
if (stat(args_[0].c_str(), &sb) == -1) {
flags_ |= SVC_DISABLED;
@@ -661,10 +674,12 @@
if (pid == 0) {
umask(077);
cgroups_activated.CloseWriteFd();
- RunService(descriptors, std::move(cgroups_activated));
+ setsid_finished.CloseReadFd();
+ RunService(descriptors, std::move(cgroups_activated), std::move(setsid_finished));
_exit(127);
} else {
cgroups_activated.CloseReadFd();
+ setsid_finished.CloseWriteFd();
}
if (pid < 0) {
@@ -721,6 +736,23 @@
return Error() << "Sending cgroups activated notification failed: " << result.error();
}
+ // Call setpgid() from the parent process to make sure that this call has
+ // finished before the parent process calls kill(-pgid, ...).
+ if (proc_attr_.console.empty()) {
+ if (setpgid(pid, pid) == -1) {
+ return ErrnoError() << "setpgid failed";
+ }
+ } else {
+ // The Read() call below will return an error if the child is killed.
+ if (Result<uint8_t> result = setsid_finished.Read(); !result.ok() || *result != 2) {
+ if (!result.ok()) {
+ return Error() << "Waiting for setsid() failed: " << result.error();
+ } else {
+ return Error() << "Waiting for setsid() failed: " << *result << " <> 2";
+ }
+ }
+ }
+
NotifyStateChange("running");
reboot_on_failure.Disable();
return {};