linux-user: retry transient private futex faults - #461
Conversation
| * remains readable across that host-only move, unlike an invalid guest | ||
| * futex address. Check it under mmap_lock to retain genuine EFAULTs. | ||
| */ | ||
| if (qemu_host_page_size <= TARGET_PAGE_SIZE || ret != -TARGET_EFAULT || |
There was a problem hiding this comment.
由于我本地并没有 4k 内核的环境,我稍后重新提交一版再帮忙审查一下。
There was a problem hiding this comment.
新的commit中加了对pressure-vessel的支持,这里应该暂时不需要了
There was a problem hiding this comment.
直接STEAMOS=1 steam可以启动吗
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
有复现环境吗?我目前的运行环境:
系统:aosc 13.3.1
runtime:runtime
steam安装包:官网下载
运行命令:STEAMOS=1 LATX_KZT=0 usr/bin/steam
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
有复现环境吗?我目前的运行环境: 系统:aosc 13.3.1 runtime:runtime steam安装包:官网下载 运行命令:
STEAMOS=1 LATX_KZT=0 usr/bin/steam
Debian 13,没有使用 runtime 而是通过 dpkg --add-architecture 的方式添加 i386 和 amd64 架构安装所需的依赖。
cat > /etc/apt/sources.list.d/trixie.sources << "EOF"
Types: deb
# http://snapshot.debian.org/archive/debian/20260824T000000Z
URIs: http://deb.debian.org/debian
Suites: trixie trixie-updates
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.pgp
Types: deb
# http://snapshot.debian.org/archive/debian-security/20260824T000000Z
URIs: http://deb.debian.org/debian-security
Suites: trixie-security
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.pgp
EOF
dpkg --add-architecture i386
dpkg --add-architecture amd64
# 修改 steam.deb DEBIAN/control 重新打包安装。There was a problem hiding this comment.
可以先合fe9c44cfc7f51e8002454213bc36e631e3b0a347这个commit,后面两个commit先撤掉吧
310b34e to
36f844c
Compare
Signed-off-by: 吴小白 <296015668@qq.com>
36f844c to
6c124ec
Compare
665800a to
fe9c44c
Compare
|
感谢修复!我这边测到一个边界问题。 先 mmap 一个文件,再用 问题应该就在 复现程序放下面了,不需要 32 位 libc。 repro.c/* Freestanding i386 regression: no guest libc or multilib headers needed. */
typedef unsigned int u32;
extern int sc(int nr, u32 a, u32 b, u32 c, u32 d, u32 e, u32 f);
__asm__(
".text\n.globl sc\nsc:\n"
"push %ebp; push %edi; push %esi; push %ebx;\n"
"mov 20(%esp), %eax; mov 24(%esp), %ebx;\n"
"mov 28(%esp), %ecx; mov 32(%esp), %edx;\n"
"mov 36(%esp), %esi; mov 40(%esp), %edi;\n"
"mov 44(%esp), %ebp; int $0x80;\n"
"pop %ebx; pop %esi; pop %edi; pop %ebp; ret;\n");
#define PTR(p) ((u32)(p))
static void say(const char *s)
{
u32 n = 0;
while (s[n]) { n++; }
sc(4, 1, PTR(s), n, 0, 0, 0);
}
static void number(int n)
{
char buf[16];
unsigned int v = n < 0 ? -n : n;
unsigned int i = sizeof(buf);
do { buf[--i] = '0' + v % 10; v /= 10; } while (v);
if (n < 0) { buf[--i] = '-'; }
sc(4, 1, PTR(buf + i), sizeof(buf) - i, 0, 0, 0);
}
static void die(const char *s, int ret)
{
say(s); number(ret); say("\n");
sc(1, 2, 0, 0, 0, 0, 0);
__builtin_unreachable();
}
void _start(void)
{
const char path[] = "futex-truncate.data";
const u32 len = 65536;
const unsigned long long zero[2] = {0, 0};
const int syscalls[2] = {240, 422}; /* futex, futex_time64 */
const int ops[2] = {128, 137}; /* PRIVATE WAIT, PRIVATE WAIT_BITSET */
int fd = sc(5, PTR(path), 0xc2, 0600, 0, 0, 0);
if (fd < 0) { die("open: ", fd); }
int ret = sc(10, PTR(path), 0, 0, 0, 0, 0);
if (ret) { die("unlink: ", ret); }
ret = sc(93, fd, len, 0, 0, 0, 0);
if (ret) { die("ftruncate: ", ret); }
u32 p = sc(192, 0, len, 3, 2, fd, 0); /* mmap2, RW, MAP_PRIVATE */
if (p >= (u32)-4095) { die("mmap2: ", p); }
ret = sc(93, fd, 0, 0, 0, 0, 0);
if (ret) { die("truncate zero: ", ret); }
int failed = 0;
/* No userspace access to p: only the kernel reads the futex word. */
for (u32 i = 0; i < 2; i++) {
for (u32 j = 0; j < 2; j++) {
ret = sc(syscalls[i], p, ops[j], 0, PTR(zero), 0, 0xffffffff);
say("syscall="); number(syscalls[i]);
say(" op="); number(ops[j]); say(" result="); number(ret);
say(" expected=-14 "); say(ret == -14 ? "PASS\n" : "FAIL\n");
failed |= ret != -14;
}
}
sc(91, p, len, 0, 0, 0, 0);
sc(6, fd, 0, 0, 0, 0, 0);
sc(1, failed, 0, 0, 0, 0, 0);
__builtin_unreachable();
}在 x86 Linux 上编译: gcc -m32 -nostdlib -static -fno-pie -no-pie -fno-stack-protector \
-fno-builtin -O2 -Wall -Wextra -Werror repro.c -o repro
./repro再把 timeout 15s /path/to/latx-i386 ./repro原生四项都返回 我的想法是保留现在的锁和页检查,在锁内再确认一下这 4 字节能不能读,读到了才改成 参考 diff--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -11061,25 +11061,34 @@
* The i386 lock-instruction interpreter transiently moves a complete
* host page. A private futex in that page can observe the short gap as
* EFAULT, including when host and target pages are both 4 KiB. The guest
- * mapping metadata persists across that host-only move. Check it under
- * mmap_lock so unmapped or unreadable guest futex addresses retain
- * EFAULT. For a mapped private WAIT, return EAGAIN so userspace reloads
- * the futex word and retries.
+ * mapping metadata persists across that host-only move. Taking
+ * mmap_lock waits for relocation to finish. Also probe the actual
+ * word: readable guest metadata can survive truncation of a mapped
+ * file. Only a successful read permits returning EAGAIN.
*/
if (ret != -TARGET_EFAULT || !(op & FUTEX_PRIVATE_FLAG)) {
return ret;
}
base_op = op & FUTEX_CMD_MASK;
- if (!h2g_valid(uaddr)) {
+ if ((base_op != FUTEX_WAIT && base_op != FUTEX_WAIT_BITSET) ||
+ !h2g_valid(uaddr)) {
return ret;
}
mmap_lock();
mapped = (page_get_flags(h2g(uaddr)) & (PAGE_VALID | PAGE_READ)) ==
(PAGE_VALID | PAGE_READ);
+ if (mapped) {
+ uint32_t word;
+ struct iovec local = { .iov_base = &word, .iov_len = sizeof(word) };
+ struct iovec remote = { .iov_base = uaddr, .iov_len = sizeof(word) };
+
+ /* Let the kernel report inaccessible backing memory as an error. */
+ if (process_vm_readv(getpid(), &local, 1, &remote, 1, 0) ==
+ sizeof(word)) {
+ ret = -TARGET_EAGAIN;
+ }
+ }
mmap_unlock();
- if ((base_op == FUTEX_WAIT || base_op == FUTEX_WAIT_BITSET) && mapped) {
- return -TARGET_EAGAIN;
- }
#endif
return ret;
}不过这个改法我还没编译实测, |

Handle transient
EFAULTfrom i386 privateFUTEX_WAIT*calls whileLATX temporarily relocates a host page. Return
EAGAINonly when QEMU'sguest page metadata still marks the futex address valid and readable, so
userspace reloads the futex word and retries while unmapped or unreadable
guest addresses retain
EFAULT.Fixes #87