Debian 13 «Segfault» аварії після оновлення: знайдіть точну невідповідність бібліотеки (Випадок №55)

Було корисно?

Ви оновили хост Debian, він завантажився, служби запустилися… а потім випадкові процеси почали падати з тією самою холодною фразою: Segmentation fault.
Жодної зрозумілої помилки, жодної очевидної закономірності. Просто ядро сипле core-файли як конфетті, а канал чергування голоснішає.

Цей випадок (№55) про те, як перетворити туманне «після оновлення segfault» на конкретну, іменовану помилку: точну невідповідність спільної бібліотеки
(який файл, який пакет, який символ/ABI), плюс шлях прийняття рішення, як це виправити, не погіршивши систему.

Що насправді відбувається, коли «воно дає segfault»

Після масштабного оновлення «segfault» зазвичай не є загадкою сучасних обчислень. Це помилка обліку.
Процес завантажив набір спільних об’єктів, які не погоджуються щодо контракту: ABI, версій символів, конвенцій виклику, розташування структур,
або навіть припущень про локальне сховище потоків. CPU виконує те, що йому наказали, а не те, що ви мали на увазі.

У Debian ці контракти зазвичай забезпечуються дисципліною пакування: залежностями, версіонуванням символів, shlibs, тригерами,
і простим правилом, що не слід змішувати бібліотеки стабільного релізу з випадковими бінарними файлами в /usr/local.
Коли після оновлення ви отримуєте segfault, часто це одна з наступних ситуацій:

  • Часткове оновлення: у вас новий елемент користувацького простору працює зі старими бібліотеками (або навпаки).
  • Затінені бібліотеки: /usr/local/lib або бандл програми перекриває бібліотеки дистрибутива.
  • Дива з RPATH/RUNPATH: бінар має зашиті шляхи пошуку, що перемагають розумні налаштування.
  • Неправильна архітектура / змішана multiarch: завантажувач знаходить бібліотеку з тим же ім’ям, але несумісного ELF-класу/архітектури.
  • Плагіни поза деревом: модулі, скомпільовані проти старих заголовків, завантажуються в новий процес і псуватимуть пам’ять.
  • Пошкоджені файли: менш поширено, але перерване оновлення або проблема диска можуть зробити ELF-об’єкти тонко зламаними.

Ваше завдання не «зупинити segfault». Ваше завдання — «виявити невідповідний об’єкт і довести це». Коли ви можете назвати файл і пакет,
виправлення стають нудними: перевстановити, прибрати перекриття, узгодити версії, перебудувати плагіни або відкотити зміни.

Швидкий план діагностики

Якщо ви в продакшні, ви не починаєте з філософської налагодження. Ви починаєте з найшвидшого шляху звузити радіус ураження і
отримати конкретну гіпотезу.

1) Спочатку: підтвердьте, що це динамічний завантажувач, а не ядро чи апаратне забезпечення

  • Перевірте journalctl на наявність послідовних IP/імен бібліотек у повідомленнях про падіння.
  • Підтвердьте, чи це багато бінарників або одна служба.
  • Проскануйте на предмет очевидної участі /usr/local або кастомного завантажувача.

2) Друге: заберіть один core dump і отримаєте backtrace з шляхами бібліотек

  • Використовуйте coredumpctl або шлях до core-файлу.
  • У gdb виведіть завантажені спільні бібліотеки і backtrace.
  • Визначте перший «дивний» шлях бібліотеки, версію або відсутню інформацію про символи.

3) Третє: перевірте цілісність пакета і узгодженість версій

  • apt policy для підозрюваних бібліотек і бінарника, що падає.
  • dpkg -V та debsums для виявлення корупції/перезаписів.
  • Шукайте утримані пакети, диверсії та пріоритети pin.

4) Четверте: простежте рішення завантажувача

  • LD_DEBUG=libs,versions для аварійного бінарника (в безпечному середовищі).
  • Підтвердіть, яка саме .so була завантажена, з якої директорії і чому.

5) П’яте: виправляйте з мінімальним радіусом ураження

  • Віддавайте перевагу видаленню перекриттів і перевстановленню офіційних пакетів.
  • Не «виправляйте через симлінки», якщо не хочете ганятися за привидами пізніше.

Цитата, яка допоможе не заблукати. Вернер Фогельс (CTO Amazon) приблизно сказав—перефразована думка:
«Усе ламається постійно; проектуйте і експлуатуйте, припускаючи відмову». Операторський варіант: припускайте, що оновлення може залишити після себе змішаний світ.

Цікаві факти та контекст (щоб ви перестали дивуватися)

  1. «Segmentation fault» старший за вашу кар’єру. Термін походить від сегментації пам’яті в ранніх режимах захисту, задовго до появи Linux.
  2. Linux повідомляє «segfault», навіть коли корінь проблеми — порушення ABI. CPU фейлить; ОС звітує; реальна помилка — на вищому рівні.
  3. Версіонування символів у glibc — сила стабільності. Бібліотеки можуть експортувати кілька версій одного й того самого символу, щоб старі бінарники продовжували працювати — поки ви не обійдете систему.
  4. Пакування Debian має всю систему метаданих для спільних бібліотек. Механізм shlibs/symbols існує, щоб запобігти саме цьому, але він безсилий проти локальних перекриттів.
  5. RPATH з’явився першим; RUNPATH — пізніше. RUNPATH змінює, як вирішуються транзитивні залежності; це може зробити помилки «працює на одній машині» нестерпними.
  6. /usr/local історично — для локальних збірок адміністратора. Воно передує культурі контейнерів і досі поширене місце для закладення часових бомб.
  7. Крупні оновлення виявляють «борг невизначеної поведінки». Програма, яка «працювала», покладаючись на UB, може зламатися, коли змінюються компілятор, libc або алокатор.
  8. Проблеми ABI в C++ — повторюваний жанр. Змішування версій компілятора й очікувань libstdc++ може викликати падіння, що виглядає як випадкова корупція пам’яті.

