um: hostfs: avoid issues on inode number reuse by host

[ Upstream commit 0bc754d1e3 ]

Some file systems (e.g. ext4) may reuse inode numbers once the inode is
not in use anymore. Usually hostfs will keep an FD open for each inode,
but this is not always the case. In the case of sockets, this cannot
even be done properly.

As such, the following sequence of events was possible:
 * application creates and deletes a socket
 * hostfs creates/deletes the socket on the host
 * inode is still in the hostfs cache
 * hostfs creates a new file
 * ext4 on the outside reuses the inode number
 * hostfs finds the socket inode for the newly created file
 * application receives -ENXIO when opening the file

As mentioned, this can only happen if the deleted file is a special file
that is never opened on the host (i.e. no .open fop).

As such, to prevent issues, it is sufficient to check that the inode
has the expected type. That said, also add a check for the inode birth
time, just to be on the safe side.

Fixes: 74ce793bcb ("hostfs: Fix ephemeral inodes")
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Reviewed-by: Mickaël Salaün <mic@digikod.net>
Tested-by: Mickaël Salaün <mic@digikod.net>
Link: https://patch.msgid.link/20250214092822.1241575-1-benjamin@sipsolutions.net
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
(cherry picked from commit 4ee8160c47e0c0413300df2f7d1423ea275d0477)
This commit is contained in:
Benjamin Berg 2025-02-14 10:28:22 +01:00 committed by Wentao Guan
parent e9c999f6c9
commit 077d74b476
3 changed files with 41 additions and 27 deletions

View File

@ -60,7 +60,7 @@ struct hostfs_stat {
unsigned int uid;
unsigned int gid;
unsigned long long size;
struct hostfs_timespec atime, mtime, ctime;
struct hostfs_timespec atime, mtime, ctime, btime;
unsigned int blksize;
unsigned long long blocks;
struct {

View File

@ -27,6 +27,7 @@ struct hostfs_inode_info {
struct inode vfs_inode;
struct mutex open_mutex;
dev_t dev;
struct hostfs_timespec btime;
};
static inline struct hostfs_inode_info *HOSTFS_I(struct inode *inode)
@ -557,6 +558,7 @@ static int hostfs_inode_set(struct inode *ino, void *data)
}
HOSTFS_I(ino)->dev = dev;
HOSTFS_I(ino)->btime = st->btime;
ino->i_ino = st->ino;
ino->i_mode = st->mode;
return hostfs_inode_update(ino, st);
@ -567,7 +569,10 @@ static int hostfs_inode_test(struct inode *inode, void *data)
const struct hostfs_stat *st = data;
dev_t dev = MKDEV(st->dev.maj, st->dev.min);
return inode->i_ino == st->ino && HOSTFS_I(inode)->dev == dev;
return inode->i_ino == st->ino && HOSTFS_I(inode)->dev == dev &&
(inode->i_mode & S_IFMT) == (st->mode & S_IFMT) &&
HOSTFS_I(inode)->btime.tv_sec == st->btime.tv_sec &&
HOSTFS_I(inode)->btime.tv_nsec == st->btime.tv_nsec;
}
static struct inode *hostfs_iget(struct super_block *sb, char *name)

View File

@ -18,39 +18,48 @@
#include "hostfs.h"
#include <utime.h>
static void stat64_to_hostfs(const struct stat64 *buf, struct hostfs_stat *p)
static void statx_to_hostfs(const struct statx *buf, struct hostfs_stat *p)
{
p->ino = buf->st_ino;
p->mode = buf->st_mode;
p->nlink = buf->st_nlink;
p->uid = buf->st_uid;
p->gid = buf->st_gid;
p->size = buf->st_size;
p->atime.tv_sec = buf->st_atime;
p->atime.tv_nsec = 0;
p->ctime.tv_sec = buf->st_ctime;
p->ctime.tv_nsec = 0;
p->mtime.tv_sec = buf->st_mtime;
p->mtime.tv_nsec = 0;
p->blksize = buf->st_blksize;
p->blocks = buf->st_blocks;
p->rdev.maj = os_major(buf->st_rdev);
p->rdev.min = os_minor(buf->st_rdev);
p->dev.maj = os_major(buf->st_dev);
p->dev.min = os_minor(buf->st_dev);
p->ino = buf->stx_ino;
p->mode = buf->stx_mode;
p->nlink = buf->stx_nlink;
p->uid = buf->stx_uid;
p->gid = buf->stx_gid;
p->size = buf->stx_size;
p->atime.tv_sec = buf->stx_atime.tv_sec;
p->atime.tv_nsec = buf->stx_atime.tv_nsec;
p->ctime.tv_sec = buf->stx_ctime.tv_sec;
p->ctime.tv_nsec = buf->stx_ctime.tv_nsec;
p->mtime.tv_sec = buf->stx_mtime.tv_sec;
p->mtime.tv_nsec = buf->stx_mtime.tv_nsec;
if (buf->stx_mask & STATX_BTIME) {
p->btime.tv_sec = buf->stx_btime.tv_sec;
p->btime.tv_nsec = buf->stx_btime.tv_nsec;
} else {
memset(&p->btime, 0, sizeof(p->btime));
}
p->blksize = buf->stx_blksize;
p->blocks = buf->stx_blocks;
p->rdev.maj = buf->stx_rdev_major;
p->rdev.min = buf->stx_rdev_minor;
p->dev.maj = buf->stx_dev_major;
p->dev.min = buf->stx_dev_minor;
}
int stat_file(const char *path, struct hostfs_stat *p, int fd)
{
struct stat64 buf;
struct statx buf;
int flags = AT_SYMLINK_NOFOLLOW;
if (fd >= 0) {
if (fstat64(fd, &buf) < 0)
return -errno;
} else if (lstat64(path, &buf) < 0) {
return -errno;
flags |= AT_EMPTY_PATH;
path = "";
}
stat64_to_hostfs(&buf, p);
if ((statx(fd, path, flags, STATX_BASIC_STATS | STATX_BTIME, &buf)) < 0)
return -errno;
statx_to_hostfs(&buf, p);
return 0;
}