generic: check that we can't write to swap files
authorDarrick J. Wong <darrick.wong@oracle.com>
Tue, 24 Sep 2019 16:39:37 +0000 (09:39 -0700)
committerEryu Guan <guaneryu@gmail.com>
Wed, 2 Oct 2019 10:51:16 +0000 (18:51 +0800)
While active, the media backing a swap file is leased to the kernel.
Userspace has no business writing to it.  Make sure we can't do this.

The two kernel patches titled as below should fix the bug:

mm: set S_SWAPFILE on blockdev swap devices
vfs: don't allow writes to swap files

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Eryu Guan <guaneryu@gmail.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
src/swapon.c
tests/generic/569 [new file with mode: 0755]
tests/generic/569.out [new file with mode: 0644]
tests/generic/570 [new file with mode: 0755]
tests/generic/570.out [new file with mode: 0644]
tests/generic/group

index 0cb7108a694aef42fc59e1fdf1d497e1473a9741..afaed405ed985fb7afb3a3a16e7dfc78ea591875 100644 (file)
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <string.h>
 #include <sys/swap.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <signal.h>
+
+static void usage(const char *prog)
+{
+       fprintf(stderr, "usage: %s [-v verb] PATH\n", prog);
+       exit(EXIT_FAILURE);
+}
+
+enum verbs {
+       TEST_SWAPON = 0,
+       TEST_WRITE,
+       TEST_MWRITE_AFTER,
+       TEST_MWRITE_BEFORE_AND_MWRITE_AFTER,
+       TEST_MWRITE_BEFORE,
+       MAX_TEST_VERBS,
+};
+
+#define BUF_SIZE 262144
+static char buf[BUF_SIZE];
+
+static void handle_signal(int signal)
+{
+       fprintf(stderr, "Caught signal %d, terminating...\n", signal);
+       exit(EXIT_FAILURE);
+}
 
 int main(int argc, char **argv)
 {
-       int ret;
+       struct sigaction act = {
+               .sa_handler     = handle_signal,
+       };
+       enum verbs verb = TEST_SWAPON;
+       void *p;
+       ssize_t sz;
+       int fd = -1;
+       int ret, c;
+
+       memset(buf, 0x58, BUF_SIZE);
+
+       while ((c = getopt(argc, argv, "v:")) != -1) {
+               switch (c) {
+               case 'v':
+                       verb = atoi(optarg);
+                       if (verb < TEST_SWAPON || verb >= MAX_TEST_VERBS) {
+                               fprintf(stderr, "Verbs must be 0-%d.\n",
+                                               MAX_TEST_VERBS - 1);
+                               usage(argv[0]);
+                       }
+                       break;
+               default:
+                       usage(argv[0]);
+                       break;
+               }
+       }
 
-       if (argc != 2) {
-               fprintf(stderr, "usage: %s PATH\n", argv[0]);
+       ret = sigaction(SIGSEGV, &act, NULL);
+       if (ret) {
+               perror("sigsegv action");
                return EXIT_FAILURE;
        }
 
-       ret = swapon(argv[1], 0);
+       ret = sigaction(SIGBUS, &act, NULL);
+       if (ret) {
+               perror("sigbus action");
+               return EXIT_FAILURE;
+       }
+
+       switch (verb) {
+       case TEST_WRITE:
+       case TEST_MWRITE_AFTER:
+       case TEST_MWRITE_BEFORE_AND_MWRITE_AFTER:
+       case TEST_MWRITE_BEFORE:
+               fd = open(argv[optind], O_RDWR);
+               if (fd < 0) {
+                       perror(argv[optind]);
+                       return EXIT_FAILURE;
+               }
+               break;
+       default:
+               break;
+       }
+
+       switch (verb) {
+       case TEST_MWRITE_BEFORE_AND_MWRITE_AFTER:
+       case TEST_MWRITE_BEFORE:
+               p = mmap(NULL, BUF_SIZE, PROT_WRITE | PROT_READ, MAP_SHARED,
+                               fd, 65536);
+               if (p == MAP_FAILED) {
+                       perror("mmap");
+                       return EXIT_FAILURE;
+               }
+               memcpy(p, buf, BUF_SIZE);
+               break;
+       default:
+               break;
+       }
+
+       if (optind != argc - 1)
+               usage(argv[0]);
+
+       ret = swapon(argv[optind], 0);
        if (ret) {
                perror("swapon");
                return EXIT_FAILURE;
        }
 
+       switch (verb) {
+       case TEST_WRITE:
+               sz = pwrite(fd, buf, BUF_SIZE, 65536);
+               if (sz < 0) {
+                       perror("pwrite");
+                       return EXIT_FAILURE;
+               }
+               break;
+       case TEST_MWRITE_AFTER:
+               p = mmap(NULL, BUF_SIZE, PROT_WRITE | PROT_READ, MAP_SHARED,
+                               fd, 65536);
+               if (p == MAP_FAILED) {
+                       perror("mmap");
+                       return EXIT_FAILURE;
+               }
+               /* fall through */
+       case TEST_MWRITE_BEFORE_AND_MWRITE_AFTER:
+               memcpy(p, buf, BUF_SIZE);
+               break;
+       default:
+               break;
+       }
+
+       if (fd >= 0) {
+               ret = fsync(fd);
+               if (ret)
+                       perror("fsync");
+               ret = close(fd);
+               if (ret)
+                       perror("close");
+       }
+
        return EXIT_SUCCESS;
 }
diff --git a/tests/generic/569 b/tests/generic/569
new file mode 100755 (executable)
index 0000000..02fd857
--- /dev/null
@@ -0,0 +1,70 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-newer
+# Copyright (c) 2019, Oracle and/or its affiliates.  All Rights Reserved.
+#
+# FS QA Test No. 569
+#
+# Check that we can't modify a file that's an active swap file.
+
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1    # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+       cd /
+       swapoff $testfile
+       rm -rf $tmp.* $testfile
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# real QA test starts here
+_supported_os Linux
+_supported_fs generic
+_require_test_program swapon
+_require_scratch_swapfile
+
+rm -f $seqres.full
+
+_scratch_mkfs > $seqres.full 2>&1
+_scratch_mount >> $seqres.full 2>&1
+
+testfile=$SCRATCH_MNT/$seq.swap
+
+_format_swapfile $testfile 20m
+
+# Can you modify the swapfile via previously open file descriptors?
+for verb in 1 2 3 4; do
+       echo "verb $verb"
+       "$here/src/swapon" -v $verb $testfile
+       swapoff $testfile
+done
+
+# Now try writing with a new file descriptor.
+swapon $testfile 2>&1 | _filter_scratch
+
+# Can we write to it?
+$XFS_IO_PROG -c 'pwrite -S 0x59 64k 64k' $testfile
+$XFS_IO_PROG -d -c 'pwrite -S 0x60 64k 64k' $testfile
+$XFS_IO_PROG -c 'mmap -rw 64k 64k' -c 'mwrite -S 0x61 64k 64k' $testfile
+
+# Can we change the file size?
+$XFS_IO_PROG -c 'truncate 18m' $testfile
+
+# Can you fallocate the file?
+$XFS_IO_PROG -c 'falloc 0 32m' $testfile
+
+# We test that you can't reflink, dedupe, or copy_file_range into a swapfile
+# in other tests.
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/569.out b/tests/generic/569.out
new file mode 100644 (file)
index 0000000..0a6c343
--- /dev/null
@@ -0,0 +1,14 @@
+QA output created by 569
+verb 1
+pwrite: Text file busy
+verb 2
+mmap: Text file busy
+verb 3
+Caught signal 7, terminating...
+verb 4
+pwrite: Text file busy
+pwrite: Text file busy
+mmap: Text file busy
+no mapped regions, try 'help mmap'
+ftruncate: Text file busy
+fallocate: Text file busy
diff --git a/tests/generic/570 b/tests/generic/570
new file mode 100755 (executable)
index 0000000..1c5f39f
--- /dev/null
@@ -0,0 +1,56 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-newer
+# Copyright (c) 2019, Oracle and/or its affiliates.  All Rights Reserved.
+#
+# FS QA Test No. 570
+#
+# Check that we can't modify a block device that's an active swap device.
+
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1    # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+       cd /
+       swapoff $SCRATCH_DEV
+       rm -rf $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# real QA test starts here
+_supported_os Linux
+_supported_fs generic
+_require_test_program swapon
+_require_scratch_nocheck
+_require_block_device $SCRATCH_DEV
+
+rm -f $seqres.full
+
+$MKSWAP_PROG "$SCRATCH_DEV" >> $seqres.full
+
+# Can you modify the swap dev via previously open file descriptors?
+for verb in 1 2 3 4; do
+       echo "verb $verb"
+       "$here/src/swapon" -v $verb $SCRATCH_DEV
+       swapoff $SCRATCH_DEV
+done
+
+swapon $SCRATCH_DEV 2>&1 | _filter_scratch
+
+# Can we write to it?
+$XFS_IO_PROG -c 'pwrite -S 0x59 64k 64k' $SCRATCH_DEV
+$XFS_IO_PROG -d -c 'pwrite -S 0x60 64k 64k' $SCRATCH_DEV
+$XFS_IO_PROG -c 'mmap -rw 64k 64k' -c 'mwrite -S 0x61 64k 64k' $SCRATCH_DEV
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/570.out b/tests/generic/570.out
new file mode 100644 (file)
index 0000000..63d8302
--- /dev/null
@@ -0,0 +1,12 @@
+QA output created by 570
+verb 1
+pwrite: Text file busy
+verb 2
+mmap: Text file busy
+verb 3
+Caught signal 7, terminating...
+verb 4
+pwrite: Text file busy
+pwrite: Text file busy
+mmap: Text file busy
+no mapped regions, try 'help mmap'
index dd971f0c6c575852c32ceacdfdb44956376b62fb..5911b4ca935a5351c7aa19cfd5b9daff6e32c3f9 100644 (file)
 566 auto quick quota metadata
 567 auto quick rw punch
 568 auto quick rw prealloc
+569 auto quick rw swap prealloc
+570 auto quick rw swap