Das Problem

Der UCS-DC bootet. Alles sieht normal aus. Und dann… macht der Server einfach nichts mehr.

free -h zeigt ein Bild, das man lieber nicht sehen möchte:

              total        used        free      shared  buff/cache   available
Mem:           7.9G        7.9G         12M       2.1M       142M         0B
Swap:          2.0G        2.0G          0B

8 GB RAM. Voll. Swap. Voll. Und das System hat gerade erst gebootet.

Die betroffenen Services:

● univention-directory-policy.service - Loaded: loaded; Active: failed
● univention-maintenance.service      - Loaded: loaded; Active: failed

journalctl lädt… lädt… lädt. Und dann kommt die Zahl, die alles erklärt:

-- Journal begins at Tue 2026-04-07 00:00:01 CET,
   ends at Wed 2026-04-08 09:14:22 CEST. --
Apr 08 06:31:44 dc-vader kernel: [  12.441] systemd[1]: Starting Univention Directory Policy...
Apr 08 06:31:45 dc-vader univention-policy-repository-sync[1337]: 
   AddressSanitizer:DEADLYSIGNAL
   <2.637.914 weitere Meldungen unterdrückt>

2,6 Millionen Log-Zeilen. Beim Boot. Von einem einzigen Prozess.

Willkommen in der wunderbaren Welt von AddressSanitizer in Produktion.


Die Diagnose

Schritt 1: Wer frisst den RAM?

ps aux --sort=-%mem | head -5
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      1337 99.1 94.7 21474836480 7892M ?   Ss   06:31   2:14 /usr/bin/python3 /usr/sbin/univention-policy-repository-sync

Da ist er. Ein einzelner Python-Prozess. 94,7 % RAM. 7,9 GB RSS. 21 TB VSZ.

21 Terabyte virtueller Adressraum. Ein Python-Prozess. Auf einem Server mit 8 GB RAM.

Das ist kein Speicherleck. Das ist kein schlechter Code. Das ist AddressSanitizer.

Schritt 2: OOM-Kills bestätigen

dmesg | grep -i oom
[   15.442] Out of memory: Killed process 1337 (python3) total-vm:21474836480kB, anon-rss:8085880kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:67328kB oom_score_adj:0
[   15.443] oom_kill_process: Kill process 1337 (python3) score 997 or sacrifice child

Score 997. Der OOM-Killer hat praktisch keine Wahl.

Schritt 3: Die Root Cause - libasan.so.5

ldd /usr/bin/univention_policy_result
        linux-vdso.so.1 (0x00007ffd1e9fd000)
        libasan.so.5 => /lib/x86_64-linux-gnu/libasan.so.5 (0x00007f8b3c200000)
        libuniventionconfig.so.0 => /usr/lib/libuniventionconfig.so.0 (0x00007f8b3be00000)
        libldap-2.5.so.0 => /usr/lib/x86_64-linux-gnu/libldap-2.5.so.0 (0x00007f8b3b900000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b3b600000)
        ...

libasan.so.5. AddressSanitizer. Direkt gelinkt. In einem Production-Binary.

Das ist das /usr/bin/univention_policy_result-Binary aus dem Paket univention-policy-tools 11.0.4-4, das mit UCS 5.0-9 ausgeliefert wurde.

dpkg -l univention-policy-tools
ii  univention-policy-tools  11.0.4-4  amd64  Univention Policy Tools

Version bestätigt. Debug-Build in Produktion bestätigt.

Schritt 4: journald unter Last

Warum war journald bei 50-60 % CPU? Weil es 2,6 Millionen Zeilen in die Datenbank schreiben wollte:

journalctl --disk-usage
Archived and active journals take up 4.2G in the filesystem.

4,2 GB Journals. Für einen einzigen Boot-Vorgang. Das sind ausschließlich AddressSanitizer:DEADLYSIGNAL-Einträge im Millionenformat.


Die Ursache

