Merge "Replace steam.toList() with Collectors.toList()" into udc-dev
diff --git a/tests/automotive/health/multiuser/tests/Android.bp b/tests/automotive/health/multiuser/tests/Android.bp
index 8409848..3be54b4 100644
--- a/tests/automotive/health/multiuser/tests/Android.bp
+++ b/tests/automotive/health/multiuser/tests/Android.bp
@@ -41,6 +41,6 @@
     ],
     srcs: ["src/**/*.java"],
     certificate: "platform",
-    test_suites: ["catbox"],
+    test_suites: ["catbox", "ats"],
     privileged: true,
 }
diff --git a/utils/shell-as/Android.bp b/utils/shell-as/Android.bp
new file mode 100644
index 0000000..96dc1c9
--- /dev/null
+++ b/utils/shell-as/Android.bp
@@ -0,0 +1,107 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary {
+    name: "shell-as",
+    cflags: [
+      "-Wall",
+      "-Werror",
+      "-Wextra",
+    ],
+    srcs: [
+      "*.cpp",
+      ":shell-as-test-app-apk-cpp",
+    ],
+    header_libs: ["libcutils_headers"],
+    static_executable: true,
+    static_libs: [
+      "libbase",
+      "libcap",
+      "liblog",
+      "libseccomp_policy",
+      "libselinux",
+    ],
+    arch: {
+        arm: {
+            srcs: ["shell-code/*-arm.S"]
+        },
+        arm64: {
+            srcs: ["shell-code/*-arm64.S"]
+        },
+        x86: {
+            srcs: ["shell-code/*-x86.S"]
+        },
+        x86_64: {
+            srcs: ["shell-code/*-x86_64.S"]
+        }
+    }
+}
+
+// A simple app that requests all non-system permissions and contains no other
+// functionality. This can be used as a target for shell-as to emulate the
+// security context of the most privileged possible non-system app.
+android_app {
+  name: "shell-as-test-app",
+  manifest: ":shell-as-test-app-manifest",
+  srcs: ["app/**/*.java"],
+  sdk_version: "9",
+  certificate: ":shell-as-test-app-cert",
+}
+
+// https://source.android.com/docs/core/ota/sign_builds#release-keys
+// Generated by running:
+// $ANDROID_BUILD_TOP/development/tools/make_key \
+//     shell-as-test-app-key \
+//     '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com
+android_app_certificate {
+    name: "shell-as-test-app-cert",
+    certificate: "shell-as-test-app-key",
+}
+
+genrule {
+  name: "shell-as-test-app-manifest",
+  srcs: [
+    ":permission-list-normal",
+    "AndroidManifest.xml.template"
+  ],
+  cmd: "$(location gen-manifest.sh) " +
+       "$(location AndroidManifest.xml.template) " +
+       "$(location :permission-list-normal) " +
+       "$(out)",
+  out: ["AndroidManifest.xml"],
+  tool_files: ["gen-manifest.sh"],
+}
+
+// A source file that contains the contents of the above shell-as-test-app APK
+// embedded as an array.
+cc_genrule {
+  name: "shell-as-test-app-apk-cpp",
+  srcs: [":shell-as-test-app"],
+  cmd: "(" +
+       "  echo '#include <stddef.h>';" +
+       "  echo '#include <stdint.h>';" +
+       "  echo '';" +
+       "  echo 'namespace shell_as {';" +
+       "  echo 'const uint8_t kTestAppApk[] = {';" +
+       "  $(location toybox) xxd -i < $(in);" +
+       "  echo '};';" +
+       "  echo 'void GetTestApk(uint8_t **apk, size_t *length) {';" +
+       "  echo '  *apk = (uint8_t*) kTestAppApk;';" +
+       "  echo '  *length = sizeof(kTestAppApk);';" +
+       "  echo '}';" +
+       "  echo '}  // namespace shell_as';" +
+       ") > $(out)",
+  out: ["test-app-apk.cpp"],
+  tools: ["toybox"]
+}
diff --git a/utils/shell-as/AndroidManifest.xml.template b/utils/shell-as/AndroidManifest.xml.template
new file mode 100644
index 0000000..07e89b1
--- /dev/null
+++ b/utils/shell-as/AndroidManifest.xml.template
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.google.tools.security.shell_as">
+
+  PERMISSIONS
+
+  <application
+      android:allowBackup="true"
+      android:label="Shell-As Test App">
+    <activity android:name=".MainActivity">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+  </application>
+</manifest>
diff --git a/utils/shell-as/OWNERS b/utils/shell-as/OWNERS
new file mode 100644
index 0000000..431db99
--- /dev/null
+++ b/utils/shell-as/OWNERS
@@ -0,0 +1,4 @@
+# Code owners for shell-as
+
+willcoster@google.com
+cdombroski@google.com
diff --git a/utils/shell-as/README.md b/utils/shell-as/README.md
new file mode 100644
index 0000000..e0f6f93
--- /dev/null
+++ b/utils/shell-as/README.md
@@ -0,0 +1,33 @@
+# shell-as
+
+shell-as is a utility that can be used to execute a binary in a less privileged
+security context. This can be useful for verifying the capabilities of a process
+on a running device or testing PoCs with different privilege levels.
+
+## Usage
+
+The security context can either be supplied explicitly, inferred from a process
+running on the device, or set to a predefined profile.
+
+For example, the following are equivalent and execute `/system/bin/id` in the
+context of the init process.
+
+```shell
+shell-as \
+    --uid 0 \
+    --gid 0 \
+    --selinux u:r:init:s0 \
+    --seccomp system \
+    /system/bin/id
+```
+
+```shell
+shell-as --pid 1 /system/bin/id
+```
+
+The "untrusted-app" profile can be used to execute a binary with all the
+possible privileges attainable by an untrusted app:
+
+```shell
+shell-as --profile untrusted-app /system/bin/id
+```
diff --git a/utils/shell-as/app/com/android/google/tools/security/shell_as/MainActivity.java b/utils/shell-as/app/com/android/google/tools/security/shell_as/MainActivity.java
new file mode 100644
index 0000000..d5d178c
--- /dev/null
+++ b/utils/shell-as/app/com/android/google/tools/security/shell_as/MainActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.google.tools.security.shell_as;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/** An empty activity for the shell-as test app. */
+public class MainActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+}
diff --git a/utils/shell-as/command-line.cpp b/utils/shell-as/command-line.cpp
new file mode 100644
index 0000000..9a893c3
--- /dev/null
+++ b/utils/shell-as/command-line.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./command-line.h"
+
+#include <getopt.h>
+
+#include <iostream>
+#include <string>
+
+#include "./context.h"
+#include "./string-utils.h"
+
+namespace shell_as {
+
+namespace {
+const std::string kUsage =
+    R"(Usage: shell-as [options] [<program> <arguments>...]
+
+shell-as executes a program in a specified Android security context. The default
+program that is executed if none is specified is `/bin/system/sh`.
+
+The following options can be used to define the target security context.
+
+--verbose, -v                      Enables verbose logging.
+--uid <uid>, -u <uid>              The target real and effective user ID.
+--gid <gid>, -g <gid>              The target real and effective group ID.
+--groups <gid1,2,..>, -G <1,2,..>  A comma separated list of supplementary group
+                                   IDs.
+--nogroups                         Specifies that all supplementary groups should
+                                   be cleared.
+--selinux <context>, -s <context>  The target SELinux context.
+--seccomp <filter>, -f <filter>    The target seccomp filter. Valid values of
+                                   filter are 'none', 'uid-inferred', 'app',
+                                   'app-zygote', and 'system'.
+--caps <capabilities>              A libcap textual expression that describes
+                                   the desired capability sets. The only
+                                   capability set that matters is the permitted
+                                   set, the other sets are ignored.
+
+                                   Examples:
+
+                                     "="                  - Clear all capabilities
+                                     "=p"                 - Raise all capabilities
+                                     "23,CAP_SYS_ADMIN+p" - Raise CAP_SYS_ADMIN
+                                                            and capability 23.
+
+                                   For a full description of the possible values
+                                   see `man 3 cap_from_text` (the libcap-dev
+                                   package provides this man page).
+--pid <pid>, -p <pid>              Infer the target security context from a
+                                   running process with the given process ID.
+                                   This option implies --seccomp uid_inferred.
+                                   This option infers the capability from the
+                                   target process's permitted capability set.
+--profile <profile>, -P <profile>  Infer the target security context from a
+                                   predefined security profile. Using this
+                                   option will install and execute a test app on
+                                   the device. Currently, the only valid profile
+                                   is 'untrusted-app' which corresponds to an
+                                   untrusted app which has been granted every
+                                   non-system permission.
+
+Options are evaluated in the order that they are given. For example, the
+following will set the target context to that of process 1234 but override the
+user ID to 0:
+
+    shell-as --pid 1234 --uid 0
+)";
+
+const char* kShellExecvArgs[] = {"/system/bin/sh", nullptr};
+
+bool ParseGroups(char* line, std::vector<gid_t>* ids) {
+  // Allow a null line as a valid input since this method is used to handle both
+  // --groups and --nogroups.
+  if (line == nullptr) {
+    return true;
+  }
+  return SplitIdsAndSkip(line, ",", /*num_to_skip=*/0, ids);
+}
+}  // namespace
+
+bool ParseOptions(const int argc, char* const argv[], bool* verbose,
+                  SecurityContext* context, char* const* execv_args[]) {
+  char short_options[] = "+s:hp:u:g:G:f:c:vP:";
+  struct option long_options[] = {
+      {"selinux", true, nullptr, 's'}, {"help", false, nullptr, 'h'},
+      {"uid", true, nullptr, 'u'},     {"gid", true, nullptr, 'g'},
+      {"pid", true, nullptr, 'p'},     {"verbose", false, nullptr, 'v'},
+      {"groups", true, nullptr, 'G'},  {"nogroups", false, nullptr, 'G'},
+      {"seccomp", true, nullptr, 'f'}, {"caps", true, nullptr, 'c'},
+      {"profile", true, nullptr, 'P'},
+  };
+  int option;
+  bool infer_seccomp_filter = false;
+  SecurityContext working_context;
+  std::vector<gid_t> supplementary_group_ids;
+  uint32_t working_id = 0;
+  while ((option = getopt_long(argc, argv, short_options, long_options,
+                               nullptr)) != -1) {
+    switch (option) {
+      case 'v':
+        *verbose = true;
+        break;
+      case 'h':
+        std::cerr << kUsage;
+        return false;
+      case 'u':
+        if (!StringToUInt32(optarg, &working_id)) {
+          return false;
+        }
+        working_context.user_id = working_id;
+        break;
+      case 'g':
+        if (!StringToUInt32(optarg, &working_id)) {
+          return false;
+        }
+        working_context.group_id = working_id;
+        break;
+      case 'c':
+        working_context.capabilities = cap_from_text(optarg);
+        if (working_context.capabilities.value() == nullptr) {
+          std::cerr << "Unable to parse capabilities" << std::endl;
+          return false;
+        }
+        break;
+      case 'G':
+        supplementary_group_ids.clear();
+        if (!ParseGroups(optarg, &supplementary_group_ids)) {
+          std::cerr << "Unable to parse supplementary groups" << std::endl;
+          return false;
+        }
+        working_context.supplementary_group_ids = supplementary_group_ids;
+        break;
+      case 's':
+        working_context.selinux_context = optarg;
+        break;
+      case 'f':
+        infer_seccomp_filter = false;
+        if (strcmp(optarg, "uid-inferred") == 0) {
+          infer_seccomp_filter = true;
+        } else if (strcmp(optarg, "app") == 0) {
+          working_context.seccomp_filter = kAppFilter;
+        } else if (strcmp(optarg, "app-zygote") == 0) {
+          working_context.seccomp_filter = kAppZygoteFilter;
+        } else if (strcmp(optarg, "system") == 0) {
+          working_context.seccomp_filter = kSystemFilter;
+        } else if (strcmp(optarg, "none") == 0) {
+          working_context.seccomp_filter.reset();
+        } else {
+          std::cerr << "Invalid value for --seccomp: " << optarg << std::endl;
+          return false;
+        }
+        break;
+      case 'p':
+        if (!SecurityContextFromProcess(atoi(optarg), &working_context)) {
+          return false;
+        }
+        infer_seccomp_filter = true;
+        break;
+      case 'P':
+        if (strcmp(optarg, "untrusted-app") == 0) {
+          if (!SecurityContextFromTestApp(&working_context)) {
+            return false;
+          }
+        } else {
+          std::cerr << "Invalid value for --profile: " << optarg << std::endl;
+          return false;
+        }
+        infer_seccomp_filter = true;
+        break;
+      default:
+        std::cerr << "Unknown option '" << (char)optopt << "'" << std::endl;
+        return false;
+    }
+  }
+
+  if (infer_seccomp_filter) {
+    if (!working_context.user_id.has_value()) {
+      std::cerr << "No user ID; unable to infer appropriate seccomp filter."
+                << std::endl;
+      return false;
+    }
+    working_context.seccomp_filter =
+        SeccompFilterFromUserId(working_context.user_id.value());
+  }
+
+  *context = working_context;
+  if (optind < argc) {
+    *execv_args = argv + optind;
+  } else {
+    *execv_args = (char**)kShellExecvArgs;
+  }
+  return true;
+}
+
+}  // namespace shell_as
diff --git a/utils/shell-as/command-line.h b/utils/shell-as/command-line.h
new file mode 100644
index 0000000..4bf495f
--- /dev/null
+++ b/utils/shell-as/command-line.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHELL_AS_COMMAND_LINE_H_
+#define SHELL_AS_COMMAND_LINE_H_
+
+#include "./context.h"
+
+namespace shell_as {
+
+// Parse command line options into a target security context and arguments that
+// can be passed to ExecuteInContext.
+//
+// The value of execv_args will either point to a sub-array of argv or to a
+// statically allocated default value. In both cases the caller should /not/
+// free the memory.
+//
+// Returns true on success and false if there is a problem parsing options.
+bool ParseOptions(const int argc, char* const argv[], bool* verbose,
+                  SecurityContext* context, char* const* execv_args[]);
+}  // namespace shell_as
+
+#endif  // SHELL_AS_COMMAND_LINE_H_
diff --git a/utils/shell-as/context.cpp b/utils/shell-as/context.cpp
new file mode 100644
index 0000000..ea7979b
--- /dev/null
+++ b/utils/shell-as/context.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./context.h"
+
+#include <private/android_filesystem_config.h>  // For AID_APP_START.
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <iostream>
+#include <string>
+
+#include "./string-utils.h"
+#include "./test-app.h"
+
+namespace shell_as {
+
+namespace {
+
+bool ParseIdFromProcStatusLine(char* line, uid_t* id) {
+  // The user and group ID lines of the status file look like:
+  //
+  // Uid: <real> <effective> <saved> <filesystem>
+  // Gid: <real> <effective> <saved> <filesystem>
+  std::vector<uid_t> ids;
+  if (!SplitIdsAndSkip(line, "\t\n ", /*num_to_skip=*/1, &ids) ||
+      ids.size() < 1) {
+    return false;
+  }
+  *id = ids[0];
+  return true;
+}
+
+bool ParseGroupsFromProcStatusLine(char* line, std::vector<gid_t>* ids) {
+  // The supplementary groups line of the status file looks like:
+  //
+  // Groups: <group1> <group2> <group3> ...
+  return SplitIdsAndSkip(line, "\t\n ", /*num_to_skip=*/1, ids);
+}
+
+bool ParseProcStatusFile(const pid_t process_id, uid_t* real_user_id,
+                         gid_t* real_group_id,
+                         std::vector<gid_t>* supplementary_group_ids) {
+  std::string proc_status_path =
+      std::string("/proc/") + std::to_string(process_id) + "/status";
+  FILE* status_file = fopen(proc_status_path.c_str(), "r");
+  if (status_file == nullptr) {
+    std::cerr << "Unable to open '" << proc_status_path << "'" << std::endl;
+  }
+  bool parsed_user = false;
+  bool parsed_group = false;
+  bool parsed_supplementary_groups = false;
+  while (true) {
+    size_t line_length = 0;
+    char* line = nullptr;
+    if (getline(&line, &line_length, status_file) < 0) {
+      free(line);
+      break;
+    }
+    if (strncmp("Uid:", line, 4) == 0) {
+      parsed_user = ParseIdFromProcStatusLine(line, real_user_id);
+    } else if (strncmp("Gid:", line, 4) == 0) {
+      parsed_group = ParseIdFromProcStatusLine(line, real_group_id);
+    } else if (strncmp("Groups:", line, 7) == 0) {
+      parsed_supplementary_groups =
+          ParseGroupsFromProcStatusLine(line, supplementary_group_ids);
+    }
+    free(line);
+  }
+  fclose(status_file);
+  return parsed_user && parsed_group && parsed_supplementary_groups;
+}
+
+}  // namespace
+
+bool SecurityContextFromProcess(const pid_t process_id,
+                                SecurityContext* context) {
+  char* selinux_context;
+  if (getpidcon(process_id, &selinux_context) != 0) {
+    std::cerr << "Unable to obtain SELinux context from process " << process_id
+              << std::endl;
+    return false;
+  }
+
+  cap_t capabilities = cap_get_pid(process_id);
+  if (capabilities == nullptr) {
+    std::cerr << "Unable to obtain capability set from process " << process_id
+              << std::endl;
+    return false;
+  }
+
+  uid_t user_id = 0;
+  gid_t group_id = 0;
+  std::vector<gid_t> supplementary_group_ids;
+  if (!ParseProcStatusFile(process_id, &user_id, &group_id,
+                           &supplementary_group_ids)) {
+    std::cerr << "Unable to obtain user and group IDs from process "
+              << process_id << std::endl;
+    return false;
+  }
+
+  context->selinux_context = selinux_context;
+  context->user_id = user_id;
+  context->group_id = group_id;
+  context->supplementary_group_ids = supplementary_group_ids;
+  context->capabilities = capabilities;
+  return true;
+}
+
+bool SecurityContextFromTestApp(SecurityContext* context) {
+  pid_t test_app_pid = 0;
+  if (!SetupAndStartTestApp(&test_app_pid)) {
+    std::cerr << "Unable to install test app." << std::endl;
+    return false;
+  }
+  return SecurityContextFromProcess(test_app_pid, context);
+}
+
+SeccompFilter SeccompFilterFromUserId(uid_t user_id) {
+  // Copied from:
+  // frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
+  return user_id >= AID_APP_START ? kAppFilter : kSystemFilter;
+}
+
+}  // namespace shell_as
diff --git a/utils/shell-as/context.h b/utils/shell-as/context.h
new file mode 100644
index 0000000..17a8cca
--- /dev/null
+++ b/utils/shell-as/context.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHELL_AS_CONTEXT_H_
+#define SHELL_AS_CONTEXT_H_
+
+#include <selinux/selinux.h>
+#include <sys/capability.h>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+namespace shell_as {
+
+// Enumeration of the possible seccomp filters that Android may apply to a
+// process.
+//
+// This should be kept in sync with the policies defined in:
+// bionic/libc/seccomp/include/seccomp_policy.h
+enum SeccompFilter {
+  kAppFilter = 0,
+  kAppZygoteFilter = 1,
+  kSystemFilter = 2,
+};
+
+typedef struct SecurityContext {
+  std::optional<uid_t> user_id;
+  std::optional<gid_t> group_id;
+  std::optional<std::vector<gid_t>> supplementary_group_ids;
+  std::optional<char *> selinux_context;
+  std::optional<SeccompFilter> seccomp_filter;
+  std::optional<cap_t> capabilities;
+} SecurityContext;
+
+// Infers the appropriate seccomp filter from a user ID.
+//
+// This mimics the behavior of the zygote process and provides a sane default
+// method of picking a filter. However, it is not 100% accurate since it does
+// not assign the app zygote filter and would not return an appropriate value
+// for processes not started by the zygote.
+SeccompFilter SeccompFilterFromUserId(uid_t user_id);
+
+// Derives a complete security context from a given process.
+//
+// If unable to determine any field of the context this method will return false
+// and not modify the given context.
+bool SecurityContextFromProcess(pid_t process_id, SecurityContext* context);
+
+// Derives a complete security context from the bundled test app.
+//
+// If unable to determine any field of the context this method will return false
+// and not modify the given context.
+bool SecurityContextFromTestApp(SecurityContext* context);
+
+}  // namespace shell_as
+
+#endif  // SHELL_AS_CONTEXT_H_
diff --git a/utils/shell-as/elf-utils.cpp b/utils/shell-as/elf-utils.cpp
new file mode 100644
index 0000000..8a82555
--- /dev/null
+++ b/utils/shell-as/elf-utils.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <elf.h>
+#include <stdio.h>
+
+#include <iostream>
+#include <string>
+
+#include "./elf.h"
+
+namespace shell_as {
+
+namespace {
+// The base address of a PIE binary when loaded with ASLR disabled.
+#if defined(__arm__) || defined(__aarch64__)
+constexpr uint64_t k32BitImageBase = 0xAAAAA000;
+constexpr uint64_t k64BitImageBase = 0x5555555000;
+#else
+constexpr uint64_t k32BitImageBase = 0x56555000;
+constexpr uint64_t k64BitImageBase = 0x555555554000;
+#endif
+}  // namespace
+
+bool GetElfEntryPoint(const pid_t process_id, uint64_t* entry_address,
+                      bool* is_arm_mode) {
+  uint8_t elf_header_buffer[sizeof(Elf64_Ehdr)];
+  std::string exe_path = "/proc/" + std::to_string(process_id) + "/exe";
+  FILE* exe_file = fopen(exe_path.c_str(), "rb");
+  if (exe_file == nullptr) {
+    std::cerr << "Unable to open executable of process " << process_id
+              << std::endl;
+    return false;
+  }
+
+  int read_size =
+      fread(elf_header_buffer, sizeof(elf_header_buffer), 1, exe_file);
+  fclose(exe_file);
+  if (read_size <= 0) {
+    std::cerr << "Unable to read executable of process " << process_id
+              << std::endl;
+    return false;
+  }
+
+  const Elf32_Ehdr* file_header_32 = (Elf32_Ehdr*)elf_header_buffer;
+  const Elf64_Ehdr* file_header_64 = (Elf64_Ehdr*)elf_header_buffer;
+  // The first handful of bytes of a header do not depend on whether the file is
+  // 32bit vs 64bit.
+  const bool is_pie_binary = file_header_32->e_type == ET_DYN;
+
+  if (file_header_32->e_ident[EI_CLASS] == ELFCLASS32) {
+    *entry_address =
+        file_header_32->e_entry + (is_pie_binary ? k32BitImageBase : 0);
+  } else if (file_header_32->e_ident[EI_CLASS] == ELFCLASS64) {
+    *entry_address =
+        file_header_64->e_entry + (is_pie_binary ? k64BitImageBase : 0);
+  } else {
+    return false;
+  }
+
+  *is_arm_mode = false;
+#if defined(__arm__)
+  if ((*entry_address & 1) == 0) {
+    *is_arm_mode = true;
+  }
+  // The entry address for ARM Elf binaries is branched to using a BX
+  // instruction. The low bit of these instructions indicates the instruction
+  // set of the code that is being jumped to. A low bit of 1 indicates thumb
+  // mode while a low bit of 0 indicates ARM mode.
+  *entry_address &= ~1;
+#endif
+
+  return true;
+}
+
+}  // namespace shell_as
diff --git a/utils/shell-as/elf-utils.h b/utils/shell-as/elf-utils.h
new file mode 100644
index 0000000..eba40f3
--- /dev/null
+++ b/utils/shell-as/elf-utils.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHELL_AS_ELF_H_
+#define SHELL_AS_ELF_H_
+
+#include <sys/types.h>
+
+namespace shell_as {
+
+// Sets entry_address to the process's entry point.
+//
+// This method assumes that PIE binaries are executing with ADDR_NO_RANDOMIZE.
+//
+// The is_arm_mode flag is set to true IFF the architecture is 32bit ARM and the
+// expected instruction set for code located at the entry address is not-thumb.
+// It is false for all other cases.
+bool GetElfEntryPoint(const pid_t process_id, uint64_t* entry_address,
+                      bool* is_arm_mode);
+}  // namespace shell_as
+
+#endif  // SHELL_AS_ELF_H_
diff --git a/utils/shell-as/execute.cpp b/utils/shell-as/execute.cpp
new file mode 100644
index 0000000..3ef5292
--- /dev/null
+++ b/utils/shell-as/execute.cpp
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./execute.h"
+
+#include <linux/securebits.h>
+#include <linux/uio.h>
+#include <seccomp_policy.h>
+#include <sys/capability.h>
+#include <sys/personality.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <memory>
+
+#include "./elf-utils.h"
+#include "./registers.h"
+#include "./shell-code.h"
+
+namespace shell_as {
+
+namespace {
+
+// Capabilities are implemented as a 64-bit bit-vector. Therefore the maximum
+// number of capabilities supported by a kernel is 64.
+constexpr cap_value_t kMaxCapabilities = 64;
+
+bool DropPreExecPrivileges(const shell_as::SecurityContext* context) {
+  // The ordering here is important:
+  //   (1) The platform's seccomp filters disallow setresgiud, so it must come
+  //       before the seccomp drop.
+  //   (2) Adding seccomp filters must happen before setresuid because setresuid
+  //       drops some capabilities which are required for seccomp.
+  if (context->group_id.has_value() &&
+      setresgid(context->group_id.value(), context->group_id.value(),
+                context->group_id.value()) != 0) {
+    std::cerr << "Unable to set group id: " << context->group_id.value()
+              << std::endl;
+    return false;
+  }
+  if (context->supplementary_group_ids.has_value() &&
+      setgroups(context->supplementary_group_ids.value().size(),
+                context->supplementary_group_ids.value().data()) != 0) {
+    std::cerr << "Unable to set supplementary groups." << std::endl;
+    return false;
+  }
+
+  if (context->seccomp_filter.has_value()) {
+    switch (context->seccomp_filter.value()) {
+      case shell_as::kAppFilter:
+        set_app_seccomp_filter();
+        break;
+      case shell_as::kAppZygoteFilter:
+        set_app_zygote_seccomp_filter();
+        break;
+      case shell_as::kSystemFilter:
+        set_system_seccomp_filter();
+        break;
+    }
+  }
+
+  // This must be set prior to setresuid, otherwise that call will drop the
+  // permitted set of capabilities.
+  if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) != 0) {
+    std::cerr << "Unable to set keep capabilities." << std::endl;
+    return false;
+  }
+
+  if (context->user_id.has_value() &&
+      setresuid(context->user_id.value(), context->user_id.value(),
+                context->user_id.value()) != 0) {
+    std::cerr << "Unable to set user id: " << context->user_id.value()
+              << std::endl;
+    return false;
+  }
+
+  // Capabilities must be reacquired after setresuid since it still modifies
+  // capabilities, but it leaves the permitted set intact.
+  if (context->capabilities.has_value()) {
+    // The first step is to raise all the capabilities possible in all sets
+    // including the inheritable set. This defines the superset of possible
+    // capabilities that can be passed on after calling execve.
+    //
+    // The reason that all capabilities are raised in the inheritable set is due
+    // to a limitation of libcap. libcap may not contain a capability definition
+    // for all capabilities supported by the kernel. If this occurs, it will
+    // silently ignore requests to raise unknown capabilities via cap_set_flag.
+    //
+    // However, when parsing a cap_t from a text value, libcap will treat "all"
+    // as all possible 64 capability bits as set.
+    cap_t all_capabilities = cap_from_text("all+pie");
+    if (cap_set_proc(all_capabilities) != 0) {
+      std::cerr << "Unable to raise inheritable capability set." << std::endl;
+      cap_free(all_capabilities);
+      return false;
+    }
+    cap_free(all_capabilities);
+
+    // The second step is to raise the /desired/ capability subset in the
+    // ambient capability set. These are the capabilities that will actually be
+    // passed to the process after execve.
+    if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) != 0) {
+      std::cerr << "Unable to clear ambient capabilities." << std::endl;
+      return false;
+    }
+    cap_t desired_capabilities = context->capabilities.value();
+    for (cap_value_t cap = 0; cap < kMaxCapabilities; cap++) {
+      // Skip capability values not supported by the kernel.
+      if (!CAP_IS_SUPPORTED(cap)) {
+        continue;
+      }
+      cap_flag_value_t value = CAP_CLEAR;
+      if (cap_get_flag(desired_capabilities, cap, CAP_PERMITTED, &value) == 0 &&
+          value == CAP_SET) {
+        if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) != 0) {
+          std::cerr << "Unable to raise capability " << cap
+                    << " in the ambient set." << std::endl;
+          return false;
+        }
+      }
+    }
+
+    // The final step is to raise the SECBIT_NOROOT flag. The kernel has special
+    // case logic that treats root calling execve differently than other users.
+    //
+    // By default all bits in the permitted set prior to calling execve will be
+    // raised after calling execve. This would ignore the work above and result
+    // in the process to have all capabilities.
+    //
+    // Setting the SECBIT_NOROOT disables this special casing for root and
+    // causes the kernel to treat it as any other UID.
+    int64_t secure_bits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0);
+    if (secure_bits < 0 ||
+        prctl(PR_SET_SECUREBITS, secure_bits | SECBIT_NOROOT, 0, 0, 0) != 0) {
+      std::cerr << "Unable to raise SECBIT_NOROOT." << std::endl;
+      return false;
+    }
+  }
+  return true;
+}
+
+uint8_t ReadChildByte(const pid_t process, const uintptr_t address) {
+  uintptr_t data = ptrace(PTRACE_PEEKDATA, process, address, nullptr);
+  return ((uint8_t*)&data)[0];
+}
+
+void WriteChildByte(const pid_t process, const uintptr_t address,
+                    const uint8_t value) {
+  // This is not the most efficient way to write data to a process. However, it
+  // reduces code complexity of handling different word sizes and reading and
+  // writing memory that is not a multiple of the native word size.
+  uintptr_t data = ptrace(PTRACE_PEEKDATA, process, address, nullptr);
+  ((uint8_t*)&data)[0] = value;
+  ptrace(PTRACE_POKEDATA, process, address, data);
+}
+
+void ReadChildMemory(const pid_t process, uintptr_t process_address,
+                     uint8_t* bytes, size_t byte_count) {
+  for (; byte_count != 0; byte_count--, bytes++, process_address++) {
+    *bytes = ReadChildByte(process, process_address);
+  }
+}
+
+void WriteChildMemory(const pid_t process, uintptr_t process_address,
+                      uint8_t const* bytes, size_t byte_count) {
+  for (; byte_count != 0; byte_count--, bytes++, process_address++) {
+    WriteChildByte(process, process_address, *bytes);
+  }
+}
+
+// Executes shell code in a target process.
+//
+// The following assumptions are made:
+//  * The process is currently being ptraced and that the process has already
+//    stopped.
+//  * The shell code will raise SIGSTOP when it has finished as signal that
+//    control flow should be handed back to the original code.
+//  * The shell code only alters registers and pushes values onto the stack.
+//
+// Execution is performed by overwriting the memory under the current
+// instruction pointer with the shell code. After the shell code signals
+// completion the original register state and memory are restored.
+//
+// If the above assumptions are met, then this function will leave the process
+// in a stopped state that is equivalent to the original state.
+bool ExecuteShellCode(const pid_t process, const uint8_t* shell_code,
+                      const size_t shell_code_size) {
+  REGISTER_STRUCT registers;
+  struct iovec registers_iovec;
+  registers_iovec.iov_base = &registers;
+  registers_iovec.iov_len = sizeof(REGISTER_STRUCT);
+  ptrace(PTRACE_GETREGSET, process, 1, &registers_iovec);
+
+  std::unique_ptr<uint8_t[]> memory_backup(new uint8_t[shell_code_size]);
+  ReadChildMemory(process, PROGRAM_COUNTER(registers), memory_backup.get(),
+                  shell_code_size);
+  WriteChildMemory(process, PROGRAM_COUNTER(registers), shell_code,
+                   shell_code_size);
+
+  // Execute the shell code and wait for the signal that it has finished.
+  ptrace(PTRACE_CONT, process, NULL, NULL);
+  int status;
+  waitpid(process, &status, 0);
+  if (status >> 8 != SIGSTOP) {
+    std::cerr << "Failed to execute SELinux shellcode." << std::endl;
+    return false;
+  }
+
+  ptrace(PTRACE_SETREGSET, process, 1, &registers_iovec);
+  WriteChildMemory(process, PROGRAM_COUNTER(registers), memory_backup.get(),
+                   shell_code_size);
+  return true;
+}
+
+bool SetProgramCounter(const pid_t process_id, uint64_t program_counter) {
+  REGISTER_STRUCT registers;
+  struct iovec registers_iovec;
+  registers_iovec.iov_base = &registers;
+  registers_iovec.iov_len = sizeof(REGISTER_STRUCT);
+  if (ptrace(PTRACE_GETREGSET, process_id, 1, &registers_iovec) != 0) {
+    return false;
+  }
+  PROGRAM_COUNTER(registers) = program_counter;
+  if ((ptrace(PTRACE_SETREGSET, process_id, 1, &registers_iovec)) != 0) {
+    return false;
+  }
+  return true;
+}
+
+bool StepToEntryPoint(const pid_t process_id) {
+  bool is_arm_mode;
+  uint64_t entry_address;
+  if (!GetElfEntryPoint(process_id, &entry_address, &is_arm_mode)) {
+    std::cerr << "Not able to determine Elf entry point." << std::endl;
+    return false;
+  }
+  if (is_arm_mode) {
+    // TODO(willcoster): If there is a need to handle ARM mode instructions in
+    // addition to thumb instructions update this with ARM mode shell code.
+    std::cerr << "Attempting to run an ARM-mode binary. "
+              << "shell-as currently only supports thumb-mode. "
+              << "Bug willcoster@ if you run into this error." << std::endl;
+    return false;
+  }
+
+  int expected_signal = 0;
+  size_t trap_code_size = 0;
+  std::unique_ptr<uint8_t[]> trap_code =
+      GetTrapShellCode(&expected_signal, &trap_code_size);
+  std::unique_ptr<uint8_t[]> backup(new uint8_t[trap_code_size]);
+
+  // Set a break point at the entry point declared by the Elf file. When a
+  // statically linked binary is executed this will be the first instruction
+  // executed.
+  //
+  // When a dynamically linked binary is executed, the dynamic linker is
+  // executed first. This brings .so files into memory and resolves shared
+  // symbols. Once this process is finished, it jumps to the entry point
+  // declared in the Elf file.
+  ReadChildMemory(process_id, entry_address, backup.get(), trap_code_size);
+  WriteChildMemory(process_id, entry_address, trap_code.get(), trap_code_size);
+  ptrace(PTRACE_CONT, process_id, NULL, NULL);
+  int status;
+  waitpid(process_id, &status, 0);
+  if (status >> 8 != expected_signal) {
+    std::cerr << "Program exited unexpectedly while stepping to entry point."
+              << std::endl;
+    std::cerr << "Expected status " << expected_signal << " but encountered "
+              << (status >> 8) << std::endl;
+    return false;
+  }
+
+  if (!SetProgramCounter(process_id, entry_address)) {
+    return false;
+  }
+  WriteChildMemory(process_id, entry_address, backup.get(), trap_code_size);
+  return true;
+}
+
+}  // namespace
+
+bool ExecuteInContext(char* const executable_and_args[],
+                      const shell_as::SecurityContext* context) {
+  // Getting an executable running in a lower privileged context is tricky with
+  // SELinux. The recommended approach in the documentation is to use setexeccon
+  // which sets the context on the next execve call.
+  //
+  // However, this doesn't work for unprivileged processes like untrusted apps
+  // in Android because they are not allowed to execute most binaries.
+  //
+  // To work around this, ptrace is used to inject shell code into the new
+  // process just after it has executed an execve syscall. This shell code then
+  // sets the desired SELinux context.
+  pid_t child = fork();
+  if (child == 0) {
+    // Disabling ASLR makes it easier to determine the entry point of the target
+    // executable.
+    personality(ADDR_NO_RANDOMIZE);
+
+    // Drop the privileges that can be dropped before executing the new binary
+    // and exit early if there is an issue.
+    if (!DropPreExecPrivileges(context)) {
+      exit(1);
+    }
+
+    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
+    raise(SIGSTOP);  // Wait for the parent process to attach.
+    execv(executable_and_args[0], executable_and_args);
+  } else {
+    // Wait for the child to reach the SIGSTOP line above.
+    int status;
+    waitpid(child, &status, 0);
+    if ((status >> 8) != SIGSTOP) {
+      // If the first status is not SIGSTOP, then the child aborted early
+      // because it was not able to set the user and group IDs.
+      return false;
+    }
+
+    // Break inside the child's execv call.
+    ptrace(PTRACE_SETOPTIONS, child, NULL,
+           PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL);
+    ptrace(PTRACE_CONT, child, NULL, NULL);
+    waitpid(child, &status, 0);
+    if (status >> 8 != (SIGTRAP | PTRACE_EVENT_EXEC << 8)) {
+      std::cerr << "Failed to execute " << executable_and_args[0] << std::endl;
+      return false;
+    }
+
+    // Allow the dynamic linker to run before dropping to a lower SELinux
+    // context. This is required for executing in some very constrained domains
+    // like mediacodec.
+    //
+    // If the context was dropped before the dynamic linker runs, then when the
+    // linker attempts to read /proc/self/exe to determine dynamic symbol
+    // information, SELinux will kill the binary if the domain is not allowed to
+    // read the binary's executable file.
+    //
+    // This happens for example, when attempting to run any toybox binary (id,
+    // sh, etc) as mediacodec.
+    if (!StepToEntryPoint(child)) {
+      std::cerr << "Something bad happened stepping to the entry point."
+                << std::endl;
+      return false;
+    }
+
+    // Run the SELinux shellcode in the child process before the child can
+    // execute any instructions in the newly loaded executable.
+    if (context->selinux_context.has_value()) {
+      size_t shell_code_size;
+      std::unique_ptr<uint8_t[]> shell_code = GetSELinuxShellCode(
+          context->selinux_context.value(), &shell_code_size);
+      bool success = ExecuteShellCode(child, shell_code.get(), shell_code_size);
+      if (!success) {
+        return false;
+      }
+    }
+
+    // Resume and detach from the child now that the SELinux context has been
+    // updated.
+    ptrace(PTRACE_DETACH, child, NULL, NULL);
+    waitpid(child, nullptr, 0);
+  }
+  return true;
+}
+
+}  // namespace shell_as
diff --git a/utils/shell-as/execute.h b/utils/shell-as/execute.h
new file mode 100644
index 0000000..2e8f511
--- /dev/null
+++ b/utils/shell-as/execute.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHELL_AS_EXECUTE_H_
+#define SHELL_AS_EXECUTE_H_
+
+#include "context.h"
+
+namespace shell_as {
+
+// Executes a command in the given security context.
+//
+// The executable_and_args parameter must contain at least two values. The first
+// value is the path to the executable to run and the last value must be null.
+// Additional arguments are passed to the executable as command line options.
+//
+// Returns true if the executable was run and false otherwise.
+bool ExecuteInContext(char* const executable_and_args[],
+                      const SecurityContext* context);
+}  // namespace shell_as
+
+#endif  // SHELL_AS_EXECUTE_H_
diff --git a/utils/shell-as/gen-manifest.sh b/utils/shell-as/gen-manifest.sh
new file mode 100755
index 0000000..9dc4d11
--- /dev/null
+++ b/utils/shell-as/gen-manifest.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generates an AndroidManifest.xml file from a template by replacing the line
+# containing the substring, 'PERMISSIONS', with a list of permissions defined in
+# another text file.
+
+set -e
+
+if [ "$#" != 3 ];
+then
+  echo "usage: gen-manifest.sh AndroidManifest.xml.template" \
+    "permissions.txt AndroidManifest.xml"
+  exit 1
+fi
+
+readonly template="$1"
+readonly permissions="$2"
+readonly output="$3"
+
+echo "template = $1"
+
+# Print the XML template file before the line containing PERMISSIONS.
+sed -e '/PERMISSIONS/,$d' "$template" > "$output"
+
+# Print the permissions formatted as XML.
+sed -r 's!(.*)!  <uses-permission android:name="\1"/>!g' "$permissions" >> "$output"
+
+# Print the XML template file after the line containing PERMISSIONS.
+sed -e '1,/PERMISSIONS/d' "$template" >> "$output"
diff --git a/utils/shell-as/registers.h b/utils/shell-as/registers.h
new file mode 100644
index 0000000..6f7af6c
--- /dev/null
+++ b/utils/shell-as/registers.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHELL_AS_REGISTERS_H_
+#define SHELL_AS_REGISTERS_H_
+
+#if defined(__aarch64__)
+
+#define REGISTER_STRUCT struct user_pt_regs
+#define PROGRAM_COUNTER(regs) (regs.pc)
+
+#elif defined(__i386__)
+
+#include "sys/user.h"
+#define REGISTER_STRUCT struct user_regs_struct
+#define PROGRAM_COUNTER(regs) (regs.eip)
+
+#elif defined(__x86_64__)
+
+#include "sys/user.h"
+#define REGISTER_STRUCT struct user_regs_struct
+#define PROGRAM_COUNTER(regs) (regs.rip)
+
+#elif defined(__arm__)
+
+#define REGISTER_STRUCT struct user_regs
+#define PROGRAM_COUNTER(regs) (regs.ARM_pc)
+
+#endif
+
+#endif  // SHELL_AS_REGISTERS_H_
diff --git a/utils/shell-as/shell-as-main.cpp b/utils/shell-as/shell-as-main.cpp
new file mode 100644
index 0000000..880cf1c
--- /dev/null
+++ b/utils/shell-as/shell-as-main.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "./command-line.h"
+#include "./context.h"
+#include "./execute.h"
+
+int main(const int argc, char* const argv[]) {
+  bool verbose = false;
+  auto context = std::make_unique<shell_as::SecurityContext>();
+  char* const* execute_arguments = nullptr;
+  if (!shell_as::ParseOptions(argc, argv, &verbose, context.get(),
+                              &execute_arguments)) {
+    return 1;
+  }
+
+  if (verbose) {
+    std::cerr << "Dropping privileges to:" << std::endl;
+    std::cerr << "\tuser ID = "
+              << (context->user_id.has_value()
+                      ? std::to_string(context->user_id.value())
+                      : "<no value>")
+              << std::endl;
+
+    std::cerr << "\tgroup ID = "
+              << (context->group_id.has_value()
+                      ? std::to_string(context->group_id.value())
+                      : "<no value>")
+              << std::endl;
+
+    std::cerr << "\tsupplementary group IDs = ";
+    if (!context->supplementary_group_ids.has_value()) {
+      std::cerr << "<no value>";
+    } else {
+      for (auto& id : context->supplementary_group_ids.value()) {
+        std::cerr << id << " ";
+      }
+    }
+    std::cerr << std::endl;
+
+    std::cerr << "\tSELinux = "
+              << (context->selinux_context.has_value()
+                      ? context->selinux_context.value()
+                      : "<no value>")
+              << std::endl;
+
+    std::cerr << "\tseccomp = ";
+    if (!context->seccomp_filter.has_value()) {
+      std::cerr << "<no value>";
+    } else {
+      switch (context->seccomp_filter.value()) {
+        case shell_as::kAppFilter:
+          std::cerr << "app";
+          break;
+        case shell_as::kAppZygoteFilter:
+          std::cerr << "app-zygote";
+          break;
+        case shell_as::kSystemFilter:
+          std::cerr << "system";
+          break;
+      }
+    }
+    std::cerr << std::endl;
+
+    std::cerr << "\tcapabilities = ";
+    if (!context->capabilities.has_value()) {
+      std::cerr << "<no value>";
+    } else {
+      std::cerr << "'" << cap_to_text(context->capabilities.value(), nullptr)
+                << "'";
+    }
+    std::cerr << std::endl;
+  }
+
+  return !shell_as::ExecuteInContext(execute_arguments, context.get());
+}
diff --git a/utils/shell-as/shell-as-test-app-key.pk8 b/utils/shell-as/shell-as-test-app-key.pk8
new file mode 100644
index 0000000..df92545
--- /dev/null
+++ b/utils/shell-as/shell-as-test-app-key.pk8
Binary files differ
diff --git a/utils/shell-as/shell-as-test-app-key.x509.pem b/utils/shell-as/shell-as-test-app-key.x509.pem
new file mode 100644
index 0000000..4e5efc9
--- /dev/null
+++ b/utils/shell-as/shell-as-test-app-key.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAvOgAwIBAgIUNyI1+/ZDui4r+jp6uy/aVRBpeR0wDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFu
+ZHJvaWQuY29tMB4XDTE5MTIwNTIyNDEwMloXDTQ3MDQyMjIyNDEwMlowgZQxCzAJ
+BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp
+biBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRyb2lkMRAwDgYD
+VQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyzHGxhfDk4VzImAGQyFV
+5+tb7Dgl9TTg57t8/LwMQX4abjB9o6tPwtZl757m4oLP8HCpjbI/kX5Wk4hLmNQ/
+I4AHG+LhCJGlz3nqBAJJxYoM//+3tUSLrq0ypuHMXNDPI5HGgE3hhzZbA9iWuGNB
+7in+bHuhPFUq8e5og6piy3s3f77GB8QXzJEKyO2FhQR1Do8t4UdRji7TWR+USHqw
+WBj/CyrpLJMwbr4Mx4YRN0JXUlFX1X/66ENonX4QZXofeiWDv5qgwFbbzgu9FLFN
+imDeeCzU0mtYEKQpmZOdEaWclkT8IzUPwPMdawEq3Wj8nutoma5CztYl+OO9BgJC
+LQIDAQABo1MwUTAdBgNVHQ4EFgQUqyxwI0Khq+xKbEGG3NCpN01wsaMwHwYDVR0j
+BBgwFoAUqyxwI0Khq+xKbEGG3NCpN01wsaMwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAQEAIx4k1g1jDQs3ekseXMvz0V+O9AArWOEmwkIcA6EISvfC
+dJ0DpmgRbZyvi0FowzOGYIZJ0Uwh4uwxETTHBQkvKoFdByukaasfX0p8axYVslT1
+87RrQDSA8fDp9K7d4kG3iXX16H5WJ0O/sI3UkZevZzVjXcoqSHA2CltGZv/EXPAh
+dwGL5OupiiJcCV4ISSgh9PHswH1tGASdg3nqFqQLZrCYZE3pyLdsiDTQADlBMpZ4
+dH7kbh8McSA/OM2Fp1y05oecYVzKOzJ/I4SLhbSGLRLHvSg9fNiJPoKm46leQtFV
+OVtzzBt6TKITRIhA8VVo45U0gVGUwlj/4BCKQLsJpA==
+-----END CERTIFICATE-----
diff --git a/utils/shell-as/shell-code.cpp b/utils/shell-as/shell-code.cpp
new file mode 100644
index 0000000..bdadf6c
--- /dev/null
+++ b/utils/shell-as/shell-code.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./shell-code.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#define PAGE_START(addr) ((uintptr_t)addr & ~(PAGE_SIZE - 1))
+
+// Shell code that sets the SELinux context of the current process.
+//
+// The shell code expects a null-terminated SELinux context string to be placed
+// immediately after it in memory. After the SELinux context has been changed
+// the shell code will stop the current process with SIGSTOP.
+//
+// This shell code must be self-contained and position-independent.
+extern "C" void __setcon_shell_code_start();
+extern "C" void __setcon_shell_code_end();
+
+// Shell code that stops execution of the current process by raising a signal.
+// The specific signal that is raised is given in __trap_shell_code_signal.
+//
+// This shell code can be used to inject break points into a traced process.
+//
+// The shell code must not modify any registers other than the program counter.
+extern "C" void __trap_shell_code_start();
+extern "C" void __trap_shell_code_end();
+extern "C" int __trap_shell_code_signal;
+
+namespace shell_as {
+
+namespace {
+void EnsureShellcodeReadable(void (*start)(), void (*end)()) {
+  mprotect((void*)PAGE_START(start),
+           PAGE_START(end) - PAGE_START(start) + PAGE_SIZE,
+           PROT_READ | PROT_EXEC);
+}
+}  // namespace
+
+std::unique_ptr<uint8_t[]> GetSELinuxShellCode(
+    char* selinux_context, size_t* total_size) {
+  EnsureShellcodeReadable(&__setcon_shell_code_start, &__setcon_shell_code_end);
+
+  size_t shell_code_size = (uintptr_t)&__setcon_shell_code_end -
+                           (uintptr_t)&__setcon_shell_code_start;
+  size_t selinux_context_size = strlen(selinux_context) + 1 /* null byte */;
+  *total_size = shell_code_size + selinux_context_size;
+
+  std::unique_ptr<uint8_t[]> shell_code(new uint8_t[*total_size]);
+  memcpy(shell_code.get(), (void*)&__setcon_shell_code_start, shell_code_size);
+  memcpy(shell_code.get() + shell_code_size, selinux_context,
+         selinux_context_size);
+  return shell_code;
+}
+
+std::unique_ptr<uint8_t[]> GetTrapShellCode(int* expected_signal,
+                                            size_t* total_size) {
+  EnsureShellcodeReadable(&__trap_shell_code_start, &__trap_shell_code_end);
+
+  *expected_signal = __trap_shell_code_signal;
+
+  *total_size =
+      (uintptr_t)&__trap_shell_code_end - (uintptr_t)&__trap_shell_code_start;
+  std::unique_ptr<uint8_t[]> shell_code(new uint8_t[*total_size]);
+  memcpy(shell_code.get(), (void*)&__trap_shell_code_start, *total_size);
+  return shell_code;
+}
+}  // namespace shell_as
diff --git a/utils/shell-as/shell-code.h b/utils/shell-as/shell-code.h
new file mode 100644
index 0000000..9c88d16
--- /dev/null
+++ b/utils/shell-as/shell-code.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHELL_AS_SHELL_CODE_H_
+#define SHELL_AS_SHELL_CODE_H_
+
+#include <selinux/selinux.h>
+#include <sys/types.h>
+
+#include <memory>
+
+#include "context.h"
+
+namespace shell_as {
+
+// Returns shell code that when executed will set the current process's SElinux
+// context to the given value and then SIGSTOP itself.
+std::unique_ptr<uint8_t[]> GetSELinuxShellCode(
+    char* selinux_context, size_t* total_size);
+
+// Returns shell code that when executed will halt the current process and raise
+// a signal. The specific signal is returned in the expected_signal argument.
+std::unique_ptr<uint8_t[]> GetTrapShellCode(int* expected_signal,
+                                            size_t* total_size);
+}  // namespace shell_as
+
+#endif  // SHELL_AS_SHELL_CODE_H_
diff --git a/utils/shell-as/shell-code/constants-arm.S b/utils/shell-as/shell-code/constants-arm.S
new file mode 100644
index 0000000..10db630
--- /dev/null
+++ b/utils/shell-as/shell-code/constants-arm.S
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Arm specific constants.
+
+.equ SYS_OPEN,     0x000005
+.equ SYS_CLOSE,    0x000006
+.equ SYS_WRITE,    0x000004
+.equ SYS_KILL,     0x000025
+.equ SYS_GETPID,   0x000014
+.equ SYS_MPROTECT, 0x00007d
diff --git a/utils/shell-as/shell-code/constants-arm64.S b/utils/shell-as/shell-code/constants-arm64.S
new file mode 100644
index 0000000..a9dec75
--- /dev/null
+++ b/utils/shell-as/shell-code/constants-arm64.S
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Arm64 specific constants.
+
+.equ SYS_OPENAT,   0x38
+.equ SYS_CLOSE,    0x39
+.equ SYS_WRITE,    0x40
+.equ SYS_KILL,     0x81
+.equ SYS_GETPID,   0xAC
+.equ SYS_MPROTECT, 0xE2
diff --git a/utils/shell-as/shell-code/constants-x86.S b/utils/shell-as/shell-code/constants-x86.S
new file mode 100644
index 0000000..afa9d14
--- /dev/null
+++ b/utils/shell-as/shell-code/constants-x86.S
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// x86 specific constants.
+
+.equ SYS_WRITE,    0x04
+.equ SYS_OPEN,     0x05
+.equ SYS_CLOSE,    0x06
+.equ SYS_GETPID,   0x14
+.equ SYS_KILL,     0x25
+.equ SYS_MPROTECT, 0x7d
diff --git a/utils/shell-as/shell-code/constants-x86_64.S b/utils/shell-as/shell-code/constants-x86_64.S
new file mode 100644
index 0000000..0bf95cc
--- /dev/null
+++ b/utils/shell-as/shell-code/constants-x86_64.S
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// x86-64 specific constants.
+
+.equ SYS_WRITE,    0x01
+.equ SYS_OPEN,     0x02
+.equ SYS_CLOSE,    0x03
+.equ SYS_GETPID,   0x27
+.equ SYS_KILL,     0x3e
+.equ SYS_MPROTECT, 0x0a
diff --git a/utils/shell-as/shell-code/constants.S b/utils/shell-as/shell-code/constants.S
new file mode 100644
index 0000000..9e2a238
--- /dev/null
+++ b/utils/shell-as/shell-code/constants.S
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Architecture independent constants.
+
+.equ AT_FDCWD, -100
+.equ O_WRONLY, 1
+
+// Possible memory access modes for mprotect.
+.equ PROT_NONE,  0
+.equ PROT_READ,  1
+.equ PROT_WRITE, 2
+.equ PROT_EXEC,  4
+
+.equ SIGILL, 4
+.equ SIGTRAP, 5
+.equ SIGSTOP, 19
diff --git a/utils/shell-as/shell-code/selinux-arm.S b/utils/shell-as/shell-code/selinux-arm.S
new file mode 100644
index 0000000..0c9480f
--- /dev/null
+++ b/utils/shell-as/shell-code/selinux-arm.S
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Shell code that sets the current SELinux context to a given string.
+//
+// The desired SELinux context is appended to the payload as a null-terminated
+// string.
+//
+// After the SELinux context has been updated the current process will raise
+// SIGSTOP.
+
+#include "./shell-code/constants.S"
+#include "./shell-code/constants-arm.S"
+
+.thumb
+
+.globl __setcon_shell_code_start
+.globl __setcon_shell_code_end
+
+__setcon_shell_code_start:
+  // Ensure that the context and SELinux /proc file are readable. This assumes
+  // that the max length of these two strings is shorter than 0x1000.
+  //
+  // mprotect(context & ~0xFFF, 0x2000, PROT_READ | PROT_EXEC)
+  mov r7, SYS_MPROTECT
+  adr r0, context
+  movw r2, 0xF000
+  movt r2, 0xFFFF
+  and r0, r0, r2
+  mov r1, 0x2000
+  mov r2, (PROT_READ | PROT_EXEC)
+  swi 0
+
+  // r10 = open("/proc/self/attr/current", O_WRONLY, O_WRONLY)
+  mov r7, SYS_OPEN
+  adr r0, selinux_proc_file
+  mov r1, O_WRONLY
+  mov r2, O_WRONLY
+  swi 0
+  mov r10, r0
+
+  // r11 = strlen(context)
+  mov r11, 0
+  adr r0, context
+strlen_start:
+  ldrb r1, [r0, r11]
+  cmp r1, 0
+  beq strlen_done
+  add r11, r11, 1
+  b strlen_start
+strlen_done:
+
+  // write(r10, context, r11)
+  mov r7, SYS_WRITE
+  mov r0, r10
+  adr r1, context
+  mov r2, r11
+  swi 0
+
+  // close(r10)
+  mov r7, SYS_CLOSE
+  mov r0, r10
+  swi 0
+
+  // r0 = getpid()
+  mov r7, SYS_GETPID
+  swi 0
+
+  // kill(r0, SIGSTOP)
+  mov r7, SYS_KILL
+  mov r1, SIGSTOP
+  swi 0
+
+selinux_proc_file:
+  .asciz "/proc/thread-self/attr/current"
+
+context:
+__setcon_shell_code_end:
diff --git a/utils/shell-as/shell-code/selinux-arm64.S b/utils/shell-as/shell-code/selinux-arm64.S
new file mode 100644
index 0000000..4e8c492
--- /dev/null
+++ b/utils/shell-as/shell-code/selinux-arm64.S
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Shell code that sets the current SELinux context to a given string.
+//
+// The desired SELinux context is appended to the payload as a null-terminated
+// string.
+//
+// After the SELinux context has been updated the current process will raise
+// SIGSTOP.
+
+#include "./shell-code/constants.S"
+#include "./shell-code/constants-arm64.S"
+
+.globl __setcon_shell_code_start
+.globl __setcon_shell_code_end
+
+__setcon_shell_code_start:
+  // Ensure that the context and SELinux /proc file are readable. This assumes
+  // that the max length of these two strings is shorter than 0x1000.
+  //
+  // mprotect(context & ~0xFFF, 0x2000, PROT_READ | PROT_EXEC)
+  mov x8, SYS_MPROTECT
+  adr X0, __setcon_shell_code_end
+  and x0, x0, ~0xFFF
+  mov x1, 0x2000
+  mov x2, (PROT_READ | PROT_EXEC)
+  svc 0
+
+  // x10 = openat(AT_FDCWD, "/proc/self/attr/current", O_WRONLY, O_WRONLY)
+  mov x8, SYS_OPENAT
+  mov x0, AT_FDCWD
+  adr x1, selinux_proc_file
+  mov x2, O_WRONLY
+  mov x3, O_WRONLY
+  svc 0
+  mov x10, x0
+
+  // x11 = strlen(context)
+  mov x11, 0
+  adr x0, context
+strlen_start:
+  ldrb w1, [x0, x11]
+  cmp w1, 0
+  b.eq strlen_done
+  add x11, x11, 1
+  b strlen_start
+strlen_done:
+
+  // write(x10, context, x11)
+  mov x8, SYS_WRITE
+  mov x0, x10
+  adr x1, context
+  mov x2, x11
+  svc 0
+
+  // close(x10)
+  mov x8, SYS_CLOSE
+  mov x0, x10
+  svc 0
+
+  // x0 = getpid()
+  mov x8, SYS_GETPID
+  svc 0
+
+  // kill(x0, SIGSTOP)
+  mov x8, SYS_KILL
+  mov x1, SIGSTOP
+  svc 0
+
+selinux_proc_file:
+  .asciz "/proc/thread-self/attr/current"
+
+context:
+__setcon_shell_code_end:
diff --git a/utils/shell-as/shell-code/selinux-x86.S b/utils/shell-as/shell-code/selinux-x86.S
new file mode 100644
index 0000000..81c150f
--- /dev/null
+++ b/utils/shell-as/shell-code/selinux-x86.S
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Shell code that sets the current SELinux context to a given string.
+//
+// The desired SELinux context is appended to the payload as a null-terminated
+// string.
+//
+// After the SELinux context has been updated the current process will raise
+// SIGSTOP.
+
+#include "./shell-code/constants.S"
+#include "./shell-code/constants-x86.S"
+
+.globl __setcon_shell_code_start
+.globl __setcon_shell_code_end
+
+__setcon_shell_code_start:
+
+  // x86 does not have RIP relative addressing. To work around this, relative
+  // calls are used to obtain the runtime address of a label. Once the location
+  // of one label is known, other labels can be addressed relative to the known
+  // label.
+  call constant_relative_address
+constant_relative_address:
+  pop %esi
+
+  // Ensure that the context and SELinux /proc file are readable. This assumes
+  // that the max length of these two strings is shorter than 0x1000.
+  //
+  // mprotect(context & ~0xFFF, 0x2000, PROT_READ | PROT_EXEC)
+  mov $SYS_MPROTECT, %eax
+  mov $~0xFFF, %ebx
+  and %esi, %ebx
+  mov $0x2000, %ecx
+  mov $(PROT_READ | PROT_EXEC), %edx
+  int $0x80
+
+  // ebx = open("/proc/self/attr/current", O_WRONLY, O_WRONLY)
+  mov $SYS_OPEN, %eax
+  lea (selinux_proc_file - constant_relative_address)(%esi), %ebx
+  mov $O_WRONLY, %ecx
+  mov $O_WRONLY, %edx
+  int $0x80
+  mov %eax, %ebx
+
+  // write(ebx, context, strlen(context))
+  xor %edx, %edx
+  leal (context - constant_relative_address)(%esi), %ecx
+strlen_start:
+  movb (%ecx, %edx), %al
+  test %al, %al
+  jz strlen_done
+  inc %edx
+  jmp strlen_start
+strlen_done:
+  mov $SYS_WRITE, %eax
+  int $0x80
+
+  // close(ebx)
+  mov $SYS_CLOSE, %eax
+  int $0x80
+
+  // ebx = getpid()
+  mov $SYS_GETPID, %eax
+  int $0x80
+  mov %eax, %ebx
+
+  // kill(ebx, SIGSTOP)
+  mov $SYS_KILL, %eax
+  mov $SIGSTOP, %ecx
+  int $0x80
+
+selinux_proc_file:
+  .asciz "/proc/self/attr/current"
+
+context:
+__setcon_shell_code_end:
diff --git a/utils/shell-as/shell-code/selinux-x86_64.S b/utils/shell-as/shell-code/selinux-x86_64.S
new file mode 100644
index 0000000..94fc876
--- /dev/null
+++ b/utils/shell-as/shell-code/selinux-x86_64.S
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Shell code that sets the current SELinux context to a given string.
+//
+// The desired SELinux context is appended to the payload as a null-terminated
+// string.
+//
+// After the SELinux context has been updated the current process will raise
+// SIGSTOP.
+
+#include "./shell-code/constants.S"
+#include "./shell-code/constants-x86_64.S"
+
+.globl __setcon_shell_code_start
+.globl __setcon_shell_code_end
+
+__setcon_shell_code_start:
+
+  // Ensure that the context and SELinux /proc file are readable. This assumes
+  // that the max length of these two strings is shorter than 0x1000.
+  //
+  // mprotect(context & ~0xFFF, 0x2000, PROT_READ | PROT_EXEC)
+  mov $SYS_MPROTECT, %rax
+  lea context(%rip), %rdi
+  and $~0xFFF, %rdi
+  mov $0x2000, %rsi
+  mov $(PROT_READ | PROT_EXEC), %rdx
+  syscall
+
+  // rdi = open("/proc/self/attr/current", O_WRONLY, O_WRONLY)
+  mov $SYS_OPEN, %eax
+  lea selinux_proc_file(%rip), %rdi
+  mov $O_WRONLY, %rsi
+  mov $O_WRONLY, %rdx
+  syscall
+  mov %rax, %rdi
+
+  // write(rdi, context, strlen(context))
+  xor %rdx, %rdx
+  lea context(%rip), %rsi
+strlen_start:
+  movb (%rsi, %rdx), %al
+  test %al, %al
+  jz strlen_done
+  inc %rdx
+  jmp strlen_start
+strlen_done:
+  mov $SYS_WRITE, %rax
+  syscall
+
+  // close(rdi)
+  mov $SYS_CLOSE, %rax
+  syscall
+
+  // rdi = getpid()
+  mov $SYS_GETPID, %rax
+  syscall
+  mov %rax, %rdi
+
+  // kill(rdi, SIGSTOP)
+  mov $SYS_KILL, %rax
+  mov $SIGSTOP, %rsi
+  syscall
+
+selinux_proc_file:
+  .asciz "/proc/self/attr/current"
+
+context:
+__setcon_shell_code_end:
diff --git a/utils/shell-as/shell-code/trap-arm.S b/utils/shell-as/shell-code/trap-arm.S
new file mode 100644
index 0000000..8bb3474
--- /dev/null
+++ b/utils/shell-as/shell-code/trap-arm.S
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./shell-code/constants.S"
+
+.thumb
+
+.globl __trap_shell_code_start
+.globl __trap_shell_code_end
+.globl __trap_shell_code_signal
+
+__trap_shell_code_start:
+bkpt
+__trap_shell_code_end:
+
+__trap_shell_code_signal:
+.int SIGTRAP
diff --git a/utils/shell-as/shell-code/trap-arm64.S b/utils/shell-as/shell-code/trap-arm64.S
new file mode 100644
index 0000000..90063ff
--- /dev/null
+++ b/utils/shell-as/shell-code/trap-arm64.S
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./shell-code/constants.S"
+
+.globl __trap_shell_code_start
+.globl __trap_shell_code_end
+.globl __trap_shell_code_signal
+
+__trap_shell_code_start:
+hlt 0
+__trap_shell_code_end:
+
+__trap_shell_code_signal:
+.int SIGILL
diff --git a/utils/shell-as/shell-code/trap-x86.S b/utils/shell-as/shell-code/trap-x86.S
new file mode 100644
index 0000000..1669bb8
--- /dev/null
+++ b/utils/shell-as/shell-code/trap-x86.S
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./shell-code/constants.S"
+
+.globl __trap_shell_code_start
+.globl __trap_shell_code_end
+.globl __trap_shell_code_signal
+
+__trap_shell_code_start:
+int $0x03
+__trap_shell_code_end:
+
+__trap_shell_code_signal:
+.int SIGTRAP
diff --git a/utils/shell-as/shell-code/trap-x86_64.S b/utils/shell-as/shell-code/trap-x86_64.S
new file mode 100644
index 0000000..1669bb8
--- /dev/null
+++ b/utils/shell-as/shell-code/trap-x86_64.S
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./shell-code/constants.S"
+
+.globl __trap_shell_code_start
+.globl __trap_shell_code_end
+.globl __trap_shell_code_signal
+
+__trap_shell_code_start:
+int $0x03
+__trap_shell_code_end:
+
+__trap_shell_code_signal:
+.int SIGTRAP
diff --git a/utils/shell-as/string-utils.cpp b/utils/shell-as/string-utils.cpp
new file mode 100644
index 0000000..8977f73
--- /dev/null
+++ b/utils/shell-as/string-utils.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./string-utils.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace shell_as {
+
+bool StringToUInt32(const char* s, uint32_t* i) {
+  uint64_t value = 0;
+  if (!StringToUInt64(s, &value)) {
+    return false;
+  }
+  if (value > UINT_MAX) {
+    return false;
+  }
+  *i = value;
+  return true;
+}
+
+bool StringToUInt64(const char* s, uint64_t* i) {
+  char* endptr = nullptr;
+  // Reset errno to a non-error value since strtoul does not clear errno.
+  errno = 0;
+  *i = strtoul(s, &endptr, 10);
+  // strtoul will return 0 if the value cannot be parsed as an unsigned long. If
+  // this occurs, ensure that the ID actually was zero. This is done by ensuring
+  // that the end pointer was advanced and that it now points to the end of the
+  // string (a null byte).
+  return errno == 0 && (*i != 0 || (endptr != s && *endptr == '\0'));
+}
+
+bool SplitIdsAndSkip(char* line, const char* separators, int num_to_skip,
+                     std::vector<uid_t>* ids) {
+  if (line == nullptr) {
+    return false;
+  }
+
+  ids->clear();
+  for (char* id_string = strtok(line, separators); id_string != nullptr;
+       id_string = strtok(nullptr, separators)) {
+    if (num_to_skip > 0) {
+      num_to_skip--;
+      continue;
+    }
+
+    gid_t id;
+    if (!StringToUInt32(id_string, &id)) {
+      return false;
+    }
+    ids->push_back(id);
+  }
+  return true;
+}
+
+}  // namespace shell_as
diff --git a/utils/shell-as/string-utils.h b/utils/shell-as/string-utils.h
new file mode 100644
index 0000000..f491089
--- /dev/null
+++ b/utils/shell-as/string-utils.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHELL_AS_STRING_UTILS_H_
+#define SHELL_AS_STRING_UTILS_H_
+
+#include <unistd.h>
+
+#include <vector>
+
+namespace shell_as {
+
+// Parses a string into an unsigned 32bit int value. Returns true on success and
+// false otherwise.
+bool StringToUInt32(const char* s, uint32_t* i);
+
+// Parses a string into a unsigned 64bit int value. Returns true on success and
+// false otherwise.
+bool StringToUInt64(const char* s, uint64_t* i);
+
+// Splits a line of uid_t/guid_t values by a given separator and returns the
+// integer values in a vector.
+//
+// The separators string may contain multiple characters and is treated as a set
+// of possible separating characters.
+//
+// If num_to_skip is non-zero, then that many entries will be skipped after
+// splitting the line and before parsing the values as integers. This is useful
+// if the line has a prefix such as "Gid: 1 2 3 4".
+//
+// Returns true on success and false otherwise.
+bool SplitIdsAndSkip(char* line, const char* separators, int num_to_skip,
+                     std::vector<uid_t>* ids);
+
+}  // namespace shell_as
+
+#endif  // SHELL_AS_STRING_UTILS_H_
diff --git a/utils/shell-as/test-app.cpp b/utils/shell-as/test-app.cpp
new file mode 100644
index 0000000..84fedca
--- /dev/null
+++ b/utils/shell-as/test-app.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "./test-app.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <string>
+
+#include "./string-utils.h"
+
+namespace shell_as {
+
+// Returns a pointer to bytes of the test app APK along with the length in bytes
+// of the APK.
+//
+// This function is defined by the shell-as-test-app-apk-cpp genrule.
+void GetTestApk(uint8_t **apk, size_t *length);
+
+namespace {
+
+// The staging path for the test app APK.
+const char kTestAppApkStagingPath[] = "/data/local/tmp/shell-as-test-app.apk";
+
+// Writes the test app to a staging location and then installs the APK via the
+// 'pm' utility. The app is granted runtime permissions on installation. Returns
+// true if the app is installed successfully.
+bool InstallTestApp() {
+  uint8_t *apk = nullptr;
+  size_t apk_size = 0;
+  GetTestApk(&apk, &apk_size);
+
+  int staging_file = open(kTestAppApkStagingPath, O_WRONLY | O_CREAT | O_TRUNC,
+                          S_IRUSR | S_IWUSR);
+  if (staging_file == -1) {
+    std::cerr << "Unable to open staging APK path." << std::endl;
+    return false;
+  }
+
+  size_t bytes_written = write(staging_file, apk, apk_size);
+  close(staging_file);
+  if (bytes_written != apk_size) {
+    std::cerr << "Unable to write entire test app APK." << std::endl;
+    return false;
+  }
+
+  const char cmd_template[] = "pm install -g %s > /dev/null 2> /dev/null";
+  char system_cmd[sizeof(cmd_template) + sizeof(kTestAppApkStagingPath) + 1] =
+      {};
+  sprintf(system_cmd, cmd_template, kTestAppApkStagingPath);
+  return system(system_cmd) == 0;
+}
+
+// Uninstalls the test app if it is installed. This method is a no-op if the app
+// is not installed.
+void UninstallTestApp() {
+  system(
+      "pm uninstall com.android.google.tools.security.shell_as"
+      " > /dev/null 2> /dev/null");
+}
+
+// Starts the main activity of the test app. This is necessary as some aspects
+// of the security context can only be inferred from a running process.
+bool StartTestApp() {
+  return system(
+             "am start-activity "
+             "com.android.google.tools.security.shell_as/"
+             ".MainActivity"
+             " > /dev/null 2> /dev/null") == 0;
+}
+
+// Obtain the process ID of the test app and returns true if it is running.
+// Returns false otherwise.
+bool GetTestAppProcessId(pid_t *test_app_pid) {
+  FILE *pgrep = popen(
+      "pgrep -f "
+      "com.android.google.tools.security.shell_as",
+      "r");
+  if (!pgrep) {
+    std::cerr << "Unable to execute pgrep." << std::endl;
+    return false;
+  }
+
+  char pgrep_output[128];
+  memset(pgrep_output, 0, sizeof(pgrep_output));
+  int bytes_read = fread(pgrep_output, 1, sizeof(pgrep_output) - 1, pgrep);
+  pclose(pgrep);
+  if (bytes_read <= 0) {
+    // Unable to find the process. This may happen if the app is still starting
+    // up.
+    return false;
+  }
+  return StringToUInt32(pgrep_output, (uint32_t *)test_app_pid);
+}
+}  // namespace
+
+bool SetupAndStartTestApp(pid_t *test_app_pid) {
+  UninstallTestApp();
+
+  if (!InstallTestApp()) {
+    std::cerr << "Unable to install test app." << std::endl;
+    return false;
+  }
+
+  if (!StartTestApp()) {
+    std::cerr << "Unable to start and obtain test app PID." << std::endl;
+    return false;
+  }
+
+  for (int i = 0; i < 5; i++) {
+    if (GetTestAppProcessId(test_app_pid)) {
+      return true;
+    }
+    sleep(1);
+  }
+  return false;
+}
+}  // namespace shell_as
diff --git a/utils/shell-as/test-app.h b/utils/shell-as/test-app.h
new file mode 100644
index 0000000..866bbfb
--- /dev/null
+++ b/utils/shell-as/test-app.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHELL_AS_TEST_APP_H_
+#define SHELL_AS_TEST_APP_H_
+
+#include <sys/types.h>
+
+namespace shell_as {
+
+// Installs and launches the embedded shell-as test app. The test app requests
+// and is granted all non-system permissions defined by the OS. The test_app_pid
+// parameter is set to the process ID of the running test app. Returns true if
+// successful.
+bool SetupAndStartTestApp(pid_t *test_app_pid);
+}
+
+#endif  // SHELL_AS_TEST_APP_H_