summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/shell-as/Android.bp107
-rw-r--r--utils/shell-as/AndroidManifest.xml.template33
-rw-r--r--utils/shell-as/OWNERS4
-rw-r--r--utils/shell-as/README.md33
-rw-r--r--utils/shell-as/app/com/android/google/tools/security/shell_as/MainActivity.java28
-rw-r--r--utils/shell-as/command-line.cpp210
-rw-r--r--utils/shell-as/command-line.h36
-rw-r--r--utils/shell-as/context.cpp138
-rw-r--r--utils/shell-as/context.h71
-rw-r--r--utils/shell-as/elf-utils.cpp89
-rw-r--r--utils/shell-as/elf-utils.h35
-rw-r--r--utils/shell-as/execute.cpp382
-rw-r--r--utils/shell-as/execute.h35
-rwxr-xr-xutils/shell-as/gen-manifest.sh43
-rw-r--r--utils/shell-as/registers.h44
-rw-r--r--utils/shell-as/shell-as-main.cpp93
-rw-r--r--utils/shell-as/shell-as-test-app-key.pk8bin0 -> 1218 bytes
-rw-r--r--utils/shell-as/shell-as-test-app-key.x509.pem24
-rw-r--r--utils/shell-as/shell-code.cpp82
-rw-r--r--utils/shell-as/shell-code.h40
-rw-r--r--utils/shell-as/shell-code/constants-arm.S24
-rw-r--r--utils/shell-as/shell-code/constants-arm64.S24
-rw-r--r--utils/shell-as/shell-code/constants-x86.S24
-rw-r--r--utils/shell-as/shell-code/constants-x86_64.S24
-rw-r--r--utils/shell-as/shell-code/constants.S30
-rw-r--r--utils/shell-as/shell-code/selinux-arm.S91
-rw-r--r--utils/shell-as/shell-code/selinux-arm64.S88
-rw-r--r--utils/shell-as/shell-code/selinux-x86.S91
-rw-r--r--utils/shell-as/shell-code/selinux-x86_64.S83
-rw-r--r--utils/shell-as/shell-code/trap-arm.S30
-rw-r--r--utils/shell-as/shell-code/trap-arm64.S28
-rw-r--r--utils/shell-as/shell-code/trap-x86.S28
-rw-r--r--utils/shell-as/shell-code/trap-x86_64.S28
-rw-r--r--utils/shell-as/string-utils.cpp72
-rw-r--r--utils/shell-as/string-utils.h50
-rw-r--r--utils/shell-as/test-app.cpp136
-rw-r--r--utils/shell-as/test-app.h31
37 files changed, 2409 insertions, 0 deletions
diff --git a/utils/shell-as/Android.bp b/utils/shell-as/Android.bp
new file mode 100644
index 000000000..96dc1c9d5
--- /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 000000000..07e89b186
--- /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 000000000..431db9920
--- /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 000000000..e0f6f93f2
--- /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 000000000..d5d178c2f
--- /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 000000000..9a893c375
--- /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 000000000..4bf495f42
--- /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 000000000..ea7979bab
--- /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 000000000..17a8cca85
--- /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 000000000..8a82555be
--- /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 000000000..eba40f303
--- /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 000000000..3ef529252
--- /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 000000000..2e8f51189
--- /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 000000000..9dc4d1142
--- /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 000000000..6f7af6c54
--- /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 000000000..880cf1c91
--- /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 000000000..df9254543
--- /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 000000000..4e5efc980
--- /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 000000000..bdadf6c5a
--- /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 000000000..9c88d165a
--- /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 000000000..10db630d9
--- /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 000000000..a9dec758b
--- /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 000000000..afa9d1472
--- /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 000000000..0bf95cc75
--- /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 000000000..9e2a238d5
--- /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 000000000..0c9480f9b
--- /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 000000000..4e8c49296
--- /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 000000000..81c150f13
--- /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 000000000..94fc876c6
--- /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 000000000..8bb347411
--- /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 000000000..90063ffee
--- /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 000000000..1669bb8ee
--- /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 000000000..1669bb8ee
--- /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 000000000..8977f73e0
--- /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 000000000..f4910894a
--- /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 000000000..84fedcab6
--- /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 000000000..866bbfb33
--- /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_