Support multiple users interacting with shm with starterd

The permission restrictions of signals make it hard to have AOS
applications started as arbitrary users communicate.  Instead, make
starter run as the provided --user with the effective UID that it
started with.  This makes shmem end up with all the right permissions so
everything can communicate.

Change-Id: I3c7fbfe8a73e7341ca32c010da1c38b5ba787523
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/aos/starter/BUILD b/aos/starter/BUILD
index cc7ea04..187b0b0 100644
--- a/aos/starter/BUILD
+++ b/aos/starter/BUILD
@@ -4,8 +4,8 @@
 filegroup(
     name = "starter",
     srcs = [
+        "aos_starter",
         "starter.sh",
-        "starter_cmd",
         "starterd",
     ],
     visibility = ["//visibility:public"],
@@ -51,6 +51,7 @@
     name = "starterd",
     srcs = ["starterd.cc"],
     target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
     deps = [
         ":starterd_lib",
         "//aos:init",
@@ -72,9 +73,10 @@
 )
 
 cc_binary(
-    name = "starter_cmd",
+    name = "aos_starter",
     srcs = ["starter_cmd.cc"],
     target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
     deps = [
         ":starter_rpc_lib",
         "//aos/time",
@@ -107,10 +109,10 @@
         "\"$(rootpaths //aos/events:pingpong_config)\"",
         "$(rootpath //aos/events:ping)",
         "$(rootpath //aos/events:pong)",
-        "$(rootpath :starter_cmd)",
+        "$(rootpath :aos_starter)",
     ],
     data = [
-        ":starter_cmd",
+        ":aos_starter",
         ":starterd",
         "//aos/events:ping",
         "//aos/events:pingpong_config",
diff --git a/aos/starter/starter.fbs b/aos/starter/starter.fbs
index 8c7cdae..4b66833 100644
--- a/aos/starter/starter.fbs
+++ b/aos/starter/starter.fbs
@@ -42,7 +42,10 @@
   // Failed to execute application - likely due to a missing executable or
   // invalid permissions. This is not reported if an application dies for
   // another reason after it is already running.
-  EXECV_ERR
+  EXECV_ERR,
+
+  // Failed to change to the requested group
+  SET_GRP_ERR
 }
 
 table Status {
diff --git a/aos/starter/starterd.cc b/aos/starter/starterd.cc
index 66786a9..b40776d 100644
--- a/aos/starter/starterd.cc
+++ b/aos/starter/starterd.cc
@@ -1,11 +1,40 @@
+#include <pwd.h>
+#include <sys/types.h>
+
 #include "aos/init.h"
 #include "gflags/gflags.h"
 #include "starterd_lib.h"
 
 DEFINE_string(config, "./config.json", "File path of aos configuration");
+DEFINE_string(user, "",
+              "Starter runs as though this user ran a SUID binary if set.");
 
 int main(int argc, char **argv) {
   aos::InitGoogle(&argc, &argv);
+  if (!FLAGS_user.empty()) {
+    uid_t uid;
+    uid_t gid;
+    {
+      struct passwd *user_data = getpwnam(FLAGS_user.c_str());
+      if (user_data != nullptr) {
+        uid = user_data->pw_uid;
+        gid = user_data->pw_gid;
+      } else {
+        LOG(FATAL) << "Could not find user " << FLAGS_user;
+        return 1;
+      }
+    }
+    constexpr int kUnchanged = -1;
+    if (setresgid(/* ruid */ gid, /* euid */ gid,
+                  /* suid */ kUnchanged) != 0) {
+      PLOG(FATAL) << "Failed to change GID to " << FLAGS_user;
+    }
+
+    if (setresuid(/* ruid */ uid, /* euid */ uid,
+                  /* suid */ kUnchanged) != 0) {
+      PLOG(FATAL) << "Failed to change UID to " << FLAGS_user;
+    }
+  }
 
   aos::FlatbufferDetachedBuffer<aos::Configuration> config =
       aos::configuration::ReadConfig(FLAGS_config);
diff --git a/aos/starter/starterd_lib.cc b/aos/starter/starterd_lib.cc
index edf61b9..2c9d6aa 100644
--- a/aos/starter/starterd_lib.cc
+++ b/aos/starter/starterd_lib.cc
@@ -23,6 +23,9 @@
       args_(1),
       user_(application->has_user() ? FindUid(application->user()->c_str())
                                     : std::nullopt),
+      group_(application->has_user()
+                 ? FindPrimaryGidForUser(application->user()->c_str())
+                 : std::nullopt),
       autostart_(application->autostart()),
       event_loop_(event_loop),
       start_timer_(event_loop_->AddTimer([this] {
@@ -82,6 +85,14 @@
     PLOG(FATAL) << "Could not set PR_SET_PDEATHSIG to SIGKILL";
   }
 
+  if (group_) {
+    if (setgid(*group_) == -1) {
+      write_pipe_.Write(
+          static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
+      PLOG(FATAL) << "Could not set group for " << name_ << " to " << *group_;
+    }
+  }
+
   if (user_) {
     if (setuid(*user_) == -1) {
       write_pipe_.Write(
@@ -93,7 +104,7 @@
   // argv[0] should be the program name
   args_.insert(args_.begin(), path_.data());
 
-  execv(path_.c_str(), args_.data());
+  execvp(path_.c_str(), args_.data());
 
   // If we got here, something went wrong
   write_pipe_.Write(
@@ -172,6 +183,7 @@
 }
 
 std::optional<uid_t> Application::FindUid(const char *name) {
+  // TODO(austin): Use the reentrant version.  This should be safe.
   struct passwd *user_data = getpwnam(name);
   if (user_data != nullptr) {
     return user_data->pw_uid;
@@ -181,6 +193,17 @@
   }
 }
 
+std::optional<gid_t> Application::FindPrimaryGidForUser(const char *name) {
+  // TODO(austin): Use the reentrant version.  This should be safe.
+  struct passwd *user_data = getpwnam(name);
+  if (user_data != nullptr) {
+    return user_data->pw_gid;
+  } else {
+    LOG(FATAL) << "Could not find user " << name;
+    return std::nullopt;
+  }
+}
+
 flatbuffers::Offset<aos::starter::ApplicationStatus>
 Application::PopulateStatus(flatbuffers::FlatBufferBuilder *builder) {
   CHECK_NOTNULL(builder);
diff --git a/aos/starter/starterd_lib.h b/aos/starter/starterd_lib.h
index cd4b40a..2bbfa22 100644
--- a/aos/starter/starterd_lib.h
+++ b/aos/starter/starterd_lib.h
@@ -109,6 +109,7 @@
       const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> &v);
 
   static std::optional<uid_t> FindUid(const char *name);
+  static std::optional<gid_t> FindPrimaryGidForUser(const char *name);
 
   // Next unique id for all applications
   static inline uint64_t next_id_ = 0;
@@ -117,6 +118,7 @@
   std::string path_;
   std::vector<char *> args_;
   std::optional<uid_t> user_;
+  std::optional<gid_t> group_;
 
   pid_t pid_ = -1;
   ScopedPipe::ScopedReadPipe read_pipe_;