/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to plugins.d/plymouth.c

  • Committer: Teddy Hogeborn
  • Date: 2016-03-17 20:40:55 UTC
  • Revision ID: teddy@recompile.se-20160317204055-bhsh5xsidq7w5cxu
Client: Fix plymouth agent; broken since 1.7.2.

Fix an very old memory bug in the plymouth agent (which has been
present since its apperance in version 1.2), but which was only
recently detected at run time due to the new -fsanitize=address
compile- time flag, which has been used since version 1.7.2.  This
detection of a memory access violation causes the program to abort,
making the Plymouth graphical boot system unable to accept interactive
input of passwords when using the Mandos client.

* plugins.d/plymouth.c (exec_and_wait): Fix memory allocation bug when
  allocating new_argv.  Also tolerate a zero-length argv.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*  -*- coding: utf-8 -*- */
 
2
/*
 
3
 * Plymouth - Read a password from Plymouth and output it
 
4
 * 
 
5
 * Copyright © 2010-2016 Teddy Hogeborn
 
6
 * Copyright © 2010-2016 Björn Påhlsson
 
7
 * 
 
8
 * This program is free software: you can redistribute it and/or
 
9
 * modify it under the terms of the GNU General Public License as
 
10
 * published by the Free Software Foundation, either version 3 of the
 
11
 * License, or (at your option) any later version.
 
12
 * 
 
13
 * This program is distributed in the hope that it will be useful, but
 
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
16
 * General Public License for more details.
 
17
 * 
 
18
 * You should have received a copy of the GNU General Public License
 
19
 * along with this program.  If not, see
 
20
 * <http://www.gnu.org/licenses/>.
 
21
 * 
 
22
 * Contact the authors at <mandos@recompile.se>.
 
23
 */
 
24
 
 
25
#define _GNU_SOURCE             /* asprintf(), TEMP_FAILURE_RETRY() */
 
26
#include <signal.h>             /* sig_atomic_t, struct sigaction,
 
27
                                   sigemptyset(), sigaddset(), SIGINT,
 
28
                                   SIGHUP, SIGTERM, sigaction(),
 
29
                                   kill(), SIG_IGN */
 
30
#include <stdbool.h>            /* bool, false, true */
 
31
#include <fcntl.h>              /* open(), O_RDONLY */
 
32
#include <iso646.h>             /* and, or, not*/
 
33
#include <sys/types.h>          /* size_t, ssize_t, pid_t, struct
 
34
                                   dirent, waitpid() */
 
35
#include <sys/wait.h>           /* waitpid() */
 
36
#include <stddef.h>             /* NULL */
 
37
#include <string.h>             /* strchr(), memcmp() */
 
38
#include <stdio.h>              /* asprintf(), perror(), fopen(),
 
39
                                   fscanf(), vasprintf(), fprintf(),
 
40
                                   vfprintf() */
 
41
#include <unistd.h>             /* close(), readlink(), read(),
 
42
                                   fork(), setsid(), chdir(), dup2(),
 
43
                                   STDERR_FILENO, execv(), access() */
 
44
#include <stdlib.h>             /* free(), EXIT_FAILURE, realloc(),
 
45
                                   EXIT_SUCCESS, malloc(), _exit(),
 
46
                                   getenv() */
 
47
#include <dirent.h>             /* scandir(), alphasort() */
 
48
#include <inttypes.h>           /* intmax_t, strtoumax(), SCNuMAX */
 
49
#include <sys/stat.h>           /* struct stat, lstat() */
 
50
#include <sysexits.h>           /* EX_OSERR, EX_UNAVAILABLE */
 
51
#include <error.h>              /* error() */
 
52
#include <errno.h>              /* TEMP_FAILURE_RETRY */
 
53
#include <argz.h>               /* argz_count(), argz_extract() */
 
54
#include <stdarg.h>             /* va_list, va_start(), ... */
 
55
 
 
56
sig_atomic_t interrupted_by_signal = 0;
 
57
 
 
58
/* Used by Ubuntu 11.04 (Natty Narwahl) */
 
59
const char plymouth_old_pid[] = "/dev/.initramfs/plymouth.pid";
 
60
/* Used by Ubuntu 11.10 (Oneiric Ocelot) */
 
61
const char plymouth_pid[] = "/run/initramfs/plymouth.pid";
 
