PATCH - ext2fs privacy (i.e. secure deletion) patch

the grugq
2004-01-28T16:33:32Z

[-- Attachment #1: Type: text/plain, Size: 3453 bytes --]

Hi,

I've written a quick patch to the ext2fs which ensures that inodes, data
blocks, and directory entries are overwritten with null bytes when
they are deleted. The patch adds a couple of inline functions to
overwrite the appropriate data with zeroes. Its quite simple and
self-explanitory. Both attached patches are to a vanilla 2.4.24 kernel.

inodes: ext2_delete_inode() now uses the inline function delete_inode()
to mark the on-disk inode as 'deleted'. If the privacy option is not
enabled, then the i_dtime is set as per usual operation, otherwise the
meta-data are all set to 0. After the inode has been truncated, the
i_data is set to 0 by delete_blocks() and the inode is written to disk
again.

Splitting the delete_inode() and delete_blocks() functions is not ideal,
I would prefer that one function remove the meta-data and the inode be
written out just once. The problem is that ext2_truncate() needs the
i_data information from the struct inode, and we want to remove this
information from the inode on the disk. To ensure that the i_data is 0
by the time it hits the disk, I write the inode to disk twice, which is
not optimal.

directory entries: ext2_delete_entry() now uses the inline function
mark_delete_dir() to indicate that the directory entry is no longer
valid. If the privacy patch is not enabled, then the dir->inode is set
to 0 as per normal operation, otherwise the entry is memset 0. Very
straightforward.

blocks: ext2_free_blocks() now uses the inline function destroy_block()
before it updates the group meta-data. The destroy_block() function
overwrites the block with zeroes. I'm not sure that it is implemented
correctly, I don't know anything about buffer_heads. The block is only
overwritten with a single pass of zeroes. It is, of course, possible to
have a full 37 pass overwriter with full bit toggling maps and all that,
but I think it might make more sense to add a new option for that,
something like: CONFIG_EXT2_FS_PRIVACY_EXTREME_PARANOIA. For preventing
simple forensic analysis (that is, what the police, employers and even
most government agencies do) a single pass with zeroes is sufficient.
Users can use userland tools like wipe or srm for more thorough overwriting.

There is also a patch to ext3fs, which adds the same functionality (in
the same way) as the ext2fs privacy patch. However, the main problem
with privacy on the ext3fs is the journal. I am not familiar enough with
how the journal transactions are handled to implement what seems to me
the obvious solution: when a transaction is complete (i.e. doesn't need
to be replayed by fsck) overwrite it with null bytes. If done correctly,
it is possible to leave sufficient information for fsck to find the
right transaction to begin replaying, without leaving any data which
would be of use to a forensic analyst.

Given the existing global political climate, privacy control over a file
system seems like a no-brainer to me. Full privacy options would include
things like mounting disks with noatime, and possibly normalizing the
mtime and ctime of all inodes (setting everthing to an arbitrary date,
e.g. 0). Certainly this will break tools like make which require atime,
but it would provide even less information to a forensic analyst
examining the file system.

peace,

--gq

ps. apologies for posting to the kernel mailing list, but I'm not sure
who is the maintainer for ext2/3.

[-- Attachment #2: privacy_patch_ext2 --]
[-- Type: text/plain, Size: 3509 bytes --]

--- linux-2.4.24/fs/Config.in 2003-11-28 18:26:21.000000000 +0000
+++ linux-2.4.24/fs/Config.in-ext2-privacy 2004-01-27 21:56:58.000000000 +0000
@@ -92,6 +92,7 @@
tristate 'ROM file system support' CONFIG_ROMFS_FS

tristate 'Second extended fs support' CONFIG_EXT2_FS
+dep_mbool ' ext2 privacy support' CONFIG_EXT2_FS_PRIVACY $CONFIG_EXT2_FS

tristate 'System V/Xenix/V7/Coherent file system support' CONFIG_SYSV_FS

--- linux-2.4.24/fs/ext2/balloc.c 2003-06-13 15:51:37.000000000 +0100
+++ linux-2.4.24/fs/ext2-privacy/balloc.c 2004-01-28 00:52:59.000000000 +0000
@@ -247,6 +247,23 @@
return slot;
}

+static inline void destroy_block(struct inode *inode, unsigned long block)
+{
+#ifdef CONFIG_EXT2_FS_PRIVACY
+ struct buffer_head * bh;
+
+ bh = sb_getblk(inode->i_sb, block);
+
+ memset(bh->b_data, 0x00, bh->b_size);
+
+ mark_buffer_dirty(bh);
+ wait_on_buffer(bh);
+ brelse(bh);
+
+ return;
+#endif
+}
+
/* Free given blocks, update quota and i_blocks field */
void ext2_free_blocks (struct inode * inode, unsigned long block,
unsigned long count)
@@ -319,6 +336,8 @@
"bit already cleared for block %lu", block);
else {
DQUOT_FREE_BLOCK(inode, 1);
+
+ destroy_block(sb, block);
gdp->bg_free_blocks_count =
cpu_to_le16(le16_to_cpu(gdp->bg_free_blocks_count)+1);
es->s_free_blocks_count =
--- linux-2.4.24/fs/ext2/dir.c 2002-11-28 23:53:15.000000000 +0000
+++ linux-2.4.24/fs/ext2-privacy/dir.c 2004-01-25 17:15:27.000000000 +0000
@@ -468,6 +468,15 @@
return err;
}

