From 07a4b904c4410939c92c47bd941e8ece42d7e45c Mon Sep 17 00:00:00 2001 From: patrick Date: Fri, 16 Oct 2026 13:12:21 +0000 Subject: [PATCH 1/2] archive-kanban: RET-11 Nachtrag - chattr +i EPERM ist Infrastrukturgrenze, kein Code-Bug Reale Reproduktion auf Host 204 (chattr, roher FS_IOC_SETFLAGS-Ioctl, Gegenprobe auf tmpfs im selben Container) zeigt: Ursache ist der unprivilegierte LXC-Container (capable(CAP_LINUX_IMMUTABLE) prueft immer gegen den Host-Namespace, nicht gegen den Container), nicht ZFS wie bisher in RET-11-PRUEFPROTOKOLL.md vermutet - betrifft jedes Dateisystem im Container. Acht geprueften Alternativen (ZFS readonly/ hold, ACL/chmod, Proxmox-Features, Capabilities, verschachtelte User-Namespaces, FUSE, externes Object-Lock-Backend) sind im Container-Scope entweder nicht verfuegbar oder strukturell keine vollwertige Entsprechung. Keine Aenderung an wormstore.go, da der bestehende Fail-Closed-Mechanismus (ImmutableLockSupported) bereits korrekt reagiert. Co-Authored-By: Claude Sonnet 5 --- .../RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 archive/docs/RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md diff --git a/archive/docs/RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md b/archive/docs/RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md new file mode 100644 index 00000000..2ba00fba --- /dev/null +++ b/archive/docs/RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md @@ -0,0 +1,180 @@ +# RET-11 – Nachtrag: Untersuchung `chattr +i` auf Host 204/131 (unprivilegierte LXC-Container) + +Bezug: PR #309 (QA-02), #310 (QA-07), #311 (QA-05) sowie +`RET-11-PRUEFPROTOKOLL.md` (Host 131) haben wiederholt denselben Befund +dokumentiert: `lockImmutable`/`Put` in +`archive/internal/wormstore/wormstore.go` schlägt auf Host 204 und 131 +mit `EPERM` fehl. Dieser Nachtrag klärt abschließend, ob das ein +behebbarer Code-/Konfigurationsfehler ist oder eine echte +Infrastrukturgrenze — damit das nicht in jedem weiteren PR erneut +untersucht werden muss. + +**Ergebnis vorweg: echte Infrastrukturgrenze, kein Code-Bug. Keine +Änderung an `wormstore.go` vorgenommen.** Die bisherige Begründung in +`RET-11-PRUEFPROTOKOLL.md` ("ZFS … kein FS_IOC_SETFLAGS-Immutable- +Support in dieser Kombination") wird hier präzisiert/korrigiert: Die +Ursache ist **nicht ZFS**, sondern dass Host 204/131 **unprivilegierte** +LXC-Container sind. Das betrifft jedes Dateisystem darunter (ext4, XFS, +Btrfs eingeschlossen), siehe Prüfung 3 unten. + +## Vorgehen + +Frischer Klon auf Host 204 nach `/root/archive-worm-check-1792155880` +(Testartefakte nach Abschluss entfernt, keine Produktivdaten +berührt — nur synthetische Testdateien wie `probe.txt`). + +## Prüfung 1: Reproduktion des Fehlers + +``` +$ echo "synthetic-test-content" > probe.txt +$ chattr +i probe.txt +chattr: Operation not permitted while setting flags on probe.txt +$ echo $? +1 +``` + +Bestätigt: identischer Fehler wie in PR #309/#310/#311 und +`RET-11-PRUEFPROTOKOLL.md` dokumentiert. + +## Prüfung 2: Ist es wirklich ZFS? + +``` +$ df -T / +Filesystem Type +rpool/data/subvol-1204-disk-0 zfs +$ mount | grep zfs +rpool/data/subvol-1204-disk-0 on / type zfs (rw,relatime,xattr,posixacl,casesensitive) +rpool/data/subvol-1204-disk-1 on /var/archivdms type zfs (...) +``` + +Host 204 ist tatsächlich ein LXC-Container mit ZFS-Root +(`rpool/data/subvol-1204-disk-0`), wie in den PRs beschrieben. + +## Prüfung 3: Container-Privileg statt Dateisystem als eigentliche Ursache + +``` +$ cat /proc/self/uid_map + 0 100000 65536 +$ cat /proc/self/gid_map + 0 100000 65536 +``` + +→ Host 204 ist ein **unprivilegierter** Container (UID 0 im Container += UID 100000 auf dem Proxmox-Host, eigener User-Namespace). + +Gegenprobe auf einem NICHT-ZFS-Dateisystem **im selben Container** +(tmpfs, `/dev/shm`): + +``` +$ echo hi > /dev/shm/probe.txt +$ chattr +i /dev/shm/probe.txt +chattr: Operation not permitted while setting flags on /dev/shm/probe.txt +``` + +Derselbe `EPERM` — obwohl tmpfs nichts mit ZFS zu tun hat. Das +widerlegt die These "ZFS unterstützt kein `FS_IOC_SETFLAGS`": Wäre es +ein ZFS-spezifisches Nicht-Unterstützen, müsste tmpfs funktionieren. + +Direkter Test des rohen Kernel-Ioctls (umgeht `chattr`-Binary und jede +Fehlermeldungs-Umdeutung durch das Tool): + +```python +FS_IOC_SETFLAGS = 0x40086602 +FS_IMMUTABLE_FL = 0x00000010 +fcntl.ioctl(fd, FS_IOC_SETFLAGS, struct.pack("i", FS_IMMUTABLE_FL)) +# -> OSError errno=1 (EPERM) +``` + +`capsh --print` zeigt `cap_linux_immutable` im Bounding/Effective-Set +des Containers — das ist aber irrelevant für diesen Check: Die +Kernel-Prüfung für das Setzen/Löschen des Immutable-Flags +(`vfs_ioc_setflags_prepare` / fs-spezifische `*_ioctl_setflags`, z. B. +`ext4_ioctl_setflags`) ruft `capable(CAP_LINUX_IMMUTABLE)` auf — das ist +die KLASSISCHE, NICHT namensraum-bewusste Prüfung (äquivalent zu +`ns_capable(&init_user_ns, …)`), sie prüft also IMMER gegen den +initialen (Host-)User-Namespace, unabhängig davon, was der Container in +seinem EIGENEN Namespace an Fähigkeiten zu haben glaubt. + +Das ist laut Kernel-Mailingliste (2014) eine **bewusste +Sicherheitsentscheidung**, kein Versehen: Ein Patch, der genau diese +Prüfung namensraum-bewusst machen wollte (`ns_capable(current_user_ns(), +CAP_LINUX_IMMUTABLE)`), wurde von Andy Lutomirski explizit abgelehnt, +mit der Begründung, dass Prozesse sonst durch Erzeugen eines eigenen +User-Namespace das Immutable-Bit manipulieren könnten, was als +Sicherheitsrisiko eingestuft wurde. +Quelle: https://lists.linuxfoundation.org/pipermail/containers/2014-April/034973.html + +**Konsequenz:** Kein Code innerhalb des Containers — unabhängig von +Sprache, Implementierung oder gewähltem Dateisystem — kann +`CAP_LINUX_IMMUTABLE` im Host-Namespace erlangen. Das ist strukturell +unmöglich, solange der Container unprivilegiert bleibt. Das betrifft +nicht nur ZFS, sondern auch ext4/XFS/Btrfs, sobald sie unter einem +unprivilegierten Container gemountet sind. + +**Korrektur an `RET-11-PRUEFPROTOKOLL.md`:** Die dortige +Handlungsempfehlung ("WORM-Basisverzeichnis MUSS auf ext2/3/4, XFS oder +Btrfs liegen, NICHT auf ZFS") ist unvollständig/irreführend — das +Dateisystem ist nicht der limitierende Faktor. Richtig ist: Das +WORM-Basisverzeichnis muss auf einem Host/Container liegen, der dem +Prozess REALE (nicht nur namensraum-gemappte) Root-Rechte gibt +(privilegierter Container, VM oder Bare-Metal) — das Dateisystem +darunter ist dabei zweitrangig, sofern es `chattr`/Ext2-Flags +grundsätzlich unterstützt (ext4/XFS/Btrfs tun das; ob modernes ZFS das +ebenfalls täte, ließ sich auf einem privilegierten Host nicht +verifizieren, ist aber plausibel, siehe TrueNAS-SCALE-Hinweise unten — +irrelevant, weil auf einem privilegierten Host ohnehin auch ZFS in +Frage käme, solange OpenZFS das Flag unterstützt). + +## Geprüfte Alternativen (alle unzureichend oder im Container nicht verfügbar) + +| # | Alternative | Befund | +|---|---|---| +| 1 | `zfs set readonly=on` (Dataset-Ebene) | `zfs`-CLI im Container **nicht vorhanden** (`which zfs` → leer, ZFS-Verwaltung ist beim unprivilegierten LXC-Container nicht delegiert). Selbst wenn verfügbar: Dataset-weit, nicht pro-Objekt — würde auch neue `Put`-Schreibvorgänge blockieren. Wer die Berechtigung hat, es zu setzen, hat auch die, es zurückzusetzen — dieselbe strukturelle Schwäche wie chmod/ACL (siehe unten). Infrastrukturoperation auf Proxmox-Host-Ebene, nicht im Container lösbar. | +| 2 | ZFS-Snapshots / `zfs hold` | Nur Nachweis-/Erkennungsmechanismus (Snapshot zeigt nachträglich, dass sich etwas geändert hat), verhindert aber NICHT das Schreiben/Löschen der aktiven Datei selbst — erfüllt nicht die Kernanforderung "Verhinderung", nur "Erkennung". `zfs`-CLI ohnehin nicht verfügbar. | +| 3 | POSIX-ACLs / Verzeichnis-Schreibschutz (wie in QA-02 als Ersatznachweis erprobt) | Strukturell geprüft per Bind-Mount-Readonly-Test: `mount -o remount,bind,ro` verhindert Schreiben, aber derselbe Account kann sofort `mount -o remount,bind,rw` erneut ausführen und weiterschreiben — **kein Schutz gegen denselben (ggf. kompromittierten) Service-Account**, nur gegen andere Accounts. Das ist dieselbe Einschränkung, die der Code-Kommentar in `wormstore.go` (Zeilen 5-15) bereits für `chmod 0400` korrekt benennt: Löschrecht hängt vom Verzeichnis ab, nicht von der Datei, und der schreibende Account braucht zwingend Verzeichnis-Schreibrecht, um neue Objekte abzulegen — er kann sich damit jederzeit selbst wieder Rechte einräumen. **Bestätigt: taugt nur als Ersatznachweis/Fallback, nicht als vollwertiger Ersatz für echte Storage-Object-Lock.** | +| 4 | Proxmox-Container-Feature (`features: mknod=1` o.ä.) damit `chattr +i` im Container greift | Aus dem Container heraus nicht konfigurierbar (Proxmox-Host-Ebene, kein Zugriff in diesem Auftrag). Fachlich ohnehin wirkungslos: `mknod=1` betrifft `CAP_MKNOD`, nicht `CAP_LINUX_IMMUTABLE`/User-Namespace-Mapping. Einzige wirksame Proxmox-seitige Abhilfe wäre, den Container als PRIVILEGIERT zu betreiben (`unprivileged: 0`) — das hebt die Isolation durch User-Namespace-Mapping komplett auf und ist eine bewusste Sicherheits-Tradeoff-Entscheidung auf Infrastruktur-Ebene, keine Code-Lösung. | +| 5 | Datei-Capability (`setcap cap_linux_immutable+ep `) auf einen Helper-Prozess innerhalb des Containers | Datei-Capabilities wirken nur innerhalb des EIGENEN User-Namespace des Containers — sie können keine Capability im übergeordneten (Host-)Namespace verleihen, gegen den die Kernel-Prüfung tatsächlich läuft. Kein Workaround möglich. | +| 6 | Verschachtelter User-Namespace (`unshare -U`) um "Eigentümer" des Dateisystems zu werden | Wirkt nicht: Das zugrunde liegende Superblock-Objekt (ZFS-Dataset, tmpfs-Mount) gehört dem HOST-User-Namespace (Ahn-Namespace), nicht dem Container. Verschachtelung erzeugt nur weitere Nachfahren-Namespaces, keiner davon wird dadurch zum Eigentümer eines bereits bestehenden, von außen gemounteten Dateisystems. | +| 7 | FUSE-basiertes eigenes WORM-Dateisystem (Durchsetzung rein in Userspace, ganz ohne `chattr`) | Technisch der einzige denkbare Weg, eine Immutable-Garantie komplett ohne `CAP_LINUX_IMMUTABLE` umzusetzen. Aber: `/dev/fuse` ist in diesem Container **nicht vorhanden** (`ls /dev/fuse` → "No such file or directory") — FUSE-Mounts sind nicht an den Container durchgereicht. Erfordert Proxmox-Host-Konfiguration (Device-Passthrough), nicht im Container lösbar. Selbst wenn verfügbar: kein "minimaler Fix in `wormstore.go`" mehr, sondern ein komplett neuer Dienst/eigenes Dateisystem — als mögliche ZUKÜNFTIGE Architekturoption vermerkt, nicht Teil dieses Nachtrags. | +| 8 | Externes echtes Object-Lock-Backend (z. B. MinIO/S3 mit aktiviertem Object Lock Compliance-Mode) statt lokalem Dateisystem | Einzige Option, die eine zu S3 Object Lock GLEICHWERTIGE Garantie ohne Abhängigkeit von Host-Kernel-Capabilities liefern würde (die Durchsetzung läge dann beim Storage-Backend-Prozess, nicht beim Kernel-Dateisystem-Flag des Containers). Das ist aber ein Architekturwechsel (neuer Storage-Treiber statt `wormstore`-Dateisystem-Pfad), kein Fix an `wormstore.go` — außerhalb des Scopes dieses Nachtrags, als Hinweis für eine mögliche künftige Kachel vermerkt. | + +## Prüfung 4: `ImmutableLockSupported` als bestehende Absicherung + +Der Code verfügt bereits über exakt den richtigen Schutzmechanismus für +genau diese Situation: `ImmutableLockSupported(dir)` +(`wormstore.go:237-251`) prüft aktiv, ob das Basisverzeichnis +`chattr +i` durchsetzt, und `Put` schlägt bei fehlgeschlagenem +`lockImmutable` fail-closed fehl (Datei wird wieder entfernt, kein +WORM-Objekt ohne echte Sperre). Das ist bereits das korrekte Verhalten +für eine Infrastrukturgrenze: lieber ehrlich ablehnen/überspringen, als +eine WORM-Garantie vorzutäuschen, die nicht existiert. Hier besteht +kein Nachbesserungsbedarf. + +## Fazit + +Die wiederholt in PR #309/#310/#311 dokumentierte `EPERM`-Meldung ist +**keine behebbare Code- oder Konfigurationslücke**, sondern eine vom +Linux-Kernel bewusst vorgesehene Sicherheitsgrenze: Ein unprivilegierter +Container kann das Immutable-Flag nicht setzen, unabhängig vom +darunterliegenden Dateisystem. Das wurde durch direkte Reproduktion +(`chattr`, roher Ioctl-Aufruf), Gegenprobe auf einer anderen +Dateisystemart (tmpfs) im selben Container und eine bestätigte +Kernel-Design-Entscheidung (2014, LKML) zweifelsfrei nachgewiesen. +Alle acht geprüften Alternativen sind entweder im Container-Scope +technisch nicht verfügbar (ZFS-CLI, FUSE), strukturell keine +vollwertige Entsprechung (ACL/chmod/Dataset-readonly sind vom selben +Account umkehrbar), oder erfordern eine Infrastruktur-/Architektur- +entscheidung außerhalb des Container-Scopes (privilegierter Container, +externes Object-Lock-Backend). + +**Keine Änderung an `wormstore.go` vorgenommen** — der bestehende +Fail-Closed-Mechanismus (`ImmutableLockSupported`, `Put` schlägt +komplett fehl statt eine Pseudo-Sperre vorzutäuschen) ist bereits die +richtige Reaktion auf diese Infrastrukturgrenze. Für einen echten +End-to-End-Nachweis von RET-11 AC1/AC2 wird weiterhin ein Testlauf auf +einem PRIVILEGIERTEN Host/Container benötigt (Dateisystem darunter ist +dabei zweitrangig) — oder ein künftiger Architekturwechsel auf ein +externes Object-Lock-fähiges Storage-Backend (siehe Alternative 8). +Dieser Nachtrag gilt als abschließende Untersuchung; eine erneute +Prüfung derselben Frage in künftigen QA-Runden ist nicht nötig, sofern +sich die Host-Konfiguration (privilegiert/unprivilegiert) nicht ändert. -- 2.47.3 From 215fe15e167bfa1f6ca4b2244ca394d885ed9823 Mon Sep 17 00:00:00 2001 From: patrick Date: Fri, 16 Oct 2026 19:00:30 +0000 Subject: [PATCH 2/2] RET-15: echter-ende-zu-ende-nachweis-storage-object-lock-chattr-faehiger-host Suche nach einem chattr-faehigen Host im Projektkontext (131, 204 bereits bekannt unprivilegiert; zusaetzlich Host 236 geprueft) ergibt: kein privilegierter Host verfuegbar. Host 236 ist ebenfalls ein unprivilegierter LXC-Container (uid_map 0->100000, ZFS-Root), realer chattr +i-Testversuch auf synthetischer Testdatei schlaegt mit EPERM fehl, identisch zu 204/131. AC1/AC2 aus RET-15 bleiben damit offen, dokumentiert als zweiter Nachtrag-Abschnitt in der bestehenden RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md (kein neues Paralleldokument). Keine Aenderung an wormstore.go. Co-Authored-By: Claude Sonnet 5 --- .../RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/archive/docs/RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md b/archive/docs/RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md index 2ba00fba..d9add22f 100644 --- a/archive/docs/RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md +++ b/archive/docs/RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md @@ -178,3 +178,100 @@ externes Object-Lock-fähiges Storage-Backend (siehe Alternative 8). Dieser Nachtrag gilt als abschließende Untersuchung; eine erneute Prüfung derselben Frage in künftigen QA-Runden ist nicht nötig, sofern sich die Host-Konfiguration (privilegiert/unprivilegiert) nicht ändert. + +--- + +## RET-15 Nachtrag: Host-Suche und Ergebnis + +Datum: 2026-10-16 + +Bezug: RET-15 (Folgeticket aus dieser Untersuchung) verlangt einen +echten End-zu-Ende-Nachweis auf einem tatsächlich `chattr +i`-fähigen +Host, statt eine erneute Grundsatzuntersuchung der oben bereits +geprüften und verworfenen Alternativen. Dieser Abschnitt dokumentiert +ausschließlich die Suche nach einem solchen Host und deren Ergebnis. + +### Vorgehen + +1. Repoweite Suche nach allen im Projektkontext dokumentierten Hosts: + `grep -rhoE "192\.168\.1\.[0-9]+" --include="*.md"` über den + gesamten frischen Checkout liefert genau drei IPs: `192.168.1.131`, + `192.168.1.204` (beide bereits oben als unprivilegierte LXC- + Container auf ZFS-Root identifiziert) und `192.168.1.236` + (referenziert in `android/dms-capture/docs/INT-21-PRUEFPROTOKOLL.md` + als Android-SDK-Build-Host). Kein weiterer Host ist in der + Projektdokumentation (`docs/`, `DEVLOG.md`, Kanban-Boards, + Prüfprotokolle) als erreichbar oder privilegiert vermerkt. +2. Host 236 war über SSH erreichbar (`root@192.168.1.236`). Vor dem + eigentlichen `chattr`-Test wurde der Container-Typ geprüft: + + ``` + $ systemd-detect-virt + lxc + $ cat /proc/self/uid_map + 0 100000 65536 + $ df -T / + rpool/data/subvol-1236-disk-0 zfs ... / + ``` + + Identisches Bild wie bei Host 204/131: UID 0 im Container ist auf + UID 100000 im Proxmox-Host gemappt (unprivilegierter Container, + ZFS-Root). `capsh --print` zeigt `cap_linux_immutable` zwar im + Bounding-Set, was laut der oben belegten Kernel-Prüfung + (`capable(CAP_LINUX_IMMUTABLE)` gegen den initialen User-Namespace, + nicht namensraum-bewusst) für sich genommen nichts aussagt. +3. Realer Test trotzdem durchgeführt (synthetische Testdatei, keine + Produktivdaten): + + ``` + $ echo "synthetic-test-content-ret15" > /tmp/ret15_probe.txt + $ chattr +i /tmp/ret15_probe.txt + chattr: Operation not permitted while setting flags on ret15_probe.txt + $ echo $? + 1 + ``` + + Testdatei anschließend entfernt (`rm -f /tmp/ret15_probe.txt`). + +### Ergebnis + +Host 236 ist, wie 204 und 131, ein unprivilegierter LXC-Container und +zeigt denselben `EPERM`-Fehlschlag. Er ist damit **kein** geeigneter +Host für den in RET-15 AC1/AC2 geforderten Nachweis. Im gesamten +projektbekannten, erreichbaren Infrastruktur-Bestand (131, 204, 236) +gibt es keinen Host mit echten, nicht nur namensraum-gemappten +Root-Rechten. + +Eine Ausweitung der Suche auf den darunterliegenden Proxmox-Hypervisor +selbst oder auf weitere, im Projekt nicht dokumentierte Hosts wurde +NICHT vorgenommen — das wäre keine "bekannte, projektkontextbezogene" +Umgebung mehr, sondern eine Grundsatz-Infrastruktur-Recherche außerhalb +des RET-15-Scopes (siehe "Nicht Bestandteil" im Ticket). + +**AC1/AC2 bleiben damit offen, wie bereits im ursprünglichen Nachtrag +festgehalten.** Das ist nach den 8 bereits geprüften Alternativen und +dieser zusätzlichen, ergebnislosen Host-Suche ein ehrliches, nicht +weiter durch Workarounds umgehbares Ergebnis — keine Lücke in der +Untersuchung, sondern eine reale Grenze der aktuell verfügbaren +Infrastruktur. + +**Bedingung, die den Nachweis ermöglichen würde** (unverändert +gegenüber der Einschätzung oben, hier noch einmal explizit als Antwort +auf RET-15 Prüfung 2 festgehalten): + +- Ein tatsächlich privilegierter Produktions-Host/Container + (`unprivileged: 0` in der Proxmox-Konfiguration, oder eine VM/ + Bare-Metal-Maschine) mit `chattr`-fähigem Dateisystem (ext4, XFS, + Btrfs; ZFS plausibel, aber auf keinem der verfügbaren Hosts + verifizierbar) — dann liefe derselbe bereits implementierte und + fail-closed abgesicherte Code (`wormstore.Put`/`ImmutableLockSupported`) + real durch, ohne jede Codeänderung. +- Ersatzweise ein Architekturwechsel auf ein echtes S3/MinIO-Backend + mit aktiviertem Object-Lock Compliance-Mode (bereits als Alternative 8 + oben vermerkt) — das verlagert die Durchsetzung vom Kernel-Dateisystem- + Flag auf den Storage-Backend-Prozess und wäre damit unabhängig von der + Privilegstufe des Containers, in dem NEXARCH läuft. + +Keine Änderung an `wormstore.go` oder an anderem Produktivcode. Kein +neues Paralleldokument — dieser Abschnitt ergänzt ausschließlich den +bestehenden Nachtrag. -- 2.47.3