Created attachment 22326 [details] libnss-role-winbind-original-full.strace Версия пакета: libnss-role-0.5.6-alt4 --- Если база group в NSS включает role перед winbind, а демон Winbind ещё не запущен, вызов initgroups() может привести к double free внутри libnss-role. При старте службы systemd вызывает initgroups() для пользователя службы. D-Bus запускается раньше Winbind и при разрешении групп пользователя messagebus попадает в проблемный путь. libnss-role читает /etc/role и /etc/role.d/*.role; при разборе участников ролей он вызывает вложенный getgrnam_r(). Запрос доходит до NSS-модуля Winbind, который сообщает о недоступном /run/samba/winbindd через NSS_STATUS_UNAVAIL и errno=ENOENT. Обработка этого результата в libnss-role ведёт к освобождению принадлежащего графу списка роли, а затем тот же список повторно освобождается при очистке графа. Падение воспроизводится отдельно от D-Bus командой getent initgroups messagebus. В загрузочном сценарии завершается с SIGABRT systemd-executor, вследствие чего dbus.service не запускается и достигает start-limit-hit. --- Шаги воспроизведения: 1) # realm join --verbose --client-software=winbind samba.testdomain --user Administrator 2) Прописать в /etc/nsswitch.conf winbind (см. https://bugzilla.altlinux.org/60769): passwd: files systemd winbind group: files [SUCCESS=merge] systemd role winbind 3) # reboot --- Фактический результат: # systemctl --failed UNIT LOAD ACTIVE SUB DESCRIPTION ● dbus.service loaded failed failed D-Bus System Message Bus ● dbus.socket loaded failed failed D-Bus System Message Bus Socket --- Дополнительно: Фрагмент `strace -f -k -o /tmp/libnss-role-winbind-original.trace getent initgroups messagebus`: newfstatat(AT_FDCWD, "/run/samba/winbindd", ..., AT_SYMLINK_NOFOLLOW) = -1 ENOENT (No such file or directory) > libnss_winbind.so.2(_nss_winbind_getgrnam_r+0xf1) > libc.so.6(getgrnam_r+0x16a) > libnss_role.so.2.0.0(librole_get_gid+0xfd) > libnss_role.so.2.0.0(parse_line+0x187) > libnss_role.so.2.0.0(librole_reading+0xf7) > libnss_role.so.2.0.0(librole_get_directory_files+0xb9) > libnss_role.so.2.0.0(_nss_role_initgroups_dyn+0x131) > libc.so.6(getgrouplist+0x69) > getent writev(2, [{iov_base="free(): double free detected in "...}, ...], 2) > libc.so.6 > libnss_role.so.2.0.0(librole_ver_free+0x16) > libnss_role.so.2.0.0(librole_graph_free+0x2c) > libnss_role.so.2.0.0(_nss_role_initgroups_dyn+0xb6) > libc.so.6(getgrouplist+0x69) > getent tgkill(3003, 3003, SIGABRT) = 0 --- Предлагаемое исправление: Патч преобразует ENOENT от getgrnam_r() при разборе необязательного участника роли в LIBROLE_NO_SUCH_GROUP и не освобождает список существующей роли на ошибочной ветке парсера, поскольку этим списком владеет граф.
Created attachment 22327 [details] libnss-role-winbind-enoent.patch
Спасибо за анализ, он подтверждается по коду: double free в parse_line() на ошибочной ветке для уже существующей роли. С частью про ENOENT в get_gid_by_groupname() есть две проблемы: 1. Регрессия в roleadd/roledel. getgrnam_r() возвращает ENOENT только когда последний модуль NSS вернул UNAVAIL. В этой конфигурации это значит, что имени нет в files, а winbind недоступен, то есть это доменные группы. Сейчас такая ошибка обрывает чтение /etc/role, и утилита завершается, не трогая файл. С патчем эти группы молча пропускаются при разборе, а roleadd/roledel затем перезаписывают /etc/role. В итоге роли и привилегии с доменными группами пропадают из файла, если утилиту запустить, пока winbind не работает. Для NSS-модуля пропуск правильный, для утилит, которые пишут файл, нет. 2. Исправление неполное. Числовые GID в /etc/role (например "1000:...") разрешаются через getgrgid_r() в librole_get_group_name(). Там ENOENT по-прежнему уходит в errno_to_result(), которая возвращает «сырое» ENOENT (2), а не код LIBROLE_*. Если такой GID не находится в files (например, доменный), одна такая строка при недоступном winbind обрывает разбор всего /etc/role. /etc/role.d при этом не читается, модуль возвращает NSS_STATUS_UNAVAIL, и служба теряет все ролевые группы. Падения уже нет, но результат неверный. Предлагаю обрабатывать ENOENT в одном месте, в errno_to_result(), и вернуть отдельный код «источник недоступен». NSS-модуль его пропускает, а roleadd/roledel на нём прерываются, не перезаписывая файл. При разборе кода нашлись и другие ошибки: запись за границу буфера в librole_get_group_name(), неработающая блокировка /etc/role, неатомарная запись файла, массивы long options без завершающего элемента и другие. Подготовлю исправления вместе с тестами в виде PR в https://github.com/Etersoft/libnss-role.
libnss-role-0.6.0-alt1 -> sisyphus: Thu Oct 01 2026 Vitaly Lipatov <lav@altlinux.ru> 0.6.0-alt1 - new version 0.6.0 - fix double free in NSS module when winbind is not running yet (closes: #60776) - skip groups from unavailable group database (winbind) in NSS module, don't rewrite role files with such groups in roleadd/roledel - treat missing /etc/role as empty configuration - roledel: show the file in /etc/role.d defining the role - fix memory errors: buffer overflow in librole_get_group_name(), realloc() handling, long options, short role file names - fix /etc/role locking, replace role files atomically - add role(5) manpage, update documentation and help - extend unit tests, run them with ctest
Исправлено в libnss-role-0.6.0-alt1 (задача #435056, уже в Sisyphus). Что сделано в upstream (https://github.com/Etersoft/libnss-role, релиз 0.6.0): - double free в parse_line() исправлен: список роли, уже добавленной в граф, на ошибочной ветке больше не освобождается (первая часть предложенного патча). - Недоступная база групп (ENOENT от winbind, пока winbindd не запущен) обрабатывается отдельно от отсутствующей группы: появился код LIBROLE_SOURCE_UNAVAIL, причём в errno_to_result(), так что он действует и для числовых GID через getgrgid_r(). NSS-модуль пропускает такие группы и выдаёт остальные роли. roleadd/roledel в этом случае отказываются перезаписывать /etc/role, чтобы не потерять записи с доменными группами (регрессия исходного варианта патча, см. комментарий выше). rolelst показывает, что удалось разрешить, и возвращает ошибку. Проверено в контейнере ALT Sisyphus с "group: files role winbind" при незапущенном winbindd: - на 0.5.6-alt4 падение воспроизводится: id и getent initgroups завершаются с "free(): double free detected"; - на 0.6.0-alt1 падения нет, роли разрешаются, valgrind чистый. Кроме того, в 0.6.0 исправлены ошибки работы с памятью, блокировка /etc/role (теперь файл заменяется атомарно), отсутствующий /etc/role считается пустым, добавлена man-страница role(5).