62
 
 
63
const char plymouth_path[] = "/bin/plymouth";
 
64
const char plymouthd_path[] = "/sbin/plymouthd";
 
65
const char *plymouthd_default_argv[] = {"/sbin/plymouthd",
 
66
                                        "--mode=boot",
 
67
                                        "--attach-to-session",
 
68
                                        NULL };
 
69
 
 
70
static void termination_handler(__attribute__((unused))int signum){
 
71
  if(interrupted_by_signal){
 
72
    return;
 
73
  }
 
74
  interrupted_by_signal = 1;
 
75
}
 
76
 
 
77
/* Function to use when printing errors */
 
78
__attribute__((format (gnu_printf, 3, 4)))
 
79
void error_plus(int status, int errnum, const char *formatstring,
 
80
                ...){
 
81
  va_list ap;
 
82
  char *text;
 
83
  int ret;
 
84
  
 
85
  va_start(ap, formatstring);
 
86
  ret = vasprintf(&text, formatstring, ap);
 
87
  if(ret == -1){
 
88
    fprintf(stderr, "Mandos plugin %s: ",
 
89
            program_invocation_short_name);
 
90
    vfprintf(stderr, formatstring, ap);
 
91
    fprintf(stderr, ": ");
 
92
    fprintf(stderr, "%s\n", strerror(errnum));
 
93
    error(status, errno, "vasprintf while printing error");
 
94
    return;
 
95
  }
 
96
  fprintf(stderr, "Mandos plugin ");
 
97
  error(status, errnum, "%s", text);
 
98
  free(text);
 
99
}
 
100
 
 
101
/* Create prompt string */
 
102
char *makeprompt(void){
 
103
  int ret = 0;
 
104
  char *prompt;
 
105
  const char *const cryptsource = getenv("cryptsource");
 
106
  const char *const crypttarget = getenv("crypttarget");
 
107
  const char prompt_start[] = "Unlocking the disk";
 
108
  const char prompt_end[] = "Enter passphrase";
 
109
  
 
110
  if(cryptsource == NULL){
 
111
    if(crypttarget == NULL){
 
112
      ret = asprintf(&prompt, "%s\n%s", prompt_start, prompt_end);
 
113
    } else {
 
114
      ret = asprintf(&prompt, "%s (%s)\n%s", prompt_start,
 
115
                     crypttarget, prompt_end);
 
116
    }
 
117
  } else {
 
118
    if(crypttarget == NULL){
 
119
      ret = asprintf(&prompt, "%s %s\n%s", prompt_start, cryptsource,
 
120
                     prompt_end);
 
121
    } else {
 
122
      ret = asprintf(&prompt, "%s %s (%s)\n%s", prompt_start,
 
123
                     cryptsource, crypttarget, prompt_end);
 
124
    }
 
125
  }
 
126
  if(ret == -1){
 
127
    return NULL;
 
128
  }
 
129
  return prompt;
 
130
}
 
131
 
 
132
void kill_and_wait(pid_t pid){
 
133
  TEMP_FAILURE_RETRY(kill(pid, SIGTERM));
 
134
  TEMP_FAILURE_RETRY(waitpid(pid, NULL, 0));
 
135
}
 
136
 
 
137
bool become_a_daemon(void){
 
138
  int ret = setuid(geteuid());
 
139
  if(ret == -1){
 
140
    error_plus(0, errno, "setuid");
 
141
  }
 
142
    
 
143
  setsid();
 
144
  ret = chdir("/");
 
145
  if(ret == -1){
 
146
    error_plus(0, errno, "chdir");
 
147
    return false;
 
148
  }
 
149
  ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */
 
150
  if(ret == -1){
 
151
    error_plus(0, errno, "dup2");
 
152
    return false;
 
153
  }
 
154
  return true;
 
155
}
 
156
 
 
157
__attribute__((nonnull (2, 3)))
 