+static inline mark_deleted_dir(struct ext2_dir_entry_2 *dir)
+{
+#ifndef CONFIG_EXT2_FS_PRIVACY
+ dir->inode = 0;
+#else
+ memset(dir, 0, dir->rec_len);
+#endif
+}
+
/*
* ext2_delete_entry deletes a directory entry by merging it with the
* previous entry. Page is up-to-date. Releases the page.
@@ -495,7 +504,7 @@
BUG();
if (pde)
pde->rec_len = cpu_to_le16(to-from);
- dir->inode = 0;
+ mark_deleted_dir(dir);
err = ext2_commit_chunk(page, from, to);
UnlockPage(page);
ext2_put_page(page);
--- linux-2.4.24/fs/ext2/inode.c 2003-06-13 15:51:37.000000000 +0100
+++ linux-2.4.24/fs/ext2-privacy/inode.c 2004-01-27 21:33:59.000000000 +0000
@@ -46,6 +46,36 @@
ext2_discard_prealloc (inode);
}

+static inline void delete_inode(struct inode *inode)
+{
+#ifndef CONFIG_EXT2_FS_PRIVACY
+ inode->u.ext2_i.i_dtime = CURRENT_TIME;
+#else
+ inode->i_mode = 0;
+ inode->i_uid = 0;
+ inode->i_gid = 0;
+ inode->i_nlink = 0;
+ inode->i_atime = 0;
+ inode->i_ctime = 0;
+ inode->i_mtime = 0;
+ inode->u.ext2_i.i_dtime = 0;
+ inode->u.ext2_i.i_flags = 0;
+ inode->u.ext2_i.i_faddr = 0;
+ inode->u.ext2_i.i_frag_no = 0;
+ inode->u.ext2_i.i_frag_size = 0;
+ inode->u.ext2_i.i_file_acl = 0;
+ inode->i_generation = 0;
+#endif
+}
+
+static inline void delete_blocks(struct inode *inode)
+{
+#ifdef CONFIG_EXT2_FS_PRIVACY
+ inode->i_blocks = 0;
+ memset(&inode->u.ext2_i.i_data, 0x00, sizeof(inode->u.ext2_i.i_data));
+#endif
+}
+
/*
* Called at the last iput() if i_nlink is zero.
*/
@@ -57,12 +87,18 @@
inode->i_ino == EXT2_ACL_IDX_INO ||
inode->i_ino == EXT2_ACL_DATA_INO)
goto no_delete;
- inode->u.ext2_i.i_dtime = CURRENT_TIME;
+
+ delete_inode(inode);
mark_inode_dirty(inode);
ext2_update_inode(inode, IS_SYNC(inode));
+
inode->i_size = 0;
if (inode->i_blocks)
ext2_truncate (inode);
+ delete_blocks(inode);
+ mark_inode_dirty(inode);
+ ext2_update_inode(inode, IS_SYNC(inode));
+
ext2_free_inode (inode);

