Skip to content

Access to buffers returned as corrupted by the block layer #904

Open
@BenBE

Description

@BenBE

While reviewing littlefs we (@Maaxxs, @stoeckmann and me) noticed an issue, where littlefs reads from potentially uninitialized memory.

A simple way to reproduce this is to apply the following patch:

diff --git a/bd/lfs_rambd.c b/bd/lfs_rambd.c
index a6a05727..d6282191 100644
--- a/bd/lfs_rambd.c
+++ b/bd/lfs_rambd.c
@@ -7,6 +7,8 @@
  */
 #include "bd/lfs_rambd.h"
 
+#include "sanitizer/asan_interface.h"
+
 int lfs_rambd_create(const struct lfs_config *cfg,
         const struct lfs_rambd_config *bdcfg) {
     LFS_RAMBD_TRACE("lfs_rambd_create(%p {.context=%p, "
@@ -69,6 +71,21 @@ int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block,
     memcpy(buffer, &bd->buffer[block*bd->cfg->erase_size + off], size);
 
     LFS_RAMBD_TRACE("lfs_rambd_read -> %d", 0);
+
+    if (block == 0 && off == 0 && size == 4) {
+        LFS_RAMBD_TRACE("Corrupted foo! -> %d", LFS_ERR_CORRUPT);
+        ASAN_POISON_MEMORY_REGION(buffer, size);
+        return LFS_ERR_CORRUPT;
+    }
+    if (block == 1 && off == 0 && size == 4) {
+        LFS_RAMBD_TRACE("Negative Serial -> %d", 0);
+        ASAN_UNPOISON_MEMORY_REGION(buffer, size);
+        ((uint8_t*)buffer)[3] |= 0x80;
+        return 0;
+    }
+
+    ASAN_UNPOISON_MEMORY_REGION(buffer, size);
+
     return 0;
 }

and compile a small demo program using that rambd driver with ASAN enabled:

BUILDDIR=$(pwd) gcc -fsanitize=address -g -DLFS_RAMBD_YES_TRACE -DLFS_YES_TRACE -I${BUILDDIR} lfs.c lfs_util.c bd/lfs_rambd.c -o thing thing.c

(thing.c is the example from the README plus the necessary structs to use lfs_rambd.c)

When running ./thing you get output like this:

bd/lfs_rambd.c:14:trace: lfs_rambd_create(0x55af2c77b160 {.context=0x55af2c77b5e0, .read=0x55af2c76dcbb, .prog=0x55af2c76e1ff, .erase=0x55af2c76e5fd, .sync=0x55af2c76e7d1}, 0x55af2c77b120 {.read_size=4, .prog_size=16, .erase_size=4096, .erase_count=1024, .buffer=(nil)})
bd/lfs_rambd.c:42:trace: lfs_rambd_create -> 0
lfs.c:5788:trace: lfs_mount(0x55af2c77b480, 0x55af2c77b160 {.context=0x55af2c77b5e0, .read=0x55af2c76dcbb, .prog=0x55af2c76e1ff, .erase=0x55af2c76e5fd, .sync=0x55af2c76e7d1, .read_size=4, .prog_size=16, .block_size=4096, .block_count=128, .block_cycles=500, .cache_size=16, .lookahead_size=16, .read_buffer=(nil), .prog_buffer=(nil), .lookahead_buffer=(nil), .name_max=0, .file_max=0, .attr_max=0})
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x0, 0, 0x7f14132001a0, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:76:trace: Corrupted foo! -> -84
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 0, 0x7f14132001a4, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:81:trace: Negative Serial -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x0, 4, 0x602000000010, 16)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 4, 0x602000000010, 16)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
lfs.c:1346:error: Corrupted dir pair at {0x0, 0x1}
lfs.c:5807:trace: lfs_mount -> -84
lfs.c:5758:trace: lfs_format(0x55af2c77b480, 0x55af2c77b160 {.context=0x55af2c77b5e0, .read=0x55af2c76dcbb, .prog=0x55af2c76e1ff, .erase=0x55af2c76e5fd, .sync=0x55af2c76e7d1, .read_size=4, .prog_size=16, .block_size=4096, .block_count=128, .block_cycles=500, .cache_size=16, .lookahead_size=16, .read_buffer=(nil), .prog_buffer=(nil), .lookahead_buffer=(nil), .name_max=0, .file_max=0, .attr_max=0})
…
lfs.c:5777:trace: lfs_format -> 0
lfs.c:5788:trace: lfs_mount(0x55af2c77b480, 0x55af2c77b160 {.context=0x55af2c77b5e0, .read=0x55af2c76dcbb, .prog=0x55af2c76e1ff, .erase=0x55af2c76e5fd, .sync=0x55af2c76e7d1, .read_size=4, .prog_size=16, .block_size=4096, .block_count=128, .block_cycles=500, .cache_size=16, .lookahead_size=16, .read_buffer=(nil), .prog_buffer=(nil), .lookahead_buffer=(nil), .name_max=0, .file_max=0, .attr_max=0})
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x0, 0, 0x7f14132008a0, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:76:trace: Corrupted foo! -> -84
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 0, 0x7f14132008a4, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:81:trace: Negative Serial -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 4, 0x6020000000d0, 16)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 20, 0x6020000000d0, 16)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 36, 0x6020000000d0, 16)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 52, 0x6020000000d0, 16)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 48, 0x6020000000d0, 16)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 64, 0x6020000000d0, 16)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 56, 0x7f14130005e0, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 44, 0x7f14130005e0, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 16, 0x7f14130005e0, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 20, 0x7f1413200760, 24)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 56, 0x7f1413000660, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 44, 0x7f1413000660, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 16, 0x7f1413000660, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 4, 0x7f1413000660, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
lfs.c:5807:trace: lfs_mount -> 0
lfs.c:5928:trace: lfs_file_open(0x55af2c77b480, 0x55af2c77b540, "boot_count", 103)
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x1, 0, 0x7f1413200ba0, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:81:trace: Negative Serial -> 0
bd/lfs_rambd.c:59:trace: lfs_rambd_read(0x55af2c77b160, 0x0, 0, 0x7f1413200ba4, 4)
bd/lfs_rambd.c:73:trace: lfs_rambd_read -> 0
bd/lfs_rambd.c:76:trace: Corrupted foo! -> -84
=================================================================
==3208565==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f1413200ba4 at pc 0x55af2c74d35f bp 0x7fff642d1d30 sp 0x7fff642d1d20
READ of size 4 at 0x7f1413200ba4 thread T0
    #0 0x55af2c74d35e in lfs_dir_fetchmatch /home/user/littlefs/lfs.c:1094
    #1 0x55af2c750434 in lfs_dir_find /home/user/littlefs/lfs.c:1516
    #2 0x55af2c75b5c6 in lfs_file_rawopencfg /home/user/littlefs/lfs.c:3031
    #3 0x55af2c75c622 in lfs_file_rawopen /home/user/littlefs/lfs.c:3177
    #4 0x55af2c76be7d in lfs_file_open /home/user/littlefs/lfs.c:5932
    #5 0x55af2c76ea08 in main /home/user/littlefs/thing.c:74
    #6 0x7f14150280cf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #7 0x7f1415028188 in __libc_start_main_impl ../csu/libc-start.c:360
    #8 0x55af2c7454c4 in _start (/home/user/littlefs/thing+0x54c4) (BuildId: c8fe4cbf8fab401c0cd61a4e7e3aa611af6a0221)

Address 0x7f1413200ba4 is located in stack of thread T0 at offset 164 in frame
    #0 0x55af2c74cfd9 in lfs_dir_fetchmatch /home/user/littlefs/lfs.c:1075

  This frame has 8 object(s):
    [32, 36) 'crc' (line 1126)
    [48, 52) 'tag' (line 1131)
    [64, 68) 'dcrc' (line 1161)
    [80, 84) 'fcrc_' (line 1309)
    [96, 104) 'fcrc' (line 1123)
    [128, 136) '<unknown>'
    [160, 168) 'revs' (line 1088) <== Memory access at offset 164 is inside this variable
    [192, 200) 'temptail' (line 1116)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/user/littlefs/lfs.c:1094 in lfs_dir_fetchmatch
Shadow bytes around the buggy address:
  0x7f1413200900: f1 f1 f1 f1 f1 f1 00 f2 f2 f2 00 00 00 00 00 00
  0x7f1413200980: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f1413200a00: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x7f1413200a80: f5 f5 f5 f5 f5 f5 f5 f5 00 00 00 00 00 00 00 00
  0x7f1413200b00: f1 f1 f1 f1 04 f2 04 f2 04 f2 04 f2 00 f2 f2 f2
=>0x7f1413200b80: 00 f2 f2 f2[04]f2 f2 f2 00 f3 f3 f3 00 00 00 00
  0x7f1413200c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f1413200c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f1413200d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f1413200d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f1413200e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==3208565==ABORTING

