Skip to content

fix(storage): unescape /proc/self/mountinfo paths in getMountInfo - #868

Open
Anand-240 wants to merge 1 commit into
urunc-dev:mainfrom
Anand-240:fix/mountinfo-octal-unescape
Open

Anand-240 wants to merge 1 commit into
urunc-dev:mainfrom
Anand-240:fix/mountinfo-octal-unescape

Conversation

@Anand-240

@Anand-240 Anand-240 commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Description

getMountInfo() in pkg/unikontainers/block.go figures out whether a bind-mount source is itself a mount point by scanning /proc/self/mountinfo and comparing the mount point field against the given path with a plain string comparison (preDash[4] == path).

The kernel octal-escapes spaces, tabs, newlines and backslashes in the root and mount point fields of that file (e.g. a space becomes \040). The old code read those fields straight off the file and never unescaped them, so a mount whose path contained any of those characters could never match, getMountInfo returned ErrMountpoint, and getBlockVolumes() silently skipped attaching it as a block device with no error surfaced anywhere.

This switches getMountInfo to use moby/sys/mountinfo.GetMounts, which is already a dependency and already used elsewhere in this file (mountinfo.Mounted in restoreBlockVolumes). That library decodes the escaping while parsing, so the comparison works correctly. The actual matching logic is pulled out into findMountInfo(mounts []*mountinfo.Info, path string) so it can be tested against synthetic mount entries instead of depending on the real /proc/self/mountinfo of whatever process runs the tests.

Related issues

How was this tested?

  • go build ./... and go vet ./... on Linux (cross-compiled from macOS, then verified inside a golang container since the affected code and unix/loop-device syscalls it depends on are Linux-only).
  • go test ./pkg/unikontainers/... in a Linux container: existing TestGetBlockDevice still passes (it exercises getMountInfo("/proc") against the real mountinfo of the test process).
  • Added TestFindMountInfoEscapedPath, which builds a synthetic mountinfo line with a mount point escaped the way the kernel would escape it (space, tab, backslash), parses it with mountinfo.GetMountsFromReader, and checks findMountInfo matches it against the real, unescaped path. This reproduces the bug and fails on the old code path, passes with the fix.
  • Confirmed two other test failures in the same package (TestCopyFile, TestMoveFile) are pre-existing and unrelated. They fail identically on unmodified main when run as root inside the container (permission-denied checks don't trigger for root).
  • I did not have a working golangci-lint/hypervisor e2e setup available in this environment, so make lint and the e2e suites were not run locally; flagging that here rather than checking those boxes.

LLM usage

Claude (Anthropic, model: claude-sonnet-5) was used to investigate the bug, write the fix, and write this description. All changes were reviewed and tested by me before opening this PR, per the project's LLM policy.

Checklist

  • I have read the contribution guide.
  • The linter passes locally (make lint). Not run locally, see note above.
  • The e2e tests of at least one tool pass locally (make test_ctr, make test_nerdctl, make test_docker, make test_crictl). Not run locally, see note above.
  • If LLMs were used: I have read the llm policy.

@netlify

netlify Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for urunc canceled.

Name Link
🔨 Latest commit 2386752
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6a6c974c3f8ba30008ee671f

@cmainas

cmainas commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Hello @Anand-240 ,

can you bring this up-to-date with the main branch?

The kernel octal-escapes spaces, tabs, newlines and backslashes in the
root and mount point fields of /proc/self/mountinfo. getMountInfo was
comparing the raw, still-escaped mount point field against the caller's
real path, so any bind-mount source containing one of those characters
never matched and getBlockVolumes silently skipped it instead of
attaching it as a block device.

Switch to moby/sys/mountinfo.GetMounts, which already decodes the
escaping, instead of hand-splitting the mountinfo lines. The matching
logic is split into findMountInfo so it can be exercised with synthetic
mountinfo entries in a test.

Fixes: urunc-dev#867
Signed-off-by: Anand-240 <anandprakashsrivastava68@gmail.com>
@Anand-240
Anand-240 force-pushed the fix/mountinfo-octal-unescape branch from 2386752 to e6133ac Compare September 16, 2026 14:00
@netlify

netlify Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for urunc canceled.

Name Link
🔨 Latest commit e6133ac
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6aaaa0f0fa08260008b87d4f

@Anand-240

Copy link
Copy Markdown
Contributor Author

rebased onto main, no conflicts. CI should run now.

@cmainas cmainas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @Anand-240 for the rebase. Overall the PR looks good and I would like to see this get merged mostly because it replaces the custom logic of reading /pros/self/mountinfo. However, the comments need to reflect the actual code and some old comments need to get updated too.

Comment on lines +60 to +64
//
// We rely on moby/sys/mountinfo to parse /proc/self/mountinfo instead of
// splitting the raw lines ourselves, because the kernel octal-escapes
// spaces, tabs, newlines and backslashes in the root and mount point
// fields (see proc(5)), and mountinfo.GetMounts() already decodes them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason for this comment.

Comment on lines +74 to +77
// findMountInfo scans already-parsed mountinfo entries for the one mounted
// at path. It is split out from getMountInfo so the matching logic can be
// unit tested against synthetic mounts, without depending on the real
// /proc/self/mountinfo of the process running the test.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep the comments aligned with the actual functionality of a function and not for unit tests or others.

// at path. It is split out from getMountInfo so the matching logic can be
// unit tested against synthetic mounts, without depending on the real
// /proc/self/mountinfo of the process running the test.
func findMountInfo(mounts []*mountinfo.Info, path string) (types.BlockDevParams, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a different name for mounts (for example mountInfo).

blockDev.Source = m.Source
blockDev.FsType = m.FSType
blockDev.MountPoint = path
// Keep the mount VFS options (field 6 of mountinfo)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to update this comment. It is stale with these changes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants