Overlay mounts have an undocumented limit on the number of `--overlay-src` layers, and exceeding it reports a misleading error
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 40/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- c, linux
- Ambito
- cli, operating-systems
Direzione di ricerca
The issue is in the overlay mount setup code, likely around where the lowerdir string is built. Start by examining the setup_overlay function and the path where mount(2) is called. The fix involves using the new mount API (fsopen/fsconfig/fsmount) for kernels >=6.7, while maintaining the old path for older kernels. Test by creating many overlay layers and verifying the mount succeeds or fails with a clear error. The PR mentioned will have the implementation details.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
bubblewrap passes every overlay layer to mount(2) in a single options string. The kernel keeps at most one page of that string and silently drops the rest, so there is a hard limit on how many --overlay-src layers can be stacked. The limit is not documented, and going over it does not produce an error that points at the layer count. It usually produces Overlay directories may not overlap, which is actively wrong: the directories do not overlap, the truncation made them look like they do.
What I hit
Running a container image with 31 overlay layers under bubblewrap 0.12.0:
bwrap: Can't make overlay mount on /... with options lowerdir=...: \
Overlay directories may not overlap
The layers are all distinct sibling directories. Nothing overlaps.
What actually happened is that all the layers live under one layer store, so the paths shared a long prefix and each entry was about 132 bytes:
/oldroot/.../common-storage/overlay/<64 hex chars>/diff
31 of those plus lowerdir= and ,userxattr comes to about 4110 bytes. copy_mount_options() keeps PAGE_SIZE - 1 = 4095, so the 31st path was cut in the middle and became /oldroot/.../common-storage/overlay — the parent of all the other layers. At that point overlayfs is right: one lower layer really is an ancestor of the others. The overlap was manufactured by the truncation.
This cost a while to diagnose, even with the help of Claude Code, because the error message is accurate about what the kernel saw and says nothing about what went wrong.
Current state on main and the just released 0.13.0
Commit c77dd38 ("setup: Use O_PATH fds and disallow symlink targets") replaced the absolute paths with /proc/self/fd/N, which is about 18 bytes per layer instead of 132 (in my case). That raises the ceiling a lot, but it is still a ceiling, and it is still reported as something unrelated to the layer count.
Measured on 8e5e757, Linux 6.12, x86_64, unprivileged:
cd "$(mktemp -d)"
for i in $(seq 1 250); do mkdir -p "l$i"; done
args=$(for i in $(seq 1 250); do printf -- '--overlay-src %s/l%s ' "$PWD" "$i"; done)
bwrap --ro-bind / / $args --ro-overlay /mnt true
- 230 layers: works.
- 250 layers: fails.
The failure is a 4495-byte error message ending in:
bwrap: Can't make overlay mount on (null) with options lowerdir=/proc/self/fd/7:...
...:/proc/self/fd/256,userxattr: Invalid argument
Two separate problems visible in that one line:
- The destination prints as
(null). The loop that collects the lower layers advancesopto the lastSETUP_OVERLAY_SRC, and those ops carry only a source, soop->destis NULL by the time the error is formatted. - The diagnosis is still wrong. Here the truncated
/proc/self/fd/NNNhappens to land on a different valid fd rather than on an ancestor directory, so the kernel saysInvalid argumentinstead of complaining about an overlap. Which wrong answer you get depends only on what the cut path happens to name.
So on current main the limit is roughly 230 layers rather than roughly 30, but the shape of the bug is unchanged.
Why this is worth fixing rather than documenting
Container layer stores routinely produce tall stacks, and the failure mode scales badly: the more layers a user has, the more likely the truncated path lands on their shared layer-store parent, which is exactly the case that yields the confusing overlap message.
The layer paths are also not under the user's control in the common case — they come out of whatever produced the image — so "use shorter paths" is not a real workaround. Squashing layers is, but only if you know that is what the error means.
Suggested fix
fsconfig(FSCONFIG_SET_STRING, "lowerdir+", ...) appends one layer per call and accumulates the list inside the kernel, so there is no options string to overflow. Mounting the overlay with fsopen() + one fsconfig("lowerdir+") per layer + fsmount() + move_mount() removes the limit entirely; the only remaining bound is overlayfs's own OVL_MAX_STACK.
I confirmed this works for an ordinary unprivileged user inside unshare(CLONE_NEWUSER | CLONE_NEWNS), which is bubblewrap's model: 300, 400 and 499 layers all mount with every layer visible, and layer 500 fails with EINVAL, which is OVL_MAX_STACK. Unlike open_tree(OPEN_TREE_CLONE), fsopen() creates a new mount instead of cloning an existing one, so none of the cross-namespace restrictions apply.
overlayfs only accepts lowerdir+ from Linux 6.7 on, so older kernels need to keep the mount(2) path. On that path the length should at least be checked up front, so the error names the layer count instead of letting the kernel truncate and overlayfs guess.
This would be a third use of the new mount API after #754 / #756. It is unrelated to #755 beyond the shared theme: that one is about open_tree() for --[ro-]bind-fd and a path-lookup race, this one is a length limit. (I'm going to add a comment to #755 to report on my findings while trying to work that one out.)
I have a patch series implementing the above and will open a PR.
Version / environment
- bubblewrap 0.12.0 for the original report; also reproduced on 8e5e757 (with a larger number of layers).
- Linux 6.12, x86_64.
- Unprivileged (user namespaces), not setuid.
- Lingua principale
- C
- Stelle
- 9k
- Fork
- 393
- Merge medio
- 1g 32m
- PR unite (30g)
- 7
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di containers/bubblewrap
-
README: writable --bind paths are not isolated from hardlinks created outside the sandboxForse già presa @sairex53 l’ha presa 3 giorni fa. Aperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
containers/bubblewrap#813 · 3 commenti · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
containers/bubblewrap#767 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Related project comparisons in README.md are outdatedForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
containers/bubblewrap#743 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
containers/bubblewrap#298 · 4 commenti · 5 reazioni ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
containers/bubblewrap#811 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di containers/bubblewrap
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 80/100
I maintainer di solito rispondono entro 1 giorno
-
`inverse()` of a controlled `CUGate` drops the phase parameter γForse già presa @rupasri118 l’ha presa oggi. Apertabug mod: circuit
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
Qiskit/qiskit#17223 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
OpenPrinting/libcupsfilters#284 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
libsdl-org/SDL#16530 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
FriendsofECCE/ECCE#336 · 1 commento ·