Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

bindflt: concurrent relative-path `NtCreateFile` on a mapped directory intermittently returns `STATUS_INVALID_PARAMETER`

未关闭
#635 7 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
5/5
预计耗时
一周以上
新手友好度
25/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
c, docker, kubernetes

调研方向

首先构建 repro.c,并使用提供的 pod 命令和控制命令从映射的 C:\work 卷运行它。将相对路径失败与绝对路径、容器 scratch 空间和主机访问进行比较,然后检查 bindflt 对 handle-relative NtCreateFile 的处理。完成的标准是并发相对路径 reproducer 不再返回 ERROR_INVALID_PARAMETER,并且各项控制仍然通过。

由索引模型根据 Issue 内容生成。

描述

bug triage

Inside a process-isolated Windows Server container, when the process's current directory is on a bindflt-mapped volume (e.g. a Kubernetes emptyDir or Docker bind mount), CreateFileW / CreateDirectoryW called with a relative path intermittently fail with ERROR_INVALID_PARAMETER (87) under concurrent load. The same path passed absolute never fails. The same files on container scratch (wcifs only, no bindflt mapping) never fail. The same files accessed from the host directly (no container) never fail.

This points at bindflt.sys's handling of handle-relative NtCreateFile (where RootDirectory is the cwd handle and ObjectName is the relative path) when multiple threads operate on overlapping targets.

Environment

  • Host: Windows Server 2022 10.0.20348.4893 and Windows Server 2025 10.0.26100.x (both reproduce; AKS Windows node pools)
  • Container: process-isolated, mcr.microsoft.com/windows/servercore:ltsc2022
  • Mount: Kubernetes emptyDir: {} → C:\work (HCS mapped directory)
  • Runtime: containerd (AKS default)
  • Filter stack on host (fltmc filters from a hostProcess pod):
    Filter Name     Num Instances   Altitude
    bindflt                    29     409800
    FsDepends                  35     407000
    WdFilter                   35     328010
    wcifs                      17     189900
    Wof                         1      40700
    

Symptom

CreateFileW("src\third_party\…\foo.h", GENERIC_READ, …, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)
  → INVALID_HANDLE_VALUE, GetLastError() = 87 (ERROR_INVALID_PARAMETER)

The failure is transient — retrying the same call after ~5 ms succeeds. Hit rate is ~0.02–0.1 % of opens under the conditions below.

Trigger (all required)

  1. Process runs inside a process-isolated container
  2. cwd is on a bindflt-mapped directory (k8s emptyDir / bind mount)
  3. Path is passed relative (not absolute, not \\?\)
  4. Multiple threads concurrently open overlapping targets (same file with an existing open handle, or shared parent directories)

Bisection — what it is not

variation err87
same files, absolute path (C:\work\src\…) 0 / 1.44 M
same files, relative path, on container scratch (wcifs-only VHDX, no bindflt mapping) 0 / 130 k
same files, relative path, from hostProcess pod against the emptyDir's host-side path (no container filters) 0 / 836 k
same files, relative path, on bindflt-mapped emptyDir ~0.1 % of 130 k–1.44 M
pure C / Win32 (CreateFileW + _beginthreadex), no Go runtime reproduces (159 / 836 k)
FILE_FLAG_BACKUP_SEMANTICS set vs. cleared irrelevant (cleared actually hit slightly more)
WS2022 vs WS2025 host both reproduce
files written by tar / gn / robocopy / Go os.WriteFile all reproduce (writer irrelevant)
CreateFileW (OPEN_EXISTING) vs CreateDirectoryW both reproduce (20 / ~117 k mkdir)

Minimal reproduction

Pod
apiVersion: v1
kind: Pod
metadata:
  name: bindflt-repro
spec:
  restartPolicy: Never
  nodeSelector:
    kubernetes.io/os: windows
  containers:
    - name: repro
      image: mcr.microsoft.com/windows/servercore:ltsc2022
      command: ["cmd", "/c", "ping -t 127.0.0.1 > nul"]
      volumeMounts:
        - name: work
          mountPath: 'C:\work'
  volumes:
    - name: work
      emptyDir: {}
C reproducer

Build with cl /O2 repro.c or zig cc -target x86_64-windows-gnu -O2 -municode repro.c.

// repro.c — concurrent relative-path double-open on a bindflt mapped dir.
// Usage: cd into the mapped volume, then:  repro.exe <relative-dir> 32 12
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_FILES 200000
static wchar_t *g_files[MAX_FILES];
static volatile LONG g_nfiles, g_idx, g_err87, g_recovered, g_other;
static HANDLE g_inner_sema;

static HANDLE open_ro(const wchar_t *p) {
  return CreateFileW(p, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
                     NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}

static void walk(const wchar_t *dir) {
  wchar_t pat[1024]; _snwprintf(pat, 1024, L"%ls\\*", dir);
  WIN32_FIND_DATAW fd; HANDLE h = FindFirstFileW(pat, &fd);
  if (h == INVALID_HANDLE_VALUE) return;
  do {
    if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L"..")) continue;
    wchar_t full[1024]; _snwprintf(full, 1024, L"%ls\\%ls", dir, fd.cFileName);
    if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) walk(full);
    else if (g_nfiles < MAX_FILES) g_files[InterlockedIncrement(&g_nfiles)-1] = _wcsdup(full);
  } while (FindNextFileW(h, &fd));
  FindClose(h);
}

