Package: mdadm
Version: 4.1-1
Severity: critical
Tags: upstream



When installing a kernel with initrd enabled, initramfs-tools calls /usr/share/initramfs-tools/hooks/mdadm. Doing this in a chroot previously created with debootstrap causes the hook to call

$MDADM --examine --scan --config=partitions

If I run this command in a chroot on a machine with md0 as host's root filesystem WITHOUT mounting /proc, /sys and /dev in the chroot, mdadm CORRUPTS the host's root filesystem (/dev/md0 with ext4 filesystem format). I can reproduce this problem every time I do this. To detect it, I made a background job reading all file in /:

> while sleep 1; do
>   find / -xdev -type f -exec cat {} + > /dev/null
>   echo 3 > /proc/sys/vm/drop_caches # drop caches to for re-read
> done
A few seconds to minutes after invoking the corrupting command, you can see messages like this in kernel log:

> EXT4-fs error (device md0): ext4_validate_inode_bitmap:100: comm uptimed: Corrupt inode bitmap - block_group = 96, inode_bitmap = 3145744 > EXT4-fs error (device md0): ext4_validate_block_bitmap:384: comm uptimed: bg 97: bad block bitmap checksum > EXT4-fs error (device md0) in ext4_free_blocks:4964: Filesystem failed CRC
> EXT4-fs error (device md0) in ext4_free_inode:357: Corrupt filesystem
We did not try to repair such filesystems but reinstalled the machine every time this occured while investigating.

I tried to debug the problem and could bring it down to a BLKPG_DEL_PARTITION ioctl issued on a temporary device inode created by mdadm while running. This call is done in

util.c:int test_partition(int fd)

which is (somehow) called by

Examine.c:int Examine(...)


Invoking the same command in the chroot after mounting /dev, /proc and /sys in the chroot does not corrupt the host's filesystem.


Please forward this bug report to upstream in order to get a fix/workaround or at least a huge warning implemented in mdadm to avoid data corruption for other users.



-- Package-specific info:


-- System Information:
Debian Release: 10.7
  APT prefers proposed-updates
  APT policy: (500, 'proposed-updates'), (500, 'stable')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 5.4.78.1.amd64-smp (SMP w/4 CPU cores)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_USER, TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE Locale: LANG=de_DE.UTF-8, LC_CTYPE=de_DE.UTF-8 (charmap=UTF-8), LANGUAGE=de_DE.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)

Versions of packages mdadm depends on:
ii  debconf [debconf-2.0]  1.5.71
ii  libc6                  2.28-10
ii  lsb-base               10.2019051400
ii  udev                   241-7~deb10u5

Versions of packages mdadm recommends:
ii  exim4-daemon-light [mail-transport-agent]  4.92-8+deb10u4
ii  kmod                                       26-1

Versions of packages mdadm suggests:
pn  dracut-core  <none>

-- Configuration Files:
/etc/cron.daily/mdadm [Errno 2] Datei oder Verzeichnis nicht gefunden: '/etc/cron.daily/mdadm'

-- debconf information excluded
/* O_DIRECT */
#define _GNU_SOURCE

/* mknod */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

/* printf */
#include <stdio.h>

/* open */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* errno */
#include <errno.h>

/* ioctl */
#include <sys/ioctl.h>

/* BLKPG */
/* #include <linux/blkpg.h> */
/*
 * following taken from linux/blkpg.h because they aren't
 * anywhere else and it isn't safe to #include linux/ * stuff.
 */

#define BLKPG      _IO(0x12,105)

/* The argument structure */
struct blkpg_ioctl_arg {
	int op;
	int flags;
	int datalen;
	void *data;
};

/* The subfunctions (for the op field) */
#define BLKPG_ADD_PARTITION	1
#define BLKPG_DEL_PARTITION	2

/* Sizes of name fields. Unused at present. */
#define BLKPG_DEVNAMELTH	64
#define BLKPG_VOLNAMELTH	64

