1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdlib.h>
18 #include <sys/capability.h>
19 #include <sys/resource.h>
20 #include <unistd.h>
21
22 #include <filesystem>
23 #include <iostream>
24 #include <iterator>
25 #include <optional>
26 #include <string>
27 #include <string_view>
28 #include <system_error>
29 #include <unordered_map>
30 #include <unordered_set>
31 #include <vector>
32
33 #include "android-base/logging.h"
34 #include "android-base/parseint.h"
35 #include "android-base/result.h"
36 #include "android-base/strings.h"
37 #include "base/macros.h"
38 #include "base/scoped_cap.h"
39 #include "palette/palette.h"
40 #include "system/thread_defs.h"
41
42 namespace {
43
44 using ::android::base::ConsumePrefix;
45 using ::android::base::Join;
46 using ::android::base::ParseInt;
47 using ::android::base::Result;
48 using ::android::base::Split;
49
50 constexpr const char* kUsage =
51 R"(A wrapper binary that configures the process and executes a command.
52
53 By default, it closes all open file descriptors except stdin, stdout, and stderr. `--keep-fds` can
54 be passed to keep some more file descriptors open.
55
56 Usage: art_exec [OPTIONS]... -- [COMMAND]...
57
58 Supported options:
59 --help: Print this text.
60 --set-task-profile=PROFILES: Apply a set of task profiles (see
61 https://source.android.com/devices/tech/perf/cgroups). Requires root access. PROFILES can be a
62 comma-separated list of task profile names.
63 --set-priority=PRIORITY: Apply the process priority. Currently, the only supported value of
64 PRIORITY is "background".
65 --drop-capabilities: Drop all root capabilities. Note that this has effect only if `art_exec` runs
66 with some root capabilities but not as the root user.
67 --keep-fds=FILE_DESCRIPTORS: A semicolon-separated list of file descriptors to keep open.
68 --env=KEY=VALUE: Set an environment variable. This flag can be passed multiple times to set
69 multiple environment variables.
70 --process-name-suffix=SUFFIX: Add a suffix in parentheses to argv[0] when calling `execv`. This
71 suffix will show up as part of the process name in tombstone when the process crashes.
72 )";
73
74 constexpr int kErrorUsage = 100;
75 constexpr int kErrorOther = 101;
76
77 struct Options {
78 int command_pos = -1;
79 std::vector<std::string> task_profiles;
80 std::optional<int> priority = std::nullopt;
81 bool drop_capabilities = false;
82 std::unordered_set<int> keep_fds{fileno(stdin), fileno(stdout), fileno(stderr)};
83 std::unordered_map<std::string, std::string> envs;
84 std::string chroot;
85 std::string process_name_suffix;
86 };
87
Usage(const std::string & error_msg)88 [[noreturn]] void Usage(const std::string& error_msg) {
89 LOG(ERROR) << error_msg;
90 std::cerr << error_msg << "\n" << kUsage << "\n";
91 exit(kErrorUsage);
92 }
93
ParseOptions(int argc,char ** argv)94 Options ParseOptions(int argc, char** argv) {
95 Options options;
96 for (int i = 1; i < argc; i++) {
97 std::string_view arg = argv[i];
98 if (arg == "--help") {
99 std::cerr << kUsage << "\n";
100 exit(0);
101 } else if (ConsumePrefix(&arg, "--set-task-profile=")) {
102 options.task_profiles = Split(std::string(arg), ",");
103 if (options.task_profiles.empty()) {
104 Usage("Empty task profile list");
105 }
106 } else if (ConsumePrefix(&arg, "--set-priority=")) {
107 if (arg == "background") {
108 options.priority = ANDROID_PRIORITY_BACKGROUND;
109 } else {
110 Usage("Unknown priority " + std::string(arg));
111 }
112 } else if (arg == "--drop-capabilities") {
113 options.drop_capabilities = true;
114 } else if (ConsumePrefix(&arg, "--keep-fds=")) {
115 for (const std::string& fd_str : Split(std::string(arg), ":")) {
116 int fd;
117 if (!ParseInt(fd_str, &fd)) {
118 Usage("Invalid fd " + fd_str);
119 }
120 options.keep_fds.insert(fd);
121 }
122 } else if (ConsumePrefix(&arg, "--env=")) {
123 size_t pos = arg.find('=');
124 if (pos == std::string_view::npos) {
125 Usage("Malformed environment variable. Must contain '='");
126 }
127 options.envs[std::string(arg.substr(/*pos=*/0, /*n=*/pos))] =
128 std::string(arg.substr(pos + 1));
129 } else if (ConsumePrefix(&arg, "--chroot=")) {
130 options.chroot = arg;
131 } else if (ConsumePrefix(&arg, "--process-name-suffix=")) {
132 options.process_name_suffix = arg;
133 } else if (arg == "--") {
134 if (i + 1 >= argc) {
135 Usage("Missing command after '--'");
136 }
137 options.command_pos = i + 1;
138 return options;
139 } else {
140 Usage("Unknown option " + std::string(arg));
141 }
142 }
143 Usage("Missing '--'");
144 }
145
DropInheritableCaps()146 Result<void> DropInheritableCaps() {
147 art::ScopedCap cap(cap_get_proc());
148 if (cap.Get() == nullptr) {
149 return ErrnoErrorf("Failed to call cap_get_proc");
150 }
151 if (cap_clear_flag(cap.Get(), CAP_INHERITABLE) != 0) {
152 return ErrnoErrorf("Failed to call cap_clear_flag");
153 }
154 if (cap_set_proc(cap.Get()) != 0) {
155 return ErrnoErrorf("Failed to call cap_set_proc");
156 }
157 return {};
158 }
159
CloseFds(const std::unordered_set<int> & keep_fds)160 Result<void> CloseFds(const std::unordered_set<int>& keep_fds) {
161 std::vector<int> open_fds;
162 std::error_code ec;
163 for (const std::filesystem::directory_entry& dir_entry :
164 std::filesystem::directory_iterator("/proc/self/fd", ec)) {
165 int fd;
166 if (!ParseInt(dir_entry.path().filename(), &fd)) {
167 return Errorf("Invalid entry in /proc/self/fd {}", dir_entry.path().filename());
168 }
169 open_fds.push_back(fd);
170 }
171 if (ec) {
172 return Errorf("Failed to list open FDs: {}", ec.message());
173 }
174 for (int fd : open_fds) {
175 if (keep_fds.find(fd) == keep_fds.end()) {
176 if (close(fd) != 0) {
177 Result<void> error = ErrnoErrorf("Failed to close FD {}", fd);
178 if (std::filesystem::exists(ART_FORMAT("/proc/self/fd/{}", fd))) {
179 return error;
180 }
181 }
182 }
183 }
184 return {};
185 }
186
187 } // namespace
188
main(int argc,char ** argv)189 int main(int argc, char** argv) {
190 android::base::InitLogging(argv);
191
192 Options options = ParseOptions(argc, argv);
193
194 if (auto result = CloseFds(options.keep_fds); !result.ok()) {
195 LOG(ERROR) << "Failed to close open FDs: " << result.error();
196 return kErrorOther;
197 }
198
199 if (!options.task_profiles.empty()) {
200 if (int ret = PaletteSetTaskProfiles(/*tid=*/0, options.task_profiles);
201 ret != PALETTE_STATUS_OK) {
202 LOG(ERROR) << "Failed to set task profile: " << ret;
203 return kErrorOther;
204 }
205 }
206
207 if (options.priority.has_value()) {
208 if (setpriority(PRIO_PROCESS, /*who=*/0, options.priority.value()) != 0) {
209 PLOG(ERROR) << "Failed to setpriority";
210 return kErrorOther;
211 }
212 }
213
214 if (options.drop_capabilities) {
215 if (auto result = DropInheritableCaps(); !result.ok()) {
216 LOG(ERROR) << "Failed to drop inheritable capabilities: " << result.error();
217 return kErrorOther;
218 }
219 }
220
221 for (const auto& [key, value] : options.envs) {
222 setenv(key.c_str(), value.c_str(), /*overwrite=*/1);
223 }
224
225 if (!options.chroot.empty()) {
226 if (chroot(options.chroot.c_str()) != 0) {
227 PLOG(ERROR) << ART_FORMAT("Failed to chroot to '{}'", options.chroot);
228 return kErrorOther;
229 }
230 }
231
232 // `argv[argc]` is `nullptr`, which `execv` needs.
233 std::vector<char*> command_args(&argv[options.command_pos], &argv[argc + 1]);
234 std::string override_program_name;
235 if (!options.process_name_suffix.empty()) {
236 override_program_name = ART_FORMAT("{} ({})", command_args[0], options.process_name_suffix);
237 command_args[0] = override_program_name.data();
238 }
239
240 execv(argv[options.command_pos], command_args.data());
241
242 // Remove the trialing `nullptr`.
243 command_args.resize(command_args.size() - 1);
244
245 PLOG(FATAL) << "Failed to execute (" << Join(command_args, ' ') << ")";
246 UNREACHABLE();
247 }
248