Botan
1.11.15
|
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 }