Home | History | Annotate | Download | only in execns
      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