Жарт №1: Segfault — це просто спосіб вашої програми сказати, що їй би хотілося на деякий час полежати в чужій пам’яті.

Практичні завдання: команди, виводи та рішення

Це не «грубо спробуй». Кожне завдання включає те, що ви шукаєте, і яке рішення застосувати далі.
Виконуйте їх на ураженому хості або, ще краще, на клоні/снапшоті, якщо працюєте з продакшеном.

Завдання 1: Підтвердьте підпис аварії в журналі

cr0x@server:~$ journalctl -b -p warning..alert | grep -E "segfault|SIGSEGV|coredump" | tail -n 30
Dec 30 10:21:14 server kernel: myapp[22198]: segfault at 0 ip 00007f2d2a8f4c90 sp 00007ffd3b2a1c10 error 4 in libssl.so.3[7f2d2a860000+87000]
Dec 30 10:21:14 server systemd-coredump[22213]: Process 22198 (myapp) of user 1001 dumped core.
Dec 30 10:21:18 server kernel: myapp[22302]: segfault at 0 ip 00007f2d2a8f4c90 sp 00007ffd3b2a1c10 error 4 in libssl.so.3[7f2d2a860000+87000]

Що це означає: Якщо ядро називає бібліотеку (in libssl.so.3), це ваш перший підозрюваний, а не останній.
IP, що впав, всередині бібліотеки вказує або на реальну помилку в тій бібліотеці, або (більш ймовірно після оновлення) на невідповідність між тим, хто викликав, і тим, кого викликали.

Рішення: Візьміть один PID/core, що падає, і дослідіть точний шлях файлу бібліотеки та версію пакета цієї бібліотеки.

Завдання 2: Подивіться, чи збіги поширені чи локалізовані

cr0x@server:~$ coredumpctl list --no-pager | tail -n 10
TIME                            PID   UID   GID SIG COREFILE  EXE
Mon 2025-12-30 10:21:14 UTC   22198  1001  1001  11 present   /usr/local/bin/myapp
Mon 2025-12-30 10:21:18 UTC   22302  1001  1001  11 present   /usr/local/bin/myapp
Mon 2025-12-30 10:22:03 UTC   22511     0     0  11 present   /usr/sbin/nginx

Що це означає: Якщо падають і системні бінарники (/usr/sbin/nginx), і локальні (/usr/local/bin/myapp),
можливо у вас проблема з системною бібліотекою. Якщо падають лише /usr/local, почніть з недовіри до /usr/local.

Рішення: Якщо падають системні бінарники, пріоритетно перевіряйте основні системні бібліотеки і цілісність пакетів.
Якщо лише локальні застосунки падають — зосередьтеся на шляхах завантаження, бандлених бібліотеках і припущеннях при збірці.

Завдання 3: Ідентифікуйте базу ОС та архітектуру

cr0x@server:~$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
VERSION="13 (trixie)"
ID=debian
cr0x@server:~$ uname -a
Linux server 6.12.0-1-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.0-1 (2025-11-20) x86_64 GNU/Linux

Що це означає: Ядро/архітектура важливі для символів налагодження і для відсіву «неправильної архітектури» бібліотек.

Рішення: Якщо бачите змішані архітектури (наприклад, i386 на amd64), будьте готові перевірити multiarch шляхи і вибір завантажувача.

Завдання 4: Перевірте походження виконуваного файлу і його лінкування

cr0x@server:~$ file /usr/local/bin/myapp
/usr/local/bin/myapp: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b7c7d6..., for GNU/Linux 3.2.0, stripped

Що це означає: «dynamically linked» означає, що ви в зоні спільних бібліотек. «stripped» означає, що backtrace буде менш інформативним.

Рішення: Якщо бінар локальний і очищений від символів, заплануйте встановлення символів налагодження для системних бібліотек і, коли можливо, отримайте незамінений (unstripped) збірник.

Завдання 5: Швидкий перегляд залежностей через ldd (але не боготворіть його)

cr0x@server:~$ ldd /usr/local/bin/myapp | head -n 20
	linux-vdso.so.1 (0x00007ffd7b7d7000)
	libssl.so.3 => /usr/local/lib/libssl.so.3 (0x00007f2d2a860000)
	libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x00007f2d2a3d0000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2d2a1ef000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f2d2a9f0000)

Що це означає: Завантажувач обирає /usr/local/lib/libssl.so.3 замість пакетованого OpenSSL Debian в
/usr/lib/x86_64-linux-gnu. Це величезний червоний прапор після оновлення.

Рішення: Вважайте /usr/local/lib/libssl.so.3 підозрілим №1. Підтвердіть його версію і чи відповідає він очікуваному ABI.

Завдання 6: Запитайте динамічний завантажувач, що він робить (LD_DEBUG)

