chiark / gitweb /
Security: Reduce impact of bogus key setup packet DoS
[secnet.git] / process.c
1 #define _GNU_SOURCE
2 #include "secnet.h"
3 #include <unistd.h>
4 #include <fcntl.h>
5 #include <errno.h>
6 #include <sys/wait.h>
7 #include <string.h>
8 #include "process.h"
9
10 /* Process handling - subprocesses, signals, etc. */
11
12 static bool_t signal_handling=False;
13 static sigset_t emptyset, fullset;
14 static sigset_t registered,pending;
15
16 struct child {
17     pid_t pid;
18     cstring_t desc;
19     process_callback_fn *cb;
20     void *cst;
21     bool_t finished;
22     struct child *next;
23 };
24
25 static struct child *children=NULL;
26
27 struct signotify {
28     int signum;
29     signal_notify_fn *notify;
30     void *cst;
31     struct signotify *next;
32 };
33
34 static struct signotify *sigs=NULL;
35
36 static int spw,spr; /* file descriptors for signal notification pipe */
37
38 static void set_default_signals(void);
39
40 /* Long-lived subprocesses can only be started once we've started
41    signal processing so that we can catch SIGCHLD for them and report
42    their exit status using the callback function.  We block SIGCHLD
43    until signal processing has begun. */
44 pid_t makesubproc(process_entry_fn *entry, process_callback_fn *cb,
45                  void *est, void *cst, cstring_t desc)
46 {
47     struct child *c;
48     pid_t p;
49
50     c=safe_malloc(sizeof(*c),"makesubproc");
51     c->desc=desc;
52     c->cb=cb;
53     c->cst=cst;
54
55     if (!signal_handling) {
56         fatal("makesubproc called before signal handling started");
57     }
58     p=fork();
59     if (p==0) {
60         /* Child process */
61         set_default_signals();
62         sigprocmask(SIG_SETMASK,&emptyset,NULL);
63         entry(est);
64         abort();
65     } else if (p==-1) {
66         fatal_perror("makesubproc (%s): fork",desc);
67     }
68     c->pid=p;
69     c->finished=False;
70     c->next=children;
71     children=c;
72     return p;
73 }
74
75 static signal_notify_fn sigchld_handler;
76 static void sigchld_handler(void *st, int signum)
77 {
78     struct child *i,*n,**p;
79     struct work {
80         pid_t pid;
81         process_callback_fn *cb;
82         void *cst;
83         int status;
84         struct work *next;
85     };
86     struct work *w=NULL, *nw;
87     pid_t rv;
88     int status;
89
90     for (i=children; i; i=i->next) {
91         rv=waitpid(i->pid,&status,WNOHANG);
92         if (rv==-1) {
93             fatal_perror("sigchld_handler: waitpid");
94         }
95         if (rv==i->pid) {
96             i->finished=True;
97             
98             nw=safe_malloc(sizeof(*nw),"sigchld_handler");
99             nw->pid=i->pid;
100             nw->cb=i->cb;
101             nw->cst=i->cst;
102             nw->status=status;
103             nw->next=w;
104             w=nw;
105         }
106     }
107
108     /* Remove all the finished tasks from the list of children */
109     for (i=children, p=&children; i; i=n) {
110         n=i->next;
111         if (i->finished) {
112             free(i);
113             *p=n;
114         } else {
115             p=&i->next;
116         }
117     }
118
119     /* Notify as appropriate, then free the list */
120     while (w) {
121         w->cb(w->cst,w->pid,w->status);
122         nw=w;
123         w=w->next;
124         free(nw);
125     }
126 }
127
128 int sys_cmd(const char *path, const char *arg, ...)
129 {
130     va_list ap;
131     int rv, rc;
132     pid_t c;
133
134     c=fork();
135     if (c) {
136         /* Parent -> wait for child */
137         do {
138             rc = waitpid(c,&rv,0);
139         } while(rc < 0 && errno == EINTR);
140         if (rc < 0)
141             fatal_perror("sys_cmd: waitpid for %s", path);
142         if (rc != c) /* OS has gone mad */
143             fatal("sys_cmd: waitpid for %s returned wrong process ID!",
144                   path);
145         if (rv) {
146             /* If the command failed reporting its exit status */
147             if (WIFEXITED(rv))
148                 Message(M_ERR, "sys_cmd(%s,%s,...) exited with status %d\n",
149                         path, arg, WEXITSTATUS(rv));
150             else if(WIFSIGNALED(rv))
151                 Message(M_ERR, "sys_cmd(%s,%s,...) exited with signal %d (%s)%s\n",
152                         path, arg, WTERMSIG(rv), strsignal(WTERMSIG(rv)),
153                         WCOREDUMP(rv) ? " - core dumped" : "");
154             else
155                 Message(M_ERR, "sys_cmd(%s,%s,...) exited with wstat %#x",
156                         path, arg, rv);
157         }
158     } else if (c==0) {
159         char *args[100];
160         int i;
161         /* Child -> exec command */
162         /* Really we ought to strcpy() the arguments into the args array,
163            since the arguments are const char *.  Since we'll exit anyway
164            if the execvp() fails this seems somewhat pointless, and
165            increases the chance of the child process failing before it
166            gets to exec(). */
167         va_start(ap,arg);
168         args[0]=(char *)arg; /* program name */
169         i=1;
170         while ((args[i++]=va_arg(ap,char *)));
171         execvp(path,args);
172         fprintf(stderr, "sys_cmd(%s,%s,...): %s\n", path, arg, strerror(errno));
173         _exit(1);
174     } else {
175         /* Error */
176         fatal_perror("sys_cmd(%s,%s,...)", path, arg);
177     }
178
179     return rv;
180 }
181
182 static beforepoll_fn signal_beforepoll;
183 static int signal_beforepoll(void *st, struct pollfd *fds, int *nfds_io,
184                              int *timeout_io)
185 {
186     if (*nfds_io<1) {
187         *nfds_io=1;
188         return ERANGE;
189     }
190     *nfds_io=1;
191     fds[0].fd=spr;
192     fds[0].events=POLLIN;
193     return 0;
194 }
195
196 static afterpoll_fn signal_afterpoll;
197 static void signal_afterpoll(void *st, struct pollfd *fds, int nfds)
198 {
199     uint8_t buf[16];
200     struct signotify *n;
201     sigset_t todo,old;
202
203     if (nfds && (fds->revents & POLLIN)) {
204         read(spr,buf,16); /* We don't actually care what we read; as
205                              long as there was at least one byte
206                              (which there was) we'll pick up the
207                              signals in the pending set */
208         
209         /* We reset 'pending' before processing any of the signals
210            that were pending so that we don't miss any signals that
211            are delivered partway-through processing (all we assume
212            about signal notification routines is that they handle all
213            the work available at their _start_ and only optionally any
214            work that arrives part-way through their execution). */
215         sigprocmask(SIG_SETMASK,&fullset,&old);
216         todo=pending;
217         sigemptyset(&pending);
218         sigprocmask(SIG_SETMASK,&old,NULL);
219         
220         for (n=sigs; n; n=n->next)
221             if (sigismember(&todo,n->signum))
222                 n->notify(n->cst,n->signum);
223     }
224 }
225
226 static void set_default_signals(void)
227 {
228     struct signotify *n;
229     sigset_t done;
230     struct sigaction sa;
231
232     sigemptyset(&done);
233     for (n=sigs; n; n=n->next)
234         if (!sigismember(&done,n->signum)) {
235             sigaddset(&done,n->signum);
236             sa.sa_handler=SIG_DFL;
237             sa.sa_mask=emptyset;
238             sa.sa_flags=0;
239             sigaction(n->signum,&sa,NULL);
240         }
241 }
242
243 static void signal_handler(int signum)
244 {
245     int saved_errno;
246     uint8_t thing=0;
247     sigaddset(&pending,signum);
248     /* XXX the write() may set errno, which can make the main program fail.
249        However, signal handlers aren't allowed to modify anything which
250        is not of type sig_atomic_t.  The world is broken. */
251     /* I have decided to save and restore errno anyway; on most
252        architectures on which secnet can run modifications to errno
253        will be atomic, and it seems to be the lesser of the two
254        evils. */
255     saved_errno=errno;
256     write(spw,&thing,1); /* We don't care if this fails (i.e. the pipe
257                             is full) because the service routine will
258                             spot the pending signal anyway */
259     errno=saved_errno;
260 }
261
262 static void register_signal_handler(struct signotify *s)
263 {
264     struct sigaction sa;
265     int rv;
266
267     if (!signal_handling) return;
268
269     if (sigismember(&registered,s->signum)) return;
270     sigaddset(&registered,s->signum);
271
272     sa.sa_handler=signal_handler;
273     sa.sa_mask=fullset;
274     sa.sa_flags=0;
275     rv=sigaction(s->signum,&sa,NULL);
276     if (rv!=0) {
277         fatal_perror("register_signal_handler: sigaction(%d)",s->signum);
278     }
279 }
280
281 void request_signal_notification(int signum, signal_notify_fn *notify,
282                                  void *cst)
283 {
284     struct signotify *s;
285     sigset_t old;
286
287     s=safe_malloc(sizeof(*s),"request_signal_notification");
288     s->signum=signum;
289     s->notify=notify;
290     s->cst=cst;
291     s->next=sigs;
292     sigprocmask(SIG_SETMASK,&fullset,&old);
293     sigs=s;
294     register_signal_handler(s);
295     sigprocmask(SIG_SETMASK,&old,NULL);
296 }
297
298 void start_signal_handling(void)
299 {
300     int p[2];
301     struct signotify *i;
302
303     sigemptyset(&emptyset);
304     sigfillset(&fullset);
305     sigemptyset(&registered);
306     sigemptyset(&pending);
307
308     if (pipe(p)!=0) {
309         fatal_perror("start_signal_handling: pipe");
310     }
311     spw=p[1];
312     spr=p[0];
313     if (fcntl(spw, F_SETFL, fcntl(spw, F_GETFL)|O_NONBLOCK)==-1) {
314         fatal_perror("start_signal_handling: fcntl(O_NONBLOCK)");
315     }
316
317     register_for_poll(NULL,signal_beforepoll,signal_afterpoll,1,"signal");
318     signal_handling=True;
319
320     /* Register signal handlers for all the signals we're interested in */
321     for (i=sigs; i; i=i->next) {
322         register_signal_handler(i);
323     }
324
325     request_signal_notification(SIGCHLD,sigchld_handler,NULL);
326 }