mount_helper: open vault with O_NOFOLLOW (P1 #33a)

ober

b2e2fc7e6ee7113fb4722918fb4357bbce0930e4

diff --git a/src/mount_helper.c b/src/mount_helper.c
index 2d9c90c..0c7d977 100644
--- a/src/mount_helper.c
+++ b/src/mount_helper.c
@@ -649,7 +649,7 @@ int jerboa_fuse_blockstore_open_bv(const unsigned char *path, int path_len) {
     char *path_c = copy_cstring(path, path_len, JERBOA_FUSE_MAX_CSTRING, 0);
     int fd;
     if (!path_c) return -1;
-    fd = open(path_c, O_RDWR | O_CLOEXEC);
+    fd = open(path_c, O_RDWR | O_NOFOLLOW | O_CLOEXEC);
     free(path_c);
     return fd;
 }
diff --git a/tests/test_mount_helper.c b/tests/test_mount_helper.c
index 2c6dbed..850a5e5 100644
--- a/tests/test_mount_helper.c
+++ b/tests/test_mount_helper.c
@@ -12,6 +12,7 @@
 #include <unistd.h>
 
 int jerboa_fuse_blockstore_create_bv(const unsigned char *path, int path_len);
+int jerboa_fuse_blockstore_open_bv(const unsigned char *path, int path_len);
 int jerboa_fuse_gcm_encrypt(const unsigned char *key, int key_len,
                             const unsigned char *plaintext, int plaintext_len,
                             unsigned char *out, int out_len);
@@ -98,6 +99,48 @@ static void test_exclusive_nofollow_create(void) {
     (void)rmdir(tmp);
 }
 
+static void test_nofollow_open(void) {
+    char tmp[] = "/tmp/jerboa-fuse-open-XXXXXX";
+    char target[PATH_MAX];
+    char linkpath[PATH_MAX];
+    char buf[8] = {0};
+    int fd;
+
+    check(mkdtemp(tmp) != NULL, "mkdtemp succeeds");
+    if (failures) return;
+    (void)snprintf(target, sizeof(target), "%s/target", tmp);
+    (void)snprintf(linkpath, sizeof(linkpath), "%s/vault", tmp);
+
+    fd = open(target, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
+    check(fd >= 0, "sentinel target create succeeds");
+    if (fd >= 0) {
+        check(write_exact(fd, "sentinel", 8) == 0,
+              "sentinel target write succeeds");
+        (void)close(fd);
+    }
+    check(symlink(target, linkpath) == 0, "vault symlink create succeeds");
+
+    errno = 0;
+    fd = jerboa_fuse_blockstore_open_bv((const unsigned char *)linkpath,
+                                         (int)strlen(linkpath));
+    check(fd < 0, "blockstore open rejects a symlink path");
+    check(errno == ELOOP, "blockstore open of a symlink fails with ELOOP");
+    if (fd >= 0) (void)close(fd);
+    check(read_exact(target, buf, sizeof(buf)) == 0 &&
+              memcmp(buf, "sentinel", sizeof(buf)) == 0,
+          "rejected symlink open leaves its target unchanged");
+
+    (void)unlink(linkpath);
+    errno = 0;
+    fd = jerboa_fuse_blockstore_open_bv((const unsigned char *)target,
+                                         (int)strlen(target));
+    check(fd >= 0, "blockstore open succeeds on a regular file");
+    if (fd >= 0) (void)close(fd);
+
+    (void)unlink(target);
+    (void)rmdir(tmp);
+}
+
 static void test_bad_gcm_tag_zeros_plaintext(void) {
     unsigned char key[32] = {0};
     unsigned char plaintext[32];
@@ -154,6 +197,7 @@ static void test_unmount_launcher_fails_safely(void) {
 
 int main(void) {
     test_exclusive_nofollow_create();
+    test_nofollow_open();
     test_bad_gcm_tag_zeros_plaintext();
     test_unmount_launcher_fails_safely();
     test_secmem_lock_failure_is_fatal();