158
bool exec_and_wait(pid_t *pid_return, const char *path,
 
159
                   const char * const *argv, bool interruptable,
 
160
                   bool daemonize){
 
161
  int status;
 
162
  int ret;
 
163
  pid_t pid;
 
164
  pid = fork();
 
165
  if(pid == -1){
 
166
    error_plus(0, errno, "fork");
 
167
    return false;
 
168
  }
 
169
  if(pid == 0){
 
170
    /* Child */
 
171
    if(daemonize){
 
172
      if(not become_a_daemon()){
 
173
        _exit(EX_OSERR);
 
174
      }
 
175
    }
 
176
    
 
177
    char **new_argv = malloc(sizeof(const char *));
 
178
    if(new_argv == NULL){
 
179
      error_plus(0, errno, "malloc");
 
180
      _exit(EX_OSERR);
 
181
    }
 
182
    char **tmp;
 
183
    int i = 0;
 
184
    for (; argv[i]!=NULL; i++){
 
185
      tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 2));
 
186
      if(tmp == NULL){
 
187
        error_plus(0, errno, "realloc");
 
188
        free(new_argv);
 
189
        _exit(EX_OSERR);
 
190
      }
 
191
      new_argv = tmp;
 
192
      new_argv[i] = strdup(argv[i]);
 
193
    }
 
194
    new_argv[i] = NULL;
 
195
    
 
196
    execv(path, (char *const *)new_argv);
 
197
    error_plus(0, errno, "execv");
 
198
    _exit(EXIT_FAILURE);
 
199
  }
 
200
  if(pid_return != NULL){
 
201
    *pid_return = pid;
 
202
  }
 
203
  do {
 
204
    ret = waitpid(pid, &status, 0);
 
205
  } while(ret == -1 and errno == EINTR
 
206
          and ((not interrupted_by_signal)
 
207
               or (not interruptable)));
 
208
  if(interrupted_by_signal and interruptable){
 
209
    return false;
 
210
  }
 
211
  if(ret == -1){
 
212
    error_plus(0, errno, "waitpid");
 
213
    return false;
 
214
  }
 
215
  if(WIFEXITED(status) and (WEXITSTATUS(status) == 0)){
 
216
    return true;
 
217
  }
 
218
  return false;
 
219
}
 
220
 
 
221
__attribute__((nonnull))
 
222
int is_plymouth(const struct dirent *proc_entry){
 
223
  int ret;
 
224
  {
 
225
    uintmax_t proc_id;
 
226
    char *tmp;
 
227
    errno = 0;
 
228
    proc_id = strtoumax(proc_entry->d_name, &tmp, 10);
 
229
 
 
230
    if(errno != 0 or *tmp != '\0'
 
231
       or proc_id != (uintmax_t)((pid_t)proc_id)){
 
232
      return 0;
 
233
    }
 
234
  }
 
235
  char exe_target[sizeof(plymouthd_path)];
 
236
  char *exe_link;
 
237
  ret = asprintf(&exe_link, "/proc/%s/exe", proc_entry->d_name);
 
238
  if(ret == -1){
 
239
    error_plus(0, errno, "asprintf");
 
240
    return 0;
 
241
  }
 
242
  
 
243
  struct stat exe_stat;
 
244
  ret = lstat(exe_link, &exe_stat);
 
245
  if(ret == -1){
 
246
    free(exe_link);
 
247
    if(errno != ENOENT){
 
248
      error_plus(0, errno, "lstat");
 
249
    }
 
250
    return 0;
 
251
  }
 
252
  
 
253
  if(not S_ISLNK(exe_stat.st_mode)
 
254
     or exe_stat.st_uid != 0
 
255
     or exe_stat.st_gid != 0){
 
256
    free(exe_link);
 
257
    return 0;
 
258
  }
 
259
  
 
260
  ssize_t sret = readlink(exe_link, exe_target, sizeof(exe_target));
 
261
  free(exe_link);
 
262
  if((sret != (ssize_t)sizeof(plymouthd_path)-1) or
 
263
      (memcmp(plymouthd_path, exe_target,
 
264
              sizeof(plymouthd_path)-1) != 0)){
 
265
    return 0;
 
266
  }
 
267
  return 1;
 
268
}
 
