processgroup.cpp revision 32375c23284704a7d044ed79060c4431468b4b4e
1/* 2 * Copyright 2014 Google, Inc 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//#define LOG_NDEBUG 0 18#define LOG_TAG "libprocessgroup" 19 20#include <assert.h> 21#include <dirent.h> 22#include <errno.h> 23#include <fcntl.h> 24#include <inttypes.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <sys/stat.h> 29#include <sys/types.h> 30#include <unistd.h> 31 32#include <chrono> 33#include <memory> 34#include <mutex> 35#include <set> 36#include <thread> 37 38#include <android-base/logging.h> 39#include <android-base/unique_fd.h> 40#include <private/android_filesystem_config.h> 41 42#include <processgroup/processgroup.h> 43 44using namespace std::chrono_literals; 45 46#define MEM_CGROUP_PATH "/dev/memcg/apps" 47#define MEM_CGROUP_TASKS "/dev/memcg/apps/tasks" 48#define ACCT_CGROUP_PATH "/acct" 49 50#define PROCESSGROUP_UID_PREFIX "uid_" 51#define PROCESSGROUP_PID_PREFIX "pid_" 52#define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs" 53#define PROCESSGROUP_MAX_UID_LEN 11 54#define PROCESSGROUP_MAX_PID_LEN 11 55#define PROCESSGROUP_MAX_PATH_LEN \ 56 ((sizeof(MEM_CGROUP_PATH) > sizeof(ACCT_CGROUP_PATH) ? \ 57 sizeof(MEM_CGROUP_PATH) : sizeof(ACCT_CGROUP_PATH)) + \ 58 sizeof(PROCESSGROUP_UID_PREFIX) + 1 + \ 59 PROCESSGROUP_MAX_UID_LEN + \ 60 sizeof(PROCESSGROUP_PID_PREFIX) + 1 + \ 61 PROCESSGROUP_MAX_PID_LEN + \ 62 sizeof(PROCESSGROUP_CGROUP_PROCS_FILE) + \ 63 1) 64 65std::once_flag init_path_flag; 66 67class ProcessGroup { 68 public: 69 ProcessGroup() : buf_ptr_(buf_), buf_len_(0) {} 70 71 bool Open(uid_t uid, int pid); 72 73 // Return positive number and sets *pid = next pid in process cgroup on success 74 // Returns 0 if there are no pids left in the process cgroup 75 // Returns -errno if an error was encountered 76 int GetOneAppProcess(pid_t* pid); 77 78 private: 79 // Returns positive number of bytes filled on success 80 // Returns 0 if there was nothing to read 81 // Returns -errno if an error was encountered 82 int RefillBuffer(); 83 84 android::base::unique_fd fd_; 85 char buf_[128]; 86 char* buf_ptr_; 87 size_t buf_len_; 88}; 89 90static const char* getCgroupRootPath() { 91 static const char* cgroup_root_path = NULL; 92 std::call_once(init_path_flag, [&]() { 93 // Check if mem cgroup is mounted, only then check for write-access to avoid 94 // SELinux denials 95 cgroup_root_path = access(MEM_CGROUP_TASKS, F_OK) || access(MEM_CGROUP_PATH, W_OK) ? 96 ACCT_CGROUP_PATH : MEM_CGROUP_PATH; 97 }); 98 return cgroup_root_path; 99} 100 101static int convertUidToPath(char *path, size_t size, uid_t uid) 102{ 103 return snprintf(path, size, "%s/%s%d", 104 getCgroupRootPath(), 105 PROCESSGROUP_UID_PREFIX, 106 uid); 107} 108 109static int convertUidPidToPath(char *path, size_t size, uid_t uid, int pid) 110{ 111 return snprintf(path, size, "%s/%s%d/%s%d", 112 getCgroupRootPath(), 113 PROCESSGROUP_UID_PREFIX, 114 uid, 115 PROCESSGROUP_PID_PREFIX, 116 pid); 117} 118 119bool ProcessGroup::Open(uid_t uid, int pid) { 120 char path[PROCESSGROUP_MAX_PATH_LEN] = {0}; 121 convertUidPidToPath(path, sizeof(path), uid, pid); 122 strlcat(path, PROCESSGROUP_CGROUP_PROCS_FILE, sizeof(path)); 123 124 int fd = open(path, O_RDONLY); 125 if (fd < 0) return false; 126 127 fd_.reset(fd); 128 129 LOG(VERBOSE) << "Initialized context for " << path; 130 131 return true; 132} 133 134int ProcessGroup::RefillBuffer() { 135 memmove(buf_, buf_ptr_, buf_len_); 136 buf_ptr_ = buf_; 137 138 ssize_t ret = read(fd_, buf_ptr_ + buf_len_, sizeof(buf_) - buf_len_ - 1); 139 if (ret < 0) { 140 return -errno; 141 } else if (ret == 0) { 142 return 0; 143 } 144 145 buf_len_ += ret; 146 buf_[buf_len_] = 0; 147 LOG(VERBOSE) << "Read " << ret << " to buffer: " << buf_; 148 149 assert(buf_len_ <= sizeof(buf_)); 150 151 return ret; 152} 153 154int ProcessGroup::GetOneAppProcess(pid_t* out_pid) { 155 *out_pid = 0; 156 157 char* eptr; 158 while ((eptr = static_cast<char*>(memchr(buf_ptr_, '\n', buf_len_))) == nullptr) { 159 int ret = RefillBuffer(); 160 if (ret <= 0) return ret; 161 } 162 163 *eptr = '\0'; 164 char* pid_eptr = nullptr; 165 errno = 0; 166 long pid = strtol(buf_ptr_, &pid_eptr, 10); 167 if (errno != 0) { 168 return -errno; 169 } 170 if (pid_eptr != eptr) { 171 errno = EINVAL; 172 return -errno; 173 } 174 175 buf_len_ -= (eptr - buf_ptr_) + 1; 176 buf_ptr_ = eptr + 1; 177 178 *out_pid = static_cast<pid_t>(pid); 179 return 1; 180} 181 182static int removeProcessGroup(uid_t uid, int pid) 183{ 184 int ret; 185 char path[PROCESSGROUP_MAX_PATH_LEN] = {0}; 186 187 convertUidPidToPath(path, sizeof(path), uid, pid); 188 ret = rmdir(path); 189 190 convertUidToPath(path, sizeof(path), uid); 191 rmdir(path); 192 193 return ret; 194} 195 196static void removeUidProcessGroups(const char *uid_path) 197{ 198 std::unique_ptr<DIR, decltype(&closedir)> uid(opendir(uid_path), closedir); 199 if (uid != NULL) { 200 dirent* dir; 201 while ((dir = readdir(uid.get())) != nullptr) { 202 char path[PROCESSGROUP_MAX_PATH_LEN]; 203 204 if (dir->d_type != DT_DIR) { 205 continue; 206 } 207 208 if (strncmp(dir->d_name, PROCESSGROUP_PID_PREFIX, strlen(PROCESSGROUP_PID_PREFIX))) { 209 continue; 210 } 211 212 snprintf(path, sizeof(path), "%s/%s", uid_path, dir->d_name); 213 LOG(VERBOSE) << "Removing " << path; 214 if (rmdir(path) == -1) PLOG(WARNING) << "Failed to remove " << path; 215 } 216 } 217} 218 219void removeAllProcessGroups() 220{ 221 LOG(VERBOSE) << "removeAllProcessGroups()"; 222 const char* cgroup_root_path = getCgroupRootPath(); 223 std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path), closedir); 224 if (root == NULL) { 225 PLOG(ERROR) << "Failed to open " << cgroup_root_path; 226 } else { 227 dirent* dir; 228 while ((dir = readdir(root.get())) != nullptr) { 229 char path[PROCESSGROUP_MAX_PATH_LEN]; 230 231 if (dir->d_type != DT_DIR) { 232 continue; 233 } 234 if (strncmp(dir->d_name, PROCESSGROUP_UID_PREFIX, strlen(PROCESSGROUP_UID_PREFIX))) { 235 continue; 236 } 237 238 snprintf(path, sizeof(path), "%s/%s", cgroup_root_path, dir->d_name); 239 removeUidProcessGroups(path); 240 LOG(VERBOSE) << "Removing " << path; 241 if (rmdir(path) == -1) PLOG(WARNING) << "Failed to remove " << path; 242 } 243 } 244} 245 246// Returns number of processes killed on success 247// Returns 0 if there are no processes in the process cgroup left to kill 248// Returns -errno on error 249static int doKillProcessGroupOnce(uid_t uid, int initialPid, int signal) { 250 ProcessGroup process_group; 251 if (!process_group.Open(uid, initialPid)) { 252 PLOG(WARNING) << "Failed to open process cgroup uid " << uid << " pid " << initialPid; 253 return -errno; 254 } 255 256 // We separate all of the pids in the cgroup into those pids that are also the leaders of 257 // process groups (stored in the pgids set) and those that are not (stored in the pids set). 258 std::set<pid_t> pgids; 259 pgids.emplace(initialPid); 260 std::set<pid_t> pids; 261 262 int ret; 263 pid_t pid; 264 int processes = 0; 265 while ((ret = process_group.GetOneAppProcess(&pid)) > 0 && pid >= 0) { 266 processes++; 267 if (pid == 0) { 268 // Should never happen... but if it does, trying to kill this 269 // will boomerang right back and kill us! Let's not let that happen. 270 LOG(WARNING) << "Yikes, we've been told to kill pid 0! How about we don't do that?"; 271 continue; 272 } 273 pid_t pgid = getpgid(pid); 274 if (pgid == -1) PLOG(ERROR) << "getpgid(" << pid << ") failed"; 275 if (pgid == pid) { 276 pgids.emplace(pid); 277 } else { 278 pids.emplace(pid); 279 } 280 } 281 282 // Erase all pids that will be killed when we kill the process groups. 283 for (auto it = pids.begin(); it != pids.end();) { 284 pid_t pgid = getpgid(pid); 285 if (pgids.count(pgid) == 1) { 286 it = pids.erase(it); 287 } else { 288 ++it; 289 } 290 } 291 292 // Kill all process groups. 293 for (const auto pgid : pgids) { 294 LOG(VERBOSE) << "Killing process group " << -pgid << " in uid " << uid 295 << " as part of process cgroup " << initialPid; 296 297 if (kill(-pgid, signal) == -1) { 298 PLOG(WARNING) << "kill(" << -pgid << ", " << signal << ") failed"; 299 } 300 } 301 302 // Kill remaining pids. 303 for (const auto pid : pids) { 304 LOG(VERBOSE) << "Killing pid " << pid << " in uid " << uid << " as part of process cgroup " 305 << initialPid; 306 307 if (kill(pid, signal) == -1) { 308 PLOG(WARNING) << "kill(" << pid << ", " << signal << ") failed"; 309 } 310 } 311 312 return ret >= 0 ? processes : ret; 313} 314 315static int killProcessGroup(uid_t uid, int initialPid, int signal, int retries) { 316 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); 317 318 int retry = retries; 319 int processes; 320 while ((processes = doKillProcessGroupOnce(uid, initialPid, signal)) > 0) { 321 LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid; 322 if (retry > 0) { 323 std::this_thread::sleep_for(5ms); 324 --retry; 325 } else { 326 break; 327 } 328 } 329 330 if (processes < 0) { 331 PLOG(ERROR) << "Error encountered killing process cgroup uid " << uid << " pid " 332 << initialPid; 333 return -1; 334 } 335 336 std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); 337 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); 338 339 // We only calculate the number of 'processes' when killing the processes. 340 // In the retries == 0 case, we only kill the processes once and therefore 341 // will not have waited then recalculated how many processes are remaining 342 // after the first signals have been sent. 343 // Logging anything regarding the number of 'processes' here does not make sense. 344 345 if (processes == 0) { 346 if (retries > 0) { 347 LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid 348 << " in " << static_cast<int>(ms) << "ms"; 349 } 350 return removeProcessGroup(uid, initialPid); 351 } else { 352 if (retries > 0) { 353 LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid 354 << " in " << static_cast<int>(ms) << "ms, " << processes 355 << " processes remain"; 356 } 357 return -1; 358 } 359} 360 361int killProcessGroup(uid_t uid, int initialPid, int signal) { 362 return killProcessGroup(uid, initialPid, signal, 40 /*retries*/); 363} 364 365int killProcessGroupOnce(uid_t uid, int initialPid, int signal) { 366 return killProcessGroup(uid, initialPid, signal, 0 /*retries*/); 367} 368 369static bool mkdirAndChown(const char *path, mode_t mode, uid_t uid, gid_t gid) 370{ 371 if (mkdir(path, mode) == -1 && errno != EEXIST) { 372 return false; 373 } 374 375 if (chown(path, uid, gid) == -1) { 376 int saved_errno = errno; 377 rmdir(path); 378 errno = saved_errno; 379 return false; 380 } 381 382 return true; 383} 384 385int createProcessGroup(uid_t uid, int initialPid) 386{ 387 char path[PROCESSGROUP_MAX_PATH_LEN] = {0}; 388 389 convertUidToPath(path, sizeof(path), uid); 390 391 if (!mkdirAndChown(path, 0750, AID_SYSTEM, AID_SYSTEM)) { 392 PLOG(ERROR) << "Failed to make and chown " << path; 393 return -errno; 394 } 395 396 convertUidPidToPath(path, sizeof(path), uid, initialPid); 397 398 if (!mkdirAndChown(path, 0750, AID_SYSTEM, AID_SYSTEM)) { 399 PLOG(ERROR) << "Failed to make and chown " << path; 400 return -errno; 401 } 402 403 strlcat(path, PROCESSGROUP_CGROUP_PROCS_FILE, sizeof(path)); 404 405 int fd = open(path, O_WRONLY); 406 if (fd == -1) { 407 int ret = -errno; 408 PLOG(ERROR) << "Failed to open " << path; 409 return ret; 410 } 411 412 char pid[PROCESSGROUP_MAX_PID_LEN + 1] = {0}; 413 int len = snprintf(pid, sizeof(pid), "%d", initialPid); 414 415 int ret = 0; 416 if (write(fd, pid, len) < 0) { 417 ret = -errno; 418 PLOG(ERROR) << "Failed to write '" << pid << "' to " << path; 419 } 420 421 close(fd); 422 return ret; 423} 424