unlock_kernel();

[-- Attachment #3: privacy_patch_ext3 --]
[-- Type: text/plain, Size: 2505 bytes --]

--- linux-2.4.24/fs/Config.in 2003-11-28 18:26:21.000000000 +0000
+++ linux-2.4.24/fs/Config.in-ext3-privacy 2004-01-27 21:57:22.000000000 +0000
@@ -34,6 +34,7 @@
# dep_tristate ' Journal Block Device support (JBD for ext3)' CONFIG_JBD $CONFIG_EXT3_FS
define_bool CONFIG_JBD $CONFIG_EXT3_FS
dep_mbool ' JBD (ext3) debugging support' CONFIG_JBD_DEBUG $CONFIG_JBD
+dep_mbool ' ext3 privacy support' CONFIG_EXT3_FS_PRIVACY $CONFIG_EXT3_FS

# msdos file systems
tristate 'DOS FAT fs support' CONFIG_FAT_FS
--- linux-2.4.24/fs/ext3/balloc.c 2003-06-13 15:51:37.000000000 +0100
+++ linux-2.4.24/fs/ext3-privacy/balloc.c 2004-01-28 00:44:56.000000000 +0000
@@ -252,6 +252,23 @@
return slot;
}

+static inline void destroy_block(struct inode *inode, unsigned long block)
+{
+#ifdef CONFIG_EXT3_FS_PRIVACY
+ struct buffer_head * bh;
+
+ bh = sb_getblk(inode->i_sb, block);
+
+ memset(bh->b_data, 0x00, bh->b_size);
+
+ mark_buffer_dirty(bh);
+ wait_on_buffer(bh);
+ brelse(bh);
+
+ return;
+#endif
+}
+
/* Free given blocks, update quota and i_blocks field */
void ext3_free_blocks (handle_t *handle, struct inode * inode,
unsigned long block, unsigned long count)
@@ -370,6 +387,7 @@
BUFFER_TRACE(bitmap_bh, "bit already cleared");
} else {
dquot_freed_blocks++;
+ destroy_block(sb, block);
gdp->bg_free_blocks_count =
cpu_to_le16(le16_to_cpu(gdp->bg_free_blocks_count)+1);
es->s_free_blocks_count =
--- linux-2.4.24/fs/ext3/inode.c 2003-08-25 12:44:43.000000000 +0100
+++ linux-2.4.24/fs/ext3-privacy/inode.c 2004-01-27 21:48:54.000000000 +0000
@@ -172,6 +172,28 @@
ext3_discard_prealloc (inode);
}

+static inline void delete_inode(struct inode *inode)
+{
+#ifndef CONFIG_EXT3_FS_PRIVACY
+ inode->u.ext3_i.i_dtime = CURRENT_TIME;
+#else
+ inode->i_mode = 0;
+ inode->i_uid = 0;
+ inode->i_gid = 0;
+ inode->i_nlink = 0;
+ inode->i_size = 0;
+ inode->i_blocks = 0;
+ inode->i_atime = 0;
+ inode->i_ctime = 0;
+ inode->i_mtime = 0;
+ inode->u.ext3_i.i_dtime = 0;
+ inode->u.ext3_i.i_flags = 0;
+ inode->u.ext3_i.i_file_acl = 0;
+ inode->i_generation = 0;
+ memset(&inode->u.ext3_i.i_data, 0x00, sizeof(inode->u.ext3_i.i_data));
+#endif
+}
+
/*
* Called at the last iput() if i_nlink is zero.
*/
@@ -211,7 +233,7 @@
* (Well, we could do this if we need to, but heck - it works)
*/
ext3_orphan_del(handle, inode);
- inode->u.ext3_i.i_dtime = CURRENT_TIME;
+ delete_inode(inode);

/*
* One subtle ordering requirement: if anything has gone wrong