Was ist AddressSanitizer?

AddressSanitizer (ASan) ist ein Memory-Error-Detector, entwickelt von Google. Wenn man ein Programm mit -fsanitize=address kompiliert, passiert folgendes:

  1. Shadow Memory Mapping: ASan reserviert 1/8 des gesamten Adressraums als Shadow Memory. Auf einem 64-Bit-System sind das theoretisch ~18 TB, in der Praxis reserviert ASan typischerweise ~21 TB virtuellen Adressraum.
  2. Jeder Speicherzugriff wird instrumentiert: Vor jedem Read/Write prüft ASan über das Shadow Memory, ob der Zugriff gültig ist.
  3. Performance-Overhead: Ca. 2x langsamer, 1,5-3x mehr RAM-Bedarf.

Das ist ein Entwicklungs- und Test-Tool. Es gehört nicht in Produktion. Niemals.

Warum ist es in UCS 5.0-9?

Das ist eine gute Frage, die Univention beantworten muss. Vermutlich ist ein Debug-Build versehentlich in den Release-Prozess gerutscht. Das passiert – aber für ein Production-Paket ist das ein schwerwiegender Fehler im Release-Management.

Das binary /usr/bin/univention_policy_result wurde mit ASan kompiliert und das Paket ohne Qualitätsprüfung ausgeliefert.

Warum explodiert es erst im LXC-Container?

Auf einer vollwertigen VM oder einem Bare-Metal-Server mit viel RAM fällt es möglicherweise nicht auf: 21 TB virtueller Adressraum ist kein Problem, solange niemand wirklich 21 TB RAM hat. Der Prozess würde trotzdem langsamer sein, aber der OOM-Killer würde nicht sofort eingreifen.

In einem LXC-Container mit 8 GB RAM passiert folgendes:

  1. ASan startet und versucht, das Shadow Memory zu mappen
  2. Das Mapping schlägt fehl (kein Speicher)
  3. ASan bricht mit DEADLYSIGNAL ab
  4. Aber vorher hat der Prozess bereits genug echten RAM allokiert, um alles andere zu verdrängen
  5. OOM-Killer beendet den Prozess
  6. Systemd startet ihn neu
  7. Das gleiche passiert wieder - in einer Schleife, bis der Service als failed markiert wird

Die Lösung

Workaround: ASan per Umgebungsvariable zähmen

Bis Univention ein Errata liefert, kann ASan per ASAN_OPTIONS so konfiguriert werden, dass es keinen Schaden anrichtet:

Systemd-Override anlegen:

mkdir -p /etc/systemd/system/univention-directory-policy.service.d/
cat > /etc/systemd/system/univention-directory-policy.service.d/asan-workaround.conf << 'EOF'
[Service]
Environment="ASAN_OPTIONS=abort_on_error=0:detect_leaks=0:log_path=/dev/null:disable_coredump=1:allocator_may_return_null=1"
EOF

Das gleiche für univention-maintenance:

mkdir -p /etc/systemd/system/univention-maintenance.service.d/
cat > /etc/systemd/system/univention-maintenance.service.d/asan-workaround.conf << 'EOF'
[Service]
Environment="ASAN_OPTIONS=abort_on_error=0:detect_leaks=0:log_path=/dev/null:disable_coredump=1:allocator_may_return_null=1"
EOF

Die wichtigsten Optionen erklärt:

OptionBedeutung
abort_on_error=0Bei Fehler kein SIGABRT mehr, Prozess läuft weiter
detect_leaks=0LeakSanitizer deaktivieren (spart RAM)
log_path=/dev/nullKeine DEADLYSIGNAL-Spam mehr in journald
disable_coredump=1Kein Coredump bei Crash
allocator_may_return_null=1Bei OOM null zurückgeben statt aborten
systemctl daemon-reload

Services temporär deaktivieren (optional, aber empfehlenswert)

