On Mon, 11 Apr 2022, Linus Torvalds wrote: > On Mon, Apr 11, 2022 at 7:13 AM Mikulas Patocka <mpatocka@xxxxxxxxxx> wrote: > > > > Should we perhaps hash the number, take 16 bits of the hash and hope > > than the collision won't happen? > > That would "work", but I think it would be incredibly annoying to > users with basically random results. > > I think the solution is to just put the bits in the high bits. Yes, > they might be masked off if people use 'MAJOR()' to pick them out, but > the common "compare st_dev and st_ino" model at least works. That's > the one that wants unique numbers. > > > For me, the failure happens in cp_compat_stat (I have a 64-bit kernel). In > > struct compat_stat in arch/x86/include/asm/compat.h, st_dev and st_rdev > > are compat_dev_t which is 16-bit. But they are followed by 16-bit > > paddings, so they could be extended. > > Ok, that actually looks like a bug. > > The compat structure should match the native structure. Those "u16 > __padX" fields seem to be just a symptom of the bug. > > The only user of that compat_stat structure is the kernel, so that > should just be fixed. > > Of course, who knows what the libraries have done, so user space could > still have screwed up. Here I'm sending a patch that makes struct compat_stat match struct stat. stat: fix inconsistency between struct stat and struct compat_stat struct stat (defined in arch/x86/include/uapi/asm/stat.h) has 32-bit st_dev and st_rdev; struct compat_stat (defined in arch/x86/include/asm/compat.h) has 16-bit st_dev and st_rdev followed by a 16-bit padding. This patch fixes struct compat_stat to match struct stat. Note that we can't change compat_dev_t because it is used by compat_loop_info. Also, if the st_dev and st_rdev values are 32-bit, we don't have to use old_valid_dev to test if the value fits into them. This fixes -EOVERFLOW on filesystems that are on NVMe because NVMe uses the major number 259. Signed-off-by: Mikulas Patocka <mpatocka@xxxxxxxxxx> --- arch/x86/include/asm/compat.h | 6 ++---- fs/stat.c | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) Index: linux-5.17.2/arch/x86/include/asm/compat.h =================================================================== --- linux-5.17.2.orig/arch/x86/include/asm/compat.h 2022-01-21 10:29:12.000000000 +0100 +++ linux-5.17.2/arch/x86/include/asm/compat.h 2022-04-12 11:27:14.000000000 +0200 @@ -28,15 +28,13 @@ typedef u16 compat_ipc_pid_t; typedef __kernel_fsid_t compat_fsid_t; struct compat_stat { - compat_dev_t st_dev; - u16 __pad1; + u32 st_dev; compat_ino_t st_ino; compat_mode_t st_mode; compat_nlink_t st_nlink; __compat_uid_t st_uid; __compat_gid_t st_gid; - compat_dev_t st_rdev; - u16 __pad2; + u32 st_rdev; u32 st_size; u32 st_blksize; u32 st_blocks; Index: linux-5.17.2/fs/stat.c =================================================================== --- linux-5.17.2.orig/fs/stat.c 2022-04-12 10:39:46.000000000 +0200 +++ linux-5.17.2/fs/stat.c 2022-04-12 10:58:28.000000000 +0200 @@ -334,9 +334,6 @@ SYSCALL_DEFINE2(fstat, unsigned int, fd, # define choose_32_64(a,b) b #endif -#define valid_dev(x) choose_32_64(old_valid_dev(x),true) -#define encode_dev(x) choose_32_64(old_encode_dev,new_encode_dev)(x) - #ifndef INIT_STRUCT_STAT_PADDING # define INIT_STRUCT_STAT_PADDING(st) memset(&st, 0, sizeof(st)) #endif @@ -345,7 +342,9 @@ static int cp_new_stat(struct kstat *sta { struct stat tmp; - if (!valid_dev(stat->dev) || !valid_dev(stat->rdev)) + if (sizeof(tmp.st_dev) < 4 && !old_valid_dev(stat->dev)) + return -EOVERFLOW; + if (sizeof(tmp.st_rdev) < 4 && !old_valid_dev(stat->rdev)) return -EOVERFLOW; #if BITS_PER_LONG == 32 if (stat->size > MAX_NON_LFS) @@ -353,7 +352,7 @@ static int cp_new_stat(struct kstat *sta #endif INIT_STRUCT_STAT_PADDING(tmp); - tmp.st_dev = encode_dev(stat->dev); + tmp.st_dev = new_encode_dev(stat->dev); tmp.st_ino = stat->ino; if (sizeof(tmp.st_ino) < sizeof(stat->ino) && tmp.st_ino != stat->ino) return -EOVERFLOW; @@ -363,7 +362,7 @@ static int cp_new_stat(struct kstat *sta return -EOVERFLOW; SET_UID(tmp.st_uid, from_kuid_munged(current_user_ns(), stat->uid)); SET_GID(tmp.st_gid, from_kgid_munged(current_user_ns(), stat->gid)); - tmp.st_rdev = encode_dev(stat->rdev); + tmp.st_rdev = new_encode_dev(stat->rdev); tmp.st_size = stat->size; tmp.st_atime = stat->atime.tv_sec; tmp.st_mtime = stat->mtime.tv_sec; @@ -644,11 +643,13 @@ static int cp_compat_stat(struct kstat * { struct compat_stat tmp; - if (!old_valid_dev(stat->dev) || !old_valid_dev(stat->rdev)) + if (sizeof(tmp.st_dev) < 4 && !old_valid_dev(stat->dev)) + return -EOVERFLOW; + if (sizeof(tmp.st_rdev) < 4 && !old_valid_dev(stat->rdev)) return -EOVERFLOW; memset(&tmp, 0, sizeof(tmp)); - tmp.st_dev = old_encode_dev(stat->dev); + tmp.st_dev = new_encode_dev(stat->dev); tmp.st_ino = stat->ino; if (sizeof(tmp.st_ino) < sizeof(stat->ino) && tmp.st_ino != stat->ino) return -EOVERFLOW; @@ -658,7 +659,7 @@ static int cp_compat_stat(struct kstat * return -EOVERFLOW; SET_UID(tmp.st_uid, from_kuid_munged(current_user_ns(), stat->uid)); SET_GID(tmp.st_gid, from_kgid_munged(current_user_ns(), stat->gid)); - tmp.st_rdev = old_encode_dev(stat->rdev); + tmp.st_rdev = new_encode_dev(stat->rdev); if ((u64) stat->size > MAX_NON_LFS) return -EOVERFLOW; tmp.st_size = stat->size;