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 }