rm(1): Formalize non-functional status of -P flag
-P was introduced in 4.4BSD-Lite2 around 1994. It overwrote file contents with a pass of 0xff, 0x00, then 0xff, in a low effort attempt to "really delete" files. It has no user-visible effect; at the end of the day, the file is unlinked via the filesystem. Furthermore, the utility of overwriting files with patterned data is extremely limited due to caveats at every layer of the stack[0] and therefore mostly futile. At the least, three passes is likely wasteful on modern hardware[1]. It could also be seen as a violation of the "Unix Philosophy" to do one thing per tiny, composable program. Since 1994, FreeBSD has left it alone; OpenBSD replaced it with a single pass of arc4random(3) output in 2012[2]; and NetBSD implemented partial, but explicitly incomplete support for U.S. DoD 5220.22-M, "National Industrial Security Program Operating Manual" in 2004[3]. NetBSD's enhanced comment above rm_overwrite makes a strong case for removing the flag entirely: > This is an expensive way to keep people from recovering files from your > non-snapshotted FFS filesystems using fsdb(8). Really. No more. > > It is impossible to actually conform to the exact procedure given in > [NISPOM] if one is overwriting a file, not an entire disk, because the > procedure requires examination and comparison of the disk's defect lists. > Any program that claims to securely erase *files* while conforming to the > standard, then, is not correct. > > Furthermore, the presence of track caches, disk and controller write > caches, and so forth make it extremely difficult to ensure that data have > actually been written to the disk, particularly when one tries to repeatedly > overwrite the same sectors in quick succession. We call fsync(), but > controllers with nonvolatile cache, as well as IDE disks that just plain lie > about the stable storage of data, will defeat this. > > [NISPOM] requires physical media destruction, rather than any technique of > the sort attempted here, for secret data. As a first step towards evental removal, make it a placebo. It's not like it was serving any security function. It is not defined in or mentioned by POSIX. If you are security conscious and need to erase your files, use a woodchipper. At a minimum, the entire disk needs to be overwritten, not just one file. [0]: https://www.ru.nl/publish/pages/909282/draft-paper.pdf [1]: https://commons.erau.edu/cgi/viewcontent.cgi?article=1131&context=jdfsl [2]: https://github.com/openbsd/src/commit/7c5c57ba81b5fe8ff2d4899ff643af18c [3]: https://github.com/NetBSD/src/commit/fdf0a7a25e59af958fca1e2159921562cd Reviewed by: markj, Daniel O'Connor <darius AT dons.net.au> (previous version) Differential Revision: https://reviews.freebsd.org/D17906
This commit is contained in:
parent
4a23879502
commit
900c4ed3ca
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=340330
51
bin/rm/rm.1
51
bin/rm/rm.1
@ -32,7 +32,7 @@
|
|||||||
.\" @(#)rm.1 8.5 (Berkeley) 12/5/94
|
.\" @(#)rm.1 8.5 (Berkeley) 12/5/94
|
||||||
.\" $FreeBSD$
|
.\" $FreeBSD$
|
||||||
.\"
|
.\"
|
||||||
.Dd September 12, 2018
|
.Dd November 10, 2018
|
||||||
.Dt RM 1
|
.Dt RM 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -42,7 +42,7 @@
|
|||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl f | i
|
.Op Fl f | i
|
||||||
.Op Fl dIPRrvWx
|
.Op Fl dIRrvWx
|
||||||
.Ar
|
.Ar
|
||||||
.Nm unlink
|
.Nm unlink
|
||||||
.Op Fl -
|
.Op Fl -
|
||||||
@ -86,26 +86,9 @@ This is a far less intrusive option than
|
|||||||
.Fl i
|
.Fl i
|
||||||
yet provides almost the same level of protection against mistakes.
|
yet provides almost the same level of protection against mistakes.
|
||||||
.It Fl P
|
.It Fl P
|
||||||
Overwrite regular files before deleting them.
|
This flag has no effect.
|
||||||
Files are overwritten three times, first with the byte pattern 0xff,
|
It is kept only for backwards compatibility with
|
||||||
then 0x00, and then 0xff again, before they are deleted.
|
.Bx 4.4 Lite2 .
|
||||||
Files with multiple links will not be overwritten nor deleted
|
|
||||||
and a warning will be issued.
|
|
||||||
If the
|
|
||||||
.Fl f
|
|
||||||
option is specified, files with multiple links will also be overwritten
|
|
||||||
and deleted.
|
|
||||||
No warning will be issued.
|
|
||||||
.Pp
|
|
||||||
Specifying this flag for a read only file will cause
|
|
||||||
.Nm
|
|
||||||
to generate an error message and exit.
|
|
||||||
The file will not be removed or overwritten.
|
|
||||||
.Pp
|
|
||||||
N.B.: The
|
|
||||||
.Fl P
|
|
||||||
flag is not considered a security feature
|
|
||||||
.Pq see Sx BUGS .
|
|
||||||
.It Fl R
|
.It Fl R
|
||||||
Attempt to remove the file hierarchy rooted in each
|
Attempt to remove the file hierarchy rooted in each
|
||||||
.Ar file
|
.Ar file
|
||||||
@ -192,12 +175,6 @@ For example:
|
|||||||
.Pp
|
.Pp
|
||||||
.Dl "rm /home/user/-filename"
|
.Dl "rm /home/user/-filename"
|
||||||
.Dl "rm ./-filename"
|
.Dl "rm ./-filename"
|
||||||
.Pp
|
|
||||||
When
|
|
||||||
.Fl P
|
|
||||||
is specified with
|
|
||||||
.Fl f
|
|
||||||
the file will be overwritten and removed even if it has hard links.
|
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
Recursively remove all files contained within the
|
Recursively remove all files contained within the
|
||||||
.Pa foobar
|
.Pa foobar
|
||||||
@ -227,6 +204,12 @@ Also, historical
|
|||||||
.Bx
|
.Bx
|
||||||
implementations prompted on the standard output,
|
implementations prompted on the standard output,
|
||||||
not the standard error output.
|
not the standard error output.
|
||||||
|
.Pp
|
||||||
|
The
|
||||||
|
.Fl P
|
||||||
|
option does not have any effect as of
|
||||||
|
.Fx 13
|
||||||
|
and may be removed in the future.
|
||||||
.Sh SEE ALSO
|
.Sh SEE ALSO
|
||||||
.Xr chflags 1 ,
|
.Xr chflags 1 ,
|
||||||
.Xr rmdir 1 ,
|
.Xr rmdir 1 ,
|
||||||
@ -250,15 +233,3 @@ A
|
|||||||
.Nm
|
.Nm
|
||||||
command appeared in
|
command appeared in
|
||||||
.At v1 .
|
.At v1 .
|
||||||
.Sh BUGS
|
|
||||||
The
|
|
||||||
.Fl P
|
|
||||||
option assumes that the underlying storage overwrites file blocks
|
|
||||||
when data is written to an existing offset.
|
|
||||||
Several factors including the file system and its backing store could defeat
|
|
||||||
this assumption.
|
|
||||||
This includes, but is not limited to file systems that use a
|
|
||||||
Copy-On-Write strategy (e.g. ZFS or UFS when snapshots are being used), Flash
|
|
||||||
media that are using a wear leveling algorithm, or when the backing datastore
|
|
||||||
does journaling, etc.
|
|
||||||
In addition, only regular files are overwritten, other types of files are not.
|
|
||||||
|
101
bin/rm/rm.c
101
bin/rm/rm.c
@ -61,7 +61,7 @@ __FBSDID("$FreeBSD$");
|
|||||||
#include <sysexits.h>
|
#include <sysexits.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
static int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
|
static int dflag, eval, fflag, iflag, vflag, Wflag, stdin_ok;
|
||||||
static int rflag, Iflag, xflag;
|
static int rflag, Iflag, xflag;
|
||||||
static uid_t uid;
|
static uid_t uid;
|
||||||
static volatile sig_atomic_t info;
|
static volatile sig_atomic_t info;
|
||||||
@ -71,7 +71,6 @@ static int check2(char **);
|
|||||||
static void checkdot(char **);
|
static void checkdot(char **);
|
||||||
static void checkslash(char **);
|
static void checkslash(char **);
|
||||||
static void rm_file(char **);
|
static void rm_file(char **);
|
||||||
static int rm_overwrite(const char *, struct stat *);
|
|
||||||
static void rm_tree(char **);
|
static void rm_tree(char **);
|
||||||
static void siginfo(int __unused);
|
static void siginfo(int __unused);
|
||||||
static void usage(void);
|
static void usage(void);
|
||||||
@ -110,7 +109,7 @@ main(int argc, char *argv[])
|
|||||||
exit(eval);
|
exit(eval);
|
||||||
}
|
}
|
||||||
|
|
||||||
Pflag = rflag = xflag = 0;
|
rflag = xflag = 0;
|
||||||
while ((ch = getopt(argc, argv, "dfiIPRrvWx")) != -1)
|
while ((ch = getopt(argc, argv, "dfiIPRrvWx")) != -1)
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case 'd':
|
case 'd':
|
||||||
@ -128,7 +127,7 @@ main(int argc, char *argv[])
|
|||||||
Iflag = 1;
|
Iflag = 1;
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
Pflag = 1;
|
/* Compatibility no-op. */
|
||||||
break;
|
break;
|
||||||
case 'R':
|
case 'R':
|
||||||
case 'r': /* Compatibility. */
|
case 'r': /* Compatibility. */
|
||||||
@ -313,12 +312,6 @@ rm_tree(char **argv)
|
|||||||
|
|
||||||
case FTS_F:
|
case FTS_F:
|
||||||
case FTS_NSOK:
|
case FTS_NSOK:
|
||||||
if (Pflag)
|
|
||||||
if (!rm_overwrite(p->fts_accpath, p->fts_info ==
|
|
||||||
FTS_NSOK ? NULL : p->fts_statp))
|
|
||||||
continue;
|
|
||||||
/* FALLTHROUGH */
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
rval = unlink(p->fts_accpath);
|
rval = unlink(p->fts_accpath);
|
||||||
if (rval == 0 || (fflag && errno == ENOENT)) {
|
if (rval == 0 || (fflag && errno == ENOENT)) {
|
||||||
@ -389,13 +382,9 @@ rm_file(char **argv)
|
|||||||
rval = undelete(f);
|
rval = undelete(f);
|
||||||
else if (S_ISDIR(sb.st_mode))
|
else if (S_ISDIR(sb.st_mode))
|
||||||
rval = rmdir(f);
|
rval = rmdir(f);
|
||||||
else {
|
else
|
||||||
if (Pflag)
|
|
||||||
if (!rm_overwrite(f, &sb))
|
|
||||||
continue;
|
|
||||||
rval = unlink(f);
|
rval = unlink(f);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (rval && (!fflag || errno != ENOENT)) {
|
if (rval && (!fflag || errno != ENOENT)) {
|
||||||
warn("%s", f);
|
warn("%s", f);
|
||||||
eval = 1;
|
eval = 1;
|
||||||
@ -409,84 +398,6 @@ rm_file(char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* rm_overwrite --
|
|
||||||
* Overwrite the file 3 times with varying bit patterns.
|
|
||||||
*
|
|
||||||
* XXX
|
|
||||||
* This is a cheap way to *really* delete files. Note that only regular
|
|
||||||
* files are deleted, directories (and therefore names) will remain.
|
|
||||||
* Also, this assumes a fixed-block file system (like FFS, or a V7 or a
|
|
||||||
* System V file system). In a logging or COW file system, you'll have to
|
|
||||||
* have kernel support.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
rm_overwrite(const char *file, struct stat *sbp)
|
|
||||||
{
|
|
||||||
struct stat sb, sb2;
|
|
||||||
struct statfs fsb;
|
|
||||||
off_t len;
|
|
||||||
int bsize, fd, wlen;
|
|
||||||
char *buf = NULL;
|
|
||||||
|
|
||||||
fd = -1;
|
|
||||||
if (sbp == NULL) {
|
|
||||||
if (lstat(file, &sb))
|
|
||||||
goto err;
|
|
||||||
sbp = &sb;
|
|
||||||
}
|
|
||||||
if (!S_ISREG(sbp->st_mode))
|
|
||||||
return (1);
|
|
||||||
if (sbp->st_nlink > 1 && !fflag) {
|
|
||||||
warnx("%s (inode %ju): not overwritten due to multiple links",
|
|
||||||
file, (uintmax_t)sbp->st_ino);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1)
|
|
||||||
goto err;
|
|
||||||
if (fstat(fd, &sb2))
|
|
||||||
goto err;
|
|
||||||
if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
|
|
||||||
!S_ISREG(sb2.st_mode)) {
|
|
||||||
errno = EPERM;
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
if (fstatfs(fd, &fsb) == -1)
|
|
||||||
goto err;
|
|
||||||
bsize = MAX(fsb.f_iosize, 1024);
|
|
||||||
if ((buf = malloc(bsize)) == NULL)
|
|
||||||
err(1, "%s: malloc", file);
|
|
||||||
|
|
||||||
#define PASS(byte) { \
|
|
||||||
memset(buf, byte, bsize); \
|
|
||||||
for (len = sbp->st_size; len > 0; len -= wlen) { \
|
|
||||||
wlen = len < bsize ? len : bsize; \
|
|
||||||
if (write(fd, buf, wlen) != wlen) \
|
|
||||||
goto err; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
PASS(0xff);
|
|
||||||
if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
|
|
||||||
goto err;
|
|
||||||
PASS(0x00);
|
|
||||||
if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
|
|
||||||
goto err;
|
|
||||||
PASS(0xff);
|
|
||||||
if (!fsync(fd) && !close(fd)) {
|
|
||||||
free(buf);
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
err: eval = 1;
|
|
||||||
if (buf)
|
|
||||||
free(buf);
|
|
||||||
if (fd != -1)
|
|
||||||
close(fd);
|
|
||||||
warn("%s", file);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
check(const char *path, const char *name, struct stat *sp)
|
check(const char *path, const char *name, struct stat *sp)
|
||||||
{
|
{
|
||||||
@ -511,10 +422,6 @@ check(const char *path, const char *name, struct stat *sp)
|
|||||||
strmode(sp->st_mode, modep);
|
strmode(sp->st_mode, modep);
|
||||||
if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
|
if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
|
||||||
err(1, "fflagstostr");
|
err(1, "fflagstostr");
|
||||||
if (Pflag)
|
|
||||||
errx(1,
|
|
||||||
"%s: -P was specified, but file is not writable",
|
|
||||||
path);
|
|
||||||
(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
|
(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
|
||||||
modep + 1, modep[10] == ' ' ? "" : " ",
|
modep + 1, modep[10] == ' ' ? "" : " ",
|
||||||
user_from_uid(sp->st_uid, 0),
|
user_from_uid(sp->st_uid, 0),
|
||||||
|
Loading…
Reference in New Issue
Block a user