diff --git a/encfs/FSConfig.h b/encfs/FSConfig.h index 4e82856..5cc8fe7 100644 --- a/encfs/FSConfig.h +++ b/encfs/FSConfig.h @@ -41,6 +41,9 @@ struct EncFS_Opts; class Cipher; class NameIO; +/** + * Persistent configuration (stored in config file .encfs6.xml) + */ struct EncFSConfig { ConfigType cfgType; diff --git a/encfs/FileUtils.cpp b/encfs/FileUtils.cpp index 277b6a8..0199d52 100644 --- a/encfs/FileUtils.cpp +++ b/encfs/FileUtils.cpp @@ -1036,6 +1036,8 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr &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(); diff --git a/encfs/FileUtils.h b/encfs/FileUtils.h index 85ae23b..0cedb14 100644 --- a/encfs/FileUtils.h +++ b/encfs/FileUtils.h @@ -58,6 +58,11 @@ typedef shared_ptr 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; } }; diff --git a/encfs/encfs.cpp b/encfs/encfs.cpp index 9ae1ce1..4d72b00 100644 --- a/encfs/encfs.cpp +++ b/encfs/encfs.cpp @@ -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 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 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 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 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 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 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 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 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)); } diff --git a/encfs/encfs.h b/encfs/encfs.h index 684ab1d..7177046 100644 --- a/encfs/encfs.h +++ b/encfs/encfs.h @@ -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 diff --git a/encfs/main.cpp b/encfs/main.cpp index 1af861a..728517f 100644 --- a/encfs/main.cpp +++ b/encfs/main.cpp @@ -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 diff --git a/tests/reverse.pl b/tests/reverse.pl index 5939652..844507f 100755 --- a/tests/reverse.pl +++ b/tests/reverse.pl @@ -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();