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;
}