Skip to content

Commit

Permalink
Allow multi-pass overwriting with specified patterns
Browse files Browse the repository at this point in the history
Also, update SPDX license tags and years.
  • Loading branch information
vt-alt committed Sep 15, 2019
1 parent 9817616 commit c1c1198
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
# SPDX-License-Identifier: GPL-2.0-only

KVER ?= $(shell uname -r)
KDIR ?= /lib/modules/$(KVER)/build/
Expand Down
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
dm-linear like target which provides discard, but replaces it with write of
random data to a discarded region. Thus, discarded data is securely deleted.
Because of abstract nature, it could support many file-systems which support
discard (such as ext3, ext4, xfs, btrfs).
erase pattern data to a discarded region. Thus, discarded data is securely
deleted (sanitized). Because of abstract nature, it could support many
file-systems which support discard (such as ext3, ext4, xfs, btrfs).

Operation notes:

Expand All @@ -10,21 +10,29 @@ mounted from that device and not from the underlying device. Make sure
file-system is mounted with `-o discard` option. Do not enable data journaling
(such as `-o data=journal` do not enable it). Note, that when you `rm` files
discards will be sent (and, thus, erasing will performed) asynchronously, so,
to make sure data is already erased issue `sync` or mount file-system with `-o
sync` option before `rm`. If you wish that filenames are wiped too, first,
to make sure data is already erased issue `sync` or remount file-system with
`-o sync` option before `rm`. If you wish that filenames are wiped too, first,
make sure file-system is created completely without journaling (such as
`mkfs.ext4 -O ^has_journal` or disable it using `tune2fs `, and second, delete
the directory itself, so its blocks are discarded and erased. If you issue
`fstrim` all free blocks of file-system will be discarded and thus erased too
(make sure that file-system is still mounted with `-o discard` though.)
`mkfs.ext4 -O ^has_journal`, and second, delete the directory itself, so its
blocks are discarded and erased. If you issue `fstrim` all free blocks of
file-system will be discarded and thus erased too (make sure that file-system
is still mounted with `-o discard` though.)

Usage:

```
secdelsetup /dev/sda5 [/dev/mapper/secdel5]
```
- will map `sda5` to `secdel5`. Then, file-system on `secdel5` should be
mounted with `-o discard`.
- will map `sda5` to `secdel5`. (With default erase more which is single pass of
(crng) random data). Alternatively:

```
secdelsetup /dev/sda5 [/dev/mapper/secdel5] 1R0
```
- Will work same as above but with with three pass overwriting: first pass of 1-bits,
second pass of (crng) random bits, and third pass of 0-bits.

Then, file-system on `secdel5` should be mounted with `-o discard`.

```
secdeltab --all or secdeltab --list
Expand All @@ -43,5 +51,5 @@ secdeltab --detach-all
- detach all active maps.

Based on the code of `dm-linear` from Linux kernel of their respective authors.
(C) 2018 <[email protected]>; License GPLv2.
(C) 2018,2019 <[email protected]>; License GPL-2.0-only.

89 changes: 70 additions & 19 deletions dm-secdel.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/*
* Copyright (C) 2001-2003 Sistina Software (UK) Limited.
* dm-secdel: secure deletion on discard
* (c) 2018-2019, [email protected].
*
* (c) 2018, [email protected]: secure deletion on discard
* Based on dm-linear:
* Copyright (C) 2001-2003 Sistina Software (UK) Limited.
*
* This file is released under the GPL.
* This file is licensed under the GPL-2.0-only.
*/

#include <linux/module.h>
Expand All @@ -24,12 +26,15 @@ MODULE_VERSION("1.0.4");

#define DM_MSG_PREFIX "secdel"

unsigned long empty_ff_page[PAGE_SIZE / sizeof(unsigned long)];

/*
* Linear: maps a linear range of a device.
*/
struct secdel_c {
struct dm_dev *dev;
sector_t start;
char patterns[];
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,18,0)
Expand All @@ -40,21 +45,27 @@ static inline struct audit_context *audit_context(void)
#endif

/*
* Construct a linear mapping: <dev_path> <offset>
* Construct a linear mapping: <dev_path> <offset> [patterns]
*/
static int secdel_ctr(struct dm_target *ti, unsigned int argc, char **argv)
{
struct secdel_c *lc;
unsigned long long tmp;
size_t passes = 0;
char dummy;
int ret;
int i;

if (argc != 2) {
if (argc < 2) {
ti->error = "Invalid argument count";
return -EINVAL;
}

lc = kmalloc(sizeof(*lc), GFP_KERNEL);
if (argc > 2)
passes = strlen(argv[2]);

/* +2 to allow empty argv[2] to be replaced with "R" */
lc = kmalloc(sizeof(*lc) + passes + 2, GFP_KERNEL);
if (lc == NULL) {
ti->error = "Cannot allocate secdel context";
return -ENOMEM;
Expand All @@ -73,6 +84,27 @@ static int secdel_ctr(struct dm_target *ti, unsigned int argc, char **argv)
goto bad;
}

/* empty or unset argv[2] is replaced with "R" */
if (argc > 2 && passes > 1)
strcpy(lc->patterns, argv[2]);
else
strcpy(lc->patterns, "R");

/* sanity check erasure patterns */
passes = strlen(lc->patterns);
for (i = 0; i < passes; i++) {
switch (lc->patterns[i]) {
case '0':
case '1':
case 'R':
continue;
default:
ti->error = "Invalid character in patterns";
ret = -EINVAL;
goto bad;
}
}

/* permit discards no matter if underlying device supports them */
ti->discards_supported = 1;

Expand All @@ -93,9 +125,9 @@ static int secdel_ctr(struct dm_target *ti, unsigned int argc, char **argv)
AUDIT_KERNEL_OTHER);
if (ab) {
audit_log_format(ab, "op=secdel action=start");
audit_log_format(ab, " dev=%s srcname=%s",
audit_log_format(ab, " dev=%s srcname=%s patterns=%s",
dm_device_name(dm_table_get_md(ti->table)),
argv[0]);
argv[0], lc->patterns);
audit_log_end(ab);
}
}
Expand Down Expand Up @@ -175,7 +207,8 @@ static void bio_end_erase(struct bio *bio)
#else
bio_for_each_segment_all(bvec, bio, i)
#endif
if (bvec->bv_page != ZERO_PAGE(0))
if (bvec->bv_page != ZERO_PAGE(0) &&
bvec->bv_page != virt_to_page(empty_ff_page))
__free_page(bvec->bv_page);
bio_put(bio);
}
Expand Down Expand Up @@ -206,12 +239,14 @@ static int op_discard(struct bio *bio)

/*
* send amount of masking data to the device
* @mode: 0 to write zeros, otherwise to write random data
* @mode: 0 to write zeros, 1 to write ff-s,
* -1 to write random data
*/
static int issue_erase(struct block_device *bdev, sector_t sector,
sector_t nr_sects, gfp_t gfp_mask, int mode)
sector_t nr_sects, int mode)
{
int ret = 0;
const gfp_t gfp_mask = GFP_NOFS;

while (nr_sects) {
struct bio *bio;
Expand All @@ -236,24 +271,26 @@ static int issue_erase(struct block_device *bdev, sector_t sector,
struct page *page = NULL;

sz = min((sector_t)PAGE_SIZE >> 9, nr_sects);
if (mode) {
if (mode < 0) {
page = alloc_page(gfp_mask);
if (!page) {
DMERR("issue_erase %lu[%lu]: no memory to allocate page for random data",
sector, nr_sects);
/* fallback to zero filling */
/* will fallback to zero filling */
} else {
void *ptr;

ptr = kmap_atomic(page);
get_random_bytes(ptr, sz << 9);
kunmap_atomic(ptr);
}
}
} else if (mode == 1)
page = virt_to_page(empty_ff_page);
if (!page)
page = ZERO_PAGE(0);
ret = bio_add_page(bio, page, sz << 9, 0);
if (!ret && page != ZERO_PAGE(0))
if (!ret && page != ZERO_PAGE(0) &&
page != virt_to_page(empty_ff_page))
__free_page(page);
nr_sects -= ret >> 9;
sector += ret >> 9;
Expand All @@ -275,6 +312,7 @@ static int secdel_map_discard(struct dm_target *ti, struct bio *sbio)
struct block_device *bdev = lc->dev->bdev;
sector_t sector = sbio->bi_iter.bi_sector;
sector_t nr_sects = bio_sectors(sbio);
size_t passes, i;

if (!bio_sectors(sbio))
return 0;
Expand All @@ -288,7 +326,19 @@ static int secdel_map_discard(struct dm_target *ti, struct bio *sbio)

bio_endio(sbio);

issue_erase(bdev, sector, nr_sects, GFP_NOFS, 1);
passes = strlen(lc->patterns);
for (i = 0; i < passes; i++) {
int mode;

switch (lc->patterns[i]) {
case '0': mode = 0; break;
case '1': mode = 1; break;
case 'R':
default:
mode = -1;
}
issue_erase(bdev, sector, nr_sects, mode);
}
return 1;
}

Expand Down Expand Up @@ -344,8 +394,9 @@ static void secdel_status(struct dm_target *ti, status_type_t type,
break;

case STATUSTYPE_TABLE:
snprintf(result, maxlen, "%s %llu", lc->dev->name,
(unsigned long long)lc->start);
snprintf(result, maxlen, "%s %llu %s", lc->dev->name,
(unsigned long long)lc->start,
lc->patterns);
break;
}
}
Expand Down Expand Up @@ -482,7 +533,7 @@ int __init dm_secdel_init(void)

if (r < 0)
DMERR("register failed %d", r);

memset(empty_ff_page, 0xff, sizeof(empty_ff_page));
return r;
}

Expand Down
22 changes: 15 additions & 7 deletions secdelsetup
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
#
# Setup script for dm-secdel
#
# (C) 2018 <[email protected]>
# License: GPLv2
# (C) 2018,2019 <[email protected]>
# License: GPL-2.0-only
#

set -efu
Expand All @@ -17,7 +17,7 @@ error() {

usage() {
echo "Usage:"
echo " secdelsetup <source-device> [mapper-name]"
echo " secdelsetup <source-device> [mapper-name] [erase-modes]"
echo "Options:"
echo " -d|--detach <device> detach device"
echo " -D|--detach-all|--stop detach all devices"
Expand All @@ -28,6 +28,14 @@ usage() {
echo " --start start devices from secdeltab"
echo " --save save active divices to secdeltab"
echo " -h|--help this text"
echo "erase-modes should be set to string of 0, 1, or R characters"
echo "without any separaters. Meaning of values are:"
echo " 0 one pass of bit zeros"
echo " 1 one pass of bit ones (i.e. 0xff bytes)"
echo " R one pass of random data"
echo "For example, when erase-modes is set to \"1R0\" it will erase"
echo "with three passes if ones, random, and zero patterns."
echo "Default value for erase-modes is R (one pass of random)."
exit
}

Expand All @@ -50,7 +58,7 @@ list_lsblk() {
list_all() {
local m=${1:-}

check && dmsetup table --target secdel | while read devx x x x devn x; do
check && dmsetup table --target secdel | while read devx x x x devn x opts; do
dev=/dev/mapper/${devx%:}
[ "$m" ] && [ "$m" != $dev ] && continue
devidx=$(stat -L -c '%t:%T' $dev)
Expand All @@ -59,7 +67,7 @@ list_all() {
if [ "$devn" = "$dn" ]; then
uuid=
[ "${UUID:-}" ] && uuid=$(find_uuid_by_hex_minmaj $devidx)
echo "$dev ${uuid:-/dev/$dv}"
echo "$dev ${uuid:-/dev/$dv} $opts"
dev=
break
fi
Expand Down Expand Up @@ -142,7 +150,7 @@ attach() {
fi
srcdev=$(find_dev $srcdev)
sz=$(blockdev --getsz $srcdev)
dmsetup create $mapbase --table "0 $sz secdel $srcdev 0" && \
dmsetup create $mapbase --table "0 $sz secdel $srcdev 0 $opts" && \
echo "$mapname is attached to $srcdev"
}

Expand All @@ -156,7 +164,7 @@ secdeltab_save() {

secdeltab_start() {
while read tgt src opts; do
[ $(expr $tgt : ^# ) = 1 ] && continue
[ $(expr $tgt : "^#") = 1 ] && continue
attach $src $tgt $opts || :
done < $secdeltab
}
Expand Down
6 changes: 3 additions & 3 deletions tests.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# SPDX-License-Identifier: GPL-2.0-only
# vim: set sw=8:

set -eu +f

PATH=$PATH:/sbin:/usr/sbin
declare -i bs=$((1024*1024)) count=100
declare -i bs=$((1024*1024)) count=${COUNT:-100}
mnt=/mnt
cd $(dirname $0)

Expand Down Expand Up @@ -91,7 +91,7 @@ dmesg_show() {
echo :: creating test disk of $((bs+count)) bytes
log dd if=/dev/zero of=$img bs=$bs count=$count status=none
lodev=$(losetup --show -f $img)
dmsetup create secdel2 --table "0 $(blockdev --getsz $lodev) secdel $lodev 0"
dmsetup create secdel2 --table "0 $(blockdev --getsz $lodev) secdel $lodev 0 1R0"
dev=/dev/mapper/secdel2
ls -lL $dev
dmsetup table
Expand Down

0 comments on commit c1c1198

Please sign in to comment.