Bis zum Errata-Fix können beide Services sicher deaktiviert werden. univention-directory-policy synchronisiert UCR-Policies aus dem LDAP – wichtig für Policy-Änderungen, aber kein kritischer Daemon für den laufenden Betrieb:

systemctl disable --now univention-directory-policy.service
systemctl disable --now univention-maintenance.service

Wann wieder aktivieren? Sobald Univention univention-policy-tools in einer bereinigten Version ausliefert. Prüfen mit:

apt-cache policy univention-policy-tools

Sobald eine Version > 11.0.4-4 verfügbar ist: Services wieder aktivieren, Override entfernen, testen.

Kollateralschaden: zabbix-agent2

Durch den RAM-Engpass beim Boot scheitert nss-ldap manchmal, bevor zabbix-agent2 startet. Das erzeugt einen kaputten PID-Directory-Fehler:

journalctl -u zabbix-agent2 --no-pager | tail -20
Apr 08 06:31:55 dc-vader zabbix_agent2[2112]: cannot create PID file [/run/zabbix/zabbix_agent2.pid]: [13] Permission denied

Fix:

mkdir -p /run/zabbix
chown zabbix:zabbix /run/zabbix
chmod 755 /run/zabbix

Damit das nach jedem Reboot erhalten bleibt:

cat > /etc/tmpfiles.d/zabbix-agent2.conf << 'EOF'
d /run/zabbix 0755 zabbix zabbix -
EOF
systemd-tmpfiles --create /etc/tmpfiles.d/zabbix-agent2.conf
systemctl restart zabbix-agent2

Journal aufräumen

Nach dem Workaround lohnt es sich, den Journal-Müll zu beseitigen:

journalctl --vacuum-size=500M

Lessons Learned

1. ASan in Production-Binaries ist ein Release-Management-Versagen.

Ein einfaches ldd /usr/bin/univention_policy_result | grep asan in der CI/CD-Pipeline hätte das verhindert. Das ist kein exotischer Check – das ist Basisqualitätssicherung für System-Packages.

2. Symptome und Root Cause können weit auseinanderliegen.

Das erste sichtbare Symptom war „Service failed". Die naheliegendste Diagnose wäre: „LDAP nicht erreichbar", „Python-Script kaputt", „Config-Fehler". Die tatsächliche Ursache war eine 20 Jahre alte Debug-Toolchain, die versehentlich in einem Production-Build landete.

3. 21 TB VSZ ist kein Speicherleck - es ist ein Red Flag.

Ein normaler Python-Prozess hat nie 21 TB virtuellen Adressraum. Wenn ps aux solche Zahlen zeigt, ist das ein sofortiger Hinweis auf ASan, Valgrind oder ähnliche Instrumentierungs-Tools.

4. ldd auf verdächtige Binaries ist unterschätzt.

ldd /usr/bin/<verdächtiges-binary> | grep -E 'asan|tsan|ubsan|msan'

Wenn dieser Befehl Output produziert: Das Binary gehört nicht in Produktion.

5. LXC-Container mit wenig RAM sind ehrlicher als fette VMs.

Auf einem Server mit 128 GB RAM wäre das Problem wahrscheinlich nie aufgefallen – der Prozess wäre nur langsamer gewesen. Tight-memory-Umgebungen zwingen dazu, solche Fehler zu finden.


Meldung an Univention

Das sollte als Bug gemeldet werden. Univention betreibt ein öffentliches Bug-Tracking-System:

  • Bugzilla: https://forge.univention.org/bugzilla/
  • Betreff: univention-policy-tools 11.0.4-4: Production binary linked against libasan.so.5 (ASan debug build)
  • Paket: univention-policy-tools
  • Version: 11.0.4-4
  • UCS-Version: 5.0-9

Je mehr Meldungen, desto schneller kommt das Errata.


Reproduziert auf: UCS 5.0-9, LXC-Container, dc-vader.deathstar.lan, 192.168.66.x-Netz Fix getestet: 2026-04-08