Truncate directory cookies to 31 bits to avoid problems exposed in Linux
binaries which cast the returned values to 64-bits and fail due to sign expansion. More details are provided in the big comment in tmpfs.h that describes how the new tmpfs_dircookie works. This is a rather ugly hack that shall be fixed with a cleaner solution, but this resolves the problem in an effective way. Fixes kern PR/32034.
This commit is contained in:
parent
a6058d8988
commit
b2603104c2
@ -1,4 +1,4 @@
|
||||
/* $NetBSD: tmpfs.h,v 1.21 2006/07/23 22:06:10 ad Exp $ */
|
||||
/* $NetBSD: tmpfs.h,v 1.22 2006/11/05 16:59:18 jmmv Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005, 2006 The NetBSD Foundation, Inc.
|
||||
@ -87,10 +87,71 @@ struct tmpfs_dirent {
|
||||
* importantly, to remove redundancy. */
|
||||
TAILQ_HEAD(tmpfs_dir, tmpfs_dirent);
|
||||
|
||||
#define TMPFS_DIRCOOKIE(dirent) ((off_t)(uintptr_t)(dirent))
|
||||
/* Each entry in a directory has a cookie that identifies it. Cookies
|
||||
* supersede offsets within directories because, given how tmpfs stores
|
||||
* directories in memory, there is no such thing as an offset. (Emulating
|
||||
* a real offset could be very difficult.)
|
||||
*
|
||||
* The '.', '..' and the end of directory markers have fixed cookies which
|
||||
* cannot collide with the cookies generated by other entries. The cookies
|
||||
* fot the other entries are generated based on the memory address on which
|
||||
* stores their information is stored.
|
||||
*
|
||||
* Ideally, using the entry's memory pointer as the cookie would be enough
|
||||
* to represent it and it wouldn't cause collisions in any system.
|
||||
* Unfortunately, this results in "offsets" with very large values which
|
||||
* later raise problems in the Linux compatibility layer (and maybe in other
|
||||
* places) as described in PR kern/32034. Hence we need to workaround this
|
||||
* with a rather ugly hack.
|
||||
*
|
||||
* Linux 32-bit binaries, unless built with _FILE_OFFSET_BITS=64, have off_t
|
||||
* set to 'long', which is a 32-bit *signed* long integer. Regardless of
|
||||
* the macro value, GLIBC (2.3 at least) always uses the getdents64
|
||||
* system call (when calling readdir) which internally returns off64_t
|
||||
* offsets. In order to make 32-bit binaries work, *GLIBC* converts the
|
||||
* 64-bit values returned by the kernel to 32-bit ones and aborts with
|
||||
* EOVERFLOW if the conversion results in values that won't fit in 32-bit
|
||||
* integers (which it assumes is because the directory is extremely large).
|
||||
* This wouldn't cause problems if we were dealing with unsigned integers,
|
||||
* but as we have signed integers, this check fails due to sign expansion.
|
||||
*
|
||||
* For example, consider that the kernel returns the 0xc1234567 cookie to
|
||||
* userspace in a off64_t integer. Later on, GLIBC casts this value to
|
||||
* off_t (remember, signed) with code similar to:
|
||||
* system call returns the offset in kernel_value;
|
||||
* off_t casted_value = kernel_value;
|
||||
* if (sizeof(off_t) != sizeof(off64_t) &&
|
||||
* kernel_value != casted_value)
|
||||
* error!
|
||||
* In this case, casted_value still has 0xc1234567, but when it is compared
|
||||
* for equality against kernel_value, it is promoted to a 64-bit integer and
|
||||
* becomes 0xffffffffc1234567, which is different than 0x00000000c1234567.
|
||||
* Then, GLIBC assumes this is because the directory is very large.
|
||||
*
|
||||
* Given that all the above happens in user-space, we have no control over
|
||||
* it; therefore we must workaround the issue here. We do this by
|
||||
* truncating the pointer value to a 32-bit integer and hope that there
|
||||
* won't be collisions. In fact, this will not cause any problems in
|
||||
* 32-bit platforms but some might arise in 64-bit machines (I'm not sure
|
||||
* if they can happen at all in practice).
|
||||
*
|
||||
* XXX A nicer solution shall be attempted. */
|
||||
#define TMPFS_DIRCOOKIE_DOT 0
|
||||
#define TMPFS_DIRCOOKIE_DOTDOT 1
|
||||
#define TMPFS_DIRCOOKIE_EOF 2
|
||||
static __inline
|
||||
off_t
|
||||
tmpfs_dircookie(struct tmpfs_dirent *de)
|
||||
{
|
||||
off_t cookie;
|
||||
|
||||
cookie = ((off_t)(uintptr_t)de >> 1) & 0x7FFFFFFF;
|
||||
KASSERT(cookie != TMPFS_DIRCOOKIE_DOT);
|
||||
KASSERT(cookie != TMPFS_DIRCOOKIE_DOTDOT);
|
||||
KASSERT(cookie != TMPFS_DIRCOOKIE_EOF);
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
@ -364,7 +425,7 @@ int tmpfs_truncate(struct vnode *, off_t);
|
||||
KASSERT((node)->tn_type == VDIR); \
|
||||
KASSERT((node)->tn_size % sizeof(struct tmpfs_dirent) == 0); \
|
||||
KASSERT((node)->tn_spec.tn_dir.tn_readdir_lastp == NULL || \
|
||||
TMPFS_DIRCOOKIE((node)->tn_spec.tn_dir.tn_readdir_lastp) == \
|
||||
tmpfs_dircookie((node)->tn_spec.tn_dir.tn_readdir_lastp) == \
|
||||
(node)->tn_spec.tn_dir.tn_readdir_lastn);
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* $NetBSD: tmpfs_subr.c,v 1.26 2006/10/30 15:11:01 jmmv Exp $ */
|
||||
/* $NetBSD: tmpfs_subr.c,v 1.27 2006/11/05 16:59:18 jmmv Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005, 2006 The NetBSD Foundation, Inc.
|
||||
@ -42,7 +42,7 @@
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__KERNEL_RCSID(0, "$NetBSD: tmpfs_subr.c,v 1.26 2006/10/30 15:11:01 jmmv Exp $");
|
||||
__KERNEL_RCSID(0, "$NetBSD: tmpfs_subr.c,v 1.27 2006/11/05 16:59:18 jmmv Exp $");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/dirent.h>
|
||||
@ -695,7 +695,7 @@ tmpfs_dir_getdotdotdent(struct tmpfs_node *node, struct uio *uio)
|
||||
if (de == NULL)
|
||||
uio->uio_offset = TMPFS_DIRCOOKIE_EOF;
|
||||
else
|
||||
uio->uio_offset = TMPFS_DIRCOOKIE(de);
|
||||
uio->uio_offset = tmpfs_dircookie(de);
|
||||
}
|
||||
}
|
||||
|
||||
@ -720,7 +720,7 @@ tmpfs_dir_lookupbycookie(struct tmpfs_node *node, off_t cookie)
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(de, &node->tn_spec.tn_dir.tn_dir, td_entries) {
|
||||
if (TMPFS_DIRCOOKIE(de) == cookie) {
|
||||
if (tmpfs_dircookie(de) == cookie) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -829,7 +829,7 @@ tmpfs_dir_getdents(struct tmpfs_node *node, struct uio *uio, off_t *cntp)
|
||||
node->tn_spec.tn_dir.tn_readdir_lastp = NULL;
|
||||
} else {
|
||||
node->tn_spec.tn_dir.tn_readdir_lastn = uio->uio_offset =
|
||||
TMPFS_DIRCOOKIE(de);
|
||||
tmpfs_dircookie(de);
|
||||
node->tn_spec.tn_dir.tn_readdir_lastp = de;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* $NetBSD: tmpfs_vnops.c,v 1.28 2006/11/02 15:35:25 jmmv Exp $ */
|
||||
/* $NetBSD: tmpfs_vnops.c,v 1.29 2006/11/05 16:59:18 jmmv Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005, 2006 The NetBSD Foundation, Inc.
|
||||
@ -42,7 +42,7 @@
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__KERNEL_RCSID(0, "$NetBSD: tmpfs_vnops.c,v 1.28 2006/11/02 15:35:25 jmmv Exp $");
|
||||
__KERNEL_RCSID(0, "$NetBSD: tmpfs_vnops.c,v 1.29 2006/11/05 16:59:18 jmmv Exp $");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/dirent.h>
|
||||
@ -1179,7 +1179,7 @@ outok:
|
||||
if (de == NULL) {
|
||||
off = TMPFS_DIRCOOKIE_EOF;
|
||||
} else {
|
||||
off = TMPFS_DIRCOOKIE(de);
|
||||
off = tmpfs_dircookie(de);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user