269
 
 
270
pid_t get_pid(void){
 
271
  int ret;
 
272
  uintmax_t proc_id = 0;
 
273
  FILE *pidfile = fopen(plymouth_pid, "r");
 
274
  /* Try the new pid file location */
 
275
  if(pidfile != NULL){
 
276
    ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
 
277
    if(ret != 1){
 
278
      proc_id = 0;
 
279
    }
 
280
    fclose(pidfile);
 
281
  }
 
282
  /* Try the old pid file location */
 
283
  if(proc_id == 0){
 
284
    pidfile = fopen(plymouth_pid, "r");
 
285
    if(pidfile != NULL){
 
286
      ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
 
287
      if(ret != 1){
 
288
        proc_id = 0;
 
289
      }
 
290
      fclose(pidfile);
 
291
    }
 
292
  }
 
293
  /* Look for a plymouth process */
 
294
  if(proc_id == 0){
 
295
    struct dirent **direntries = NULL;
 
296
    ret = scandir("/proc", &direntries, is_plymouth, alphasort);
 
297
    if(ret == -1){
 
298
      error_plus(0, errno, "scandir");
 
299
    }
 
300
    if(ret > 0){
 
301
      ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &proc_id);
 
302
      if(ret < 0){
 
303
        error_plus(0, errno, "sscanf");
 
304
      }
 
305
    }
 
306
    /* scandir might preallocate for this variable (man page unclear).
 
307
       even if ret == 0, therefore we need to free it. */
 
308
    free(direntries);
 
309
  }
 
310
  pid_t pid;
 
311
  pid = (pid_t)proc_id;
 
312
  if((uintmax_t)pid == proc_id){
 
313
    return pid;
 
314
  }
 
315
  
 
316
  return 0;
 
317
}
 
318
 
 
319
const char * const * getargv(pid_t pid){
 
320
  int cl_fd;
 
321
  char *cmdline_filename;
 
322
  ssize_t sret;
 
323
  int ret;
 
324
  
 
325
  ret = asprintf(&cmdline_filename, "/proc/%" PRIuMAX "/cmdline",
 
326
                 (uintmax_t)pid);
 
327
  if(ret == -1){
 
328
    error_plus(0, errno, "asprintf");
 
329
    return NULL;
 
330
  }
 
331
  
 
332
  /* Open /proc/<pid>/cmdline  */
 
333
  cl_fd = open(cmdline_filename, O_RDONLY);
 
334
  free(cmdline_filename);
 
335
  if(cl_fd == -1){
 
336
    error_plus(0, errno, "open");
 
337
    return NULL;
 
338
  }
 
339
  
 
340
  size_t cmdline_allocated = 0;
 
341
  size_t cmdline_len = 0;
 
342
  char *cmdline = NULL;
 
343
  char *tmp;
 
344
  const size_t blocksize = 1024;
 
345
  do {
 
346
    /* Allocate more space? */
 
347
    if(cmdline_len + blocksize > cmdline_allocated){
 
348
      tmp = realloc(cmdline, cmdline_allocated + blocksize);
 
349
      if(tmp == NULL){
 
350
        error_plus(0, errno, "realloc");
 
351
        free(cmdline);
 
352
        close(cl_fd);
 
353
        return NULL;
 
354
      }
 
355
      cmdline = tmp;
 
356
      cmdline_allocated += blocksize;
 
357
    }
 
358
    
 
359
    /* Read data */
 
360
    sret = read(cl_fd, cmdline + cmdline_len,
 
361
                cmdline_allocated - cmdline_len);
 
362
    if(sret == -1){
 
363
      error_plus(0, errno, "read");
 
364
      free(cmdline);
 
365
      close(cl_fd);
 
366
      return NULL;
 
367
    }
 
368
    cmdline_len += (size_t)sret;
 
369
  } while(sret != 0);
 
370
  ret = close(cl_fd);
 
371
  if(ret == -1){
 
372
    error_plus(0, errno, "close");
 
373
    free(cmdline);
 
374
    return NULL;
 
375
  }
 
376
  
 
377
  /* we got cmdline and cmdline_len, ignore rest... */
 
378
  char **argv = malloc((argz_count(cmdline, cmdline_len) + 1)
 
379
                       * sizeof(char *)); /* Get number of args */
 
380
  if(argv == NULL){
 
381
    error_plus(0, errno, "argv = malloc()");
 
382
    free(cmdline);
 
383
    return NULL;
 
384
  }
 
385
  argz_extract(cmdline, cmdline_len, argv); /* Create argv */
 
386
  return (const char * const *)argv;
 
387
}
 
