mirror of
https://github.com/vgough/encfs.git
synced 2024-12-23 15:18:58 +01:00
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:
parent
d1363578fc
commit
32102447e0
@ -41,6 +41,9 @@ struct EncFS_Opts;
|
||||
class Cipher;
|
||||
class NameIO;
|
||||
|
||||
/**
|
||||
* Persistent configuration (stored in config file .encfs6.xml)
|
||||
*/
|
||||
struct EncFSConfig {
|
||||
ConfigType cfgType;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user