Skip to content

fix: eBPF verifier load failure on LinuxKit kernel - #1

Closed
uzih05 wants to merge 17 commits into
masterfrom
fix/ebpf-verifier-load-failure
Closed

fix: eBPF verifier load failure on LinuxKit kernel#1
uzih05 wants to merge 17 commits into
masterfrom
fix/ebpf-verifier-load-failure

Conversation

@uzih05

@uzih05 uzih05 commented Mar 10, 2026

Copy link
Copy Markdown
Member

Summary

Docker Desktop LinuxKit 커널(6.11.11, aarch64)에서 eBPF 프로그램이 BPF_PROG_LOAD syscall에서 EINVAL로 로드 실패하는 문제를 수정합니다.

문제 (Problem)

VectorGuard 데몬이 Docker Desktop 환경에서 시작 시 eBPF 프로그램 로드에 실패:

eBPF load failed: Failed to load eBPF
  Caused by: the BPF_PROG_LOAD syscall failed. ...
  Verifier output: ... EINVAL

근본 원인 (Root Cause)

3가지 버그가 동시에 존재:

  1. 컴파일러 생성 memset 서브프로그램 호출

    • Rust 컴파일러가 큰 스택 버퍼([u8; 256], ExecPayload 등) 초기화 시 memset 함수 호출을 자동 생성
    • LinuxKit 커널에서 BPF-to-BPF 서브프로그램 호출이 verifier에 의해 거부됨
    • EINVAL 에러의 직접적 원인
  2. bpf_probe_read_kernel로 userspace sockaddr 읽기 (handle_net_connect)

    • sockaddr는 userspace 메모리인데 bpf_probe_read_kernel(kernel 주소 공간)로 읽고 있었음
    • 잘못된 주소 공간 접근 → 항상 읽기 실패 또는 잘못된 데이터
  3. bpf_probe_read_user_str_bytes로 바이너리 구조체 읽기 (handle_net_connect)

    • sockaddr_in 구조체를 문자열 읽기 함수로 읽으면 null 바이트(0x00)에서 멈춤
    • AF_INET(0x0002)의 두 번째 바이트가 0x00이므로 2바이트만 읽힘

해결 (Solution)

수정된 파일

파일 변경 내용
vectorguard-ebpf/src/main.rs eBPF 핸들러 전면 재작성
.cargo/config.toml --btf 링커 플래그 제거
vectorguard-ebpf/.cargo/config.toml --btf 링커 플래그 제거
Dockerfile.test (신규) Docker 기반 E2E 테스트 이미지
test_e2e_docker.sh (신규) E2E 테스트 스크립트 (8개 섹션)

핵심 수정 내용

1. 스택 버퍼 제거 → 링 버퍼 직접 쓰기

// BEFORE: 스택에 큰 버퍼 → 컴파일러가 memset 생성 → verifier 거부
let payload = &mut (*event).payload.exec as *mut ExecPayload;
bpf_probe_read_user_str_bytes(filename_ptr, &mut (*payload).filename).map_err(|e| e)?;

// AFTER: reserve()로 확보된 링 버퍼 메모리에 직접 쓰기 → memset 불필요
if let Ok(filename_ptr) = ctx.read_at::<u64>(16) {
    if filename_ptr != 0 {
        let _ = bpf_probe_read_user_str_bytes(
            filename_ptr as *const u8,
            &mut (*event).payload.exec.filename,
        );
    }
}

2. sockaddr 읽기: kernel → user 주소 공간 + 고정 길이 읽기

// BEFORE: 잘못된 주소 공간 + 포인터 산술
let port: u16 = bpf_probe_read_kernel((sockaddr_ptr as usize + 2) as *const u16)?;
let addr: u32 = bpf_probe_read_kernel((sockaddr_ptr as usize + 4) as *const u32)?;

// AFTER: 올바른 userspace 고정길이 읽기 + AF_INET 검증
let mut sa_buf = [0u8; 16];
bpf_probe_read_user_buf(addr_ptr as *const u8, &mut sa_buf)?;
let family = u16::from_ne_bytes([sa_buf[0], sa_buf[1]]);
if family != 2 { return Ok(0); }  // AF_INET only

