Skip to content

fix(backends): copy links when the filesystem rejects symlinks (CIFS/SMB) (#10890)#10893

Open
localai-bot wants to merge 4 commits into
masterfrom
fix/oci-symlink-cifs-10890
Open

fix(backends): copy links when the filesystem rejects symlinks (CIFS/SMB) (#10890)#10893
localai-bot wants to merge 4 commits into
masterfrom
fix/oci-symlink-cifs-10890

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

Description

Fixes #10890.

Backend installation on a CIFS/SMB-backed /backends volume fails for every backend with:

failed to extract image tar: symlink libcublas.so.12.8.5.5 /backends/cuda12-llama-cpp.install-tmp/lib/libcublas.so: operation not supported

The OCI image tar is extracted via containerd's archive.Apply, which creates symlinks with os.Symlink. CIFS/SMB mounts (a common way to store /backends on a NAS) do not support symlinks and reject the syscall with operation not supported, aborting the whole install and leaving an empty backend directory. The CUDA llama.cpp image trips on the libcublas.so -> libcublas.so.12.x symlink.

Fix

When archive.Apply fails with a link-unsupported error (ENOTSUP/EOPNOTSUPP/EPERM, or the matching "operation not supported/permitted" text), the staging directory is reset and re-extracted with a pure-Go tar walker that:

  • still attempts real symlinks/hardlinks first, so link semantics are preserved wherever the filesystem allows them;
  • degrades an unsupported symlink/hardlink into a copy of the target file's contents in place, so the backend files remain usable;
  • defers link copies to a second pass so a link appearing before its target in the tar still resolves;
  • guards every entry against path traversal.

mutate.Extract already flattens the layers before this point, so the tar carries no whiteouts to interpret; a plain walker is sufficient. The primary archive.Apply path is unchanged, so this only affects filesystems that were already failing 100% of installs. The staging directory is the ephemeral, per-install *.install-tmp dir, so wiping it before the fallback pass is safe.

Tests

Added pkg/oci/extract_internal_test.go (Ginkgo/Gomega):

  • isLinkUnsupportedError truth table (syscall errnos + wrapped *os.LinkError + negatives).
  • safeJoin keeps entries in-root and rejects traversal.
  • extractTarCopyingLinks preserves symlinks on a symlink-capable FS, and, with symlink() stubbed to return ENOTSUP, materialises the target as a real regular file copy with the right bytes.

Note

Built and verified via CI (no Go toolchain in the authoring sandbox).

Assisted-by: Claude:opus-4.8 [Claude Code]

… symlinks (#10890)

Backend installation extracts the OCI image tar via containerd's
archive.Apply, which calls os.Symlink directly. On filesystems that do
not support symlinks (notably CIFS/SMB mounts, commonly used to back the
/backends volume) the syscall fails with "operation not supported" and
the whole install aborts, leaving an empty backend directory. The CUDA
llama.cpp image trips this on the libcublas.so -> libcublas.so.12.x
symlink.

When archive.Apply fails with a link-unsupported error, reset the
staging directory and re-extract with a pure-Go walker that still
attempts real symlinks/hardlinks first and degrades to copying the link
target's contents in place when the filesystem rejects them.
mutate.Extract already flattened the layers, so the tar carries no
whiteouts to interpret. Link copies are deferred to a second pass so
forward references resolve.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Comment thread pkg/oci/image.go Fixed
Comment thread pkg/oci/image.go Fixed
Comment thread pkg/oci/image.go Fixed
Comment thread pkg/oci/image.go Fixed
Comment thread pkg/oci/image.go Fixed
Comment thread pkg/oci/image.go Fixed
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Comment thread pkg/oci/image.go Fixed
mudler added 2 commits July 17, 2026 18:39
safeJoin sanitized "../.." entries by clamping them under root instead of
rejecting them, so a malicious entry was silently redirected rather than
refused. Join without the leading-slash trick and reject any entry whose
cleaned path resolves outside root; absolute link targets are still mapped
under root (image-root relative) rather than escaping.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Use hdr.FileInfo().Mode() instead of converting the int64 tar mode to
os.FileMode (removes two G115 overflow findings), and annotate the tar
extraction file operations with justified #nosec comments: every path is
validated by safeJoin against the extraction root before use (G304/G305).

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
@mudler

mudler commented Jul 17, 2026

Copy link
Copy Markdown
Owner

@localai-bot this is not right, OCI images have layers and this as-is its not unpacking layers.

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.

Cannot install any backend: operation not supported

3 participants