388
 
 
389
int main(__attribute__((unused))int argc,
 
390
         __attribute__((unused))char **argv){
 
391
  char *prompt;
 
392
  char *prompt_arg;
 
393
  pid_t plymouth_command_pid;
 
394
  int ret;
 
395
  bool bret;
 
396
 
 
397
  /* test -x /bin/plymouth */
 
398
  ret = access(plymouth_path, X_OK);
 
399
  if(ret == -1){
 
400
    /* Plymouth is probably not installed.  Don't print an error
 
401
       message, just exit. */
 
402
    exit(EX_UNAVAILABLE);
 
403
  }
 
404
  
 
405
  { /* Add signal handlers */
 
406
    struct sigaction old_action,
 
407
      new_action = { .sa_handler = termination_handler,
 
408
                     .sa_flags = 0 };
 
409
    sigemptyset(&new_action.sa_mask);
 
410
    for(int *sig = (int[]){ SIGINT, SIGHUP, SIGTERM, 0 };
 
411
        *sig != 0; sig++){
 
412
      ret = sigaddset(&new_action.sa_mask, *sig);
 
413
      if(ret == -1){
 
414
        error_plus(EX_OSERR, errno, "sigaddset");
 
415
      }
 
416
      ret = sigaction(*sig, NULL, &old_action);
 
417
      if(ret == -1){
 
418
        error_plus(EX_OSERR, errno, "sigaction");
 
419
      }
 
420
      if(old_action.sa_handler != SIG_IGN){
 
421
        ret = sigaction(*sig, &new_action, NULL);
 
422
        if(ret == -1){
 
423
          error_plus(EX_OSERR, errno, "sigaction");
 
424
        }
 
425
      }
 
426
    }
 
427
  }
 
428
  
 
429
  /* plymouth --ping */
 
430
  bret = exec_and_wait(&plymouth_command_pid, plymouth_path,
 
431
                       (const char *[])
 
432
                       { plymouth_path, "--ping", NULL },
 
433
                       true, false);
 
434
  if(not bret){
 
435
    if(interrupted_by_signal){
 
436
      kill_and_wait(plymouth_command_pid);
 
437
      exit(EXIT_FAILURE);
 
438
    }
 
439
    /* Plymouth is probably not running.  Don't print an error
 
440
       message, just exit. */
 
441
    exit(EX_UNAVAILABLE);
 
442
  }
 
443
  
 
444
  prompt = makeprompt();
 
445
  ret = asprintf(&prompt_arg, "--prompt=%s", prompt);
 
446
  free(prompt);
 
447
  if(ret == -1){
 
448
    error_plus(EX_OSERR, errno, "asprintf");
 
449
  }
 
450
  
 
451
  /* plymouth ask-for-password --prompt="$prompt" */
 
452
  bret = exec_and_wait(&plymouth_command_pid,
 
453
                       plymouth_path, (const char *[])
 
454
                       { plymouth_path, "ask-for-password",
 
455
                           prompt_arg, NULL },
 
456
                       true, false);
 
457
  free(prompt_arg);
 
458
  if(bret){
 
459
    exit(EXIT_SUCCESS);
 
460
  }
 
461
  if(not interrupted_by_signal){
 
462
    /* exec_and_wait failed for some other reason */
 
463
    exit(EXIT_FAILURE);
 
464
  }
 
465
  kill_and_wait(plymouth_command_pid);
 
466
  
 
467
  const char * const *plymouthd_argv;
 
468
  pid_t pid = get_pid();
 
469
  if(pid == 0){
 
470
    error_plus(0, 0, "plymouthd pid not found");
 
471
    plymouthd_argv = plymouthd_default_argv;
 
472
  } else {
 
473
    plymouthd_argv = getargv(pid);
 
474
  }
 
475
  
 
476
  bret = exec_and_wait(NULL, plymouth_path, (const char *[])
 
477
                       { plymouth_path, "quit", NULL },
 
478
                       false, false);
 
479
  if(not bret){
 
480
    exit(EXIT_FAILURE);
 
481
  }
 
482
  bret = exec_and_wait(NULL, plymouthd_path, plymouthd_argv,
 
483
                       false, true);
 
484
  if(not bret){
 
485
    exit(EXIT_FAILURE);
 
486
  }
 
487
  exec_and_wait(NULL, plymouth_path, (const char *[])
 
488
                { plymouth_path, "show-splash", NULL },
 
489
                false, false);
 
490
  exit(EXIT_FAILURE);
 
491
}