1 /* 2 * Copyright (C) 2017 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 #define LOG_TAG "execns" 18 #include <log/log.h> 19 20 #include <errno.h> 21 #include <fcntl.h> 22 #include <grp.h> 23 #include <pwd.h> 24 #include <sched.h> 25 #include <stdio.h> 26 #include <string.h> 27 #include <unistd.h> 28 #include <sys/stat.h> 29 #include <sys/types.h> 30 31 #include <string> 32 #include <vector> 33 34 static bool isTerminal = false; 35 // Print errors to stderr if running from a terminal, otherwise print to logcat 36 // This is useful for debugging from a terminal 37 #define LOGE(...) do { \ 38 if (isTerminal) { \ 39 fprintf(stderr, __VA_ARGS__); \ 40 fprintf(stderr, "\n"); \ 41 } else { \ 42 ALOGE(__VA_ARGS__); \ 43 } \ 44 } while (0) 45 46 static const char kNetNsDir[] = "/data/vendor/var/run/netns"; 47 48 class FileDescriptor { 49 public: 50 explicit FileDescriptor(int fd) : mFd(fd) { } 51 FileDescriptor(const FileDescriptor&) = delete; 52 ~FileDescriptor() { 53 if (mFd != -1) { 54 close(mFd); 55 mFd = -1; 56 } 57 } 58 int get() const { return mFd; } 59 FileDescriptor& operator=(const FileDescriptor&) = delete; 60 private: 61 int mFd; 62 }; 63 64 class File { 65 public: 66 explicit File(FILE* file) : mFile(file) { } 67 File(const File&) = delete; 68 ~File() { 69 if (mFile) { 70 ::fclose(mFile); 71 mFile = nullptr; 72 } 73 } 74 75 FILE* get() const { return mFile; } 76 File& operator=(const File&) = delete; 77 private: 78 FILE* mFile; 79 }; 80 81 static void printUsage(const char* program) { 82 LOGE("%s [-u user] [-g group] <namespace> <program> [options...]", program); 83 } 84 85 static bool isNumericString(const char* str) { 86 while (isdigit(*str)) { 87 ++str; 88 } 89 return *str == '\0'; 90 } 91 92 static std::string readNamespacePid(const char* ns) { 93 char nsPath[PATH_MAX]; 94 snprintf(nsPath, sizeof(nsPath), "%s/%s.pid", kNetNsDir, ns); 95 96 File file(::fopen(nsPath, "r")); 97 if (file.get() == nullptr) { 98 LOGE("Unable to open file %s for namespace %s: %s", 99 nsPath, ns, strerror(errno)); 100 return std::string(); 101 } 102 103 char buffer[32]; 104 size_t bytesRead = ::fread(buffer, 1, sizeof(buffer), file.get()); 105 if (bytesRead < sizeof(buffer) && feof(file.get())) { 106 // Reached end-of-file, null-terminate 107 buffer[bytesRead] = '\0'; 108 if (isNumericString(buffer)) { 109 // File is valid and contains a number, return it 110 return buffer; 111 } 112 LOGE("File %s does not contain a valid pid '%s'", nsPath, buffer); 113 } else if (ferror(file.get())) { 114 LOGE("Error reading from file %s: %s", nsPath, strerror(errno)); 115 } else { 116 LOGE("Invalid contents of pid file %s", nsPath); 117 } 118 return std::string(); 119 } 120 121 static bool setNetworkNamespace(const char* ns) { 122 // There is a file in the net namespace dir (/data/vendor/var/run/netns) with 123 // the name "<namespace>.pid". This file contains the pid of the createns 124 // process that created the namespace. 125 // 126 // To switch network namespace we're going to call setns which requires an 127 // open file descriptor to /proc/<pid>/ns/net where <pid> refers to a 128 // process already running in that namespace. So using the pid from the file 129 // above we can determine which path to use. 130 std::string pid = readNamespacePid(ns); 131 if (pid.empty()) { 132 return false; 133 } 134 char nsPath[PATH_MAX]; 135 snprintf(nsPath, sizeof(nsPath), "/proc/%s/ns/net", pid.c_str()); 136 137 FileDescriptor nsFd(open(nsPath, O_RDONLY | O_CLOEXEC)); 138 if (nsFd.get() == -1) { 139 LOGE("Cannot open network namespace '%s' at '%s': %s", 140 ns, nsPath, strerror(errno)); 141 return false; 142 } 143 144 if (setns(nsFd.get(), CLONE_NEWNET) == -1) { 145 LOGE("Cannot set network namespace '%s': %s", 146 ns, strerror(errno)); 147 return false; 148 } 149 return true; 150 } 151 152 static bool changeUser(const char* user) { 153 struct passwd* pwd = ::getpwnam(user); 154 if (pwd == nullptr) { 155 LOGE("Could not find user '%s'", user); 156 return false; 157 } 158 159 if (::setuid(pwd->pw_uid) != 0) { 160 LOGE("Cannot switch to user '%s': %s", user, strerror(errno)); 161 return false; 162 } 163 return true; 164 } 165 166 static bool changeGroup(const char* group) { 167 struct group* grp = ::getgrnam(group); 168 if (grp == nullptr) { 169 LOGE("Could not find group '%s'", group); 170 return false; 171 } 172 173 if (::setgid(grp->gr_gid) != 0) { 174 LOGE("Cannot switch to group '%s': %s", group, strerror(errno)); 175 return false; 176 } 177 return true; 178 } 179 180 // Append a formatted string to the end of |buffer|. The total size in |buffer| 181 // is |size|, including any existing string data. The string to append is 182 // specified by |fmt| and any additional arguments required by the format 183 // string. If the function fails it returns -1, otherwise it returns the number 184 // of characters printed (excluding the terminating NULL). On success the 185 // string is always null-terminated. 186 static int sncatf(char* buffer, size_t size, const char* fmt, ...) { 187 size_t len = strnlen(buffer, size); 188 if (len >= size) { 189 // The length exceeds the available size, if len == size then there is 190 // also a terminating null after len bytes which would then be outside 191 // the provided buffer. 192 return -1; 193 } 194 195 va_list args; 196 va_start(args, fmt); 197 int printed = vsnprintf(buffer + len, size - len, fmt, args); 198 buffer[size - 1] = '\0'; 199 va_end(args); 200 return printed; 201 } 202 203 /** 204 * Execute a given |command| with |argc| number of parameters that are located 205 * in |argv|. The first parameter in |argv| is the command that should be run 206 * followed by its arguments. 207 */ 208 static int execCommand( int argc, char** argv) { 209 if (argc <= 0 || argv == nullptr || argv[0] == nullptr) { 210 LOGE("No command specified"); 211 return 1; 212 } 213 214 std::vector<char*> arguments; 215 // Place all the arguments in the vector and the terminating null 216 arguments.insert(arguments.begin(), argv, argv + argc); 217 arguments.push_back(nullptr); 218 219 char buffer[4096]; 220 if (execvp(argv[0], arguments.data()) == -1) { 221 // Save errno in case it gets changed by printing stuff. 222 int error = errno; 223 int printed = snprintf(buffer, sizeof(buffer), 224 "Could not execute command '%s", argv[0]); 225 if (printed < 0) { 226 LOGE("Could not execute command: %s", strerror(error)); 227 return error; 228 } 229 for (int i = 1; i < argc; ++i) { 230 // Be nice to the user and print quotes if there are spaces to 231 // indicate how we saw it. If there are already single quotes in 232 // there confusion will ensue. 233 if (strchr(argv[i], ' ')) { 234 sncatf(buffer, sizeof(buffer), " \"%s\"", argv[i]); 235 } else { 236 sncatf(buffer, sizeof(buffer), " %s", argv[i]); 237 } 238 } 239 sncatf(buffer, sizeof(buffer), "': %s", strerror(error)); 240 LOGE("%s", buffer); 241 return error; 242 } 243 // execvp never returns unless it fails so this is just to return something. 244 return 0; 245 } 246 247 /** 248 * Enter a given network namespace argv[1] and execute command argv[2] with 249 * options argv[3..argc-1] in that namespace. 250 */ 251 int main(int argc, char* argv[]) { 252 isTerminal = isatty(STDOUT_FILENO) != 0; 253 254 // Parse parameters 255 const char* user = nullptr; 256 const char* group = nullptr; 257 int nsArg = -1; 258 int execArg = -1; 259 for (int i = 1; i < argc; ++i) { 260 if (::strcmp(argv[i], "-u") == 0) { 261 if (user || i + 1 >= argc) { 262 LOGE("Missing argument to option -u"); 263 return 1; 264 } 265 user = argv[++i]; 266 } else if (::strcmp(argv[i], "-g") == 0) { 267 if (group || i + 1 >= argc) { 268 LOGE("Missing argument to option -g"); 269 return 1; 270 } 271 group = argv[++i]; 272 } else { 273 // Break on the first non-option and treat it as the namespace name 274 nsArg = i; 275 if (i + 1 < argc) { 276 execArg = i + 1; 277 } 278 break; 279 } 280 } 281 282 if (nsArg < 0 || execArg < 0) { 283 // Missing namespace and/or exec arguments 284 printUsage(argv[0]); 285 return 1; 286 } 287 288 // First set the new network namespace for this process 289 if (!setNetworkNamespace(argv[nsArg])) { 290 return 1; 291 } 292 293 // Changing namespace is the privileged operation, so now we can drop 294 // privileges by changing user and/or group if the user requested it. Note 295 // that it's important to change group first because it must be done as a 296 // privileged user. Otherwise an attacker might be able to restore group 297 // privileges by using the group ID that is saved by setgid when running 298 // as a non-privileged user. 299 if (group && !changeGroup(group)) { 300 return 1; 301 } 302 303 if (user && !changeUser(user)) { 304 return 1; 305 } 306 307 // Now run the command with all the remaining parameters 308 return execCommand(argc - execArg, &argv[execArg]); 309 } 310 311