There are two issues in littlefs demonstrated by this PoC:

  1. There is a chance, that certain code paths read memory despite the block layer returning that memory to be corrupted
  2. When only the second block of a dir_pair is readable AND the revision in that dir_pair is negative, the sector is ignored, despite being the only usable one.

The first issue is a problem with how memory is handled when a sector could not be properly read.

The second issue is a bug in how the most recent revision is determined in lfs_dir_fetchmatch:

    // find the block with the most recent revision
    uint32_t revs[2] = {0, 0};
    int r = 0;
    for (int i = 0; i < 2; i++) {
        int err = lfs_bd_read(lfs,
                NULL, &lfs->rcache, sizeof(revs[i]),
                pair[i], 0, &revs[i], sizeof(revs[i]));
        revs[i] = lfs_fromle32(revs[i]); // Invalid read, should go AFTER the error check
        if (err && err != LFS_ERR_CORRUPT) {
            return err;
        }

        // \/--- Should ALWAYS be true for the first (valid) read we encounter
        if (err != LFS_ERR_CORRUPT &&
                lfs_scmp(revs[i], revs[(i+1)%2]) > 0) {
            r = i;
        }
    }

If you have any questions or need more information, feel free to ask away. :)

thing.c
#include "lfs.h"
#include "bd/lfs_rambd.h"

// variables used by the filesystem
lfs_t lfs;
lfs_file_t file;


struct lfs_rambd_config lfs_rambd_cfg = {
    // Minimum size of a read operation in bytes.
    .read_size = 4,
    // Minimum size of a program operation in bytes.
    .prog_size = 16,
    // Size of an erase operation in bytes.
    .erase_size = 4096,
    // Number of erase blocks on the device.
    .erase_count = 1024,
    // Optional statically allocated buffer for the block device.
    .buffer = NULL,
};

lfs_rambd_t lfs_rambd;

// configuration of the filesystem is provided by this struct
struct lfs_config cfg = {
    .context = &lfs_rambd,
    // block device operations
    .read  = lfs_rambd_read,
    .prog  = lfs_rambd_prog,
    .erase = lfs_rambd_erase,
    .sync  = lfs_rambd_sync,

    // block device configuration
    .read_size = 4,
    .prog_size = 16,
    .block_size = 4096,
    .block_count = 128,
    .cache_size = 16,
    .lookahead_size = 16,
    .block_cycles = 500,
};

// entry point
int main(void) {
    if (lfs_rambd_create(&cfg, &lfs_rambd_cfg) != 0) {
        puts("could not create rambd");
        exit(1);
    }

    // mount the filesystem
    int err = lfs_mount(&lfs, &cfg);

    // reformat if we can't mount the filesystem
    // this should only happen on the first boot
    if (err) {
        lfs_format(&lfs, &cfg);
        lfs_mount(&lfs, &cfg);
    }

    // read current count
    uint32_t boot_count = 0;
    lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
    lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));

    // update boot count
    boot_count += 1;
    lfs_file_rewind(&lfs, &file);
    lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));

    // remember the storage is not updated until the file is closed successfully
    lfs_file_close(&lfs, &file);

    // release any resources we were using
    lfs_unmount(&lfs);

    // print the boot count
    printf("boot_count: %d\n", boot_count);

    lfs_rambd_destroy(&cfg);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs fixwe know what is wrong

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions