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.dup; 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() { shutdown(); } 276 277 this(ref UnixProcess other) 278 { 279 std.algorithm.swap(m_fd, other.m_fd); 280 std.algorithm.swap(m_pid, other.m_pid); 281 } 282 private: 283 int m_fd = -1; 284 pid_t m_pid = -1; 285 } 286 287 string[] nextSource() 288 { 289 string[] src = m_sources[m_sources_idx]; 290 m_sources_idx = (m_sources_idx + 1) % m_sources.length; 291 return src; 292 } 293 294 295 Vector!string m_trusted_paths; 296 const size_t m_concurrent; 297 298 Vector!(string[]) m_sources; 299 size_t m_sources_idx = 0; 300 301 Vector!UnixProcess m_procs; 302 } 303 304 final class UnixProcessInfoEntropySource : EntropySource 305 { 306 public: 307 @property string name() const { return "Unix Process Info"; } 308 309 void poll(ref EntropyAccumulator accum) 310 { 311 accum.add(getpid(), 0.0); 312 accum.add(getppid(), 0.0); 313 accum.add(getuid(), 0.0); 314 accum.add(getgid(), 0.0); 315 accum.add(getsid(0), 0.0); 316 accum.add(getpgrp(), 0.0); 317 318 rusage usage; 319 getrusage(RUSAGE_SELF, &usage); 320 accum.add(usage, 0.0); 321 322 getrusage(RUSAGE_CHILDREN, &usage); 323 accum.add(usage, 0.0); 324 } 325 326 } 327 328 private: 329 330 string find_full_path_if_exists(const ref Vector!string trusted_path, in string proc) 331 { 332 foreach (dir; trusted_path[]) 333 { 334 const string full_path = dir ~ "/" ~ proc; 335 if (access(full_path.toStringz, X_OK) == 0) 336 return full_path; 337 } 338 339 return ""; 340 } 341 342 size_t concurrent_processes(size_t user_request) 343 { 344 __gshared immutable size_t DEFAULT_CONCURRENT = 2; 345 __gshared immutable size_t MAX_CONCURRENT = 8; 346 347 if (user_request > 0 && user_request < MAX_CONCURRENT) 348 return user_request; 349 350 const long online_cpus = sysconf(_SC_NPROCESSORS_ONLN); 351 352 if (online_cpus > 0) 353 return cast(size_t)(online_cpus); // maybe fewer? 354 355 return DEFAULT_CONCURRENT; 356 } 357 358 void do_exec(in string[] args) 359 { 360 // cleaner way to do this? 361 immutable(char*) arg0 = (args.length > 0) ? args[0].toStringz : null; 362 immutable(char*) arg1 = (args.length > 1) ? args[1].toStringz : null; 363 immutable(char*) arg2 = (args.length > 2) ? args[2].toStringz : null; 364 immutable(char*) arg3 = (args.length > 3) ? args[3].toStringz : null; 365 immutable(char*) arg4 = (args.length > 4) ? args[4].toStringz : null; 366 367 execl(arg0, arg0, arg1, arg2, arg3, arg4, null); 368 } 369 370 371 @nogc nothrow pure private: 372 373 alias __fd_mask = c_long; 374 enum uint __NFDBITS = 8 * __fd_mask.sizeof; 375 376 auto __FDELT( int d ) 377 { 378 return d / __NFDBITS; 379 } 380 381 auto __FDMASK( int d ) 382 { 383 return cast(__fd_mask) 1 << ( d % __NFDBITS ); 384 } 385 386 enum FD_SETSIZE = 1024; 387 388 void FD_CLR( int fd, fd_set* fdset ) 389 { 390 fdset.fds_bits[__FDELT( fd )] &= ~__FDMASK( fd ); 391 } 392 393 bool FD_ISSET( int fd, const(fd_set)* fdset ) 394 { 395 return (fdset.fds_bits[__FDELT( fd )] & __FDMASK( fd )) != 0; 396 } 397 398 void FD_SET( int fd, fd_set* fdset ) 399 { 400 fdset.fds_bits[__FDELT( fd )] |= __FDMASK( fd ); 401 } 402 403 void FD_ZERO( fd_set* fdset ) 404 { 405 fdset.fds_bits[0 .. $] = 0; 406 }