reverse: Filesystem is read-only of uniqueIV is enabled

Writing to the ciphertext files can rewrite the header. This
would mean we had to re-encrypt the whole file with the new IV.

This could be made more fine-grained, for example allowing
writes to everywhere but the header. However, this is
something that needs a lot of testing to ensure correctness.
Writing to the ciphertext is a niche use case of the niche
use case of using reverse mode, so it is unlikely it would
get the test coverage it needs.

To be safe, we deny all modifications of the ciphertext with
read-only filesystem error (EROFS) if uniqueIV is enabled.

Reverse mode with uniqueIV disabled still supports writing,
if somebody really needs it. This use case is not covered
by the test suite at the moment.
This commit is contained in:
Jakob Unterwurzacher 2014-11-30 14:13:25 +01:00
parent d1363578fc
commit 32102447e0
7 changed files with 106 additions and 16 deletions

View File

@ -41,6 +41,9 @@ struct EncFS_Opts;
class Cipher;
class NameIO;
/**
* Persistent configuration (stored in config file .encfs6.xml)
*/
struct EncFSConfig {
ConfigType cfgType;

View File

@ -1036,6 +1036,8 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts) {
if (reverseEncryption) {
cout << _("reverse encryption - chained IV and MAC disabled") << "\n";
uniqueIV = selectUniqueIV();
if (uniqueIV == 0)
opts->readOnly = 0; // Reverse supports rw mode only if uniqueIV is disabled
} else {
chainedIV = selectChainedIV();
uniqueIV = selectUniqueIV();

View File

@ -58,6 +58,11 @@ typedef shared_ptr<EncFS_Root> RootPtr;
enum ConfigMode { Config_Prompt, Config_Standard, Config_Paranoia };
/**
* EncFS_Opts stores internal settings
*
* See struct EncFS_Args (main.cpp) for the parsed command line arguments
*/
struct EncFS_Opts {
std::string rootDir;
bool createIfNotFound; // create filesystem if not found
@ -81,6 +86,8 @@ struct EncFS_Opts {
* behind the back of EncFS (for example, in reverse mode).
* See main.cpp for a longer explaination. */
bool readOnly; // Mount read-only
ConfigMode configMode;
EncFS_Opts() {
@ -96,6 +103,7 @@ struct EncFS_Opts {
reverseEncryption = false;
configMode = Config_Prompt;
noCache = false;
readOnly = false;
}
};

View File

@ -70,6 +70,18 @@ static EncFS_Context *context() {
return (EncFS_Context *)fuse_get_context()->private_data;
}
/**
* Helper function - determine if the filesystem is read-only
* Optionally takes a pointer to the EncFS_Context, will get it from FUSE
* if the argument is NULL.
*/
static bool isReadOnly(EncFS_Context *ctx) {
if (ctx == NULL)
ctx = (EncFS_Context *)fuse_get_context()->private_data;
return ctx->opts->readOnly;
}
// helper function -- apply a functor to a cipher path, given the plain path
static int withCipherPath(const char *opName, const char *path,
function<int(EncFS_Context *, const string &)> op,
@ -130,11 +142,10 @@ static int withFileNode(const char *opName, const char *path,
}
/*
The rLog messages below always print out encrypted filenames, not
The rLog messages below always prints out encrypted filenames, not
plaintext. The reason is so that it isn't possible to leak information
about the encrypted data through rlog interfaces.
The purpose of this layer of code is to take the FUSE request and dispatch
to the internal interfaces. Any marshaling of arguments and return types
can be done here.
@ -215,6 +226,8 @@ int encfs_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) {
int encfs_mknod(const char *path, mode_t mode, dev_t rdev) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -255,6 +268,8 @@ int encfs_mkdir(const char *path, mode_t mode) {
fuse_context *fctx = fuse_get_context();
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -287,6 +302,8 @@ int encfs_mkdir(const char *path, mode_t mode) {
int encfs_unlink(const char *path) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -307,6 +324,7 @@ int _do_rmdir(EncFS_Context *, const string &cipherPath) {
}
int encfs_rmdir(const char *path) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("rmdir", path, bind(_do_rmdir, _1, _2));
}
@ -346,6 +364,8 @@ int encfs_readlink(const char *path, char *buf, size_t size) {
int encfs_symlink(const char *from, const char *to) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -384,6 +404,8 @@ int encfs_symlink(const char *from, const char *to) {
int encfs_link(const char *from, const char *to) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -400,6 +422,8 @@ int encfs_link(const char *from, const char *to) {
int encfs_rename(const char *from, const char *to) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -418,6 +442,7 @@ int _do_chmod(EncFS_Context *, const string &cipherPath, mode_t mode) {
}
int encfs_chmod(const char *path, mode_t mode) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("chmod", path, bind(_do_chmod, _1, _2, mode));
}
@ -427,16 +452,19 @@ int _do_chown(EncFS_Context *, const string &cyName, uid_t u, gid_t g) {
}
int encfs_chown(const char *path, uid_t uid, gid_t gid) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("chown", path, bind(_do_chown, _1, _2, uid, gid));
}
int _do_truncate(FileNode *fnode, off_t size) { return fnode->truncate(size); }
int encfs_truncate(const char *path, off_t size) {
if (isReadOnly(NULL)) return -EROFS;
return withFileNode("truncate", path, NULL, bind(_do_truncate, _1, size));
}
int encfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) {
if (isReadOnly(NULL)) return -EROFS;
return withFileNode("ftruncate", path, fi, bind(_do_truncate, _1, size));
}
@ -446,6 +474,7 @@ int _do_utime(EncFS_Context *, const string &cyName, struct utimbuf *buf) {
}
int encfs_utime(const char *path, struct utimbuf *buf) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("utime", path, bind(_do_utime, _1, _2, buf));
}
@ -462,12 +491,16 @@ int _do_utimens(EncFS_Context *, const string &cyName,
}
int encfs_utimens(const char *path, const struct timespec ts[2]) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("utimens", path, bind(_do_utimens, _1, _2, ts));
}
int encfs_open(const char *path, struct fuse_file_info *file) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx) && (file->flags & O_WRONLY || file->flags & O_RDWR))
return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -508,6 +541,7 @@ int _do_flush(FileNode *fnode) {
return res;
}
// Called on each close() of a file descriptor
int encfs_flush(const char *path, struct fuse_file_info *fi) {
return withFileNode("flush", path, fi, bind(_do_flush, _1));
}
@ -545,6 +579,7 @@ int _do_fsync(FileNode *fnode, int dataSync) {
}
int encfs_fsync(const char *path, int dataSync, struct fuse_file_info *file) {
if (isReadOnly(NULL)) return -EROFS;
return withFileNode("fsync", path, file, bind(_do_fsync, _1, dataSync));
}
@ -557,6 +592,7 @@ int _do_write(FileNode *fnode, unsigned char *ptr, size_t size, off_t offset) {
int encfs_write(const char *path, const char *buf, size_t size, off_t offset,
struct fuse_file_info *file) {
if (isReadOnly(NULL)) return -EROFS;
return withFileNode("write", path, file,
bind(_do_write, _1, (unsigned char *)buf, size, offset));
}
@ -595,6 +631,7 @@ int _do_setxattr(EncFS_Context *, const string &cyName, const char *name,
}
int encfs_setxattr(const char *path, const char *name, const char *value,
size_t size, int flags, uint32_t position) {
if (isReadOnly(NULL)) return -EROFS;
(void)flags;
return withCipherPath("setxattr", path, bind(_do_setxattr, _1, _2, name,
value, size, position));
@ -606,6 +643,7 @@ int _do_setxattr(EncFS_Context *, const string &cyName, const char *name,
}
int encfs_setxattr(const char *path, const char *name, const char *value,
size_t size, int flags) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("setxattr", path,
bind(_do_setxattr, _1, _2, name, value, size, flags));
}
@ -663,6 +701,8 @@ int _do_removexattr(EncFS_Context *, const string &cyName, const char *name) {
}
int encfs_removexattr(const char *path, const char *name) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("removexattr", path,
bind(_do_removexattr, _1, _2, name));
}

View File

@ -83,6 +83,7 @@ int encfs_write(const char *path, const char *buf, size_t size, off_t offset,
int encfs_statfs(const char *, struct statvfs *fst);
int encfs_flush(const char *, struct fuse_file_info *info);
int encfs_fsync(const char *path, int flags, struct fuse_file_info *info);
int encfs_readonly_error(...);
#ifdef HAVE_XATTR

View File

@ -65,6 +65,12 @@ using gnu::autosprintf;
// Maximum number of arguments that we're going to pass on to fuse. Doesn't
// affect how many arguments we can handle, just how many we can pass on..
const int MaxFuseArgs = 32;
/**
* EncFS_Args stores the parsed command-line arguments
*
* See also: struct EncFS_Opts (FileUtils.h), stores internal settings that are
* derived from the arguments
*/
struct EncFS_Args {
string mountPoint; // where to make filesystem visible
bool isDaemon; // true == spawn in background, log to syslog
@ -273,20 +279,23 @@ static bool processArgs(int argc, char *argv[],
case 'D':
out->opts->forceDecode = true;
break;
/* By default, the kernel caches file metadata for one second.
* This is fine for EncFS' normal mode, but for --reverse, this
* means that the encrypted view will be up to one second out of
* date.
* Quoting Goswin von Brederlow:
* "Caching only works correctly if you implement a disk based
* filesystem, one where only the fuse process can alter
* metadata and all access goes only through fuse. Any overlay
* filesystem where something can change the underlying
* filesystem without going through fuse can run into
* inconsistencies."
* Enabling reverse automatically enables noCache. */
case 'r':
out->opts->reverseEncryption = true;
/* Reverse encryption does not support writing unless uniqueIV
* is disabled (expert mode) */
out->opts->readOnly = true;
/* By default, the kernel caches file metadata for one second.
* This is fine for EncFS' normal mode, but for --reverse, this
* means that the encrypted view will be up to one second out of
* date.
* Quoting Goswin von Brederlow:
* "Caching only works correctly if you implement a disk based
* filesystem, one where only the fuse process can alter
* metadata and all access goes only through fuse. Any overlay
* filesystem where something can change the underlying
* filesystem without going through fuse can run into
* inconsistencies."
* Enabling reverse automatically enables noCache */
case 514:
/* Disable EncFS block cache
* Causes reverse grow tests to fail because short reads

View File

@ -3,10 +3,11 @@
# Test EncFS --reverse mode
use warnings;
use Test::More qw( no_plan );
use Test::More tests => 25;
use File::Path;
use File::Temp;
use IO::Handle;
use Errno qw(EROFS);
require("tests/common.inc");
@ -68,7 +69,6 @@ sub copy_test
{
ok(system("cp -a encfs $plain")==0, "copying files to plain");
ok(system("diff -r -q $plain $decrypted")==0, "decrypted files are identical");
ok(-f "$plain/encfs/encfs.cpp", "file exists");
unlink("$plain/encfs/encfs.cpp");
ok(! -f "$decrypted/encfs.cpp", "file deleted");
@ -137,6 +137,32 @@ sub largeRead {
ok(sizeVerify($cfh, 1024*1024+8), "1M file size");
}
# Check that the reverse mount is read-only
# (writing is not supported in reverse mode because of the added
# complexity and the marginal use case)
sub writesDenied {
$fn = "$plain/writesDenied";
writeZeroes($fn, 1024);
my $efn = $ciphertext . "/" . encName("writesDenied");
open(my $fh, ">", $efn);
if( ok( $! == EROFS, "open for write denied, EROFS")) {
ok( 1, "writing denied, filhandle not open");
}
else {
print($fh "foo");
ok( $! == EROFS, "writing denied, EROFS");
}
$target = $ciphertext . "/" . encName("writesDenied2");
rename($efn, $target);
ok( $! == EROFS, "rename denied, EROFS") or die();
unlink($efn);
ok( $! == EROFS, "unlink denied, EROFS");
utime(undef, undef, $efn) ;
ok( $! == EROFS, "touch denied, EROFS");
truncate($efn, 10);
ok( $! == EROFS, "truncate denied, EROFS");
}
# Setup mounts
newWorkingDir();
mount();
@ -149,6 +175,7 @@ symlink_test("/"); # absolute
symlink_test("foo"); # relative
symlink_test("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/15/17/18"); # long
symlink_test("!§\$%&/()\\<>#+="); # special characters
writesDenied();
# Umount and delete files
cleanup();