cr0x@server:~$ LD_DEBUG=libs,versions /usr/local/bin/myapp 2>&1 | head -n 40
     22891:	find library=libssl.so.3 [0]; searching
     22891:	 search path=/usr/local/lib:/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu	(system search path)
     22891:	  trying file=/usr/local/lib/libssl.so.3
     22891:	find library=libcrypto.so.3 [0]; searching
     22891:	  trying file=/usr/local/lib/libcrypto.so.3
     22891:	checking for version `OPENSSL_3.2.0' in file /usr/local/lib/libssl.so.3 [0] required by file /usr/local/bin/myapp [0]
     22891:	checking for version `GLIBC_2.38' in file /lib/x86_64-linux-gnu/libc.so.6 [0] required by file /usr/local/lib/libssl.so.3 [0]

Що це означає: Тепер у вас є доказ порядку розв’язування та вимог версій символів.
Якщо ви бачите, що вимагається версія символу, якої не забезпечують встановлені бібліотеки, отримаєте помилки завантажувача.
Але segfault-и відбуваються, коли завантажувач успішний, а ABI все одно не відповідає очікуванням виклику.

Рішення: Якщо пріоритет шляху неправильний — виправте пріоритет. Не намагайтесь «підлатати» ABI під час виконання.

Завдання 7: Підтвердіть, який пакет володіє підозрілою бібліотекою (або що жоден)

cr0x@server:~$ dpkg -S /usr/local/lib/libssl.so.3
dpkg-query: no path found matching pattern /usr/local/lib/libssl.so.3

Що це означає: Це не файл, яким керує Debian. Може бути вручну встановлений OpenSSL, tarball від вендора або залишки «швидкого виправлення».

Рішення: Якщо криптобібліотека не керується пакетом, наполегливо розгляньте видалення її з шляхів завантажувача і використання пакетованої бібліотеки Debian.

Завдання 8: Проінспектуйте ELF-метадані підозрілої бібліотеки та BuildID

cr0x@server:~$ readelf -n /usr/local/lib/libssl.so.3 | sed -n '1,80p'
Displaying notes found in: .note.gnu.build-id
  Owner                Data size 	Description
  GNU                  0x00000014	NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 4b2f5d0a1c6b0f7a9e6d5d3a2b1f0c9d8e7a6b5c

Що це означає: BuildID дозволяє зв’язати бінарники зі символами налагодження та відомими збірками. Локальна бібліотека з випадковим BuildID — ще один підказник.

Рішення: Якщо не можете відстежити походження, плануйте замінити її на пакетовану бібліотеку або перебудувати в контрольованому пайплайні.

Завдання 9: Перевірте версію бібліотеки OpenSSL в Debian і кандидати

cr0x@server:~$ apt-cache policy libssl3
libssl3:
  Installed: 3.3.2-1
  Candidate: 3.3.2-1
  Version table:
 *** 3.3.2-1 500
        500 http://deb.debian.org/debian trixie/main amd64 Packages
        100 /var/lib/dpkg/status

Що це означає: OpenSSL від Debian встановлений і сумісний із релізом.
Якщо ваш додаток його не використовує, у вас проблема перекриття, а не «Debian зламав крипто».

Рішення: Віддавайте перевагу /usr/lib/x86_64-linux-gnu/libssl.so.3 від Debian, якщо у вас немає вагомих, перевірених причин інакше.

Завдання 10: Перевірте конфігурацію завантажувача і шляхи пошуку

