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 }