Package: wipe Version: 0.22-1 Severity: normal Tags: upstream patch The wipe man-page states: "Normally, wipe tries to cover file names by renaming them"
However, this does not work for me for different versions of wipe. Using fls from the sleuthkit, its easy to see the name of the wiped file in the filesystem on the ext{2,3,4} filesystems. On vfat, btrfs and xfs the filename can be retrieved easily, too. My testcase was: $ dd if=/dev/zero bs=1M count=256 of=wipeimagefile-ext2.bin; $ losetup /dev/loop2 wipeimagefile-ext2.bin; $ mkfs.ext2 /dev/loop2; $ mkdir /mnt/wipetest; $ mount /dev/loop2 /mnt/wipetest; $ echo "very secret data that should be deleted after use" > /mnt/wipetest/secretfile.txt; $ wipe -fc /mnt/wipetest/secretfile.txt; $ umount /mnt/wipetest; $ losetup -d /dev/loop2; $ rmdir /mnt/wipetest; $ fls wipeimagefile-ext2.bin wipeimagefile-ext2.bin d/d 11: lost+found r/- * 0: secretfile.txt r/- * 0: 4hv06IB9SyTgVt d/d 2561: $OrphanFiles In contrast to this behaviour, shred from coreutils-8.5 succeeds in hiding the filenames. shred was used with the parameters -fu for this comparison. I believe that this is from the different usage of sync() in wipe and fsync(dir_fd, dirname) in shred. Note that this isn't a limitation of the filesystem or the operating system, as shred succeeds in hiding the original filename. Used for the test were the following versions of wipe: wipe-0.21-9 from debian squeeze, wipe-0.22-1 from debian wheezy and sid, wipe-0.21-5.fc15 from fedora 15, wipe-0.21-8.fc19 from fedora 19 on ext2, ext3, ext4, vfat, xfs and btrfs. I have written a patch that fixes the mentioned issue. It is heavily based on the source of shred from coreutils by Colin Plumb. While shred is now licensed under GPLv3, the parts of my patch that were derived from shred could easily be changed to a version of shred (git cad884a..) that was licensed under GPLv2. For the same testcase as written in my first mail, the fls output is: d/d 11: lost+found r/- * 0: 0 r/- * 0: 00 d/d 2561: $OrphanFiles Note that this patch only keeps the filename from being readable in the filesystem structure. For journaling fileystems, such as ext{3,4}, the filename is still retained in the journal.
Description: really delete filenames of deleted files Author: Timo Boettcher <timo.boettc...@redteam-pentesting.de> Last-Update: 2013-10-15 --- a/wipe.c +++ b/wipe.c @@ -77,6 +79,7 @@ #ifdef HAVE_GETOPT #include <getopt.h> #endif +#include <assert.h> #include <ctype.h> #include <string.h> #include <errno.h> @@ -174,6 +177,9 @@ /* End of Options ***/ +static int ignorable_sync_errno (int errno_val); +static int dosync (int fd, char const *qname); +static int incname (char *name, size_t len); static int wipe_filename_and_remove (char *fn); /*** do_remove */ @@ -501,73 +507,176 @@ static char valid_filename_chars[64] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-."; +static int +ignorable_sync_errno (int errno_val) +{ + return (errno_val == EINVAL + || errno_val == EBADF + /* HP-UX does this */ + || errno_val == EISDIR); +} + + +#define HAVE_FDATASYNC 1 +static int +dosync (int fd, char const *qname) +{ + int err; + +#if HAVE_FDATASYNC + if (fdatasync (fd) == 0) + return 0; + err = errno; + if ( ! ignorable_sync_errno (err)) { + fprintf (stderr, "%s: fdatasync failed", qname); + errno = err; + return -1; + } +#endif + + if (fsync (fd) == 0) + return 0; + err = errno; + if ( ! ignorable_sync_errno (err)) { + fprintf (stderr, "%s: fsync failed", qname); + errno = err; + return -1; + } + + sync (); + return 0; +} + +static int +incname (char *name, size_t len) +{ + while (len--) { + char const *p = strchr (valid_filename_chars, name[len]); + + /* Given that NAME is composed of bytes from NAMESET, + P will never be NULL here. */ + assert (p); + + /* If this character has a successor, use it. */ + if (p[1]) { + name[len] = p[1]; + return 0; + } + + /* Otherwise, set this digit to 0 and increment the prefix. */ + name[len] = valid_filename_chars[0]; + } + + return -1; +} + +#ifndef ISSLASH +# define ISSLASH(C) ((C) == '/') +#endif + +char * +last_component (char const *name) +{ + char const *base = name; + char const *p; + int saw_slash = -1; + + while (ISSLASH (*base)) + base++; + + for (p = base; *p; p++) { + if (ISSLASH (*p)) + saw_slash = -1; + else if (saw_slash) { + base = p; + saw_slash = 0; + } + } + + return (char *) base; +} + + /*** wipe_filename_and_remove */ /* actually, after renaming a file, the only way to make sure that the * name change is physically carried out is to call sync (), which flushes * out ALL the disk caches of the system, whereas for - * reading and writing one can use the O_SYNC bit to get syncrhonous + * reading and writing one can use the O_SYNC bit to get synchronous * I/O for one file. as sync () is very slow, calling sync () after * every rename () makes wipe extremely slow. */ static int wipe_filename_and_remove (char *fn) { - int i, j, k, l; + int len; int r = -1; int fn_l, dn_l; - /* char *dn; */ - char *buf[2]; + char *oldname, *newname; + char *dir, *dirc; + dirc = strdup(fn); + dir = dirname(dirc); struct stat st; - int t_l; /* target length */ - /* dn = directory_name (fn); */ fn_l = strlen (fn); dn_l = directory_name_length (fn); - buf[0] = malloc (fn_l + NAME_MAX + 1); - buf[1] = malloc (fn_l + NAME_MAX + 1); + oldname = malloc (fn_l + NAME_MAX + 1); + newname = malloc (fn_l + NAME_MAX + 1); r = 0; - t_l = fn_l - dn_l; /* first target length */ + if (oldname && newname) { + strcpy (oldname, fn); + strcpy (newname, fn); + + int dir_fd = open (dir, O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK); + + + char *base = last_component(newname); + len = strlen(base); + fprintf (stderr, "\n"); + while (len) { + memset (base, valid_filename_chars[0], len); + base[len] = 0; + do { + if (lstat (newname, &st) < 0) { + if (!o_silent) { + fprintf (stderr, "\rRenaming %32.32s -> %32.32s", oldname, newname); + middle_of_line = 1; + fflush (stderr); + } + if (rename (oldname, newname) == 0) { + if (0 <= dir_fd && dosync (dir_fd, dir) != 0) + r = -1; + memcpy (oldname + (base - newname), base, len + 1); + break; + } else { + /* The rename failed: give up on this length. */ + fprintf (stderr, "%.32s: could not rename '%s' to '%s': %s (%d)\n", fn, oldname, newname, strerror (errno), errno); + break; + } + } else { + //fprintf (stderr, "%.32s: rename target '%s' exists\n", fn, newname); + } + } while (incname (base, len)); + len--; + } - if (buf[0] && buf[1]) { - strcpy (buf[0], fn); - strcpy (buf[1], fn); - for (j = 1, i = 0; i < o_name_max_passes; j ^= 1, i++) { - for (k = o_name_max_tries; k; k--) { - l = t_l; - fill_random_from_table (buf[j] + dn_l, l, - valid_filename_chars, 0x3f); - buf[j][dn_l + l] = 0; - if (stat (buf[j], &st)) break; - } - if (k) { - if (!o_silent) { - fprintf (stderr, "\rRenaming %32.32s -> %32.32s", buf[j^1], buf[j]); - middle_of_line = 1; - fflush (stderr); - } - if (rename (buf[j^1], buf[j])) { - FLUSH_MIDDLE - fprintf (stderr, "%.32s: could not rename '%s' to '%s': %s (%d)\n", - fn, buf[j^1], buf[j], strerror (errno), errno); - r = -1; - break; - } - (void) sync (); - } else { - /* we could not find a target name of desired length, so - * increase target length until we find one. */ - t_l ++; - j ^= 1; + if (remove (oldname)) { + fprintf (stderr, "%.32s: failed to unlink '%s'\n", fn, oldname); + r = -1; + } + if (0 <= dir_fd) { + dosync (dir_fd, dir); + if (close (dir_fd) != 0) { + fprintf (stderr, "%s: failed to close\n", dir); + r = -1; } } - if (remove (buf[j^1])) r = -1; } - free (buf[0]); free (buf[1]); + free (oldname); free (newname); free(dirc); return r; } @@ -1017,7 +1126,7 @@ } #ifndef HAVE_OSYNC - if (fsync (fd)) { + if (dosync (fd,fn)) { fnerror ("fsync error [1]"); close (fd); return -1; @@ -1025,7 +1134,7 @@ #endif } - if (fsync (fd)) { + if (dosync (fd,fn)) { fnerror ("fsync error [2]"); close (fd); return -1; @@ -1221,7 +1330,7 @@ "Web site: http://lambda-diode.com/software/wipe/\n" "Release date: " WIPE_DATE "\n" "Compiled: " __DATE__ "\n" - "Git version: " WIPE_GIT "\n" + "Git version: TEST\n" "\n" "Based on data from \"Secure Deletion of Data from Magnetic and Solid-State\n" "Memory\" by Peter Gutmann <pgut...@cs.auckland.ac.nz>.\n");