TSI: impersonated INET sockets always get SO_REUSEPORT, so a second VM's `listen()` silently shares the host port
メンテナーはふだん 2 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 42/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 領域
- backend, networking
調査の方向性
Start by reading the INET socket setup at src/devices/src/virtio/vsock/tsi_stream/unix.rs:64 and compare it with the earlier implementation at src/devices/src/virtio/vsock/tsi_stream.rs:102; the issue identifies unconditional SO_REUSEPORT as the suspected cause. Check how TSI can observe the guest socket option and consider the UNIX-socket precedent in #251. Done means guest-requested reuseport behavior remains supported while two VMs binding the same port no longer silently share connections; verify with the supplied two-VM reproducer and controls.
索引モデルが issue の本文から書いたものです。
説明
Summary
With KRUN_TSI_FLAGS_HIJACK_INET, the VMM creates a host-side socket for each guest INET socket. It sets SO_REUSEPORT on that socket unconditionally, whether or not the guest asked for it:
main(at24d714b5):src/devices/src/virtio/vsock/tsi_stream/unix.rs:64- v1.19.4:
src/devices/src/virtio/vsock/tsi_stream.rs:102
Take two TSI VMs run by the same user in the same network namespace, where both guests listen() on the same port. The second listen() does not fail with EADDRINUSE. Instead, both impersonated listeners join one reuseport group, and the host kernel spreads incoming connections across them.
The guest's connect to its own 127.0.0.1:<port> is also performed on the host, so it is answered by the other VM's guest:
- On Linux, the kernel spreads connections across the group.
- On macOS, the most recently bound listener takes every connection. A second VM's
listen()therefore silently takes the port over, and the first VM can no longer reach its own loopback service at all.
Reproduction
The setup:
- two VMs, each in its own process;
krun_vsock_device_new(3, HIJACK_INET|HIJACK_UNIX);- each guest listens on its own
127.0.0.1:6379and answers with its own role letter; - each guest then connects to
127.0.0.1:6379eight times.
=== host listeners on 6379 ===
LISTEN 127.0.0.1:6379 users:(("tsi_repro_main",pid=433519,fd=56))
LISTEN 127.0.0.1:6379 users:(("tsi_repro_main",pid=433518,fd=56))
=== VM A === === VM B ===
PROBE A: attempt 0 answered by B PROBE B: attempt 0 answered by B
PROBE A: attempt 1 answered by B PROBE B: attempt 1 answered by B
PROBE A: attempt 2 answered by B PROBE B: attempt 2 answered by B
PROBE A: attempt 3 answered by B PROBE B: attempt 3 answered by A
PROBE A: attempt 4 answered by A PROBE B: attempt 4 answered by B
PROBE A: attempt 5 answered by B PROBE B: attempt 5 answered by A
PROBE A: attempt 6 answered by A PROBE B: attempt 6 answered by B
PROBE A: attempt 7 answered by B PROBE B: attempt 7 answered by A
VM A was answered by VM B in 6 of its 8 attempts. Both listeners are visible on the host while the VMs run.
Controls:
- The same two VMs with
tsi_features = 0: no host listener appears, and each guest answers itself 8/8. - A single VM with the hijack on: answered by itself 8/8.
So HIJACK_INET is exactly the switch, and a second VM in the same namespace is what triggers it.
macOS (libkrun 1.19.4)
The same experiment on macOS reproduces it deterministically. It uses the 1.19.x harness in macos/, which runs on a released libkrun, and HIJACK_INET only, since HIJACK_UNIX is refused on macOS.
Where each VM's 8 connections to its own 127.0.0.1:6379 were answered:
| Start order | VM A's 8 connects answered by | VM B's 8 connects answered by |
|---|---|---|
| A, then B (0.5 s apart) | B 8/8 | B 8/8 |
| B, then A (0.5 s apart) | A 8/8 | A 8/8 |
| A, then B again | B 8/8 | B 8/8 |
-
Both listens succeeded. Both impersonated listeners were visible on the host while the VMs ran:
tsi_repro 89171 hansjorg 96u IPv4 ... TCP 127.0.0.1:6379 (LISTEN) tsi_repro 89172 hansjorg 96u IPv4 ... TCP 127.0.0.1:6379 (LISTEN) -
Controls:
- the same pair with
tsi_features = 0: each VM answered itself 8/8, and no host listener appeared; - a single VM with the hijack on: it answered itself 8/8.
- the same pair with
-
No connect hung. The guest-side probe carries a 3 s connect timeout in case #344 applied here. The loopback path through an impersonated listener worked; it just led to the wrong VM.
Environment: macOS 15 (Darwin 24.6) on Apple Silicon, Hypervisor.framework, Homebrew libkrun 1.19.4 and libkrunfw 5.5.0.
Files
Every file below is inlined at the end of this issue, under Sources.
probe.rs(the guest program).- Build it with
rustc --target x86_64-unknown-linux-musl -O -o probe probe.rs. - Place it at
/probein a rootfs directory that contains only it plus emptydev proc sys tmp etc.
- Build it with
tsi_loopback_repro.c(the host harness, written against the currentmainAPI).- Build libkrun with
make FFI=1 BLK=1 NET=1. - Then build the harness with
gcc -o tsi_repro_main tsi_loopback_repro.c -I<libkrun>/include -L<libkrun>/target/release -lkrun -lkrun_init. - Usage is
./tsi_repro_main <rootfs> <role-letter> [tsi_flags]. - Run two instances at once with roles
AandB. For the control, pass0astsi_flags. - While they run,
ss -tlnp 'sport = :6379'shows both listeners.
- Build libkrun with
macos/(the same experiment against the 1.19.x API, so it runs on a released libkrun):tsi_loopback_repro_legacy.cis the harness, usingkrun_create_ctx,krun_disable_implicit_vsock+krun_add_vsock(ctx, tsi_features), andkrun_start_enter.probe/is the guest probe with a connect timeout, built withcargo zigbuild --target aarch64-unknown-linux-musl.run.shbuilds both, signs the harness with the hypervisor entitlement, and runs the pair, the control and a single VM.
Environment:
- libkrun
mainat24d714b5(2.0.0-dev). The listener setup is unchanged from v1.19.4. - libkrunfw 5.5.0.
- Linux x86_64 with KVM.
Who this affects
Only TSI VMs run by the same user in one network namespace.
- Runtimes that give each VM its own network namespace: separate VMs stay apart. crun/podman's krun handler does this for separate containers.
- An embedder that starts several TSI VMs from one namespace: this embedder is affected.
- macOS: there are no network namespaces, so any two TSI VMs run by the same user collide. There, the second one takes the port from the first entirely (shown above).
We hit it in a test harness. Our production VMs have a network device, so TSI is off there, and nothing on our side depends on a change here. We're reporting it because it is easy to hit and very hard to diagnose: a guest's 127.0.0.1 looks private and isn't.
The question
Is the unconditional SO_REUSEPORT on INET sockets deliberate? #251 removed it from impersonated UNIX sockets, where it was invalid, and INET kept it.
- If it exists so a guest can listen again on a port it just closed:
SO_REUSEADDRcovers that on Linux (binding pastTIME_WAIT). - If it exists for guest programs that use
SO_REUSEPORTthemselves, for example several workers on one port: setting it only when the guest's socket asked for it keeps that working, if TSI can observe the guest's option. An unintended collision between VMs would then fail withEADDRINUSE, which the guest can see and handle, instead of silently sharing connections with another VM.
Related:
- #818: TSI
listen()binds in the VMM's own namespace. Same mechanism, different symptom. - #510: guest-internal AF_UNIX traffic is intercepted by TSI.
- #251: the precedent for UNIX sockets.
Sources
probe.rs - guest program for the Linux runs
// Guest-side probe for the libkrun TSI loopback-sharing question.
//
// Each VM runs one of these with a distinct role letter. It listens on
// the guest's own 127.0.0.1:6379, then connects to 127.0.0.1:6379 and
// prints who answered. In an isolated guest the answer is always its
// own role. Any other letter means the connection left this VM.
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::thread;
use std::time::Duration;
fn main() {
let role = std::env::args().nth(1).unwrap_or_else(|| "X".to_string());
let listener = match TcpListener::bind("127.0.0.1:6379") {
Ok(l) => l,
Err(e) => {
println!("PROBE {role}: bind 127.0.0.1:6379 failed: {e}");
return;
}
};
println!("PROBE {role}: listening on 127.0.0.1:6379");
let served = role.clone();
thread::spawn(move || {
for stream in listener.incoming().flatten() {
let mut stream = stream;
let mut buf = [0u8; 64];
let _ = stream.read(&mut buf);
let _ = stream.write_all(format!("SERVED-BY-{served}\n").as_bytes());
}
});
thread::sleep(Duration::from_secs(3));
for attempt in 0..8 {
match TcpStream::connect("127.0.0.1:6379") {
Ok(mut s) => {
let _ = s.write_all(b"ping\n");
let _ = s.set_read_timeout(Some(Duration::from_secs(2)));
let mut out = String::new();
let _ = s.read_to_string(&mut out);
println!("PROBE {role}: attempt {attempt} answered by {}", out.trim());
}
Err(e) => println!("PROBE {role}: attempt {attempt} connect failed: {e}"),
}
thread::sleep(Duration::from_millis(400));
}
println!("PROBE {role}: done");
}
tsi_loopback_repro.c - host harness, libkrun `main` API (Linux runs)
/*
* Minimal reproducer: does a guest's loopback listen/connect under
* libkrun TSI reach the *host's* loopback, so that two concurrently
* running VMs share one host port?
*
* Usage: tsi_loopback_repro <rootfs-dir> <role-letter>
*
* Run two of these at once with different role letters. Each guest
* listens on its own 127.0.0.1:6379 and then connects to it. If a
* guest is ever answered by the other guest's role letter, guest
* loopback is not guest-private.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <libkrun.h>
#include <libkrun_init.h>
static bool push_to_stderr(void *userdata, KrunStr s)
{
(void)userdata;
fwrite(s.data, 1, s.len, stderr);
return true;
}
static KrunVtableHandle init_stderr_writer = KRUN_VTABLE_HANDLE(
KRUN_INIT_PUSH_STR_TYPE_TAG,
((KrunInitPushStrVtable){ .drop = NULL, .push = push_to_stderr }),
NULL);
static KrunVtableHandle krun_stderr_writer = KRUN_VTABLE_HANDLE(
KRUN_PUSH_STR_TYPE_TAG,
((KrunPushStrVtable){ .drop = NULL, .push = push_to_stderr }),
NULL);
#define CHECK(call) \
err = NULL; \
call; \
if (err) { \
fprintf(stderr, "%s failed: ", #call); \
krun_error_message(err, &krun_stderr_writer); \
fputc('\n', stderr); \
krun_error_destroy(err); \
return 1; \
}
int main(int argc, char **argv)
{
KrunError err;
if (argc < 3) {
fprintf(stderr, "usage: %s <rootfs-dir> <role-letter>\n", argv[0]);
return 2;
}
const char *rootfs = argv[1];
const char *role = argv[2];
CHECK(krun_init_log(-1, KRUN_LOG_LEVEL_WARN, KRUN_LOG_STYLE_AUTO, 0, &err));
KrunFsOverlay overlay = krun_fs_overlay_new();
KrunPayload payload = krun_payload_load_krunfw(&err);
if (!payload) { fprintf(stderr, "loading krunfw payload failed\n"); return 1; }
{
KrunInitError ierr = NULL;
KrunInitBuilder b = krun_init_config_builder();
krun_init_builder_arg(&b, KRUN_STR("/probe"));
krun_init_builder_arg(&b, KRUN_STR((char *)role));
krun_init_builder_env_var(&b, KRUN_STR("PATH=/"));
krun_init_builder_workdir(&b, KRUN_STR("/"));
KrunInitConfig cfg = krun_init_builder_build(&b);
krun_init_config_apply(cfg, overlay, payload, &ierr);
if (ierr) {
fprintf(stderr, "applying init config failed: ");
krun_init_error_message(ierr, &init_stderr_writer);
fputc('\n', stderr);
krun_init_error_destroy(ierr);
return 1;
}
}
KrunMmioDeviceManager devices = krun_mmio_device_manager_new();
{
KrunConsoleBuilder cb = krun_console_device_builder();
CHECK(krun_console_builder_add_default_console(cb, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, &err));
KrunConsoleDevice console = krun_console_builder_build(cb, &err);
krun_mmio_device_manager_add(devices, console);
}
{
KrunFsDevice fs;
CHECK(fs = krun_fs_device_new(KRUN_STR("/dev/root"), KRUN_STR((char *)rootfs), &err));
krun_fs_device_set_overlay(fs, overlay);
krun_mmio_device_manager_add(devices, fs);
}
{
/* No net device; TSI hijacks the guest's INET sockets - the
* exact configuration the krun sccache test boots on Linux. */
KrunVsockDevice vsock;
uint32_t tsi_flags = (argc > 3) ? (uint32_t)strtoul(argv[3], NULL, 0)
: (KRUN_TSI_FLAGS_HIJACK_INET | KRUN_TSI_FLAGS_HIJACK_UNIX);
fprintf(stderr, "tsi_flags=0x%x\n", tsi_flags);
CHECK(vsock = krun_vsock_device_new(3, tsi_flags, &err));
krun_mmio_device_manager_add(devices, vsock);
}
{
KrunRngDevice rng;
CHECK(rng = krun_rng_device_new(&err));
krun_mmio_device_manager_add(devices, rng);
}
KrunVmmBuilder builder = krun_vmm_builder_new();
CHECK(krun_vmm_builder_vcpus(&builder, 1, &err));
CHECK(krun_vmm_builder_ram_mib(&builder, 512, &err));
krun_vmm_builder_payload(&builder, payload);
krun_vmm_builder_devices(&builder, devices);
CHECK(krun_vmm_builder_split_irqchip(&builder, false, &err));
KrunVmm vmm;
CHECK(vmm = krun_vmm_builder_build(&builder, &err));
krun_vmm_run(vmm);
return 0;
}
macos/tsi_loopback_repro_legacy.c - host harness, libkrun 1.19.x API (macOS runs)
/*
* The same experiment as ../tsi_loopback_repro.c, written against the
* libkrun 1.19.x API: the one Homebrew and the distributions ship. It
* runs on a released library, macOS included.
*
* Usage: tsi_loopback_repro_legacy <rootfs-dir> <role-letter> [tsi_flags]
*
* tsi_flags defaults to KRUN_TSI_HIJACK_INET (HIJACK_UNIX is refused on
* macOS). Pass 0 for the control: no impersonation at all.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <libkrun.h>
#define CHECK(call) \
do { \
int32_t rc_ = (call); \
if (rc_ < 0) { \
fprintf(stderr, "%s failed: %d\n", #call, rc_); \
return 1; \
} \
} while (0)
int main(int argc, char **argv)
{
if (argc < 3) {
fprintf(stderr, "usage: %s <rootfs-dir> <role-letter> [tsi_flags]\n", argv[0]);
return 2;
}
const char *rootfs = argv[1];
const char *role = argv[2];
uint32_t tsi_flags = (argc > 3) ? (uint32_t)strtoul(argv[3], NULL, 0)
: KRUN_TSI_HIJACK_INET;
fprintf(stderr, "role=%s tsi_flags=0x%x\n", role, tsi_flags);
CHECK(krun_set_log_level(KRUN_LOG_LEVEL_WARN));
int32_t ctx = krun_create_ctx();
if (ctx < 0) {
fprintf(stderr, "krun_create_ctx failed: %d\n", ctx);
return 1;
}
CHECK(krun_set_vm_config(ctx, 1, 512));
CHECK(krun_set_root(ctx, rootfs));
/* No network device - the configuration in which libkrun's implicit
* vsock would turn HIJACK_INET on by itself. Configure the device
* explicitly instead, so the flags are the only thing that differs
* between the experiment and the control. */
CHECK(krun_disable_implicit_vsock(ctx));
CHECK(krun_add_vsock(ctx, tsi_flags));
CHECK(krun_set_workdir(ctx, "/"));
/* argv excludes argv[0]; libkrun fills that in with the exec path. */
const char *const exec_argv[] = { role, NULL };
const char *const exec_envp[] = { "PATH=/", NULL };
CHECK(krun_set_exec(ctx, "/probe", exec_argv, exec_envp));
/* Only returns if the VM could not be started. */
CHECK(krun_start_enter(ctx));
return 0;
}
macos/probe/src/main.rs - guest program for the macOS runs: `probe.rs` plus a connect timeout
// Guest-side probe for the libkrun TSI loopback-sharing question.
//
// Same as ../../probe.rs, except that the connect has a timeout. Under
// TSI on macOS a guest connect to 127.0.0.1 is known to be able to wedge
// (libkrun/libkrun#344), and a probe stuck in connect() reports nothing.
//
// Each VM runs one of these with a distinct role letter. It listens on
// the guest's own 127.0.0.1:6379, then connects to 127.0.0.1:6379 and
// prints who answered. In an isolated guest the answer is always its
// own role. Any other letter means the connection left this VM.
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::thread;
use std::time::Duration;
fn main() {
let role = std::env::args().nth(1).unwrap_or_else(|| "X".to_string());
let listener = match TcpListener::bind("127.0.0.1:6379") {
Ok(l) => l,
Err(e) => {
println!("PROBE {role}: bind 127.0.0.1:6379 failed: {e}");
return;
}
};
println!("PROBE {role}: listening on 127.0.0.1:6379");
let served = role.clone();
thread::spawn(move || {
for stream in listener.incoming().flatten() {
let mut stream = stream;
let mut buf = [0u8; 64];
let _ = stream.read(&mut buf);
let _ = stream.write_all(format!("SERVED-BY-{served}\n").as_bytes());
}
});
thread::sleep(Duration::from_secs(3));
let addr: SocketAddr = "127.0.0.1:6379".parse().expect("a literal socket address");
for attempt in 0..8 {
match TcpStream::connect_timeout(&addr, Duration::from_secs(3)) {
Ok(mut s) => {
let _ = s.write_all(b"ping\n");
let _ = s.set_read_timeout(Some(Duration::from_secs(2)));
let mut out = String::new();
let _ = s.read_to_string(&mut out);
let who = match out.trim() {
"" => "nobody (connected, empty reply)",
other => other,
};
println!("PROBE {role}: attempt {attempt} answered by {who}");
}
Err(e) => println!("PROBE {role}: attempt {attempt} connect failed: {e}"),
}
thread::sleep(Duration::from_millis(400));
}
println!("PROBE {role}: done");
}
macos/probe/Cargo.toml - builds the macOS guest program with `cargo zigbuild`
[package]
name = "probe"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "probe"
path = "src/main.rs"
[profile.release]
opt-level = "s"
# Standalone: not a member of any enclosing workspace.
[workspace]
macos/run.sh - builds, signs and runs the macOS pair, control and single VM
#!/usr/bin/env bash
# Build and run the TSI loopback experiment on macOS against Homebrew's
# libkrun (brew tap libkrun/krun && brew install libkrun).
#
# ./run.sh build, then run all three experiments
#
# 1. two VMs at once with HIJACK_INET - does a guest's connect to its own
# 127.0.0.1:6379 reach the other VM?
# 2. the same two VMs with flags 0 - the control
# 3. one VM alone with HIJACK_INET
#
# Each VM gets a hard deadline, because a TSI loopback connect on macOS
# is known to be able to wedge (libkrun/libkrun#344).
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
cd "$HERE"
export PATH="$(brew --prefix)/bin:$HOME/.cargo/bin:$PATH"
LK="$(brew --prefix libkrun)"
LKFW="$(brew --prefix libkrunfw)"
DEADLINE=60
OUT="$HERE/out"
build() {
# The guest probe cross-compiles, so the Rust toolchain in use needs the
# musl target. Set RUSTUP_TOOLCHAIN to pick a toolchain that has it.
if ! rustup target list --installed 2>/dev/null | grep -qx aarch64-unknown-linux-musl; then
echo "the active Rust toolchain ($(rustup show active-toolchain 2>/dev/null | cut -d' ' -f1)) lacks" \
"aarch64-unknown-linux-musl: run 'rustup target add aarch64-unknown-linux-musl'," \
"or set RUSTUP_TOOLCHAIN to a toolchain that has it" >&2
exit 1
fi
echo "== building the guest probe (aarch64-unknown-linux-musl)"
(cd probe && cargo zigbuild --quiet --release --target aarch64-unknown-linux-musl)
rm -rf rootfs
mkdir -p rootfs/dev rootfs/proc rootfs/sys rootfs/tmp rootfs/etc
cp probe/target/aarch64-unknown-linux-musl/release/probe rootfs/probe
echo "== building and signing the host harness against $LK"
cc -O2 -Wall -o tsi_repro_legacy tsi_loopback_repro_legacy.c \
-I"$LK/include" -L"$LK/lib" -lkrun
codesign --force --sign - --entitlements hv.entitlements tsi_repro_legacy
}
# run_vms <label> <flags> <role>...: start one VM per role at once, show
# the host's listeners on 6379 while they run, then collect the output.
run_vms() {
local label="$1" flags="$2"
shift 2
local pids=() role
mkdir -p "$OUT"
echo
echo "================ $label (tsi_flags=$flags, VMs: $*) ================"
for role in "$@"; do
DYLD_LIBRARY_PATH="$LK/lib:$LKFW/lib" ./tsi_repro_legacy "$HERE/rootfs" "$role" "$flags" \
> "$OUT/$label-$role.log" 2>&1 &
pids+=("$!")
done
sleep 2
echo "-- host listeners on 6379, 2s in:"
lsof -nP -iTCP:6379 -sTCP:LISTEN 2>/dev/null | sed 's/^/ /' || true
local waited=0
while [ "$waited" -lt "$DEADLINE" ]; do
local alive=0 pid
for pid in "${pids[@]}"; do kill -0 "$pid" 2>/dev/null && alive=1; done
[ "$alive" = 0 ] && break
sleep 1
waited=$((waited + 1))
done
local pid
for pid in "${pids[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
echo "-- pid $pid still running after ${DEADLINE}s: killing it"
kill -9 "$pid" 2>/dev/null || true
fi
done
for role in "$@"; do
echo "-- VM $role:"
grep -E "PROBE|failed|error|tsi_flags" "$OUT/$label-$role.log" | sed 's/^/ /' || true
done
}
build
run_vms pair-hijack 1 A B
run_vms pair-control 0 A B
run_vms single-hijack 1 A
macos/hv.entitlements - hypervisor entitlement the macOS harness is signed with
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hypervisor</key>
<true/>
</dict>
</plist>
- 主要言語
- Rust
- スター
- 2.8k
- フォーク
- 279
- 平均マージ
- 3日 7時間
- マージ済み PR(30日)
- 24
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
libkrun/libkrun のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
libkrun/libkrun#895 · コメント 2 件 ·
メンテナーはふだん 2 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 8/100
libkrun/libkrun#929 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 12/100
libkrun/libkrun#926 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 15/100
libkrun/libkrun#924 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 12/100
メンテナーはふだん 2 日以内に返信
libkrun/libkrun の issue をすべて見る
似ている issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
dependencies effort:low flow:chore rust
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
jaroslawherod/micold-ai-ide#875 ·
メンテナーはふだん 1 日以内に返信
-
priority: low
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
storytold/photocraft#6286 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Four problem loader browser tests fail on Windows対応中かも @Patriciaath999 が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
Kazakh (kk_KZ) and Georgian (ka_GE) system locales get English instead of the shipped translationsオープンbug
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
メンテナーはふだん 1 日以内に返信