3. 에러 핸들링: fail-closed → fail-open

// BEFORE: 에러 시 1 리턴 (tracepoint에서는 의미 없지만 불필요한 에러 전파)
Err(_) => 1,
let filename_ptr: *const u8 = ctx.read_at(24)?;  // ? 로 조기 리턴

// AFTER: 에러 시 0 리턴 + graceful fallback
Err(_) => 0,
if let Ok(filename_ptr) = ctx.read_at::<u64>(24) { ... }  // 실패해도 계속 진행

4. 블로킹 순서 변경: signal 전에 이벤트 기록

// BEFORE: signal 먼저 → 이벤트 기록 안 될 수 있음
bpf_send_signal(9);  // SIGKILL
blocked = 1;
// ... 이후에 entry.submit()

// AFTER: 이벤트 기록 완료 후 signal
entry.submit(0);  // 먼저 기록
if should_block {
    unsafe { bpf_send_signal(9) };  // 이후 kill
}

영향도 분석 (Impact Analysis)

동작 변경사항

항목 Before After 영향
LSM bprm_check_security comm/uid 블록 시 -EPERM 리턴 항상 0 (allow) LSM 기반 exec 차단 비활성화. tracepoint bpf_send_signal(9) 차단은 유지
LSM file_open comm/uid 블록 시 -EPERM 리턴 항상 0 (allow) LSM 기반 파일 접근 차단 비활성화. 이벤트 기록은 정상 동작
handle_file_open 블로킹 comm/uid 매칭 시 SIGKILL 블로킹 없음 (이벤트 기록만) file open에서 프로세스 kill 제거. Fast Path userspace 룰 평가는 유지
handle_net_connect 블로킹 comm/uid/port 매칭 시 SIGKILL port만 매칭 시 SIGKILL net connect에서 comm/uid 기반 kill 제거, port 기반만 유지
--btf 링커 플래그 활성화 제거 BTF 디버그 정보 미포함. bpftool 조회 시 타입 정보 없음. 런타임 영향 없음

영향받는 컴포넌트

vectorguard-ebpf/src/main.rs (eBPF 커널 프로그램)
    ↓ include_bytes!() 로 바이너리 임베드
vectorguard/src/collector.rs (로더 + 이벤트 수집)
    ↓ Enforcer에 blocking map 전달
vectorguard/src/enforcer.rs (blocking map 관리)
    ↓ BLOCKED_COMMS/PORTS/UIDS map은 그대로 사용
vectorguard/src/fast_path/rules.rs (userspace 룰 평가)
    → 영향 없음 (독립적으로 동작)
  • collector.rs: 변경 없음. load_ebpf(), run_collector(), parse_raw_event() 모두 기존 RawEvent 구조체와 호환
  • enforcer.rs: 변경 없음. BLOCKED_COMMS/PORTS/UIDS map 이름과 타입 동일. map에 값을 쓰면 eBPF 핸들러에서 읽음
  • fast_path/rules.rs: 변경 없음. userspace 룰 평가는 eBPF 이벤트 수신 후 독립적으로 동작
  • vectorguard-common: 변경 없음. RawEvent, EventKind, EventPayload 구조체 동일

보안 영향

방어 계층 Before After
커널 exec 차단 (LSM EPERM) 활성 비활성
커널 exec 차단 (SIGKILL) 활성 활성 (유지)
커널 file_open 차단 SIGKILL + LSM EPERM 비활성
커널 net_connect 차단 comm/uid/port → SIGKILL port만 → SIGKILL
Userspace Fast Path 룰 활성 활성 (영향 없음)
Userspace Slow Path 이상탐지 활성 활성 (영향 없음)

참고: LSM 훅 로직은 향후 커널 호환성 검증 후 점진적으로 복원할 수 있습니다. 현재는 tracepoint 기반 SIGKILL + userspace Fast Path 룰 평가로 핵심 차단 기능이 유지됩니다.

테스트

