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 <sched.h>
     23 #include <stdio.h>
     24 #include <string.h>
     25 #include <unistd.h>
     26 #include <sys/stat.h>
     27 #include <sys/types.h>
     28 
     29 #include <string>
     30 #include <vector>
     31 
     32 static bool isTerminal = false;
     33 // Print errors to stderr if running from a terminal, otherwise print to logcat
     34 // This is useful for debugging from a terminal
     35 #define LOGE(...) do { \
     36     if (isTerminal) { \
     37         fprintf(stderr, __VA_ARGS__); \
     38         fprintf(stderr, "\n"); \
     39     } else { \
     40         ALOGE(__VA_ARGS__); \
     41     } \
     42 } while (0)
     43 
     44 static const char kNetNsDir[] = "/data/vendor/var/run/netns";
     45 
     46 class FileDescriptor {
     47 public:
     48     explicit FileDescriptor(int fd) : mFd(fd) { }
     49     FileDescriptor(const FileDescriptor&) = delete;
     50     ~FileDescriptor() {
     51         if (mFd != -1) {
     52             close(mFd);
     53             mFd = -1;
     54         }
     55     }
     56     int get() const { return mFd; }
     57     FileDescriptor& operator=(const FileDescriptor&) = delete;
     58 private:
     59     int mFd;
     60 };
     61 
     62 class File {
     63 public:
     64     explicit File(FILE* file) : mFile(file) { }
     65     File(const File&) = delete;
     66     ~File() {
     67         if (mFile) {
     68             ::fclose(mFile);
     69             mFile = nullptr;
     70         }
     71     }
     72 
     73     FILE* get() const { return mFile; }
     74     File& operator=(const File&) = delete;
     75 private:
     76     FILE* mFile;
     77 };
     78 
     79 static void printUsage(const char* program) {
     80     LOGE("%s <namespace> <program> [options...]", program);
     81 }
     82 
     83 static bool isNumericString(const char* str) {
     84     while (isdigit(*str)) {
     85         ++str;
     86     }
     87     return *str == '\0';
     88 }
     89 
     90 static std::string readNamespacePid(const char* ns) {
     91     char nsPath[PATH_MAX];
     92     snprintf(nsPath, sizeof(nsPath), "%s/%s.pid", kNetNsDir, ns);
     93 
     94     File file(::fopen(nsPath, "r"));
     95     if (file.get() == nullptr) {
     96         LOGE("Unable to open file %s for namespace %s: %s",
     97              nsPath, ns, strerror(errno));
     98         return std::string();
     99     }
    100 
    101     char buffer[32];
    102     size_t bytesRead = ::fread(buffer, 1, sizeof(buffer), file.get());
    103     if (bytesRead < sizeof(buffer) && feof(file.get())) {
    104         // Reached end-of-file, null-terminate
    105         buffer[bytesRead] = '\0';
    106         if (isNumericString(buffer)) {
    107             // File is valid and contains a number, return it
    108             return buffer;
    109         }
    110         LOGE("File %s does not contain a valid pid '%s'", nsPath, buffer);
    111     } else if (ferror(file.get())) {
    112         LOGE("Error reading from file %s: %s", nsPath, strerror(errno));
    113     } else {
    114         LOGE("Invalid contents of pid file %s", nsPath);
    115     }
    116     return std::string();
    117 }
    118 
    119 static bool setNetworkNamespace(const char* ns) {
    120     // There is a file in the net namespace dir (/data/vendor/var/run/netns) with
    121     // the name "<namespace>.pid". This file contains the pid of the createns
    122     // process that created the namespace.
    123     //
    124     // To switch network namespace we're going to call setns which requires an
    125     // open file descriptor to /proc/<pid>/ns/net where <pid> refers to a
    126     // process already running in that namespace. So using the pid from the file
    127     // above we can determine which path to use.
    128     std::string pid = readNamespacePid(ns);
    129     if (pid.empty()) {
    130         return false;
    131     }
    132     char nsPath[PATH_MAX];
    133     snprintf(nsPath, sizeof(nsPath), "/proc/%s/ns/net", pid.c_str());
    134 
    135     FileDescriptor nsFd(open(nsPath, O_RDONLY | O_CLOEXEC));
    136     if (nsFd.get() == -1) {
    137         LOGE("Cannot open network namespace '%s' at '%s': %s",
    138              ns, nsPath, strerror(errno));
    139         return false;
    140     }
    141 
    142     if (setns(nsFd.get(), CLONE_NEWNET) == -1) {
    143         LOGE("Cannot set network namespace '%s': %s",
    144              ns, strerror(errno));
    145         return false;
    146     }
    147     return true;
    148 }
    149 
    150 // Append a formatted string to the end of |buffer|. The total size in |buffer|
    151 // is |size|, including any existing string data. The string to append is
    152 // specified by |fmt| and any additional arguments required by the format
    153 // string. If the function fails it returns -1, otherwise it returns the number
    154 // of characters printed (excluding the terminating NULL). On success the
    155 // string is always null-terminated.
    156 static int sncatf(char* buffer, size_t size, const char* fmt, ...) {
    157     size_t len = strnlen(buffer, size);
    158     if (len >= size) {
    159         // The length exceeds the available size, if len == size then there is
    160         // also a terminating null after len bytes which would then be outside
    161         // the provided buffer.
    162         return -1;
    163     }
    164 
    165     va_list args;
    166     va_start(args, fmt);
    167     int printed = vsnprintf(buffer + len, size - len, fmt, args);
    168     buffer[size - 1] = '\0';
    169     va_end(args);
    170     return printed;
    171 }
    172 
    173 /**
    174  * Execute a given |command| with |argc| number of parameters that are located
    175  * in |argv|. The first parameter in |argv| is the command that should be run
    176  * followed by its arguments.
    177  */
    178 static int execCommand( int argc, char** argv) {
    179     if (argc <= 0 || argv == nullptr || argv[0] == nullptr) {
    180         LOGE("No command specified");
    181         return 1;
    182     }
    183 
    184     std::vector<char*> arguments;
    185     // Place all the arguments in the vector and the terminating null
    186     arguments.insert(arguments.begin(), argv, argv + argc);
    187     arguments.push_back(nullptr);
    188 
    189     char buffer[4096];
    190     if (execvp(argv[0], arguments.data()) == -1) {
    191         // Save errno in case it gets changed by printing stuff.
    192         int error = errno;
    193         int printed = snprintf(buffer, sizeof(buffer),
    194                                "Could not execute command '%s", argv[0]);
    195         if (printed < 0) {
    196             LOGE("Could not execute command: %s", strerror(error));
    197             return error;
    198         }
    199         for (int i = 1; i < argc; ++i) {
    200             // Be nice to the user and print quotes if there are spaces to
    201             // indicate how we saw it. If there are already single quotes in
    202             // there confusion will ensue.
    203             if (strchr(argv[i], ' ')) {
    204                 sncatf(buffer, sizeof(buffer), " \"%s\"", argv[i]);
    205             } else {
    206                 sncatf(buffer, sizeof(buffer), " %s", argv[i]);
    207             }
    208         }
    209         sncatf(buffer, sizeof(buffer), "': %s", strerror(error));
    210         LOGE("%s", buffer);
    211         return error;
    212     }
    213     // execvp never returns unless it fails so this is just to return something.
    214     return 0;
    215 }
    216 
    217 /**
    218  * Enter a given network namespace argv[1] and execute command argv[2] with
    219  * options argv[3..argc-1] in that namespace.
    220  */
    221 int main(int argc, char* argv[]) {
    222     isTerminal = isatty(STDOUT_FILENO) != 0;
    223     if (argc < 3) {
    224         printUsage(argv[0]);
    225         return 1;
    226     }
    227 
    228     // First set the new network namespace for this process
    229     if (!setNetworkNamespace(argv[1])) {
    230         return 1;
    231     }
    232 
    233     // Now run the command with all the remaining parameters
    234     return execCommand(argc - 2, &argv[2]);
    235 }
    236 
    237