summaryrefslogtreecommitdiff
path: root/libarttools/art_exec.cc
blob: 715ab399a50964b0dec59989e9928fada354e368 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/*
 * Copyright (C) 2022 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 <stdlib.h>
#include <sys/capability.h>
#include <sys/resource.h>
#include <unistd.h>

#include <filesystem>
#include <iostream>
#include <iterator>
#include <optional>
#include <string>
#include <string_view>
#include <system_error>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "android-base/logging.h"
#include "android-base/parseint.h"
#include "android-base/result.h"
#include "android-base/strings.h"
#include "base/macros.h"
#include "base/os.h"
#include "base/scoped_cap.h"
#include "palette/palette.h"
#include "system/thread_defs.h"

namespace {

using ::android::base::ConsumePrefix;
using ::android::base::Join;
using ::android::base::ParseInt;
using ::android::base::Result;
using ::android::base::Split;
using ::art::OS;

constexpr const char* kUsage =
    R"(A wrapper binary that configures the process and executes a command.

By default, it closes all open file descriptors except stdin, stdout, and stderr. `--keep-fds` can
be passed to keep some more file descriptors open.

Usage: art_exec [OPTIONS]... -- [COMMAND]...

Supported options:
  --help: Print this text.
  --set-task-profile=PROFILES: Apply a set of task profiles (see
      https://source.android.com/devices/tech/perf/cgroups). Requires root access. PROFILES can be a
      comma-separated list of task profile names.
  --set-priority=PRIORITY: Apply the process priority. Currently, the only supported value of
      PRIORITY is "background".
  --drop-capabilities: Drop all root capabilities. Note that this has effect only if `art_exec` runs
      with some root capabilities but not as the root user.
  --keep-fds=FILE_DESCRIPTORS: A semicolon-separated list of file descriptors to keep open.
  --env=KEY=VALUE: Set an environment variable. This flag can be passed multiple times to set
      multiple environment variables.
  --process-name-suffix=SUFFIX: Add a suffix in parentheses to argv[0] when calling `execv`. This
      suffix will show up as part of the process name in tombstone when the process crashes.
)";

constexpr int kErrorUsage = 100;
constexpr int kErrorOther = 101;

struct Options {
  int command_pos = -1;
  std::vector<std::string> task_profiles;
  std::optional<int> priority = std::nullopt;
  bool drop_capabilities = false;
  std::unordered_set<int> keep_fds{fileno(stdin), fileno(stdout), fileno(stderr)};
  std::unordered_map<std::string, std::string> envs;
  std::string chroot;
  std::string process_name_suffix;
};

[[noreturn]] void Usage(const std::string& error_msg) {
  LOG(ERROR) << error_msg;
  std::cerr << error_msg << "\n" << kUsage << "\n";
  exit(kErrorUsage);
}

Options ParseOptions(int argc, char** argv) {
  Options options;
  for (int i = 1; i < argc; i++) {
    std::string_view arg = argv[i];
    if (arg == "--help") {
      std::cerr << kUsage << "\n";
      exit(0);
    } else if (ConsumePrefix(&arg, "--set-task-profile=")) {
      options.task_profiles = Split(std::string(arg), ",");
      if (options.task_profiles.empty()) {
        Usage("Empty task profile list");
      }
    } else if (ConsumePrefix(&arg, "--set-priority=")) {
      if (arg == "background") {
        options.priority = ANDROID_PRIORITY_BACKGROUND;
      } else {
        Usage("Unknown priority " + std::string(arg));
      }
    } else if (arg == "--drop-capabilities") {
      options.drop_capabilities = true;
    } else if (ConsumePrefix(&arg, "--keep-fds=")) {
      for (const std::string& fd_str : Split(std::string(arg), ":")) {
        int fd;
        if (!ParseInt(fd_str, &fd)) {
          Usage("Invalid fd " + fd_str);
        }
        options.keep_fds.insert(fd);
      }
    } else if (ConsumePrefix(&arg, "--env=")) {
      size_t pos = arg.find('=');
      if (pos == std::string_view::npos) {
        Usage("Malformed environment variable. Must contain '='");
      }
      options.envs[std::string(arg.substr(/*pos=*/0, /*n=*/pos))] =
          std::string(arg.substr(pos + 1));
    } else if (ConsumePrefix(&arg, "--chroot=")) {
      options.chroot = arg;
    } else if (ConsumePrefix(&arg, "--process-name-suffix=")) {
      options.process_name_suffix = arg;
    } else if (arg == "--") {
      if (i + 1 >= argc) {
        Usage("Missing command after '--'");
      }
      options.command_pos = i + 1;
      return options;
    } else {
      Usage("Unknown option " + std::string(arg));
    }
  }
  Usage("Missing '--'");
}

