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:
jmmv 2006-11-05 16:59:18 +00:00
parent a6058d8988
commit b2603104c2
3 changed files with 72 additions and 11 deletions

View File

@ -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);
/* --------------------------------------------------------------------- */

View File

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

View File

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