/* The data structure for ADD_PARTITION and DEL_PARTITION */
struct blkpg_partition {
	long long start;		/* starting offset in bytes */
	long long length;		/* length in bytes */
	int pno;			/* partition number */
	char devname[BLKPG_DEVNAMELTH];	/* partition name, like sda5 or c0d1p2,
					   to be used in kernel messages */
	char volname[BLKPG_VOLNAMELTH];	/* volume label */
};

/* memset */
#include <string.h>

/* lseek */
#include <sys/types.h>
#include <unistd.h>

/* BLKGETSIZE64 */
#ifndef BLKGETSIZE64
#define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */
#endif


#define align(p, a) (((long)(p) + (a - 1)) & ~(a - 1))




int do_seek(int fd, unsigned long long offset, int whence) {
  printf("lseek(%d, %ld, SEEK_SET)\n", fd, offset);
  printf("lseek()=%ld\n", lseek(fd, offset, whence));
  return 1;
}

int do_read(int fd, size_t size) {
  char unalignedbuffer[10000];
  char* buffer;
  ssize_t bytes;
  if (size > 4096) {
    printf("Reading %d bytes not supported yet!\n", size);
    return 0;
  }
  buffer = (char*)align(unalignedbuffer, 512);
  printf("read(%d, buffer, %d)\n", fd, size);
  bytes = read(fd, buffer, size);
  if (bytes < 0) {
    printf("read()=%d, errno=%d\n", bytes, errno);
  } else {
    printf("read()=%d\n", bytes);
  }
  return 1;
}

int do_ioctl_del_partition(int fd) {
  /* the following code is designed for amme2/srv-40-616 */
  struct blkpg_ioctl_arg a;
  struct blkpg_partition p;
  printf("Preparing ioctl for %d\n", fd);
  a.op = BLKPG_DEL_PARTITION;
  a.data = (void*)&p;
  a.datalen = sizeof(p);
  a.flags = 0;
  memset(a.data, 0, a.datalen);
  p.pno = 1<<30;
  printf("Sending ioctl to %d\n", fd);
  if (! ioctl(fd, BLKPG, &a)) {
    printf("ioctl BLKPG_DEL_PARTITION succeded\n");
  } else {
    printf("ioctl BLKPG_DEL_PARTITION failed, errno=%d, ENXIO=%d, EINVAL=%d\n", errno, ENXIO, EINVAL);
  }
  return 1;
}

int do_ioctl_blkgetsize64(int fd) {
  unsigned long long ldsize;
  printf("ioctl(%d, BLKGETSIZE64, &ldsize)\n", fd);
  ioctl(fd, BLKGETSIZE64, &ldsize);
  printf("ldsize=%ld\n", ldsize);
  return 0;
}

int main(int argc, char* argv[]) {
  int fd = 0;
  const char* dev = "/dev/.tmp.md.18210:9:0";
  unsigned long long offset = 0ul;
  
  if (argc > 1) {
    dev = argv[1];
  } else {
    printf("Creating temp. dev %s\n", dev);
    if (mknod(dev, S_IFBLK|0600, makedev(9, 0))) {
      printf("mknod failed, errno=%d\n", errno);
      return 1;
    }
  }

  printf("Opening %s\n", dev);
  fd = openat(AT_FDCWD, dev, O_RDONLY|O_DIRECT);
  if (fd < 0) {
    printf("openat failed, errno=%d\n", errno);
    return 2;
  }
  if (argc == 1) {
    printf("Unlinking %s\n", dev);
    unlink(dev);
  }

  do_ioctl_blkgetsize64(fd);
  do_seek(fd, 42949541888, SEEK_SET);
  do_read(fd, 4096);
  
  do_seek(fd, 4096, SEEK_SET);
  do_read(fd, 4096);
  do_ioctl_del_partition(fd);
  
  do_seek(fd, 42949606912, SEEK_SET);
  do_read(fd, 512);
  do_ioctl_del_partition(fd);
  
  do_ioctl_del_partition(fd);
  
  printf("Closing %s\n", dev);
  close(fd);
  return 0;
}

Reply via email to