cr0x@server:~$ grep -R --line-number "/usr/local/lib" /etc/ld.so.conf /etc/ld.so.conf.d/* 2>/dev/null
/etc/ld.so.conf.d/local.conf:1:/usr/local/lib
cr0x@server:~$ ldconfig -v 2>/dev/null | head -n 25
/usr/local/lib: (from /etc/ld.so.conf.d/local.conf:1)
	libssl.so.3 -> libssl.so.3
	libcrypto.so.3 -> libcrypto.so.3
/usr/lib/x86_64-linux-gnu:
	libssl.so.3 -> libssl.so.3
	libcrypto.so.3 -> libcrypto.so.3

Що це означає: Кеш завантажувача системи включає обидві бібліотеки, але пріоритет фактично дає /usr/local/lib місце в першому ряду.
Так ви отримуєте невідповідності після оновлення: бібліотеки дистрибутива змінилися, ваші локальні — ні (або змінилися інакше).

Рішення: Видаліть /usr/local/lib з глобальної конфігурації завантажувача, якщо ви не впевнені, що хочете, щоб воно перекривало бібліотеки дистрибутива.
Якщо вам потрібні локальні бібліотеки, обмежуйте їх використання для конкретних застосунків, а не глобально.

Завдання 11: Захопіть core dump і витягніть завантажені бібліотеки

cr0x@server:~$ coredumpctl info /usr/local/bin/myapp | sed -n '1,80p'
           PID: 22198
           UID: 1001
           GID: 1001
        Signal: 11 (SEGV)
     Timestamp: Mon 2025-12-30 10:21:14 UTC
  Command Line: /usr/local/bin/myapp --serve
    Executable: /usr/local/bin/myapp
 Control Group: /system.slice/myapp.service
          Unit: myapp.service
       Message: Process 22198 (myapp) of user 1001 dumped core.
cr0x@server:~$ coredumpctl debug /usr/local/bin/myapp
GNU gdb (Debian 15.2-1) 15.2
Reading symbols from /usr/local/bin/myapp...
(No debugging symbols found in /usr/local/bin/myapp)
(gdb) info sharedlibrary
From                To                  Syms Read   Shared Object Library
0x00007f2d2a9f5000  0x00007f2d2aa1b000  Yes (*)     /lib64/ld-linux-x86-64.so.2
0x00007f2d2a860000  0x00007f2d2a8e2000  Yes (*)     /usr/local/lib/libssl.so.3
0x00007f2d2a3d0000  0x00007f2d2a84d000  Yes (*)     /usr/local/lib/libcrypto.so.3
0x00007f2d2a1ef000  0x00007f2d2a3b8000  Yes (*)     /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0  0x00007f2d2a8f4c90 in ?? () from /usr/local/lib/libssl.so.3
#1  0x000000000040f23a in ?? ()
#2  0x0000000000410a11 in ?? ()
#3  0x00007f2d2a20a2ca in __libc_start_call_main () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007f2d2a20a385 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#5  0x000000000040b4e5 in ?? ()

Що це означає: Ви підтвердили, що краш відбувається всередині локального libssl. Це не доводить, що libssl «несправний»;
воно доводить, що він перебуває в лінії вогню. Ключ — невідповідність між очікуваннями викликача і ABI/структурою викликаного.

Рішення: Встановіть символи налагодження для підозрілих бібліотек (або перейдіть на бібліотеки Debian), щоб отримати осмислений backtrace.

Завдання 12: Встановіть символи налагодження для системних бібліотек (коли доступні)

cr0x@server:~$ sudo apt-get install -y gdb libc6-dbg libssl3-dbgsym
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  gdb libc6-dbg libssl3-dbgsym
0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.

Що це означає: Зі символами ви побачите імена функцій і іноді підказки по аргументам. Якщо падаюча бібліотека в /usr/local,
dbgsym від дистрибутива не допоможе для цього файлу — але він допоможе зрозуміти межу виклику і чи причетна glibc.

Рішення: Якщо підозріла бібліотека не керується пакетом, або отримайте відповідні символи від того, хто її збирав, або перестаньте її використовувати.

Завдання 13: Перевірте на часткові оновлення, утримання пакетів і pin

cr0x@server:~$ apt-mark showhold
libc6
cr0x@server:~$ apt-get -s dist-upgrade | sed -n '1,80p'
Reading package lists... Done
Building dependency tree... Done
Calculating upgrade... Done
The following packages will be upgraded:
  libc6 libc6-dev
2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

Що це означає: Утримання libc6 — це власноруч створена суміснісна вилка. Якщо ви оновили юзера та зафіксували glibc, ви ризикуєте.

Рішення: Зніміть утримання і завершіть оновлення, або відкотіть і тримайте всю систему в консистентному стані. «Напів-новий Debian» — середовище, де народжуються segfault-и.

Завдання 14: Перевірте цілісність пакетів для ключових компонентів

cr0x@server:~$ sudo dpkg -V libc6 libssl3 | head -n 50
..5......  /lib/x86_64-linux-gnu/libc.so.6

Що це означає: 5 означає невідповідність MD5: файл відрізняється від того, що очікує пакет.
Це може статись, якщо щось його перезаписало, або ви на файловому знімку зі специфічними особливостями, або при перерваному оновленні.

Рішення: Негайно перевстановіть уражені пакети. Якщо невідповідність повертається, підозрівайте корупцію диска або інструмент конфігураційного управління, що пише в системні шляхи.

Завдання 15: Перевстановіть пошкоджені пакети (хірургічний ремонт)

cr0x@server:~$ sudo apt-get install --reinstall -y libc6 libssl3
Reading package lists... Done
Building dependency tree... Done
0 upgraded, 0 newly installed, 2 reinstalled, 0 to remove and 0 not upgraded.

Що це означає: Ви відновили пакетовані бібліотеки до очікуваних бітів.

Рішення: Повторно перевірте службу. Якщо вона все ще підхоплює /usr/local/lib, ви не виправили невідповідність — лише очистили базу.

Виявлення точної невідповідності (справжня мета)

«Невідповідність бібліотеки» — це розмитий діагноз. Ми хочемо висловлення, яке можна покласти в інцидентний звіт без сорому:
«/usr/local/lib/libssl.so.3, зібраний проти OpenSSL X і glibc Y, був підвантажений глобальним ld.so.conf і впав, коли його викликав myapp, зібраний проти libssl3 Debian».
Така конкретика доступна після кількох цілеспрямованих перевірок.

Крок A: Доведіть, що вибрана неправильна бібліотека

Ви вже бачили це в ldd і LD_DEBUG. Тепер доведіть, чи це через глобальну конфігурацію завантажувача чи через налаштування конкретного застосунку.

cr0x@server:~$ /lib64/ld-linux-x86-64.so.2 --list /usr/local/bin/myapp | head -n 30
	linux-vdso.so.1 (0x00007ffda91f2000)
	libssl.so.3 => /usr/local/lib/libssl.so.3 (0x00007f6b4d3c0000)
	libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x00007f6b4cf30000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6b4cd4f000)

Рішення: Якщо сам завантажувач підтверджує мапінг — припиніть дебати. Виправте мапінг.

Крок B: Перевірте RPATH/RUNPATH і старі звички збірки

cr0x@server:~$ readelf -d /usr/local/bin/myapp | grep -E "RPATH|RUNPATH"
 0x000000000000001d (RUNPATH)            Library runpath: [/usr/local/lib]

Що це означає: Бінар має зашитий пріоритет на /usr/local/lib.
Навіть якщо ви приберете /usr/local/lib з ld.so.conf, цей бінар усе одно шукатиме там.

Рішення: Перебудуйте бінар без цього RUNPATH, або патчуйте його обережно (і документуйте як дорослий).

Крок C: Визначте очікування версій символів

Інколи невідповідність тонка: бібліотека існує, завантажується, експортує правильні імена, але версії не відповідають тому, що очікує бінар.

cr0x@server:~$ objdump -T /usr/local/bin/myapp | grep -E "OPENSSL_|GLIBC_" | head -n 20
0000000000000000      DF *UND*	0000000000000000 (OPENSSL_3.3.0) EVP_MD_fetch
0000000000000000      DF *UND*	0000000000000000 (OPENSSL_3.3.0) SSL_CTX_new
0000000000000000      DF *UND*	0000000000000000 (GLIBC_2.38) memcpy
cr0x@server:~$ objdump -T /usr/local/lib/libssl.so.3 | grep -E "OPENSSL_" | head -n 20
000000000003d2a0 g    DF .text	00000000000000f5  OPENSSL_3.2.0 SSL_CTX_new
0000000000047b10 g    DF .text	0000000000000120  OPENSSL_3.2.0 EVP_MD_fetch

Що це означає: Бінар очікує символи OPENSSL_3.3.0. Бібліотека експортує OPENSSL_3.2.0.
Зазвичай це б викликало помилку завантаження «version not found». Але невідповідності можуть ховатися, якщо бінар менш суворий, або якщо плагіни викликають різні версії опосередковано.
У будь-якому разі — у вас тепер чіткий висновок про несумісність.

Рішення: Узгодьте версії OpenSSL: використайте пакетований libssl від Debian, що відповідає збірці бінарника, або перебудуйте бінар проти бібліотеки, яку збираєтесь постачати.

Крок D: Виявлення «двох копій тієї самої залежності» в одному процесі

Тут починається гостра ситуація: дві копії бібліотеки з подібними символами, завантажені з різних шляхів, в одному процесі.
Це може статися з плагінами, dlopen і SDK від вендорів.

cr0x@server:~$ sudo gdb -q /usr/local/bin/myapp -ex 'set pagination off' -ex 'run --serve' -ex 'info proc mappings' -ex 'quit'
Reading symbols from /usr/local/bin/myapp...
(No debugging symbols found in /usr/local/bin/myapp)
Starting program: /usr/local/bin/myapp --serve
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
process 23102
Mapped address spaces:
      Start Addr           End Addr       Size     Offset  Perms  objfile
      0x7f2d2a860000     0x7f2d2a8e2000    0x82000        0x0  r-xp   /usr/local/lib/libssl.so.3
      0x7f2d29f00000     0x7f2d29f82000    0x82000        0x0  r-xp   /usr/lib/x86_64-linux-gnu/libssl.so.3

Що це означає: Обидві копії замаплені. Це не «трохи погано». Це «невизначена поведінка в костюмі».

Рішення: Знайдіть, хто завантажує другу копію (часто плагін або шлях dlopen) і усуньте дублювання. Один процес — одна libssl.

Крок E: Знайдіть тригер завантажувача: середовище, systemd-юнит, обгортки

cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/usr/local/bin/myapp --serve
Environment=LD_LIBRARY_PATH=/usr/local/lib

Що це означає: Хтось встановив LD_LIBRARY_PATH у юніті systemd. Це підриває більшість захисних механізмів пакування Debian.

Рішення: Видаліть змінну середовища або не використовуйте глобальне перекриття, та правильно постачайте залежності. Якщо ви мусите використовувати LD_LIBRARY_PATH, обмежте його сферу і задокументуйте причину.

Крок F: Підтвердіть консистентність стану пакетів після оновлення

cr0x@server:~$ dpkg -l | awk '$1 ~ /^(ii|iF|iU|rc)$/ {print $0}' | grep -E '^(iF|iU)'
iU  libgcc-s1:amd64  14.2.0-3  amd64  GCC support library
cr0x@server:~$ sudo dpkg --configure -a
Setting up libgcc-s1:amd64 (14.2.0-3) ...
Processing triggers for libc-bin (2.38-6) ...

Що це означає: «Неконфігурований» пакет може залишити напівзаписаний стан, включаючи старі записи кешу бібліотек, непрогнані тригери і несумісні залежності.

Рішення: Завжди завершуйте конфігурацію і тригери після перерваного оновлення, потім перезапустіть ldconfig при потребі.

Крок G: Перевірте очікування ABI glibc і libstdc++ (поширений вектор падінь після оновлення)

Якщо кадри падіння всередині libstdc++.so.6, libgcc_s.so.1 або алокаторів пам’яті, можливо це C++ ABI.

cr0x@server:~$ ldd /usr/local/bin/myapp | grep -E "libstdc\+\+|libgcc_s"
	libstdc++.so.6 => /usr/local/lib/libstdc++.so.6 (0x00007f7a1a400000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7a1a1e0000)
cr0x@server:~$ strings /usr/local/lib/libstdc++.so.6 | grep -E "GLIBCXX_" | tail -n 5
GLIBCXX_3.4.29
GLIBCXX_3.4.30
GLIBCXX_3.4.31
GLIBCXX_3.4.32
GLIBCXX_3.4.33

Що це означає: Локальна libstdc++, що перекриває системну, часто є причиною падінь після оновлення, особливо коли плагіни зібрані іншим інструментарієм.
Версії GLIBCXX_ допомагають зрозуміти, що бібліотека обіцяє підтримувати.

Рішення: Уникайте глобального перекриття libstdc++. Використовуйте системний тулчейн наскрізь або вендоруйте весь runtime в контрольованому контейнері/пісочниці.

Жарт №2: Якщо ви налагоджуєте segfault з LD_LIBRARY_PATH, встановленим глобально — ви не досліджуєте проблему, ви відтворюєте фільм жахів.

Три міні-історії з корпоративного життя (анонімізовано, болісно реальні)

Міні-історія 1: Інцидент через неправильне припущення

Середня компанія експлуатувала флот серверів Debian для клієнтського API. Оновлення до Debian 13 проводили поетапно: спочатку ОС, потім додаток.
Команда вважала, що бінарники додатка «достатньо самодостатні», бо постачали tarball із кількома .so поруч з виконуваним файлом.

Першим симптомом проблем не був даунтайм. Це був шум: нова хвиля 502 та кілька воркерів, що вмирали на годину. Логи ядра звинувачували libcrypto.
Розробники знизували плечима — «ми не чіпали крипто» — і інцидент затримався в невизначеній зоні: не настільки зламано, щоб відкатувати, але не стабільно, щоб ігнорувати.

Неправильне припущення було тонким: вони вважали, що «бандлені бібліотеки використовуються тільки нашим додатком». Насправді попередній адміністратор додав /opt/vendor/lib
у /etc/ld.so.conf.d роками раніше, щоб задовольнити спадковий інструмент. Після оновлення ОС завантажувач почав вибирати вендорський OpenSSL першим
для багатьох процесів, не лише для API-воркерів.

Виправлення було банальним і миттєвим: прибрати глобальний шлях завантажувача, запустити ldconfig, перезапустити уражені служби і переконатися, що API-воркери використовують OpenSSL від Debian
(або явно постачати власний у ізольованому RUNPATH, що не забруднює хост).

Урок: спільні бібліотеки — це спільно. Якщо ви вчите завантажувача новому трюку глобально, кожен процес його навчається, включно з тими, які вам подобаються.

Міні-історія 2: Оптимізація, яка обернулась проти

Інша організація оптимізувала pipeline збірки, створюючи один «портативний» бінарний пакет. Вони збирали на новішому тулчейні й лінкували динамічно
проти підбірки бібліотек, скопійованих у /usr/local/lib на кожному хості. Це зменшило артефакти збірки і прискорило деплойн.
Але це зробило оновлення крихкими.

Після переходу на Debian 13 вони бачили інтермітентні segfault-и під навантаженням. Збої корелювали з TLS-handshake і HTTP/2 трафіком,
а стек-трейси змінювалися. Деякі вказували на libssl, інші — на алокацію пам’яті. Інженери шукали race condition декілька днів.
Додавали ретраї, налаштовували пул потоків, навіть міняли флаги компілятора. Segfault-и ввічливо сміялися і продовжувалися.

Зрештою хтось запустив LD_DEBUG=libs і помітив закономірність: іноді процес завантажував Debian-івський libcrypto, іноді локальний,
залежно від того, як завантажувалися плагіни й які шляхи були активні. Дві копії OpenSSL в одному адресному просторі — не підтримувана конфігурація; це генератор хаосу.

«Оптимізація» полягала в використанні спільних хостових шляхів як субстрату розгортання. Це здавалося ефективним, але розмив межі власності і версіонування.
Виправлення вимагало скасувати це: або лінкувати статично там, де дозволено, або постачати ізольований runtime (контейнер, chroot або принаймні RUNPATH, обмежений сервісом),
і припинити змішувати хостові й вендорські копії.

Висновок після розбору був грубим: зручність — це залежність. Якщо ви оптимізуєте, обходячи розв’язувач залежностей дистрибутива, ви самі стаєте цим розв’язувачем.

Міні-історія 3: Нудна, але правильна практика, яка врятувала день

Фінансова компанія мала правило: перед будь-яким великим оновленням Debian брати знімок файлової системи, зберігати вибірки dpkg,
і експортувати точний стан APT pin. Ніхто не любив це правило. Воно виглядало як паперова робота для комп’ютерів.

Під час їхнього оновлення до Debian 13 фонове завдання почало падати зі segfault-ами всередині libpq. Додаток мав плагіни, і
вендор плагіна постачав двійкове розширення, скомпільоване «для Debian». Та фраза нічого не значить без деталей, але закупівля була задоволена.

SRE на чергуванні порівняв інвентар бібліотек до і після оновлення і помітив, що установник вендора кілька тижнів тому кинув копію libstdc++.so.6
в /usr/local/lib на підмножині хостів. Більшість машин її не мали. Ті, що мали — падали.
Оскільки у них були знімки, вони змогли швидко зробити diff стану кешу завантажувача і підтвердити зміну.

Виправлення було простим: видалити зайву локальну бібліотеку, відновити власність пакетів Debian і ввести політику, що інсталятори вендора не повинні змінювати глобальні шляхи завантажувача.
Плагін вендора перестроїли правильно пізніше, але інцидент зупинили за годину, бо команда могла довести «що змінилося» без здогадок.

Нудна практика — не геройство. Це гігієна: знімки, інвентар і відтворюваність. Вона не робить вас швидшим щодня. Вона робить вас швидким, коли треба.

Поширені помилки: симптом → корінна причина → виправлення

Ось повторні помилки, які я бачу після оновлень Debian. Мета — розпізнання шаблонів: ви бачите симптом, переходите до правильного підсистеми,
і уникаєте «випадкового налагодження».

1) Симптом: падають лише локально розгорнуті додатки; сервіси Debian у порядку

  • Корінна причина: Локальні бінарники зібрані проти старіших/новіших бібліотек; RUNPATH вказує на /usr/local/lib; бандлені бібліотеки затінюють дистро.
  • Виправлення: Видаліть RUNPATH до глобальних локацій; перебудуйте проти тулчейну Debian 13; постачайте залежності у ізольовану директорію, а не глобальні шляхи завантажувача.

2) Симптом: багато несумісних бінарників падають невдовзі після оновлення

  • Корінна причина: Часткове оновлення або утримання ядерних пакетів (glibc, libgcc); несумісний кеш завантажувача; перерване конфігурування dpkg.
  • Виправлення: Завершіть оновлення (dpkg --configure -a, apt-get dist-upgrade), приберіть утримання, перевстановіть ключові бібліотеки, перебудуйте initramfs якщо потрібно.

3) Симптом: backtrace вказує в libssl/libcrypto, але код додатку змінився мало

  • Корінна причина: Дві копії OpenSSL завантажено; неправильний libssl.so резолвиться з /usr/local або вендор-директорій; плагін зібраний проти іншого OpenSSL.
  • Виправлення: Забезпечте одну OpenSSL на процес; приберіть глобальні перекриття; перебудуйте плагіни; використайте дистро libssl або вендоруйте повний ізольований стек.

4) Симптом: падіння відбувається тільки при завантаженні плагіна/модуля

  • Корінна причина: Невідповідність ABI плагіна (C++ ABI, розміри структур, очікування glibc). Хост-додаток оновився; плагін — ні.
  • Виправлення: Перебудуйте плагін проти оновлених заголовків/бібліотек; впровадьте перевірки сумісності версій плагінів; уникайте двійкових плагінів, якщо не контролюєте тулчейн.

5) Симптом: dpkg -V показує невідповідності в libc або інших основних бібліотеках

  • Корінна причина: Файл перезаписано, корупція, перерване оновлення або інструмент керування конфігураціями записує в системні директорії.
  • Виправлення: Перевстановіть пакети; перевірте, хто модифікував файли (аудит інструментів керування конфігурацією); проведіть перевірку стану диска якщо невідповідності повторюються.

6) Симптом: Помилки завантажувача типу «version `GLIBC_2.xx’ not found» або «undefined symbol»

  • Корінна причина: Жорстка невідповідність у вимозі версій символів; бінар зібраний на новішому glibc/libstdc++, ніж цільовий хост.
  • Виправлення: Збирайте на найстарішій цільовій платформі, яка вам потрібна (або всередині середовища Debian 13); не копіюйте випадково libc.so.6 навколо; узгодьте тулчейн.

Чеклисти / покроковий план

Чеклист A: Тріаж за перші 15 хвилин

  1. Отримайте один чіткий підпис аварії з journalctl (ім’я бібліотеки, сервіс, частота).
  2. Підтвердіть, чи виконуваний файл управляється дистрибутивом (dpkg -S) чи локальний (/usr/local, /opt).
  3. Заберіть core dump за допомогою coredumpctl і перераховуйте завантажені бібліотеки в gdb.
  4. Запустіть ldd і readelf -d, щоб ідентифікувати RPATH/RUNPATH і вибір бібліотеки.
  5. Перевірте LD_LIBRARY_PATH в systemd-юнитах, обгортках, cronjob-ах.

Чеклист B: Підтвердіть консистентність пакетів (припиніть жити в частковому оновленні)

  1. Перевірте утримання: apt-mark showhold. Знімайте утримання, якщо у вас немає ретельно протестованої причини.
  2. Перевірте на зламаний стан: dpkg --audit, потім dpkg --configure -a.
  3. Смоделюйте повне оновлення: apt-get -s dist-upgrade.
  4. Перевірте цілісність: dpkg -V для libc і підозрілих бібліотек. Перевстановіть усе, що не відповідає.

Чеклист C: Виправте невідповідність з мінімальними побічними ефектами

  1. Переважно: перестаньте глобально перекривати бібліотеки дистрибутива. Видаліть /usr/local/lib з ld.so.conf, якщо воно не критичне.
  2. Видаліть або перейменуйте некеровані конфліктні бібліотеки (зберігайте копію для відкату у безпечному місці, не в шляхах завантажувача).
  3. Запустіть ldconfig і перезапустіть лише уражені служби (або перезавантажте, якщо стан завантажувача сумнівний).
  4. Якщо застосунок потребує кастомних бібліотек, розгорніть їх у директорії, що належить застосунку, і вказуйте через RUNPATH, обмежений цим застосунком — або використайте контейнери.
  5. Перебудуйте плагіни/модулі під Debian 13 і тулчейн; ставтеся до двійкових плагінів як до підозрілих до доведення сумісності.

Чеклист D: Щоб це не повторилося

  1. Політика: жоден вендорський інсталятор не може змінювати /etc/ld.so.conf.d або скидати бібліотеки в /usr/local/lib без рецензії.
  2. Інвентар: періодично скануйте на предмет некерованих ELF-бібліотек у шляхах завантажувача.
  3. Runbook оновлення: забороніть утримання для glibc/libgcc/libstdc++ під час релізних оновлень.
  4. Дисципліна збірки: збирайте в середовищі Debian 13; публікуйте маніфест потрібних версій бібліотек.

Часті питання

1) Чому невідповідність бібліотеки викликає segfault, а не чисту помилку завантажувача?

Помилки завантажувача виникають, коли потрібні символи не можуть бути розв’язані. Segfault-и виникають, коли символи розв’язані, але ABI-пакт порушено:
неправильний розмір структури, хибні очікування про володіння алокацією, несумісності типів C++ або дубльовані бібліотеки в процесі.

2) Чи надійний ldd для діагностики того, що відбудеться під час виконання?

Це хороший перший погляд, але не є євангелієм. ldd може залежати від змінних середовища і не показувати всю поведінку dlopen/плагінів.
Для істини в рантаймі використайте LD_DEBUG=libs і інспектуйте карти живого процесу або дамп ядра.

3) Чи може оновлення Debian 13 легітимно ввести segfault-и в стабільному софті?

Це можливо, але трапляється рідше, ніж думають. Більшість пост-оновлювальних segfault-ів спричинені локальними перекриттями, плагінами, частковими оновленнями
або бінарниками, зібраними на іншій базі. Ставте гіпотезу «Debian зламав це» як ту, яку потрібно заробити доказами.

4) Чи «вирішити» це можна за допомогою симлінка /usr/local/lib/libssl.so.3 на libssl від Debian?

Ні. Це крихкий хак, що робить походження невідстежуваним і порушує очікування керування пакетами. Приберіть перекриття і дозвольте завантажувачу вибрати пакетовану бібліотеку,
або перебудуйте застосунок правильно. Симлінк-хаки — це шлях, яким майбутній ви опиниться змушеним реагувати на інцидент у свято.

5) Що робити, якщо я маю постачати кастомний OpenSSL з причин відповідності?

Тоді постачайте його в ізольованій директорії, що належить застосунку, і переконайтеся, що лише цей застосунок його використовує.
Уникайте глобальних змін шляхів завантажувача. Зробіть кордони залежностей явними (RUNPATH, обмежений для застосунку; контейнер; або chroot).

6) Як з’ясувати, чи завантажено дві копії однієї бібліотеки?

Використайте gdb (info proc mappings), інспектуйте /proc/$PID/maps або аналізуйте відображення дампа.
Якщо ви бачите і /usr/local/lib/libssl.so.3, і /usr/lib/x86_64-linux-gnu/libssl.so.3, ви знайшли серйозну корінну причину.

7) Чому ці краші іноді проявляються тільки під навантаженням?

Невідповідності ABI і дублікати бібліотек можуть поводитися як race conditions, бо розташування пам’яті і таймінги змінюються з конкуренцією.
При легкому навантаженні ви можете не натрапити на шлях, що псує пам’ять. Під навантаженням — ви обов’язково натрапите.

8) Яке найчистіше виправлення, коли крашить бінар у /usr/local?

Перебудуйте його проти Debian 13 у контрольованому середовищі, приберіть зашитий RUNPATH на глобальні локації і припиніть залежність від глобального LD_LIBRARY_PATH.
Якщо перебудова неможлива, ізолюйте runtime (контейнер/chroot), щоб він не заважав хосту.

9) Чи створюють core dumps ризик безпеки?

Так: дампи можуть містити секрети. Керуйте зберіганням systemd-coredump політиками, обмежуйте доступ і розгляньте відключення core для чутливих сервісів.
Але для цієї категорії проблем один коректно оброблений дамп може заощадити дні припущень.

10) Коли слід відкотитися замість налагоджувати далі?

Якщо основні системні бібліотеки показують невідповідності цілісності, які ви не можете пояснити, якщо кілька критичних сервісів падають,
або якщо ви підозрюєте корупцію зберігання — відкат до відомого робочого знімка часто безпечніший. Налагоджуйте на клоні, а не на «кровоточивому» хості.

Висновок: наступні кроки, які ви можете виконати

«Segfault після оновлення» перестане лякати, коли ви сприймете його як проблему інвентаризації: який бінар завантажив які точні спільні об’єкти з яких точних шляхів,
і чи погоджуються ці об’єкти щодо ABI і версій символів.

Якщо забрати нічого іншого з випадку №55, візьміть це: перестаньте дозволяти /usr/local/lib (або шляхи вендора) глобально перекривати бібліотеки Debian.
Визначайте невідповідність за допомогою coredumpctl + gdb + LD_DEBUG, доведіть її шляхами файлів і версіями символів,
а потім виправте, відновивши єдиний узгоджений набір бібліотек.

Практичні наступні кроки:

  1. Візьміть один краш і витягніть список завантажених бібліотек з core dump.
  2. Використайте LD_DEBUG=libs,versions, щоб довести вибір завантажувача і вимоги до версій.
  3. Усуньте глобальні перекриття (ld.so.conf.d, LD_LIBRARY_PATH, RUNPATH у загальних локаціях).
  4. Перевстановіть і перевірте основні пакети, якщо dpkg -V показує невідповідності.
  5. Перебудуйте або ізолюйте все, що не керується пакуванням Debian.
← Попередня
Домашня віртуалізація: які функції CPU справді важливі
Наступна →
Ubuntu 24.04 tmpfs/ramdisk вийшов із-під контролю: як зупинити поїдання RAM (не ламаючи програми)

Залишити коментар