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