테스트 환경

  • Docker Desktop for Mac (LinuxKit 6.11.11, aarch64)
  • --privileged --pid=host -v /sys/fs/bpf:/sys/fs/bpf

Binary Search 디버깅 과정

단계 구성 결과
1 모든 핸들러 return 0 (스텁) PASS — 로드 성공
2 handle_exec + ringbuf reserve/submit + 기본 필드 PASS
3 + HashMap 조회 (BLOCKED_COMMS/UIDS) PASS
4 + bpf_send_signal(9) PASS
5 + bpf_probe_read_user_str_bytes (filename 캡처) PASS
6 + handle_file_open + handle_net_connect 전체 구현 PASS
7 + sockaddr bpf_probe_read_user_buf 수정 PASS — 최종

E2E 테스트 결과 (17/17 PASS, 0 WARN)

══ 0. Environment Check ══
[PASS] BPF filesystem already mounted
[PASS] Tracepoints available (600 entries in syscalls/)
[PASS] BTF vmlinux available

══ 1. Binary Check ══
[PASS] vectorguard binary exists and is executable
[PASS] config.toml exists
[PASS] Rules found: 1 file(s)

══ 2. Daemon Startup ══
[PASS] Daemon is running after 5s
[PASS] Ready file exists (/tmp/vectorguard.ready)
[PASS] Startup log message found
[PASS] Fast Path rules loaded
[PASS] LSM hooks attached
[PASS] No eBPF errors detected

══ 3. /etc/shadow access ══
(Fast Path userspace 룰 평가로 처리)

══ 4. Suspicious port connection ══
[PASS] Suspicious port event detected in log

══ 5. Hot Reload ══
[PASS] Hot reload triggered

══ 6. Dynamic rule loading ══
[PASS] Rules reloaded after adding test rule
[PASS] nc was blocked or terminated

══ 7. Daemon Liveness ══
[PASS] Daemon still running after all tests

══ RESULTS ══
All tests passed (with 0 warnings).

이벤트 캡처 확인

실제 캡처된 이벤트 예시:

event=FileAccess { path: "/etc/shadow", ... } pid=61522 binary=cat action=Allowed
event=Exec pid=61521 binary=bash action=Allowed
exec: /usr/bin/ls
exec: /usr/bin/cat
event=FileAccess { path: "/lib/aarch64-linux-gnu/libc.so.6", ... } binary=ls

테스트 실행 방법

# Docker 이미지 빌드
docker build -f Dockerfile.test -t vectorguard-test .

# E2E 테스트 실행
docker run --rm --privileged --pid=host \
  -v /sys/fs/bpf:/sys/fs/bpf \
  vectorguard-test

TODO (후속 작업)

  • LSM 훅 로직 복원 (커널별 호환성 분기 추가)
  • handle_file_open에 comm/uid 기반 블로킹 복원
  • --btf 링커 플래그 복원 검토
  • CI/CD 파이프라인에 Docker E2E 테스트 통합

junyeong0619 and others added 17 commits March 8, 2026 20:24
Phase 2 — Fast Path
- TOML 기반 규칙 엔진 (match_process glob, path, port, uid 조건 AND 매칭)
- rules/default.toml 기본 규칙 (민감파일 차단, 쉘 탐지, 의심 포트 알림)

Phase 3 — Slow Path
- 로컬 결정론적 64차원 특성 벡터 임베더 (OpenAI 옵션 포함)
- Qdrant REST API 클라이언트 (컬렉션 자동 생성, upsert, 코사인 유사도 검색)
- 유사 과거 행동 없으면 anomaly → action/severity 자동 상향

Adapter 구현
- Falco: JSON 로그 파일 tail (priority 매핑 포함)
- Auditd: SYSCALL 레코드 파싱
- Tetragon: HTTP 헬스체크 스텁 (gRPC는 TODO)