Result<void> DropInheritableCaps() {
  art::ScopedCap cap(cap_get_proc());
  if (cap.Get() == nullptr) {
    return ErrnoErrorf("Failed to call cap_get_proc");
  }
  if (cap_clear_flag(cap.Get(), CAP_INHERITABLE) != 0) {
    return ErrnoErrorf("Failed to call cap_clear_flag");
  }
  if (cap_set_proc(cap.Get()) != 0) {
    return ErrnoErrorf("Failed to call cap_set_proc");
  }
  return {};
}

Result<void> CloseFds(const std::unordered_set<int>& keep_fds) {
  std::vector<int> open_fds;
  std::error_code ec;
  for (const std::filesystem::directory_entry& dir_entry :
       std::filesystem::directory_iterator("/proc/self/fd", ec)) {
    int fd;
    if (!ParseInt(dir_entry.path().filename(), &fd)) {
      return Errorf("Invalid entry in /proc/self/fd {}", dir_entry.path().filename());
    }
    open_fds.push_back(fd);
  }
  if (ec) {
    return Errorf("Failed to list open FDs: {}", ec.message());
  }
  for (int fd : open_fds) {
    if (keep_fds.find(fd) == keep_fds.end()) {
      if (close(fd) != 0) {
        Result<void> error = ErrnoErrorf("Failed to close FD {}", fd);
        if (std::filesystem::exists(ART_FORMAT("/proc/self/fd/{}", fd))) {
          return error;
        }
      }
    }
  }
  return {};
}

}  // namespace

int main(int argc, char** argv) {
  android::base::InitLogging(argv);

  Options options = ParseOptions(argc, argv);

  if (auto result = CloseFds(options.keep_fds); !result.ok()) {
    LOG(ERROR) << "Failed to close open FDs: " << result.error();
    return kErrorOther;
  }

  if (!options.task_profiles.empty()) {
    if (int ret = PaletteSetTaskProfiles(/*tid=*/0, options.task_profiles);
        ret != PALETTE_STATUS_OK) {
      LOG(ERROR) << "Failed to set task profile: " << ret;
      return kErrorOther;
    }
  }

  if (options.priority.has_value()) {
    if (setpriority(PRIO_PROCESS, /*who=*/0, options.priority.value()) != 0) {
      PLOG(ERROR) << "Failed to setpriority";
      return kErrorOther;
    }
  }

  if (options.drop_capabilities) {
    if (auto result = DropInheritableCaps(); !result.ok()) {
      LOG(ERROR) << "Failed to drop inheritable capabilities: " << result.error();
      return kErrorOther;
    }
  }

  for (const auto& [key, value] : options.envs) {
    setenv(key.c_str(), value.c_str(), /*overwrite=*/1);
  }

  if (!options.chroot.empty()) {
    if (chroot(options.chroot.c_str()) != 0) {
      PLOG(ERROR) << ART_FORMAT("Failed to chroot to '{}'", options.chroot);
      return kErrorOther;
    }
  }

  // `argv[argc]` is `nullptr`, which `execv` needs.
  std::vector<char*> command_args(&argv[options.command_pos], &argv[argc + 1]);
  std::string program_path = argv[options.command_pos];
  // "/mnt/compat_env" is prepared by dexopt_chroot_setup on Android V.
  constexpr const char* kCompatArtdPath = "/mnt/compat_env/apex/com.android.art/bin/artd";
  if (program_path == "/apex/com.android.art/bin/artd" && OS::FileExists(kCompatArtdPath)) {
    LOG(INFO) << "Overriding program path to " << kCompatArtdPath;
    program_path = kCompatArtdPath;
    command_args[0] = program_path.data();
  }
  std::string override_program_name;
  if (!options.process_name_suffix.empty()) {
    override_program_name = ART_FORMAT("{} ({})", command_args[0], options.process_name_suffix);
    command_args[0] = override_program_name.data();
  }

  execv(program_path.c_str(), command_args.data());

  // Remove the trialing `nullptr`.
  command_args.resize(command_args.size() - 1);

  PLOG(FATAL) << "Failed to execute (" << Join(command_args, ' ') << ")";
  UNREACHABLE();
}