]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/savecore/savecore.c
ifconfig: properly detect invalid mediaopt keywords.
[FreeBSD/FreeBSD.git] / sbin / savecore / savecore.c
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2002 Poul-Henning Kamp
5  * Copyright (c) 2002 Networks Associates Technology, Inc.
6  * All rights reserved.
7  *
8  * This software was developed for the FreeBSD Project by Poul-Henning Kamp
9  * and NAI Labs, the Security Research Division of Network Associates, Inc.
10  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
11  * DARPA CHATS research program.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. The names of the authors may not be used to endorse or promote
22  *    products derived from this software without specific prior written
23  *    permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  *
37  * Copyright (c) 1986, 1992, 1993
38  *      The Regents of the University of California.  All rights reserved.
39  *
40  * Redistribution and use in source and binary forms, with or without
41  * modification, are permitted provided that the following conditions
42  * are met:
43  * 1. Redistributions of source code must retain the above copyright
44  *    notice, this list of conditions and the following disclaimer.
45  * 2. Redistributions in binary form must reproduce the above copyright
46  *    notice, this list of conditions and the following disclaimer in the
47  *    documentation and/or other materials provided with the distribution.
48  * 3. Neither the name of the University nor the names of its contributors
49  *    may be used to endorse or promote products derived from this software
50  *    without specific prior written permission.
51  *
52  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
53  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
54  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
55  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
56  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
57  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
58  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
59  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
60  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
61  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
62  * SUCH DAMAGE.
63  */
64
65 #include <sys/cdefs.h>
66 __FBSDID("$FreeBSD$");
67
68 #include <sys/param.h>
69 #include <sys/disk.h>
70 #include <sys/kerneldump.h>
71 #include <sys/mount.h>
72 #include <sys/stat.h>
73
74 #include <capsicum_helpers.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <fcntl.h>
78 #include <fstab.h>
79 #include <paths.h>
80 #include <signal.h>
81 #include <stdarg.h>
82 #include <stdbool.h>
83 #include <stdio.h>
84 #include <stdlib.h>
85 #include <string.h>
86 #include <syslog.h>
87 #include <time.h>
88 #include <unistd.h>
89
90 #include <libcasper.h>
91 #include <casper/cap_fileargs.h>
92 #include <casper/cap_syslog.h>
93
94 #include <libxo/xo.h>
95
96 /* The size of the buffer used for I/O. */
97 #define BUFFERSIZE      (1024*1024)
98
99 #define STATUS_BAD      0
100 #define STATUS_GOOD     1
101 #define STATUS_UNKNOWN  2
102
103 static cap_channel_t *capsyslog;
104 static fileargs_t *capfa;
105 static bool checkfor, compress, clear, force, keep;     /* flags */
106 static int verbose;
107 static int nfound, nsaved, nerr;                        /* statistics */
108 static int maxdumps;
109
110 extern FILE *zdopen(int, const char *);
111
112 static sig_atomic_t got_siginfo;
113 static void infohandler(int);
114
115 static void
116 logmsg(int pri, const char *fmt, ...)
117 {
118         va_list ap;
119
120         va_start(ap, fmt);
121         if (capsyslog != NULL)
122                 cap_vsyslog(capsyslog, pri, fmt, ap);
123         else
124                 vsyslog(pri, fmt, ap);
125         va_end(ap);
126 }
127
128 static FILE *
129 xfopenat(int dirfd, const char *path, int flags, const char *modestr, ...)
130 {
131         va_list ap;
132         FILE *fp;
133         mode_t mode;
134         int error, fd;
135
136         if ((flags & O_CREAT) == O_CREAT) {
137                 va_start(ap, modestr);
138                 mode = (mode_t)va_arg(ap, int);
139                 va_end(ap);
140         } else
141                 mode = 0;
142
143         fd = openat(dirfd, path, flags, mode);
144         if (fd < 0)
145                 return (NULL);
146         fp = fdopen(fd, modestr);
147         if (fp == NULL) {
148                 error = errno;
149                 (void)close(fd);
150                 errno = error;
151         }
152         return (fp);
153 }
154
155 static void
156 printheader(xo_handle_t *xo, const struct kerneldumpheader *h,
157     const char *device, int bounds, const int status)
158 {
159         uint64_t dumplen;
160         time_t t;
161         struct tm tm;
162         char time_str[64];
163         const char *stat_str;
164         const char *comp_str;
165
166         xo_flush_h(xo);
167         xo_emit_h(xo, "{Lwc:Dump header from device}{:dump_device/%s}\n",
168             device);
169         xo_emit_h(xo, "{P:  }{Lwc:Architecture}{:architecture/%s}\n",
170             h->architecture);
171         xo_emit_h(xo,
172             "{P:  }{Lwc:Architecture Version}{:architecture_version/%u}\n",
173             dtoh32(h->architectureversion));
174         dumplen = dtoh64(h->dumplength);
175         xo_emit_h(xo, "{P:  }{Lwc:Dump Length}{:dump_length_bytes/%lld}\n",
176             (long long)dumplen);
177         xo_emit_h(xo, "{P:  }{Lwc:Blocksize}{:blocksize/%d}\n",
178             dtoh32(h->blocksize));
179         switch (h->compression) {
180         case KERNELDUMP_COMP_NONE:
181                 comp_str = "none";
182                 break;
183         case KERNELDUMP_COMP_GZIP:
184                 comp_str = "gzip";
185                 break;
186         case KERNELDUMP_COMP_ZSTD:
187                 comp_str = "zstd";
188                 break;
189         default:
190                 comp_str = "???";
191                 break;
192         }
193         xo_emit_h(xo, "{P:  }{Lwc:Compression}{:compression/%s}\n", comp_str);
194         t = dtoh64(h->dumptime);
195         localtime_r(&t, &tm);
196         if (strftime(time_str, sizeof(time_str), "%F %T %z", &tm) == 0)
197                 time_str[0] = '\0';
198         xo_emit_h(xo, "{P:  }{Lwc:Dumptime}{:dumptime/%s}\n", time_str);
199         xo_emit_h(xo, "{P:  }{Lwc:Hostname}{:hostname/%s}\n", h->hostname);
200         xo_emit_h(xo, "{P:  }{Lwc:Magic}{:magic/%s}\n", h->magic);
201         xo_emit_h(xo, "{P:  }{Lwc:Version String}{:version_string/%s}",
202             h->versionstring);
203         xo_emit_h(xo, "{P:  }{Lwc:Panic String}{:panic_string/%s}\n",
204             h->panicstring);
205         xo_emit_h(xo, "{P:  }{Lwc:Dump Parity}{:dump_parity/%u}\n", h->parity);
206         xo_emit_h(xo, "{P:  }{Lwc:Bounds}{:bounds/%d}\n", bounds);
207
208         switch (status) {
209         case STATUS_BAD:
210                 stat_str = "bad";
211                 break;
212         case STATUS_GOOD:
213                 stat_str = "good";
214                 break;
215         default:
216                 stat_str = "unknown";
217                 break;
218         }
219         xo_emit_h(xo, "{P:  }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str);
220         xo_flush_h(xo);
221 }
222
223 static int
224 getbounds(int savedirfd)
225 {
226         FILE *fp;
227         char buf[6];
228         int ret;
229
230         /*
231          * If we are just checking, then we haven't done a chdir to the dump
232          * directory and we should not try to read a bounds file.
233          */
234         if (checkfor)
235                 return (0);
236
237         ret = 0;
238
239         if ((fp = xfopenat(savedirfd, "bounds", O_RDONLY, "r")) == NULL) {
240                 if (verbose)
241                         printf("unable to open bounds file, using 0\n");
242                 return (ret);
243         }
244         if (fgets(buf, sizeof(buf), fp) == NULL) {
245                 if (feof(fp))
246                         logmsg(LOG_WARNING, "bounds file is empty, using 0");
247                 else
248                         logmsg(LOG_WARNING, "bounds file: %s", strerror(errno));
249                 fclose(fp);
250                 return (ret);
251         }
252
253         errno = 0;
254         ret = (int)strtol(buf, NULL, 10);
255         if (ret == 0 && (errno == EINVAL || errno == ERANGE))
256                 logmsg(LOG_WARNING, "invalid value found in bounds, using 0");
257         fclose(fp);
258         return (ret);
259 }
260
261 static void
262 writebounds(int savedirfd, int bounds)
263 {
264         FILE *fp;
265
266         if ((fp = xfopenat(savedirfd, "bounds", O_WRONLY | O_CREAT | O_TRUNC,
267             "w", 0644)) == NULL) {
268                 logmsg(LOG_WARNING, "unable to write to bounds file: %m");
269                 return;
270         }
271
272         if (verbose)
273                 printf("bounds number: %d\n", bounds);
274
275         fprintf(fp, "%d\n", bounds);
276         fclose(fp);
277 }
278
279 static bool
280 writekey(int savedirfd, const char *keyname, uint8_t *dumpkey,
281     uint32_t dumpkeysize)
282 {
283         int fd;
284
285         fd = openat(savedirfd, keyname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
286         if (fd == -1) {
287                 logmsg(LOG_ERR, "Unable to open %s to write the key: %m.",
288                     keyname);
289                 return (false);
290         }
291
292         if (write(fd, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) {
293                 logmsg(LOG_ERR, "Unable to write the key to %s: %m.", keyname);
294                 close(fd);
295                 return (false);
296         }
297
298         close(fd);
299         return (true);
300 }
301
302 static off_t
303 file_size(int savedirfd, const char *path)
304 {
305         struct stat sb;
306
307         /* Ignore all errors, this file may not exist. */
308         if (fstatat(savedirfd, path, &sb, 0) == -1)
309                 return (0);
310         return (sb.st_size);
311 }
312
313 static off_t
314 saved_dump_size(int savedirfd, int bounds)
315 {
316         static char path[PATH_MAX];
317         off_t dumpsize;
318
319         dumpsize = 0;
320
321         (void)snprintf(path, sizeof(path), "info.%d", bounds);
322         dumpsize += file_size(savedirfd, path);
323         (void)snprintf(path, sizeof(path), "vmcore.%d", bounds);
324         dumpsize += file_size(savedirfd, path);
325         (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds);
326         dumpsize += file_size(savedirfd, path);
327         (void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds);
328         dumpsize += file_size(savedirfd, path);
329         (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds);
330         dumpsize += file_size(savedirfd, path);
331         (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds);
332         dumpsize += file_size(savedirfd, path);
333
334         return (dumpsize);
335 }
336
337 static void
338 saved_dump_remove(int savedirfd, int bounds)
339 {
340         static char path[PATH_MAX];
341
342         (void)snprintf(path, sizeof(path), "info.%d", bounds);
343         (void)unlinkat(savedirfd, path, 0);
344         (void)snprintf(path, sizeof(path), "vmcore.%d", bounds);
345         (void)unlinkat(savedirfd, path, 0);
346         (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds);
347         (void)unlinkat(savedirfd, path, 0);
348         (void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds);
349         (void)unlinkat(savedirfd, path, 0);
350         (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds);
351         (void)unlinkat(savedirfd, path, 0);
352         (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds);
353         (void)unlinkat(savedirfd, path, 0);
354 }
355
356 static void
357 symlinks_remove(int savedirfd)
358 {
359
360         (void)unlinkat(savedirfd, "info.last", 0);
361         (void)unlinkat(savedirfd, "key.last", 0);
362         (void)unlinkat(savedirfd, "vmcore.last", 0);
363         (void)unlinkat(savedirfd, "vmcore.last.gz", 0);
364         (void)unlinkat(savedirfd, "vmcore.last.zst", 0);
365         (void)unlinkat(savedirfd, "vmcore_encrypted.last", 0);
366         (void)unlinkat(savedirfd, "vmcore_encrypted.last.gz", 0);
367         (void)unlinkat(savedirfd, "textdump.tar.last", 0);
368         (void)unlinkat(savedirfd, "textdump.tar.last.gz", 0);
369 }
370
371 /*
372  * Check that sufficient space is available on the disk that holds the
373  * save directory.
374  */
375 static int
376 check_space(const char *savedir, int savedirfd, off_t dumpsize, int bounds)
377 {
378         char buf[100];
379         struct statfs fsbuf;
380         FILE *fp;
381         off_t available, minfree, spacefree, totfree, needed;
382
383         if (fstatfs(savedirfd, &fsbuf) < 0) {
384                 logmsg(LOG_ERR, "%s: %m", savedir);
385                 exit(1);
386         }
387         spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
388         totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024;
389
390         if ((fp = xfopenat(savedirfd, "minfree", O_RDONLY, "r")) == NULL)
391                 minfree = 0;
392         else {
393                 if (fgets(buf, sizeof(buf), fp) == NULL)
394                         minfree = 0;
395                 else {
396                         char *endp;
397
398                         errno = 0;
399                         minfree = strtoll(buf, &endp, 10);
400                         if (minfree == 0 && errno != 0)
401                                 minfree = -1;
402                         else {
403                                 while (*endp != '\0' && isspace(*endp))
404                                         endp++;
405                                 if (*endp != '\0' || minfree < 0)
406                                         minfree = -1;
407                         }
408                         if (minfree < 0)
409                                 logmsg(LOG_WARNING,
410                                     "`minfree` didn't contain a valid size "
411                                     "(`%s`). Defaulting to 0", buf);
412                 }
413                 (void)fclose(fp);
414         }
415
416         available = minfree > 0 ? spacefree - minfree : totfree;
417         needed = dumpsize / 1024 + 2;   /* 2 for info file */
418         needed -= saved_dump_size(savedirfd, bounds);
419         if (available < needed) {
420                 logmsg(LOG_WARNING,
421                     "no dump: not enough free space on device (need at least "
422                     "%jdkB for dump; %jdkB available; %jdkB reserved)",
423                     (intmax_t)needed,
424                     (intmax_t)available + minfree,
425                     (intmax_t)minfree);
426                 return (0);
427         }
428         if (spacefree - needed < 0)
429                 logmsg(LOG_WARNING,
430                     "dump performed, but free space threshold crossed");
431         return (1);
432 }
433
434 static bool
435 compare_magic(const struct kerneldumpheader *kdh, const char *magic)
436 {
437
438         return (strncmp(kdh->magic, magic, sizeof(kdh->magic)) == 0);
439 }
440
441 #define BLOCKSIZE (1<<12)
442 #define BLOCKMASK (~(BLOCKSIZE-1))
443
444 static int
445 DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse, char *buf,
446     const char *device, const char *filename, FILE *fp)
447 {
448         int he, hs, nr, nw, wl;
449         off_t dmpcnt, origsize;
450
451         dmpcnt = 0;
452         origsize = dumpsize;
453         he = 0;
454         while (dumpsize > 0) {
455                 wl = BUFFERSIZE;
456                 if (wl > dumpsize)
457                         wl = dumpsize;
458                 nr = read(fd, buf, roundup(wl, sectorsize));
459                 if (nr != (int)roundup(wl, sectorsize)) {
460                         if (nr == 0)
461                                 logmsg(LOG_WARNING,
462                                     "WARNING: EOF on dump device");
463                         else
464                                 logmsg(LOG_ERR, "read error on %s: %m", device);
465                         nerr++;
466                         return (-1);
467                 }
468                 if (!sparse) {
469                         nw = fwrite(buf, 1, wl, fp);
470                 } else {
471                         for (nw = 0; nw < nr; nw = he) {
472                                 /* find a contiguous block of zeroes */
473                                 for (hs = nw; hs < nr; hs += BLOCKSIZE) {
474                                         for (he = hs; he < nr && buf[he] == 0;
475                                             ++he)
476                                                 /* nothing */ ;
477                                         /* is the hole long enough to matter? */
478                                         if (he >= hs + BLOCKSIZE)
479                                                 break;
480                                 }
481
482                                 /* back down to a block boundary */
483                                 he &= BLOCKMASK;
484
485                                 /*
486                                  * 1) Don't go beyond the end of the buffer.
487                                  * 2) If the end of the buffer is less than
488                                  *    BLOCKSIZE bytes away, we're at the end
489                                  *    of the file, so just grab what's left.
490                                  */
491                                 if (hs + BLOCKSIZE > nr)
492                                         hs = he = nr;
493
494                                 /*
495                                  * At this point, we have a partial ordering:
496                                  *     nw <= hs <= he <= nr
497                                  * If hs > nw, buf[nw..hs] contains non-zero
498                                  * data. If he > hs, buf[hs..he] is all zeroes.
499                                  */
500                                 if (hs > nw)
501                                         if (fwrite(buf + nw, hs - nw, 1, fp)
502                                             != 1)
503                                         break;
504                                 if (he > hs)
505                                         if (fseeko(fp, he - hs, SEEK_CUR) == -1)
506                                                 break;
507                         }
508                 }
509                 if (nw != wl) {
510                         logmsg(LOG_ERR,
511                             "write error on %s file: %m", filename);
512                         logmsg(LOG_WARNING,
513                             "WARNING: vmcore may be incomplete");
514                         nerr++;
515                         return (-1);
516                 }
517                 if (verbose) {
518                         dmpcnt += wl;
519                         printf("%llu\r", (unsigned long long)dmpcnt);
520                         fflush(stdout);
521                 }
522                 dumpsize -= wl;
523                 if (got_siginfo) {
524                         printf("%s %.1lf%%\n", filename, (100.0 - (100.0 *
525                             (double)dumpsize / (double)origsize)));
526                         got_siginfo = 0;
527                 }
528         }
529         return (0);
530 }
531
532 /*
533  * Specialized version of dump-reading logic for use with textdumps, which
534  * are written backwards from the end of the partition, and must be reversed
535  * before being written to the file.  Textdumps are small, so do a bit less
536  * work to optimize/sparsify.
537  */
538 static int
539 DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf,
540     const char *device, const char *filename, FILE *fp)
541 {
542         int nr, nw, wl;
543         off_t dmpcnt, totsize;
544
545         totsize = dumpsize;
546         dmpcnt = 0;
547         wl = 512;
548         if ((dumpsize % wl) != 0) {
549                 logmsg(LOG_ERR, "textdump uneven multiple of 512 on %s",
550                     device);
551                 nerr++;
552                 return (-1);
553         }
554         while (dumpsize > 0) {
555                 nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl);
556                 if (nr != wl) {
557                         if (nr == 0)
558                                 logmsg(LOG_WARNING,
559                                     "WARNING: EOF on dump device");
560                         else
561                                 logmsg(LOG_ERR, "read error on %s: %m", device);
562                         nerr++;
563                         return (-1);
564                 }
565                 nw = fwrite(buf, 1, wl, fp);
566                 if (nw != wl) {
567                         logmsg(LOG_ERR,
568                             "write error on %s file: %m", filename);
569                         logmsg(LOG_WARNING,
570                             "WARNING: textdump may be incomplete");
571                         nerr++;
572                         return (-1);
573                 }
574                 if (verbose) {
575                         dmpcnt += wl;
576                         printf("%llu\r", (unsigned long long)dmpcnt);
577                         fflush(stdout);
578                 }
579                 dumpsize -= wl;
580         }
581         return (0);
582 }
583
584 static void
585 DoFile(const char *savedir, int savedirfd, const char *device)
586 {
587         xo_handle_t *xostdout, *xoinfo;
588         static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX];
589         static char keyname[PATH_MAX];
590         static char *buf = NULL;
591         char *temp = NULL;
592         struct kerneldumpheader kdhf, kdhl;
593         uint8_t *dumpkey;
594         off_t mediasize, dumpextent, dumplength, firsthd, lasthd;
595         FILE *core, *info;
596         int fdcore, fddev, error;
597         int bounds, status;
598         u_int sectorsize, xostyle;
599         uint32_t dumpkeysize;
600         bool iscompressed, isencrypted, istextdump, ret;
601
602         bounds = getbounds(savedirfd);
603         dumpkey = NULL;
604         mediasize = 0;
605         status = STATUS_UNKNOWN;
606
607         xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0);
608         if (xostdout == NULL) {
609                 logmsg(LOG_ERR, "%s: %m", infoname);
610                 return;
611         }
612
613         if (maxdumps > 0 && bounds == maxdumps)
614                 bounds = 0;
615
616         if (buf == NULL) {
617                 buf = malloc(BUFFERSIZE);
618                 if (buf == NULL) {
619                         logmsg(LOG_ERR, "%m");
620                         return;
621                 }
622         }
623
624         if (verbose)
625                 printf("checking for kernel dump on device %s\n", device);
626
627         fddev = fileargs_open(capfa, device);
628         if (fddev < 0) {
629                 logmsg(LOG_ERR, "%s: %m", device);
630                 return;
631         }
632
633         error = ioctl(fddev, DIOCGMEDIASIZE, &mediasize);
634         if (!error)
635                 error = ioctl(fddev, DIOCGSECTORSIZE, &sectorsize);
636         if (error) {
637                 logmsg(LOG_ERR,
638                     "couldn't find media and/or sector size of %s: %m", device);
639                 goto closefd;
640         }
641
642         if (verbose) {
643                 printf("mediasize = %lld bytes\n", (long long)mediasize);
644                 printf("sectorsize = %u bytes\n", sectorsize);
645         }
646
647         if (sectorsize < sizeof(kdhl)) {
648                 logmsg(LOG_ERR,
649                     "Sector size is less the kernel dump header %zu",
650                     sizeof(kdhl));
651                 goto closefd;
652         }
653
654         lasthd = mediasize - sectorsize;
655         temp = malloc(sectorsize);
656         if (temp == NULL) {
657                 logmsg(LOG_ERR, "%m");
658                 goto closefd;
659         }
660         if (lseek(fddev, lasthd, SEEK_SET) != lasthd ||
661             read(fddev, temp, sectorsize) != (ssize_t)sectorsize) {
662                 logmsg(LOG_ERR,
663                     "error reading last dump header at offset %lld in %s: %m",
664                     (long long)lasthd, device);
665                 goto closefd;
666         }
667         memcpy(&kdhl, temp, sizeof(kdhl));
668         iscompressed = istextdump = false;
669         if (compare_magic(&kdhl, TEXTDUMPMAGIC)) {
670                 if (verbose)
671                         printf("textdump magic on last dump header on %s\n",
672                             device);
673                 istextdump = true;
674                 if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) {
675                         logmsg(LOG_ERR,
676                             "unknown version (%d) in last dump header on %s",
677                             dtoh32(kdhl.version), device);
678
679                         status = STATUS_BAD;
680                         if (force == false)
681                                 goto closefd;
682                 }
683         } else if (compare_magic(&kdhl, KERNELDUMPMAGIC)) {
684                 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
685                         logmsg(LOG_ERR,
686                             "unknown version (%d) in last dump header on %s",
687                             dtoh32(kdhl.version), device);
688
689                         status = STATUS_BAD;
690                         if (force == false)
691                                 goto closefd;
692                 }
693                 switch (kdhl.compression) {
694                 case KERNELDUMP_COMP_NONE:
695                         break;
696                 case KERNELDUMP_COMP_GZIP:
697                 case KERNELDUMP_COMP_ZSTD:
698                         if (compress && verbose)
699                                 printf("dump is already compressed\n");
700                         compress = false;
701                         iscompressed = true;
702                         break;
703                 default:
704                         logmsg(LOG_ERR, "unknown compression type %d on %s",
705                             kdhl.compression, device);
706                         break;
707                 }
708         } else {
709                 if (verbose)
710                         printf("magic mismatch on last dump header on %s\n",
711                             device);
712
713                 status = STATUS_BAD;
714                 if (force == false)
715                         goto closefd;
716
717                 if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) {
718                         if (verbose)
719                                 printf("forcing magic on %s\n", device);
720                         memcpy(kdhl.magic, KERNELDUMPMAGIC, sizeof(kdhl.magic));
721                 } else {
722                         logmsg(LOG_ERR, "unable to force dump - bad magic");
723                         goto closefd;
724                 }
725                 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
726                         logmsg(LOG_ERR,
727                             "unknown version (%d) in last dump header on %s",
728                             dtoh32(kdhl.version), device);
729
730                         status = STATUS_BAD;
731                         if (force == false)
732                                 goto closefd;
733                 }
734         }
735
736         nfound++;
737         if (clear)
738                 goto nuke;
739
740         if (kerneldump_parity(&kdhl)) {
741                 logmsg(LOG_ERR,
742                     "parity error on last dump header on %s", device);
743                 nerr++;
744                 status = STATUS_BAD;
745                 if (force == false)
746                         goto closefd;
747         }
748         dumpextent = dtoh64(kdhl.dumpextent);
749         dumplength = dtoh64(kdhl.dumplength);
750         dumpkeysize = dtoh32(kdhl.dumpkeysize);
751         firsthd = lasthd - dumpextent - sectorsize - dumpkeysize;
752         if (lseek(fddev, firsthd, SEEK_SET) != firsthd ||
753             read(fddev, temp, sectorsize) != (ssize_t)sectorsize) {
754                 logmsg(LOG_ERR,
755                     "error reading first dump header at offset %lld in %s: %m",
756                     (long long)firsthd, device);
757                 nerr++;
758                 goto closefd;
759         }
760         memcpy(&kdhf, temp, sizeof(kdhf));
761
762         if (verbose >= 2) {
763                 printf("First dump headers:\n");
764                 printheader(xostdout, &kdhf, device, bounds, -1);
765
766                 printf("\nLast dump headers:\n");
767                 printheader(xostdout, &kdhl, device, bounds, -1);
768                 printf("\n");
769         }
770
771         if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) {
772                 logmsg(LOG_ERR,
773                     "first and last dump headers disagree on %s", device);
774                 nerr++;
775                 status = STATUS_BAD;
776                 if (force == false)
777                         goto closefd;
778         } else {
779                 status = STATUS_GOOD;
780         }
781
782         if (checkfor) {
783                 printf("A dump exists on %s\n", device);
784                 close(fddev);
785                 exit(0);
786         }
787
788         if (kdhl.panicstring[0] != '\0')
789                 logmsg(LOG_ALERT, "reboot after panic: %.*s",
790                     (int)sizeof(kdhl.panicstring), kdhl.panicstring);
791         else
792                 logmsg(LOG_ALERT, "reboot");
793
794         if (verbose)
795                 printf("Checking for available free space\n");
796
797         if (!check_space(savedir, savedirfd, dumplength, bounds)) {
798                 nerr++;
799                 goto closefd;
800         }
801
802         writebounds(savedirfd, bounds + 1);
803
804         saved_dump_remove(savedirfd, bounds);
805
806         snprintf(infoname, sizeof(infoname), "info.%d", bounds);
807
808         /*
809          * Create or overwrite any existing dump header files.
810          */
811         if ((info = xfopenat(savedirfd, infoname,
812             O_WRONLY | O_CREAT | O_TRUNC, "w", 0600)) == NULL) {
813                 logmsg(LOG_ERR, "open(%s): %m", infoname);
814                 nerr++;
815                 goto closefd;
816         }
817
818         isencrypted = (dumpkeysize > 0);
819         if (compress)
820                 snprintf(corename, sizeof(corename), "%s.%d.gz",
821                     istextdump ? "textdump.tar" :
822                     (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds);
823         else if (iscompressed && !isencrypted)
824                 snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds,
825                     (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst");
826         else
827                 snprintf(corename, sizeof(corename), "%s.%d",
828                     istextdump ? "textdump.tar" :
829                     (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds);
830         fdcore = openat(savedirfd, corename, O_WRONLY | O_CREAT | O_TRUNC,
831             0600);
832         if (fdcore < 0) {
833                 logmsg(LOG_ERR, "open(%s): %m", corename);
834                 fclose(info);
835                 nerr++;
836                 goto closefd;
837         }
838
839         if (compress)
840                 core = zdopen(fdcore, "w");
841         else
842                 core = fdopen(fdcore, "w");
843         if (core == NULL) {
844                 logmsg(LOG_ERR, "%s: %m", corename);
845                 (void)close(fdcore);
846                 (void)fclose(info);
847                 nerr++;
848                 goto closefd;
849         }
850         fdcore = -1;
851
852         xostyle = xo_get_style(NULL);
853         xoinfo = xo_create_to_file(info, xostyle, 0);
854         if (xoinfo == NULL) {
855                 logmsg(LOG_ERR, "%s: %m", infoname);
856                 fclose(info);
857                 nerr++;
858                 goto closeall;
859         }
860         xo_open_container_h(xoinfo, "crashdump");
861
862         if (verbose)
863                 printheader(xostdout, &kdhl, device, bounds, status);
864
865         printheader(xoinfo, &kdhl, device, bounds, status);
866         xo_close_container_h(xoinfo, "crashdump");
867         xo_flush_h(xoinfo);
868         xo_finish_h(xoinfo);
869         fclose(info);
870
871         if (isencrypted) {
872                 dumpkey = calloc(1, dumpkeysize);
873                 if (dumpkey == NULL) {
874                         logmsg(LOG_ERR, "Unable to allocate kernel dump key.");
875                         nerr++;
876                         goto closeall;
877                 }
878
879                 if (read(fddev, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) {
880                         logmsg(LOG_ERR, "Unable to read kernel dump key: %m.");
881                         nerr++;
882                         goto closeall;
883                 }
884
885                 snprintf(keyname, sizeof(keyname), "key.%d", bounds);
886                 ret = writekey(savedirfd, keyname, dumpkey, dumpkeysize);
887                 explicit_bzero(dumpkey, dumpkeysize);
888                 if (!ret) {
889                         nerr++;
890                         goto closeall;
891                 }
892         }
893
894         logmsg(LOG_NOTICE, "writing %s%score to %s/%s",
895             isencrypted ? "encrypted " : "", compress ? "compressed " : "",
896             savedir, corename);
897
898         if (istextdump) {
899                 if (DoTextdumpFile(fddev, dumplength, lasthd, buf, device,
900                     corename, core) < 0)
901                         goto closeall;
902         } else {
903                 if (DoRegularFile(fddev, dumplength, sectorsize,
904                     !(compress || iscompressed || isencrypted), buf, device,
905                     corename, core) < 0) {
906                         goto closeall;
907                 }
908         }
909         if (verbose)
910                 printf("\n");
911
912         if (fclose(core) < 0) {
913                 logmsg(LOG_ERR, "error on %s: %m", corename);
914                 nerr++;
915                 goto closefd;
916         }
917
918         symlinks_remove(savedirfd);
919         if (symlinkat(infoname, savedirfd, "info.last") == -1) {
920                 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m",
921                     savedir, "info.last");
922         }
923         if (isencrypted) {
924                 if (symlinkat(keyname, savedirfd, "key.last") == -1) {
925                         logmsg(LOG_WARNING,
926                             "unable to create symlink %s/%s: %m", savedir,
927                             "key.last");
928                 }
929         }
930         if (compress || iscompressed) {
931                 snprintf(linkname, sizeof(linkname), "%s.last.%s",
932                     istextdump ? "textdump.tar" :
933                     (isencrypted ? "vmcore_encrypted" : "vmcore"),
934                     (kdhl.compression == KERNELDUMP_COMP_ZSTD) ? "zst" : "gz");
935         } else {
936                 snprintf(linkname, sizeof(linkname), "%s.last",
937                     istextdump ? "textdump.tar" :
938                     (isencrypted ? "vmcore_encrypted" : "vmcore"));
939         }
940         if (symlinkat(corename, savedirfd, linkname) == -1) {
941                 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m",
942                     savedir, linkname);
943         }
944
945         nsaved++;
946
947         if (verbose)
948                 printf("dump saved\n");
949
950 nuke:
951         if (!keep) {
952                 if (verbose)
953                         printf("clearing dump header\n");
954                 memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic));
955                 memcpy(temp, &kdhl, sizeof(kdhl));
956                 if (lseek(fddev, lasthd, SEEK_SET) != lasthd ||
957                     write(fddev, temp, sectorsize) != (ssize_t)sectorsize)
958                         logmsg(LOG_ERR,
959                             "error while clearing the dump header: %m");
960         }
961         xo_close_container_h(xostdout, "crashdump");
962         xo_finish_h(xostdout);
963         free(dumpkey);
964         free(temp);
965         close(fddev);
966         return;
967
968 closeall:
969         fclose(core);
970
971 closefd:
972         free(dumpkey);
973         free(temp);
974         close(fddev);
975 }
976
977 /* Prepend "/dev/" to any arguments that don't already have it */
978 static char **
979 devify(int argc, char **argv)
980 {
981         char **devs;
982         int i, l;
983
984         devs = malloc(argc * sizeof(*argv));
985         if (devs == NULL) {
986                 logmsg(LOG_ERR, "malloc(): %m");
987                 exit(1);
988         }
989         for (i = 0; i < argc; i++) {
990                 if (strncmp(argv[i], _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
991                         devs[i] = strdup(argv[i]);
992                 else {
993                         char *fullpath;
994
995                         fullpath = malloc(PATH_MAX);
996                         if (fullpath == NULL) {
997                                 logmsg(LOG_ERR, "malloc(): %m");
998                                 exit(1);
999                         }
1000                         l = snprintf(fullpath, PATH_MAX, "%s%s", _PATH_DEV,
1001                             argv[i]);
1002                         if (l < 0) {
1003                                 logmsg(LOG_ERR, "snprintf(): %m");
1004                                 exit(1);
1005                         } else if (l >= PATH_MAX) {
1006                                 logmsg(LOG_ERR, "device name too long");
1007                                 exit(1);
1008                         }
1009                         devs[i] = fullpath;
1010                 }
1011         }
1012         return (devs);
1013 }
1014
1015 static char **
1016 enum_dumpdevs(int *argcp)
1017 {
1018         struct fstab *fsp;
1019         char **argv;
1020         int argc, n;
1021
1022         /*
1023          * We cannot use getfsent(3) in capability mode, so we must
1024          * scan /etc/fstab and build up a list of candidate devices
1025          * before proceeding.
1026          */
1027         argc = 0;
1028         n = 8;
1029         argv = malloc(n * sizeof(*argv));
1030         if (argv == NULL) {
1031                 logmsg(LOG_ERR, "malloc(): %m");
1032                 exit(1);
1033         }
1034         for (;;) {
1035                 fsp = getfsent();
1036                 if (fsp == NULL)
1037                         break;
1038                 if (strcmp(fsp->fs_vfstype, "swap") != 0 &&
1039                     strcmp(fsp->fs_vfstype, "dump") != 0)
1040                         continue;
1041                 if (argc >= n) {
1042                         n *= 2;
1043                         argv = realloc(argv, n * sizeof(*argv));
1044                         if (argv == NULL) {
1045                                 logmsg(LOG_ERR, "realloc(): %m");
1046                                 exit(1);
1047                         }
1048                 }
1049                 argv[argc] = strdup(fsp->fs_spec);
1050                 if (argv[argc] == NULL) {
1051                         logmsg(LOG_ERR, "strdup(): %m");
1052                         exit(1);
1053                 }
1054                 argc++;
1055         }
1056         *argcp = argc;
1057         return (argv);
1058 }
1059
1060 static void
1061 init_caps(int argc, char **argv)
1062 {
1063         cap_rights_t rights;
1064         cap_channel_t *capcas;
1065
1066         capcas = cap_init();
1067         if (capcas == NULL) {
1068                 logmsg(LOG_ERR, "cap_init(): %m");
1069                 exit(1);
1070         }
1071         /*
1072          * The fileargs capability does not currently provide a way to limit
1073          * ioctls.
1074          */
1075         (void)cap_rights_init(&rights, CAP_PREAD, CAP_WRITE, CAP_IOCTL);
1076         capfa = fileargs_init(argc, argv, checkfor || keep ? O_RDONLY : O_RDWR,
1077             0, &rights, FA_OPEN);
1078         if (capfa == NULL) {
1079                 logmsg(LOG_ERR, "fileargs_init(): %m");
1080                 exit(1);
1081         }
1082         caph_cache_catpages();
1083         caph_cache_tzdata();
1084         if (caph_enter_casper() != 0) {
1085                 logmsg(LOG_ERR, "caph_enter_casper(): %m");
1086                 exit(1);
1087         }
1088         capsyslog = cap_service_open(capcas, "system.syslog");
1089         if (capsyslog == NULL) {
1090                 logmsg(LOG_ERR, "cap_service_open(system.syslog): %m");
1091                 exit(1);
1092         }
1093         cap_close(capcas);
1094 }
1095
1096 static void
1097 usage(void)
1098 {
1099         xo_error("%s\n%s\n%s\n",
1100             "usage: savecore -c [-v] [device ...]",
1101             "       savecore -C [-v] [device ...]",
1102             "       savecore [-fkvz] [-m maxdumps] [directory [device ...]]");
1103         exit(1);
1104 }
1105
1106 int
1107 main(int argc, char **argv)
1108 {
1109         cap_rights_t rights;
1110         const char *savedir;
1111         char **devs;
1112         int i, ch, error, savedirfd;
1113
1114         checkfor = compress = clear = force = keep = false;
1115         verbose = 0;
1116         nfound = nsaved = nerr = 0;
1117         savedir = ".";
1118
1119         openlog("savecore", LOG_PERROR, LOG_DAEMON);
1120         signal(SIGINFO, infohandler);
1121
1122         argc = xo_parse_args(argc, argv);
1123         if (argc < 0)
1124                 exit(1);
1125
1126         while ((ch = getopt(argc, argv, "Ccfkm:vz")) != -1)
1127                 switch(ch) {
1128                 case 'C':
1129                         checkfor = true;
1130                         break;
1131                 case 'c':
1132                         clear = true;
1133                         break;
1134                 case 'f':
1135                         force = true;
1136                         break;
1137                 case 'k':
1138                         keep = true;
1139                         break;
1140                 case 'm':
1141                         maxdumps = atoi(optarg);
1142                         if (maxdumps <= 0) {
1143                                 logmsg(LOG_ERR, "Invalid maxdump value");
1144                                 exit(1);
1145                         }
1146                         break;
1147                 case 'v':
1148                         verbose++;
1149                         break;
1150                 case 'z':
1151                         compress = 1;
1152                         break;
1153                 case '?':
1154                 default:
1155                         usage();
1156                 }
1157         if (checkfor && (clear || force || keep))
1158                 usage();
1159         if (clear && (compress || keep))
1160                 usage();
1161         if (maxdumps > 0 && (checkfor || clear))
1162                 usage();
1163         argc -= optind;
1164         argv += optind;
1165         if (argc >= 1 && !checkfor && !clear) {
1166                 error = chdir(argv[0]);
1167                 if (error) {
1168                         logmsg(LOG_ERR, "chdir(%s): %m", argv[0]);
1169                         exit(1);
1170                 }
1171                 savedir = argv[0];
1172                 argc--;
1173                 argv++;
1174         }
1175         if (argc == 0)
1176                 devs = enum_dumpdevs(&argc);
1177         else
1178                 devs = devify(argc, argv);
1179
1180         savedirfd = open(savedir, O_RDONLY | O_DIRECTORY);
1181         if (savedirfd < 0) {
1182                 logmsg(LOG_ERR, "open(%s): %m", savedir);
1183                 exit(1);
1184         }
1185         (void)cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL, CAP_FSTATAT,
1186             CAP_FSTATFS, CAP_PREAD, CAP_SYMLINKAT, CAP_FTRUNCATE, CAP_UNLINKAT,
1187             CAP_WRITE);
1188         if (caph_rights_limit(savedirfd, &rights) < 0) {
1189                 logmsg(LOG_ERR, "cap_rights_limit(): %m");
1190                 exit(1);
1191         }
1192
1193         /* Enter capability mode. */
1194         init_caps(argc, devs);
1195
1196         for (i = 0; i < argc; i++)
1197                 DoFile(savedir, savedirfd, devs[i]);
1198
1199         /* Emit minimal output. */
1200         if (nfound == 0) {
1201                 if (checkfor) {
1202                         if (verbose)
1203                                 printf("No dump exists\n");
1204                         exit(1);
1205                 }
1206                 if (verbose)
1207                         logmsg(LOG_WARNING, "no dumps found");
1208         } else if (nsaved == 0) {
1209                 if (nerr != 0) {
1210                         if (verbose)
1211                                 logmsg(LOG_WARNING,
1212                                     "unsaved dumps found but not saved");
1213                         exit(1);
1214                 } else if (verbose)
1215                         logmsg(LOG_WARNING, "no unsaved dumps found");
1216         }
1217
1218         return (0);
1219 }
1220
1221 static void
1222 infohandler(int sig __unused)
1223 {
1224         got_siginfo = 1;
1225 }