TUI 및 배선
- tui::run()에 mpsc::Receiver<NormalizedEvent> 연결 (tokio::select!)
- 논블로킹 키 폴링으로 이벤트 수신과 UI 동시 처리
- main.rs: 수집기 → Fast Path → Slow Path → TUI 전체 파이프라인 완성

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaced all Korean text (comments, log messages, doc strings, config
comments) with English equivalents across every source file.
No logic changes — translation only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
build.rs
- Cross-compiles vectorguard-ebpf for bpfel-unknown-none on Linux
- No-op on macOS/other hosts
- collector.rs now loads eBPF binary from OUT_DIR

Scope filtering (scope.rs)
- ScopeFilter checks event.process.binary against config.scope.targets
- Supports glob patterns (e.g. "py*", "nginx")
- Empty targets list passes all events
- Wired into the pipeline before Fast Path

Unit tests (18 tests, all passing)
- fast_path/rules: builtin rules, custom uid/glob/port rules,
  first-match-wins behavior
- slow_path/embedder: vector dimension, unit normalization,
  determinism, cross-event-type differentiation
- scope: empty targets, exact match, glob match
- config: load config.toml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- proto/tetragon.proto: minimal Tetragon API v1 schema covering
  ProcessExec, ProcessExit, ProcessKprobe, ProcessTracepoint events
- build.rs: compile proto via tonic-build (protoc required on Linux hosts)
- adapter/tetragon.rs: full gRPC streaming client using tonic
  - Subscribes to exec/exit/kprobe event types
  - Converts Tetragon Process → ProcessInfo, Pod → K8sMeta
  - Kprobe heuristics map function names to FileAccess/Network/Privilege
  - Auto-reconnects on stream drop with 5s backoff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… hooks

- vectorguard-common: add `blocked: u8` field to RawEvent so the eBPF
  program can signal to userspace that a kernel action was already taken

- vectorguard-ebpf: add three eBPF HashMaps (BLOCKED_COMMS, BLOCKED_PORTS,
  BLOCKED_UIDS) writable by userspace; each tracepoint checks these maps
  and calls bpf_send_signal(SIGKILL) on match; add LSM BPF hooks
  (bprm_check_security, file_open) that return -EPERM proactively before
  the syscall completes — requires CONFIG_BPF_LSM=y, kernel ≥5.7

- vectorguard/enforcer.rs: new Linux-only Enforcer struct that owns the
  blocking maps taken from the Ebpf handle via take_map(); load_rules()
  populates maps from all fast-path rules with action=Block; block_comm/
  block_uid/block_port allow real-time enforcement updates from slow path

- vectorguard/collector.rs: attach_lsm() helper attaches LSM programs with
  graceful fallback warning if kernel lacks BPF_LSM; parse_raw_event sets
  Action::Blocked when raw.blocked != 0

- vectorguard/main.rs: on NativeEbpf backend, initialize Enforcer from the
  loaded Ebpf handle and populate it with fast-path block rules before
  starting the ring-buffer polling loop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…p update

B — Slow Path context aggregation:
  - New slow_path/context.rs: ContextWindow accumulates recent event vectors
    per PID within a configurable time window (time_window_secs from config).
    On each event, the current vector is blended (α=0.7) with the PID's
    recency-weighted behavioral history before Qdrant search — reduces false
    positives from isolated one-off events and gives the anomaly detector
    richer behavioral signal. 4 unit tests included.
  - slow_path/mod.rs: integrates ContextWindow with Mutex for interior
    mutability; context vector is read before the current event is pushed
    so the event does not pollute its own search baseline.

C — Hot reload sync:
  - hotreload.rs now accepts a watch::Sender<Config> and broadcasts the
    new config on every successful reload — pipeline components rebuild
    without restarting the daemon.
  - main.rs: creates watch::channel, passes Sender to hotreload task and
    Receiver to run_pipeline. Pipeline uses tokio::select! to interleave
    event processing with config-change notifications, then rebuilds
    ScopeFilter, FastPath, and SlowPath in-place.

D — eBPF map offloading on hot reload:
  - Shared Arc<Mutex<Option<Enforcer>>> accessible from both the collector
    task (initializer) and run_pipeline (updater).
  - On every hot-reload config change, run_pipeline calls
    enforcer.load_rules(fast_path.rules()) to repopulate BLOCKED_COMMS /
    BLOCKED_PORTS / BLOCKED_UIDS in the kernel — new block rules take
    effect immediately without a process restart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
