[hackers] [ubase][PATCH] Add shred(1)

From: Mattias Andrée <maandree_AT_kth.se>
Date: Sun, 27 Mar 2016 15:00:43 +0200

Signed-off-by: Mattias Andrée <maandree_AT_kth.se>
---
 Makefile     |   2 +
 config.def.h |   1 +
 shred.1      |  66 ++++++++++++
 shred.c      | 332 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 401 insertions(+)
 create mode 100644 shred.1
 create mode 100644 shred.c
diff --git a/Makefile b/Makefile
index 59616a4..ee348bb 100644
--- a/Makefile
+++ b/Makefile
_AT_@ -73,6 +73,7 @@ BIN = \
 	readahead         \
 	respawn           \
 	rmmod             \
+	shred             \
 	stat              \
 	su                \
 	swaplabel         \
_AT_@ -107,6 +108,7 @@ MAN1 = \
 	pidof.1             \
 	ps.1                \
 	respawn.1           \
+	shred.1             \
 	stat.1              \
 	su.1                \
 	truncate.1          \
diff --git a/config.def.h b/config.def.h
index 577833e..8651cd5 100644
--- a/config.def.h
+++ b/config.def.h
_AT_@ -9,3 +9,4 @@
 #define BTMP_PATH	"/var/log/btmp"
 #undef WTMP_PATH
 #define WTMP_PATH	"/var/log/wtmp"
+#define URANDOM_PATH    "/dev/urandom"
diff --git a/shred.1 b/shred.1
new file mode 100644
index 0000000..18add0d
--- /dev/null
+++ b/shred.1
_AT_@ -0,0 +1,66 @@
+.Dd March 26, 2016
+.Dt SHRED 1
+.Os ubase
+.Sh NAME
+.Nm shred
+.Nd securely erase the content of a file
+.Sh SYNOPSIS
+.Nm
+.Op Fl n Ar num
+.Op Fl r Ar source
+.Op Fl fuvxz
+.Ar file ...
+.Sh DESCRIPTION
+.Nm
+overrides each specified
+.Ar file
+with random data to hide its original content.
+If
+.Ar file
+is -, erase standard output.
+.Pp
+This program is useless on solid state drives
+and modern filesystems that have not been
+especially configured. If someone care to write
+an essay on all assumptions
+.Nm
+makes, that would be great!
+.Pp
+.Nm
+never writes to directories or symbolic links,
+only the flags
+.Fl fur
+applies to directories. For symbolic links, this
+is the case because it impossible to rewrite
+a symbolic link without removing it first.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl f
+Erase the file even if it is write-protected.
+.It Fl n Ar num
+Override the file with random data. If not
+specified, the file will be overridden 3 times.
+.Ar num
+times.
+.It Fl r Ar source
+Read random data from the file
+.Ar source .
+.It Fl u
+After erasing, truncate the file, obfuscate the
+filename, obfuscate the file's atime and mtime,
+and unlink it. The filename and timestamps will
+be obfuscated 20 times.
+.It Fl v
+Show progress.
+.It Fl x
+Do not round up file sizes to the full blocks.
+This option is always applied on non-regular files.
+.It Fl z
+After the last erasure, hide the shredding by
+overridding the file once more but with zeroes.
+.El
+.Sh NOTES
+.Nm
+will not warn you about existence unlisted hard links.
+.Sh SEE ALSO
+.Xr rm 1
diff --git a/shred.c b/shred.c
new file mode 100644
index 0000000..b2375f6
--- /dev/null
+++ b/shred.c
_AT_@ -0,0 +1,332 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "util.h"
+
+static int fflag = 0;
+static long long nflag = 3;
+static char *rflag = URANDOM_PATH;
+static int uflag = 0;
+static int vflag = 0;
+static int xflag = 0;
+static int zflag = 0;
+static int source;
+static int zsource;
+static FILE *logfp;
+
+static void
+usage(void)
+{
+	eprintf("usage: %s [-n num] [-r source] [-fuvxz] file ...\n", argv0);
+}
+
+static int
+check_dir_empty(char *path)
+{
+	DIR *dir;
+	struct dirent *file;
+
+	if (!(dir = opendir(path))) {
+		weprintf("opendir %s:", path);
+		return 1;
+	}
+
+	while (errno = 0, (file = readdir(dir))) {
+		if (strcmp(file->d_name, ".") && strcmp(file->d_name, "..")) {
+			weprintf("%s: directory is not empty\n", path);
+			return 1;
+		}
+	}
+
+	if (errno) {
+		weprintf("readdir %s:", path);
+		return 1;
+	}
+
+	closedir(dir);
+	return 0;
+}
+
+static void
+fill_random(int fd, const char *path, void *buffer, size_t n)
+{
+	ssize_t m;
+	size_t ptr;
+
+	for (ptr = 0; ptr < n; ptr += m) {
+		m = read(fd, (char *)buffer + ptr, n - ptr);
+		if (m < 0)
+			eprintf("read %s:", path);
+		if (!m)
+			eprintf("read %s: end of file reached\n", path);
+	}
+}
+
+static void
+do_shred_content(int dest, char *destpath, int src, const char *srcpath, off_t size, blksize_t blksize)
+{
+	static char *buffer = 0;
+	static blksize_t buffer_size = 0;
+	off_t n, ptr, size_saved = size;
+	ssize_t m;
+	double progress;
+
+	if (blksize > buffer_size) {
+		buffer_size = blksize;
+		buffer = erealloc(buffer, blksize);
+	}
+
+	for (; size; size -= n) {
+		progress = (double)(size_saved - size) / size_saved;
+		fprintf(logfp, "    %i %%\n", (int)(progress * 100));
+
+		n = size < (off_t)blksize ? size : (off_t)blksize;
+
+		fill_random(src, srcpath, buffer, n);
+
+		for (ptr = 0; ptr < n; ptr += m)
+			if ((m = write(dest, buffer + ptr, n - ptr)) < 0)
+				eprintf("write %s:", destpath);
+		fprintf(logfp, "\033[A\033[K");
+	}
+
+	lseek(dest, 0, SEEK_SET);
+}
+
+static void
+shred_content(int fd, char *path, off_t size, blksize_t blksize)
+{
+	long long i;
+
+	if (blksize < 512)
+		blksize = 8 << 10;
+
+	for (i = 0; i < nflag; i++) {
+		fprintf(logfp, "  Shredding content, pass %llu of %llu\n", i + 1, nflag);
+		do_shred_content(fd, path, source, rflag, size, blksize);
+	}
+
+	if (zflag) {
+		fprintf(logfp, "  Zeroing content\n");
+		do_shred_content(fd, path, zsource, "/dev/null", size, blksize);
+	}
+}
+
+static int
+shred_times(char *path)
+{
+	struct timespec times[2];
+	int i;
+
+	fill_random(source, rflag, times, sizeof(times));
+
+	for (i = 0; i < 2; i++) {
+		if (times[i].tv_nsec < 0)
+			times[i].tv_nsec = ~times[i].tv_nsec;
+		times[i].tv_nsec %= 1000000000L;
+	}
+
+	if (utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW)) {
+		weprintf("utime %s:", path);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int
+shred_filename(char *path, char *name, char *orig)
+{
+	char old[PATH_MAX];
+	size_t r, w, n;
+
+	strcpy(old, path);
+
+	n = PATH_MAX - 1 - (size_t)(name - path);
+	if (n > NAME_MAX)
+		n = NAME_MAX;
+
+	do {
+		fill_random(source, rflag, name, n);
+		for (r = w = 0; r < n; r++)
+			if (name[r] && name[r] != '/')
+				name[w++] = name[r];
+		name[w] = 0;
+	} while (!w || !access(path, F_OK));
+
+	if (rename(old, path)) {
+		weprintf("rename %s:", orig);
+		strcpy(path, old);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int
+shred(char *path)
+{
+	struct stat attr;
+	int fd;
+	size_t n;
+	char *p;
+	char tmppath[PATH_MAX];
+
+	fprintf(logfp, "Shredding %s\n", path);
+
+	if (lstat(path, &attr)) {
+		weprintf("stat %s:", path);
+		return 1;
+	}
+
+	if (fflag && access(path, W_OK) && chmod(path, attr.st_mode | S_IWUSR)) {
+		weprintf("chmod %s:", path);
+		return 1;
+	}
+
+	switch (attr.st_mode & S_IFMT) {
+	case S_IFLNK:
+		/* Cannot do anything because of missing system call. */
+		goto no_content_shred;
+	case S_IFDIR:
+		if (check_dir_empty(path))
+			return 1;
+		goto no_content_shred;
+	default:
+		fd = open(path, O_WRONLY | O_NOCTTY | O_NOFOLLOW | O_NOATIME | O_LARGEFILE);
+		if (fd < 0) {
+			weprintf("open %s:", path);
+			return 1;
+		}
+	}
+
+	switch (attr.st_mode & S_IFMT) {
+	case S_IFREG:
+		if (!xflag && attr.st_size % attr.st_blksize) {
+			attr.st_size -= attr.st_size % attr.st_blksize;
+			attr.st_size += attr.st_blksize;
+		}
+		break;
+	case S_IFBLK:
+		if (ioctl(fd, BLKGETSIZE64, &attr.st_size) < 0) {
+			weprintf("BLKGETSIZE64 %s:", path);
+			close(fd);
+			return 1;
+		}
+		break;
+	default:
+		attr.st_size = (off_t)~0;
+		attr.st_size ^= (off_t)1 << (8 * sizeof(off_t) - 1);
+		break;
+	}
+
+	shred_content(fd, path, attr.st_size, attr.st_blksize);
+
+	if (uflag && ftruncate(fd, 0)) {
+		fprintf(logfp, "  Truncating\n");
+		weprintf("ftruncate %s:", path);
+		close(fd);
+		return 1;
+	}
+
+	if (close(fd) && errno != EINTR) {
+		weprintf("close %s:", path);
+		return 1;
+	}
+
+no_content_shred:
+
+	if (!uflag)
+		return 0;
+
+	for (n = 0; n < 20; n++) {
+		fprintf(logfp, "  Shredding timestamps, pass %zu of 20\n", n + 1);
+		if (shred_times(path))
+			break;
+	}
+	fprintf(logfp, "  Shredding timestamps, done\n");
+
+	strcpy(tmppath, path);
+	for (n = strlen(path); n > 1 && tmppath[n - 1] == '/';)
+		tmppath[--n] = '\0';
+	p = strrchr(tmppath, '/');
+	p = p ? p + 1 : tmppath;
+	for (n = 0; n < 20; n++) {
+		fprintf(logfp, "  Shredding filename, pass %zu of 20\n", n + 1);
+		if (shred_filename(tmppath, p, path))
+			break;
+	}
+	fprintf(logfp, "  Shredding filename, done\n");
+
+	fprintf(logfp, "  Unlinking\n");
+	if (remove(tmppath)) {
+		weprintf("remove %s:", path);
+		return 1;
+	}
+
+	return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+	int ret = 0;
+
+	ARGBEGIN {
+	case 'f':
+		fflag = 1;
+		break;
+	case 'n':
+		nflag = estrtonum(EARGF(usage()), 0, LLONG_MAX);
+		break;
+	case 'r':
+		rflag = EARGF(usage());
+		break;
+	case 'u':
+		uflag = 1;
+		break;
+	case 'v':
+		vflag = 1;
+		break;
+	case 'x':
+		xflag = 1;
+		break;
+	case 'z':
+		zflag = 1;
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if (!argc)
+		usage();
+
+	logfp = stderr;
+	if ((source = open(rflag, O_RDONLY | O_NOCTTY | O_LARGEFILE)) < 0)
+		eprintf("open %s:", rflag);
+	if (zflag && (zsource = open("/dev/zero", O_RDONLY | O_NOCTTY | O_LARGEFILE)) < 0)
+		eprintf("open /dev/zero:");
+	if (!vflag && !(logfp = fopen("/dev/null", "r")))
+		eprintf("fopen /dev/null:");
+
+	for (; *argv; argv++) {
+		if (!strcmp(*argv, "-"))
+			*argv = "/dev/stdout";
+		ret |= shred(*argv);
+	}
+
+	close(source);
+	if (zsource >= 0)
+		close(zsource);
+	return ret;
+}
-- 
2.7.4
Received on Sun Mar 27 2016 - 15:00:43 CEST

This archive was generated by hypermail 2.3.0 : Sun Mar 27 2016 - 15:12:15 CEST