Botan  1.11.15
src/lib/entropy/unix_procs/unix_procs.cpp
Go to the documentation of this file.
00001  /*
00002 * Gather entropy by running various system commands in the hopes that
00003 * some of the output cannot be guessed by a remote attacker.
00004 *
00005 * (C) 1999-2009,2013 Jack Lloyd
00006 *     2012 Markus Wanner
00007 *
00008 * Botan is released under the Simplified BSD License (see license.txt)
00009 */
00010 
00011 #include <botan/internal/unix_procs.h>
00012 #include <botan/parsing.h>
00013 #include <algorithm>
00014 #include <atomic>
00015 
00016 #include <sys/time.h>
00017 #include <sys/stat.h>
00018 #include <sys/wait.h>
00019 #include <sys/resource.h>
00020 #include <unistd.h>
00021 #include <signal.h>
00022 #include <stdlib.h>
00023 
00024 namespace Botan {
00025 
00026 namespace {
00027 
00028 std::string find_full_path_if_exists(const std::vector<std::string>& trusted_path,
00029                                      const std::string& proc)
00030    {
00031    for(auto dir : trusted_path)
00032       {
00033       const std::string full_path = dir + "/" + proc;
00034       if(::access(full_path.c_str(), X_OK) == 0)
00035          return full_path;
00036       }
00037 
00038    return "";
00039    }
00040 
00041 size_t concurrent_processes(size_t user_request)
00042    {
00043    const size_t DEFAULT_CONCURRENT = 2;
00044    const size_t MAX_CONCURRENT = 8;
00045 
00046    if(user_request > 0 && user_request < MAX_CONCURRENT)
00047       return user_request;
00048 
00049    const long online_cpus = ::sysconf(_SC_NPROCESSORS_ONLN);
00050 
00051    if(online_cpus > 0)
00052       return static_cast<size_t>(online_cpus); // maybe fewer?
00053 
00054    return DEFAULT_CONCURRENT;
00055    }
00056 
00057 }
00058 
00059 /**
00060 * Unix_EntropySource Constructor
00061 */
00062 Unix_EntropySource::Unix_EntropySource(const std::vector<std::string>& trusted_path,
00063                                        size_t proc_cnt) :
00064    m_trusted_paths(trusted_path),
00065    m_concurrent(concurrent_processes(proc_cnt))
00066    {
00067    }
00068 
00069 void UnixProcessInfo_EntropySource::poll(Entropy_Accumulator& accum)
00070    {
00071    static std::atomic<int> last_pid;
00072 
00073    int pid = ::getpid();
00074 
00075    accum.add(pid, 0.0);
00076 
00077    if(pid != last_pid)
00078       {
00079       last_pid = pid;
00080       accum.add(::getppid(), 0.0);
00081       accum.add(::getuid(),  0.0);
00082       accum.add(::getgid(),  0.0);
00083       accum.add(::getsid(0),  0.0);
00084       accum.add(::getpgrp(), 0.0);
00085       }
00086 
00087    struct ::rusage usage;
00088    ::getrusage(RUSAGE_SELF, &usage);
00089    accum.add(usage, 0.0);
00090    }
00091 
00092 namespace {
00093 
00094 void do_exec(const std::vector<std::string>& args)
00095    {
00096    // cleaner way to do this?
00097    const char* arg0 = (args.size() > 0) ? args[0].c_str() : nullptr;
00098    const char* arg1 = (args.size() > 1) ? args[1].c_str() : nullptr;
00099    const char* arg2 = (args.size() > 2) ? args[2].c_str() : nullptr;
00100    const char* arg3 = (args.size() > 3) ? args[3].c_str() : nullptr;
00101    const char* arg4 = (args.size() > 4) ? args[4].c_str() : nullptr;
00102 
00103    ::execl(arg0, arg0, arg1, arg2, arg3, arg4, NULL);
00104    }
00105 
00106 }
00107 
00108 void Unix_EntropySource::Unix_Process::spawn(const std::vector<std::string>& args)
00109    {
00110    shutdown();
00111 
00112    int pipe[2];
00113    if(::pipe(pipe) != 0)
00114       return;
00115 
00116    pid_t pid = ::fork();
00117 
00118    if(pid == -1)
00119       {
00120       ::close(pipe[0]);
00121       ::close(pipe[1]);
00122       }
00123    else if(pid > 0) // in parent
00124       {
00125       m_pid = pid;
00126       m_fd = pipe[0];
00127       ::close(pipe[1]);
00128       }
00129    else // in child
00130       {
00131       if(::dup2(pipe[1], STDOUT_FILENO) == -1)
00132          ::exit(127);
00133       if(::close(pipe[0]) != 0 || ::close(pipe[1]) != 0)
00134          ::exit(127);
00135       if(close(STDERR_FILENO) != 0)
00136          ::exit(127);
00137 
00138       do_exec(args);
00139       ::exit(127);
00140       }
00141    }
00142 
00143 void Unix_EntropySource::Unix_Process::shutdown()
00144    {
00145    if(m_pid == -1)
00146       return;
00147 
00148    ::close(m_fd);
00149    m_fd = -1;
00150 
00151    pid_t reaped = waitpid(m_pid, nullptr, WNOHANG);
00152 
00153    if(reaped == 0)
00154       {
00155       /*
00156       * Child is still alive - send it SIGTERM, sleep for a bit and
00157       * try to reap again, if still alive send SIGKILL
00158       */
00159       kill(m_pid, SIGTERM);
00160 
00161       struct ::timeval tv;
00162       tv.tv_sec = 0;
00163       tv.tv_usec = 1000;
00164       select(0, nullptr, nullptr, nullptr, &tv);
00165 
00166       reaped = ::waitpid(m_pid, nullptr, WNOHANG);
00167 
00168       if(reaped == 0)
00169          {
00170          ::kill(m_pid, SIGKILL);
00171          do
00172             reaped = ::waitpid(m_pid, nullptr, 0);
00173          while(reaped == -1);
00174          }
00175       }
00176 
00177    m_pid = -1;
00178    }
00179 
00180 const std::vector<std::string>& Unix_EntropySource::next_source()
00181    {
00182    const auto& src = m_sources.at(m_sources_idx);
00183    m_sources_idx = (m_sources_idx + 1) % m_sources.size();
00184    return src;
00185    }
00186 
00187 void Unix_EntropySource::poll(Entropy_Accumulator& accum)
00188    {
00189    // refuse to run setuid or setgid, or as root
00190    if((getuid() != geteuid()) || (getgid() != getegid()) || (geteuid() == 0))
00191       return;
00192 
00193    std::lock_guard<std::mutex> lock(m_mutex);
00194 
00195    if(m_sources.empty())
00196       {
00197       auto sources = get_default_sources();
00198 
00199       for(auto src : sources)
00200          {
00201          const std::string path = find_full_path_if_exists(m_trusted_paths, src[0]);
00202          if(path != "")
00203             {
00204             src[0] = path;
00205             m_sources.push_back(src);
00206             }
00207          }
00208       }
00209 
00210    if(m_sources.empty())
00211       return; // still empty, really nothing to try
00212 
00213    const size_t MS_WAIT_TIME = 32;
00214    const double ENTROPY_ESTIMATE = 1.0 / 1024;
00215 
00216    secure_vector<byte>& io_buffer = accum.get_io_buffer(4*1024); // page
00217 
00218    while(!accum.polling_goal_achieved())
00219       {
00220       while(m_procs.size() < m_concurrent)
00221          m_procs.emplace_back(Unix_Process(next_source()));
00222 
00223       fd_set read_set;
00224       FD_ZERO(&read_set);
00225 
00226       std::vector<int> fds;
00227 
00228       for(auto& proc : m_procs)
00229          {
00230          int fd = proc.fd();
00231          if(fd > 0)
00232             {
00233             fds.push_back(fd);
00234             FD_SET(fd, &read_set);
00235             }
00236          }
00237 
00238       if(fds.empty())
00239          break;
00240 
00241       const int max_fd = *std::max_element(fds.begin(), fds.end());
00242 
00243       struct ::timeval timeout;
00244       timeout.tv_sec = (MS_WAIT_TIME / 1000);
00245       timeout.tv_usec = (MS_WAIT_TIME % 1000) * 1000;
00246 
00247       if(::select(max_fd + 1, &read_set, nullptr, nullptr, &timeout) < 0)
00248          return; // or continue?
00249 
00250       for(auto& proc : m_procs)
00251          {
00252          int fd = proc.fd();
00253 
00254          if(FD_ISSET(fd, &read_set))
00255             {
00256             const ssize_t got = ::read(fd, &io_buffer[0], io_buffer.size());
00257             if(got > 0)
00258                accum.add(&io_buffer[0], got, ENTROPY_ESTIMATE);
00259             else
00260                proc.spawn(next_source());
00261             }
00262          }
00263       }
00264    }
00265 
00266 }