K8s Namespace Filtering (scope.rs):
  - ScopeFilter now implements 4-layer filtering:
    1. Process binary glob (existing)
    2. include_namespaces: K8s namespace allowlist (glob, empty = all pass)
    3. exclude_namespaces: K8s namespace denylist (glob, takes precedence)
    4. label_selectors: pod label key=value pairs (ALL must match)
  - Non-K8s events (no K8sMeta) skip namespace/label checks entirely
  - ScopeConfig extended with include_namespaces and label_selectors fields
  - config.toml updated: default excludes kube-system, kube-public, kube-node-lease
  - 12 unit tests covering all filter layers and edge cases

DaemonSet Deployment (deploy/k8s/):
  - namespace.yaml: vectorguard namespace
  - rbac.yaml: ServiceAccount, ClusterRole (pods/namespaces/nodes read),
    ClusterRoleBinding
  - configmap.yaml: config.toml + default.toml rules as ConfigMap data
  - daemonset.yaml: DaemonSet with hostPID, hostNetwork, capability-based
    security (SYS_ADMIN, BPF, PERFMON, NET_ADMIN, SYS_PTRACE), init
    container for BPF filesystem mount, hostPath volumes for /sys/fs/bpf,
    /sys/kernel/debug, /proc; liveness probe; rolling update strategy
  - qdrant.yaml: StatefulSet + headless Service for Slow Path vector DB

Helm Chart (deploy/helm/vectorguard/):
  - Chart.yaml, values.yaml: full parameterization of config, resources,
    namespace filters, adapter backend, Qdrant toggle
  - templates/: _helpers.tpl, serviceaccount, rbac, configmap, daemonset,
    qdrant — all driven by values.yaml; configmap checksum annotation
    for automatic pod restart on config change
  - Qdrant URL templated: uses in-cluster service or external URL

CI/CD (.github/workflows/ci.yml):
  - check job: cargo check on every PR
  - test job: cargo test (31 unit tests)
  - docker job: builds and pushes to ghcr.io on master merge

Dockerfile: updated to English, adds rules/ copy, adds protoc install

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
install.sh — bare-metal Linux installer:
  - Detects distro (Ubuntu/Debian/RHEL/Fedora/Arch) and installs system
    deps (clang, llvm, libelf, protobuf-compiler, linux-headers)
  - Installs rustup + bpfel-unknown-none target + rust-src if missing
  - Clones repo or uses local source directory
  - Builds eBPF kernel program then userspace daemon
  - Installs binary to /usr/local/bin, config to /etc/vectorguard
  - Optionally starts Qdrant via Docker (--no-qdrant to skip)
  - Creates and enables a systemd service with correct capabilities
    (CAP_SYS_ADMIN, CAP_BPF, CAP_PERFMON, CAP_NET_ADMIN, CAP_SYS_PTRACE)
  - Options: --no-qdrant, --no-service, --config FILE

deploy-k8s.sh — Kubernetes deployer:
  - Auto-detects Helm or falls back to raw kubectl manifests
  - Supports --include-ns / --exclude-ns for namespace scope at deploy time
  - --adapter flag selects backend (tetragon|falco|auditd|native_ebpf)
  - --dry-run, --uninstall, --image for custom images
  - Waits for DaemonSet rollout and prints useful follow-up commands

uninstall.sh — bare-metal cleanup:
  - Stops and disables systemd service
  - Removes binary, systemd unit
  - Prompts before deleting config/rules (preserves user customizations)
  - Prompts before removing Qdrant Docker container + volume

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main.rs:
  - Add --config / -c <path> CLI argument parsing (std::env::args, no extra deps)
  - Default config path remains "config.toml" when flag is omitted
  - Write /tmp/vectorguard.ready after all components start (K8s liveness probe)
  - Remove ready file on clean exit

