1 /** 2 * Unix EntropySource 3 * 4 * Copyright: 5 * (C) 1999-2009,2013 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.entropy.unix_procs; 12 13 import botan.constants; 14 version(Posix): 15 static if (BOTAN_HAS_ENTROPY_SRC_UNIX_PROCESS_RUNNER): 16 17 import botan.entropy.entropy_src; 18 import botan.utils.types; 19 import botan.utils.parsing; 20 import std.algorithm; 21 import core.stdc.config; 22 import core.sys.posix.sys.types; 23 import core.sys.posix.sys.stat; 24 import core.sys.posix.unistd; 25 import core.sys.posix.sys.socket; 26 import core.sys.posix.sys.wait; 27 import core.sys.posix.sys.resource; 28 import core.sys.posix.signal; 29 import core.stdc.stdlib; 30 import std.string : toStringz; 31 32 extern(C) int getrusage(int, rusage*); 33 34 /** 35 * Entropy source for generic Unix. Runs various programs trying to 36 * gather data hard for a remote attacker to guess. Probably not too 37 * effective against local attackers as they can sample from the same 38 * distribution. 39 */ 40 final class UnixEntropySource : EntropySource 41 { 42 public: 43 @property string name() const { return "Unix Process Runner"; } 44 45 void poll(ref EntropyAccumulator accum) 46 { 47 // refuse to run as root (maybe instead setuid to nobody before exec?) 48 // fixme: this should also check for setgid 49 if (getuid() == 0 || geteuid() == 0) 50 return; 51 52 if (m_sources.empty) 53 { 54 auto sources = getDefaultSources(); 55 56 foreach (src; sources[]) 57 { 58 const string path = find_full_path_if_exists(m_trusted_paths, src[0]); 59 if (path != "") 60 { 61 src[0] = path; 62 m_sources.pushBack(src); 63 } 64 } 65 } 66 67 if (m_sources.empty) 68 return; // still empty, really nothing to try 69 70 __gshared immutable size_t MS_WAIT_TIME = 32; 71 __gshared immutable double ENTROPY_ESTIMATE = 1.0 / 1024; 72 73 SecureVector!ubyte* io_buffer = &accum.getIoBuffer(4*1024); // page 74 75 while (!accum.pollingGoalAchieved()) 76 { 77 while (m_procs.length < m_concurrent) 78 m_procs.pushBack(UnixProcess(nextSource())); 79 80 fd_set read_set; 81 FD_ZERO(&read_set); 82 83 Vector!int fds; 84 85 foreach (ref proc; m_procs[]) 86 { 87 int fd = proc.fd(); 88 if (fd > 0) 89 { 90 fds.pushBack(fd); 91 FD_SET(fd, &read_set); 92 } 93 } 94 95 if (fds.empty) 96 break; 97 98 99 int max_fd; 100 foreach (fd; fds[]) { 101 if (fd > max_fd) 102 max_fd = fd; 103 } 104 timeval timeout; 105 timeout.tv_sec = (MS_WAIT_TIME / 1000); 106 timeout.tv_usec = (MS_WAIT_TIME % 1000) * 1000; 107 108 if (select(max_fd + 1, &read_set, null, null, &timeout) < 0) 109 return; // or continue? 110 111 foreach (ref proc; m_procs[]) 112 { 113 int fd = proc.fd(); 114 115 if (FD_ISSET(fd, &read_set)) 116 { 117 const ssize_t got = read(fd, io_buffer.ptr, io_buffer.length); 118 if (got > 0) 119 accum.add(io_buffer.ptr, got, ENTROPY_ESTIMATE); 120 else 121 proc.spawn(nextSource()); 122 } 123 } 124 } 125 } 126 127 128 /** 129 * Params: 130 * trusted_path = is a list of directories that are assumed 131 * to contain only 'safe' binaries. If an attacker can write 132 * an executable to one of these directories then we will 133 * run arbitrary code. 134 * proc_cnt = The amount of concurrent processes to use 135 */ 136 this()(auto const ref Vector!string trusted_path, size_t proc_cnt = 0) 137 { 138 m_trusted_paths = trusted_path.dup; 139 m_concurrent = concurrent_processes(proc_cnt); 140 } 141 private: 142 /** 143 * Default Commands for Entropy Gathering 144 */ 145 static Vector!(string[]) getDefaultSources() 146 { 147 Vector!(string[]) srcs; 148 149 srcs.pushBack([ "netstat", "-in" ]); 150 srcs.pushBack([ "pfstat" ]); 151 srcs.pushBack([ "vmstat", "-s" ]); 152 srcs.pushBack([ "vmstat" ]); 153 154 srcs.pushBack([ "arp", "-a", "-n" ]); 155 srcs.pushBack([ "ifconfig", "-a" ]); 156 srcs.pushBack([ "iostat" ]); 157 srcs.pushBack([ "ipcs", "-a" ]); 158 srcs.pushBack([ "mpstat" ]); 159 srcs.pushBack([ "netstat", "-an" ]); 160 srcs.pushBack([ "netstat", "-s" ]); 161 srcs.pushBack([ "nfsstat" ]); 162 srcs.pushBack([ "portstat" ]); 163 srcs.pushBack([ "procinfo", "-a" ]); 164 srcs.pushBack([ "pstat", "-T" ]); 165 srcs.pushBack([ "pstat", "-s" ]); 166 srcs.pushBack([ "uname", "-a" ]); 167 srcs.pushBack([ "uptime" ]); 168 169 srcs.pushBack([ "listarea" ]); 170 srcs.pushBack([ "listdev" ]); 171 srcs.pushBack([ "ps", "-A" ]); 172 srcs.pushBack([ "sysinfo" ]); 173 174 srcs.pushBack([ "finger" ]); 175 srcs.pushBack([ "mailstats" ]); 176 srcs.pushBack([ "rpcinfo", "-p", "localhost" ]); 177 srcs.pushBack([ "who" ]); 178 179 srcs.pushBack([ "df", "-l" ]); 180 srcs.pushBack([ "dmesg" ]); 181 srcs.pushBack([ "last", "-5" ]); 182 srcs.pushBack([ "ls", "-alni", "/proc" ]); 183 srcs.pushBack([ "ls", "-alni", "/tmp" ]); 184 srcs.pushBack([ "pstat", "-f" ]); 185 186 srcs.pushBack([ "ps", "-elf" ]); 187 srcs.pushBack([ "ps", "aux" ]); 188 189 srcs.pushBack([ "lsof", "-n" ]); 190 srcs.pushBack([ "sar", "-A" ]); 191 192 return srcs; 193 } 194 195 struct UnixProcess 196 { 197 public: 198 int fd() const { return m_fd; } 199 200 void spawn(in string[] args) 201 { 202 shutdown(); 203 204 int[2] pipe; 205 if (.pipe(pipe) != 0) 206 return; 207 208 pid_t pid = fork(); 209 210 if (pid == -1) 211 { 212 close(pipe[0]); 213 close(pipe[1]); 214 } 215 else if (pid > 0) // in parent 216 { 217 m_pid = pid; 218 m_fd = pipe[0]; 219 close(pipe[1]); 220 } 221 else // in child 222 { 223 if (dup2(pipe[1], STDOUT_FILENO) == -1) 224 exit(127); 225 if (close(pipe[0]) != 0 || close(pipe[1]) != 0) 226 exit(127); 227 if (close(STDERR_FILENO) != 0) 228 exit(127); 229 230 do_exec(args); 231 exit(127); 232 } 233 } 234 235 void shutdown() 236 { 237 if (m_pid == -1) 238 return; 239 240 close(m_fd); 241 m_fd = -1; 242 243 pid_t reaped = waitpid(m_pid, null, WNOHANG); 244 245 if (reaped == 0) 246 { 247 /* 248 * Child is still alive - send it SIGTERM, sleep for a bit and 249 * try to reap again, if still alive send SIGKILL 250 */ 251 kill(m_pid, SIGTERM); 252 253 timeval tv; 254 tv.tv_sec = 0; 255 tv.tv_usec = 1000; 256 select(0, null, null, null, &tv); 257 258 reaped = waitpid(m_pid, null, WNOHANG); 259 260 if (reaped == 0) 261 { 262 kill(m_pid, SIGKILL); 263 do 264 reaped = waitpid(m_pid, null, 0); 265 while (reaped == -1); 266 } 267 } 268 269 m_pid = -1; 270 } 271 272 this(in string[] args) { spawn(args); } 273 274 ~this() { shutdown(); } 275 276 this(ref UnixProcess other) 277 { 278 std.algorithm.swap(m_fd, other.m_fd); 279 std.algorithm.swap(m_pid, other.m_pid); 280 } 281 private: 282 int m_fd = -1; 283 pid_t m_pid = -1; 284 } 285 286 string[] nextSource() 287 { 288 string[] src = m_sources[m_sources_idx]; 289 m_sources_idx = (m_sources_idx + 1) % m_sources.length; 290 return src; 291 } 292 293 294 Vector!string m_trusted_paths; 295 const size_t m_concurrent; 296 297 Vector!(string[]) m_sources; 298 size_t m_sources_idx = 0; 299 300 Vector!UnixProcess m_procs; 301 } 302 303 final class UnixProcessInfoEntropySource : EntropySource 304 { 305 public: 306 @property string name() const { return "Unix Process Info"; } 307 308 void poll(ref EntropyAccumulator accum) 309 { 310 accum.add(getpid(), 0.0); 311 accum.add(getppid(), 0.0); 312 accum.add(getuid(), 0.0); 313 accum.add(getgid(), 0.0); 314 accum.add(getsid(0), 0.0); 315 accum.add(getpgrp(), 0.0); 316 317 rusage usage; 318 getrusage(RUSAGE_SELF, &usage); 319 accum.add(usage, 0.0); 320 321 getrusage(RUSAGE_CHILDREN, &usage); 322 accum.add(usage, 0.0); 323 } 324 325 } 326 327 private: 328 329 string find_full_path_if_exists(const ref Vector!string trusted_path, in string proc) 330 { 331 foreach (dir; trusted_path[]) 332 { 333 const string full_path = dir ~ "/" ~ proc; 334 if (access(full_path.toStringz, X_OK) == 0) 335 return full_path; 336 } 337 338 return ""; 339 } 340 341 size_t concurrent_processes(size_t user_request) 342 { 343 __gshared immutable size_t DEFAULT_CONCURRENT = 2; 344 __gshared immutable size_t MAX_CONCURRENT = 8; 345 346 if (user_request > 0 && user_request < MAX_CONCURRENT) 347 return user_request; 348 349 const long online_cpus = sysconf(_SC_NPROCESSORS_ONLN); 350 351 if (online_cpus > 0) 352 return cast(size_t)(online_cpus); // maybe fewer? 353 354 return DEFAULT_CONCURRENT; 355 } 356 357 void do_exec(in string[] args) 358 { 359 // cleaner way to do this? 360 immutable(char*) arg0 = (args.length > 0) ? args[0].toStringz : null; 361 immutable(char*) arg1 = (args.length > 1) ? args[1].toStringz : null; 362 immutable(char*) arg2 = (args.length > 2) ? args[2].toStringz : null; 363 immutable(char*) arg3 = (args.length > 3) ? args[3].toStringz : null; 364 immutable(char*) arg4 = (args.length > 4) ? args[4].toStringz : null; 365 366 execl(arg0, arg0, arg1, arg2, arg3, arg4, null); 367 } 368 369 370 @nogc nothrow pure private: 371 372 alias __fd_mask = c_long; 373 enum uint __NFDBITS = 8 * __fd_mask.sizeof; 374 375 auto __FDELT( int d ) 376 { 377 return d / __NFDBITS; 378 } 379 380 auto __FDMASK( int d ) 381 { 382 return cast(__fd_mask) 1 << ( d % __NFDBITS ); 383 } 384 385 enum FD_SETSIZE = 1024; 386 387 void FD_CLR( int fd, fd_set* fdset ) 388 { 389 fdset.fds_bits[__FDELT( fd )] &= ~__FDMASK( fd ); 390 } 391 392 bool FD_ISSET( int fd, const(fd_set)* fdset ) 393 { 394 return (fdset.fds_bits[__FDELT( fd )] & __FDMASK( fd )) != 0; 395 } 396 397 void FD_SET( int fd, fd_set* fdset ) 398 { 399 fdset.fds_bits[__FDELT( fd )] |= __FDMASK( fd ); 400 } 401 402 void FD_ZERO( fd_set* fdset ) 403 { 404 fdset.fds_bits[0 .. $] = 0; 405 }