struct inner { const wchar_t *p; DWORD err; };
static unsigned __stdcall inner_thread(void *a) {
  struct inner *ia = a;
  WaitForSingleObject(g_inner_sema, INFINITE);
  HANDLE h = open_ro(ia->p);
  ia->err = (h == INVALID_HANDLE_VALUE) ? GetLastError() : (CloseHandle(h), 0);
  ReleaseSemaphore(g_inner_sema, 1, NULL);
  return 0;
}

static unsigned __stdcall worker(void *_) {
  for (;;) {
    LONG i = InterlockedIncrement(&g_idx) - 1;
    if (i >= g_nfiles) return 0;
    const wchar_t *p = g_files[i];
    HANDLE h1 = open_ro(p);
    if (h1 == INVALID_HANDLE_VALUE) {
      (GetLastError()==ERROR_INVALID_PARAMETER ? InterlockedIncrement(&g_err87)
                                               : InterlockedIncrement(&g_other));
      continue;
    }
    struct inner a = { p, 0 };
    HANDLE th = (HANDLE)_beginthreadex(NULL, 0, inner_thread, &a, 0, NULL);
    WaitForSingleObject(th, INFINITE); CloseHandle(th);
    if (a.err == ERROR_INVALID_PARAMETER) {
      InterlockedIncrement(&g_err87);
      Sleep(5);
      HANDLE h2 = open_ro(p);
      if (h2 != INVALID_HANDLE_VALUE) { InterlockedIncrement(&g_recovered); CloseHandle(h2); }
      fwprintf(stderr, L"err87 %ls\n", p);
    } else if (a.err) InterlockedIncrement(&g_other);
    CloseHandle(h1);
  }
}

int wmain(int argc, wchar_t **argv) {
  const wchar_t *dir = argc>1 ? argv[1] : L"tree";
  int nw = argc>2 ? _wtoi(argv[2]) : 32, rounds = argc>3 ? _wtoi(argv[3]) : 12;
  walk(dir);
  wprintf(L"files=%ld workers=%d rounds=%d\n", g_nfiles, nw, rounds);
  if (!g_nfiles) return 1;
  g_inner_sema = CreateSemaphoreW(NULL, nw, nw, NULL);
  HANDLE *ths = calloc(nw, sizeof(HANDLE));
  for (int r=1; r<=rounds; r++) {
    g_idx = 0;
    for (int w=0; w<nw; w++) ths[w] = (HANDLE)_beginthreadex(NULL,0,worker,NULL,0,NULL);
    for (int w=0; w<nw; w++) { WaitForSingleObject(ths[w], INFINITE); CloseHandle(ths[w]); }
    wprintf(L"round %d/%d  err87=%ld\n", r, rounds, g_err87);
  }
  wprintf(L"\nopens=%ld err87=%ld recovered=%ld other=%ld\n",
          (long)g_nfiles*rounds, g_err87, g_recovered, g_other);
  return 0;
}
Steps
# inside the container
cd C:\work
# create a deep-ish tree (~60k files across 4096 dirs)
0..63 | % { $a=$_; 0..63 | % { mkdir "tree\d$a\d$_" -Force } } | Out-Null
0..59999 | % { Set-Content ("tree\d{0}\d{1}\f{2}.txt" -f ($_ % 64), ([math]::Floor($_ / 64) % 64), $_) "x" }
# relative path, from inside the mapped volume:
.\repro.exe tree 32 12
# → nonzero err87

# controls (all zero):
.\repro.exe C:\work\tree 32 12              # absolute path
robocopy C:\work\tree C:\scratch\tree /E    # scratch = wcifs-only
cd C:\scratch ; ..\work\repro.exe tree 32 12

Real-world impact

Chromium's build tool siso (and by extension Electron's CI) opens ~10–90 k .ninja files from a relative cwd under out/, with each file opened twice (outer open + chunked re-open). On Windows ARC runners backed by a k8s emptyDir, ~3–8 of those opens fail per build with The parameter is incorrect., aborting the manifest load.

Any tool that does concurrent relative-path file or directory operations on a bind-mounted volume inside a Windows container is exposed — build systems, parallel extractors, test harnesses, etc.

Workarounds

  • Pass absolute paths to CreateFileW / CreateDirectoryW
  • Avoid opening a path while another handle to the same target is live in the process
  • Retry on ERROR_INVALID_PARAMETER (clears in ≤5 ms)
  • Place the working tree on container scratch instead of a bind mount (requires bumping containerd sandbox size; scratch is wcifs-only and does not reproduce)
主要语言
PowerShell
星标
551
派生
76
PR 合并指标
30 天内没有已合并 PR

环境准备

我们还没有检查这个项目的环境配置文件。先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

microsoft/Windows-Containers 的其他 Issue

查看 microsoft/Windows-Containers 的全部 Issue

相似的 Issue

更多 DevOps Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。