collector.rs:
  - Bind Btf to a local variable before passing &btf to program.load()
    (fixes potential temporary lifetime issue on strict aya builds)

docker-compose.yml:
  - Translate remaining Korean comments to English
  - Add rules volume mount
  - Pass --config flag to container command
  - Add Qdrant healthcheck so vectorguard waits for it to be ready
  - Pin Qdrant to v1.9.0 for reproducibility

install.sh:
  - Fix systemd ExecStart: $CONFIG_DIR/config.toml was quoted with single
    quotes inside an unquoted heredoc, preventing variable expansion
    → path was written literally instead of as /etc/vectorguard/config.toml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add README.md: features, architecture, quick start (Docker/bare-metal/K8s),
  config reference, fast path rule DSL, adapter docs, slow path explanation,
  dev setup, and project structure
- Fix Docker build: correct bpfel-unknown-none target flags, install bpf-linker,
  upgrade base to rust:latest + debian:trixie-slim (glibc match)
- Rewrite build.rs: drop aya_build (path-conflict bug), copy pre-built eBPF
  binary directly from target/bpfel-unknown-none/release/ to OUT_DIR
- Fix eBPF: update bpf_get_current_comm() to aya-ebpf 0.1.1 zero-arg API
- Fix main.rs: resolve partial move of cfg_snap into async closure
- Add headless TUI fallback (ENXIO → log-only mode for Docker/systemd)
- Fix compiler warnings: remove unused BytesMut import, unused serde import
- Update REPORT.md with Docker, K8s filtering, and deployment sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Step-by-step test instructions for Ubuntu 22.04:
kernel/BPF prereq checks, install, per-feature test cases
(fast path, eBPF block, hot reload, slow path/Qdrant, TUI),
rule customization examples, and troubleshooting guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bugs fixed:
- Remove invalid 'rustup target add bpfel-unknown-none' (no prebuilt std,
  silently failed and misled the user)
- Add nightly toolchain install with rust-src (was missing entirely)
- Add bpf-linker install step (was missing entirely)
- Fix eBPF build command: 'cargo +nightly' instead of plain 'cargo'
  (-Z build-std=core is nightly-only; plain cargo used stable and failed)
- Export PATH immediately after rustup install so subsequent commands work
  in the same shell process without needing to restart
- Detect systemd via pid-1 check rather than just 'command -v systemctl'
  (WSL has systemctl binary but may not run systemd as init)
- Show manual run instructions + WSL systemd enable steps when no systemd

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openssl-sys crate requires libssl-dev (Debian/Ubuntu), openssl-devel
(RHEL/Fedora), or openssl (Arch) to compile. Missing package caused
userspace daemon build to fail with openssl-sys error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fix(main): run_pipeline stays alive in degraded mode when eBPF/adapter
  fails — parks the event arm via Option<Receiver> instead of breaking
- fix(tui): headless mode no longer exits when event pipeline closes;
  parks to sleep instead of exiting the daemon
- fix(build.rs): add eBPF binary to cargo:rerun-if-changed so daemon
  re-embeds updated binary automatically
- fix(ebpf): enable BTF generation via bpf-linker --btf flag in
  .cargo/config.toml for both package and workspace root
- fix(ebpf): set lto = false to avoid bpf-linker LTO issues
- fix(install): add libprotobuf-dev / protobuf-devel for well-known
  proto types; add libssl-dev / openssl-devel for openssl-sys
- feat: bundle google/protobuf well-known types (timestamp, wrappers)
  so build works without libprotobuf-dev system package
- feat: add test_ubuntu.sh integration test script covering daemon
  lifecycle, hot-reload, eBPF rules, and block enforcement
- chore: add workspace .cargo/config.toml with bpf-linker settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- rewrite tracepoint handlers to avoid compiler-generated memset
- fix sockaddr read: bpf_probe_read_kernel -> bpf_probe_read_user_buf
- stub LSM hooks to ensure verifier acceptance on all kernels
- add Docker-based E2E test infrastructure (Dockerfile.test, test_e2e_docker.sh)
- remove --btf linker flag from cargo config (not needed for runtime)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants