bits & bicycles
80 subscribers
2 photos
1 video
25 links
Download Telegram
Channel created
Всем привет! (кто “вы”, я тебя спрашиваю, я здесь один)

Меня зовут Андрей, я тимлидос с 10+ лет опыта в вебе, крестовик и байтослесарь в душе.

Этим ноябрьским вечером я подумал и решил, что в моей голове слишком много светлых и не очень мыслей, и было бы неплохо делиться ими не только с ящиком моего стола, а ещё и с тобой.

В ближайшее время тебя ждут восхитительные истории в свободном формате про линукс, гну линкеры, сборку мусора и иногда даже про веб.
Пользователь – user-space конструкт

Давай начнём с чего-нибудь лайтового в #linux'е, например, с айдишников пользователей, uid'ов.

Ты, скорее всего, привык, что у пользователя есть логин и пароль. И что вообще есть такая сущность как "пользователь", у которой уже есть айдишник. Но на самом деле нет никаких пользователей, это социальный user-space конструкт.

У каждого процесса есть аттрибут-чиселка uid, его можно получить через getuid(2) и поменять через setuid(2) (но только у процесса там уже 0 aka root или есть другая чиселка в #capabilities). (А ещё его можно подсмотреть через #procfs, про него потом.)

Аналогично есть аттрибут-чиселка guid.

Когда ты создаёшь новый дочерний процесс через clone, он наследует эти чиселки. Это значит, что они копируются в него один раз при создании, больше процесс-родитель на них повлиять на них никак не может.

У файла есть свои чиселки. Когда процесс пытается обратиться к файлу, ядро сверяет чиселки процесса и чиселки файла.

В первом приближении – всё. (Ещё есть effective uid/gid, это примерно то же самое, но немного другое.)

То, что пользователи хранятся в страшном текстовом формате в /etc/passwd, совершенно никак не волнует ядро. Твой systemd/upsttart/sysv-init (которые полностью user-space) прочитает /etc/passwd, вызовет setuid и таким образом тебя "залогинит".

https://flapenguin.me/bnb/uid
vfs - нет никаких файлов

Вчера я упомянул про #procfs. И это отличный повод поговорить про файловые системы.

Кто-то может сказать, что procfs – не настоящая файловая система. На это я могу ответить, что любая файловая система, которая умеет read/write/chmod/… – настоящая.

Ты можешь написать модуль ядра, который делает на эти вызовы всё что угодно (в разумных пределах): ходит в жёсткий диск, по сети или просто на лету генерирует какую-то информацию. Последним и занимается procfs.

(Если уж честно, то и ваш ext4 генерирует информацию на лету. Какая разница, берёт он её с жёсткого диска или из каких-то структур в памяти? А чтобы было ещё более запутано, все файловые системы называются виртуальными, #vfs. Про #fuse, ещё одну из них, как-нибудь в следующий раз.)

Логика у procfs и похожих фс (например, sysfs) следующая: зачем городить отдельные syscall'ы, чтобы прокинуть какие-то функции из ядра, если я могу отдавать её в виде байтов на read, которые ты можешь хоть grep’ать, что в vim'е открывать. Более того, информацию можно гонять и в другую сторону, если поддержать write.

Почитать подробно про то, что "хранится", т.е. на лету генерируется ядром, в файлах procfs можно, внезапно, в доке. Будь готов к тому, что она старая и неполная. Да-да, не только ты не любишь писать документацию.

Пара интересных моментов, которые хочется подсветить:

1. procfs (как и любая vfs) при любой операции видит pid вашего процесса. Это позволяет ей иметь свой /proc/self на каждый процесс.
2. procfs можно монтировать куда угодно. Обычно он смонтирован в /proc, но ты можешь монтировать его в /top/kek/wow/kernel-info!. Только будть готов, что 99% софта сломается, потому что /proc гарантируется POSIX'ом.

https://flapenguin.me/bnb/vfs
fuse - монтируем в фс что угодно

Шок! read/write начинает ходить в user space, стоит только поставить один единственный пакет…

Завёл речь про файловые системы, так ещё и #fuse упомянул. Придётся теперь про него рассказывать.

Помнишь, я говорил, что ты можешь написать модуль ядра со своей файловой системой и делать в нём что угодно на read/write? Вот вообще всё что угодно. Что бы ты такое написал?

Скорее всего самое маргинальное, что ты придумал – ходить в сеть, чтобы смонтировать по NFS ту самую пекарню с фильмами 😏 в локалке. На заветной машине для этого запущен NFS сервер (в user space, как и любой другой приличный софт). Как он реализует NFS протокол, чтением с диска или генерацией "файлов" на лету, – его дело.

Что если мы случайно начнём ходить в ту же самую машину? В свой же user space? Смонтируем свои же файлы, что довольно бесполезно. Но мы всё ещё можем написать NFS сервер таким образом, чтобы он генерировал "файлы" на лету.

Собственно, мы в одном шаге от fuse'а. NFS подразумевает, что машины разные, а мы точно знаем, что машина одна. Значит байты можно перекладывать чуть более эффективно и чуть больше давать знать про систему.

Это и есть Filesystem in USErspace, что-то типа NFS, но со знанием, что машина на клиенте и сервере одна и та же. По сути, прокси между VFS и user space'ом, что позволяет писать файловые системы хоть в плагинах на lua для neovim'а. (Чтобы открывать их тем же neovim'ом, главное рекурсивно в самого себя не уйти.)

(NFS здесь для примера и наглядности, протокол у fuse совсем другой)

И в чём же профит?
- Писать обычные user space программы гораздо проще, чем модули ядра.
- Если у тебя есть готовая реализация какого-нибудь протокола, то не надо затаскивать его в kernel space с его малюсеньким стеком и без curl’а.

А как же безопасность?
- Так у тебя теперь всё в user space запускается. Ты по определению более безопасный. Не можешь ни kernel panic'овать, ни игнорировать права доступа. И вообще молодец.

Что на этом уже понаписали?
- sshfs, s3fs, restic-mount, WikipediaFS и т.д.

https://flapenguin.me/bnb/fuse
Короче, у постов сверху не будет комментов
(И настраивать я их не буду. Я всё же байтослесарь, а не сммщик.)
👍2
binfmt_misc – почти что реестр, но в linux

Пока мы обсуждали как можно писать свои реализации read / write через #vfs или #fuse, только отдавая информацию из чего-то похожего на привычные файловые хранилища, но всё ещё не делали ничего странного на write. И в эту субботу самое время.

Зайдём из далека. Помнишь реестр и привязанные к расширениям программы? Тот самый момент когда ты кликаешь по xml файлу, а он запускает тебе Visual Studio Very Enterpise Edition (repack by Vasya)? Хорошо. Я сейчас про более узкую вещь, про то что ты можешь запустить batch/python или другой скрипт двойным кликом, а система сама знает каким интерпретатором его запускать.

Так вот в #linux'е тоже есть своеобразный реестр для ассоциации файлов с программами. (Можно, конечно, написать свой модуль ядра, но это удел храбрых, а не user space холопов вроде нас.)

Реестр этот называется #binfmt_misc. Вместо того чтобы городить отдельные syscall'ы для реестра интерпретаторов, ребята написали файловую систему, которая делает странное на write.

Ты пишешь в /proc/sys/fs/binfmt_misc (монтируешь куда хочешь, само собой) строчку в определённом формате – регистрируешь интерпретатор в ядре. (Да, формат с двоеточиями, ты удивлён?) Пишешь -1 в другой файл – дерегестрируешь интерпретатор.

Можно регистрировать интерпретатор для расширения (любого суффикса) или для строчки байт по оффсету. Но shebang #! реализовать не получится, поэтому он – отдельный модуль ядра.

Всё, теперь ядро автоматически начнёт выполнять что-то типа execve("/usr/bin/python", "./my.py") вместо execve("./my.py"). Учти, реестр этот глобальный на всё ядро и даже root не может его обойти.

Ставь binfmt и подписывайся на интерпретатор, если тебя задолбало писать wine solitare.exe, но теперь ты знаешь, что делать.

https://flapenguin.me/bnb/binfmt_misc
🔥5
setuid - получаем root

Я рассказывал, что у процесса есть точно такие же числа uid/gid, как у файлов. И то, что когда процесс пытается получить доступ к файлу, эти числа сраниваются.

Но иногда оно работает в обратную сторону. Когда процесс делает execve файла, ядро может взять его uid/gid и запустить процесс с ними.

Фича эта называется setuid (чтобы было не скучно и ты путал её с setuid(2) при каждом случае). Аналогично есть setgid. Включается проставлением флага на файле через chmod: fchmod(fd, S_ISUID) (только не забудь чекнуть свои привелегии).

Это единственный способ поднять привелегии процесса. Без него если кто-то из родителей процесса добровольно отдал root, то больше процесс больше никак не может позвать setuid(2). А бывает нужно.

Иногда тупо нужно запустить что-то от имени другого пользователя. Ну, через su или sudo. Запускаешь такую программу, она насильно запускается от root'а, читает закрытый от всех /etc/sudoers и понимает, можно тебе запускать команду, или нет. Если да, то по максимуму сбрасывает окружение (а то мало ли ты LD_PRELOAD передал) и вперёд.

Бывает, логически у тебя должен быть доступ, но данные уже хранятся в таком виде, что доступ туда есть только у root'а. Например, ты хочешь поменять пароль, а он лежит в /etc/shadow, куда у тебя, очевидно, нет доступа. Приходится звать passwd с setuid'ом. Он не только проследит, что ты меняешь только свой пароль, но и проверит его на всякие политики безопасности (так что отдельные файлики /etc/users/:uid/password не сработают, увы).

Другой классический пример: ping. Рядовому пользователю нельзя слать по сети произвольные байты. ping занимается плюс-минус этим, поэтому запустится от root’а, параноидально сделает свои дела и завершится. (К слову, во FreeBSD только что нашли RCE именно в pinghttps://www.opennet.ru/opennews/art.shtml?num=58232)

Можно было бы написать демонов passwdd и pingd и общаться с ним по какому-нибудь сокету, но это банально сложнее.

Мне фича всегда казалась плохо спроектированным грязным хаком и зияющей дырой в безопасности. Но хак хорош. Настолько хорош, что у Денниса Ритчи даже был на него патент.

Ах да, монтируй только проверенные харды без nosuid'а.

https://flapenguin.me/bnb/setuid
👍4
init - запускаем голый exe на голом ядре

root тяжело найти, легко потерять и невозможно забыть. Осталось понять откуда он изначально берётся.

Ответ простой. Ядро #linux'а при старте запускает один единственный процесс со всеми правами на свете. Но мало знать ответ, хочется же пощупать что там происходит на самом деле.

Общество и, возможно, лично Поттеринг хочет, чтобы ты считал, что самому собирать системы сложно. Ведь иначе люди увидят насколько плох GNU-софт, или, ещё хуже, перестанут заносить деньги в Red Hat. На ум сразу приходит LFS, где надо долго и нудно копипастить команды сборки GNU софта спотыкаясь об autohell. В итоге, вроде, и команды сам ввёл, и систему сам собрал, но ни черта не понятно.

Нам с тобой полноценный GNU/Linux собирать не надо, поэтому обойдёмся чернозёмом и палками. Понадобится виртуалка, ядро, и какой-нибудь бинарник для init'а. Возьмём #qemu, собранное ядро украдём из netboot#alpine'а, а вместо init'а у нас будет интерактивный шел sh -i из #busybox'а. Последний тоже качнём уже собранный. Потому что навряд ли ты вечер четверга хотел провести в Makefile'ах.

qemu умеет запускать линуксовое ядро из коробки, это значит нам не придётся возиться с grub'ом и bios/efi. Нужно только сварить ramdisk, файловую систему, с которой стартанёт ядро. В него мы положим busybox и init из пары строчек.

Полный скрипт для подготовки и запуска виртуалки занял всего 30 строк. https://github.com/flapenguin/stuff/blob/master/linux-boot/boot-x86_64.sh

Последней строчкой скрипт запускает qemu и... Поздравляю, у тебя root. И виртуалка с голым линуксом в придачу. Всё, можешь писать убийцу systemd.

(убить виртуалку можно через poweroff -f в ней или kill … из соседнего терминала)

$ qemu-system-x86_64 ... -kernel ./deps/x86_64/vmlinuz-lts -initrd ./initrd/x86_64.gz
...
/ # id
uid=0 gid=0


https://flapenguin.me/bnb/init
👍3🌚2🔥1
capabilities - root по кусочкам

Что-то мне подсказывает, uid 0 был выбран для суперпользователя по банальной причине: удобно писать if (uid && !allowed(uid, ...)) goto fail;.

Со временем пришло осознание, что во множестве ситуаций нужен не весь root, а только кусочек. Ну странно же давать право менять модули ядра nginx'у, которому всего-то надо забиндить 80й порт.

В итоге, права суперпользователя нарезали на capabilities. В 6.1.3 их целых 43 штуки. Например, можно запретить всё и выдать только разрешения биндить 0-1023 порты CAP_NET_BIND_SERVICE и посылать raw-пакеты CAP_NET_RAW. Или выдать разрешение выдавать разрешения CAP_SETPCAP.

capabilities, так же как и uid, – атрибут не пользователя, а процесса (на самом деле треда, но не важно). Напоминаю, пользователей не существует. Раньше у процесса был только атрибут-число uid, теперь к нему добавился атрибут-битсет capabilities. При этом capabilities и uid никак не связаны. У тебя может быть всемогущий uid 1337 и никчёмный uid 0.

Посмотреть этот битсет можно в #procfs

$ grep '^Cap' /proc/self/status
CapInh: 10000000a80425fb
CapPrm: 20000000a80425fb
CapEff: 30000000a80425fb
CapBnd: 40000000a80425fb
CapAmb: 5000000000000000


Работать с привелегиями можно через пару syscall'ов capset и prctl. Привелегии можно повышать, понижать, понижать временно, настраивать наследование дочерними процессами.

Также для повышения привилегий есть механизм setcap на файле, аналогичный suid биту. Но это если файловая система поддерживает атрибуты у файлов, если ты ретроград с ext1, то остаётся только пить галоперидол получать классический root.

https://flapenguin.me/bnb/capabilities
👍3🤩3🔥2💯1
Тут у Casey Muratori вышло видео с рассуждением о том, как Clean Code подходы убивают производительность софта. (Настоятельно рекомендую канал, кстати, набрасывает Casey как боженька. Только держите в голове, что он и сам фанатик.)

Видео о том, как адепты сферического ООП пропагандируют, что всё должно быть слабо связано, полиморфно, инкапсулировано и изолировано. И, конечно же, раздроблено на двухстрочники в отдельных файлах. Тогда, мол, поддерживать проект будет просто, а о производительности позаботится компилятор с рантаймом.

И как ты уже догадался, кроме тебя о производительности никто не позаботится. Сферический ООП по лучшим практикам замедляет твой код на порядок. И речь не о преждевременной оптимизации. Преждевременная оптимизация и заведомо неэффективный код – разные вещи.

Я хочу немного порассуждать о первой части обещания, о лёгкой поддержке. Реальность в том, что абстракции почти всегда плохие и текут. Да текут так, что разработчику надо знать реализацию самой сущности и всех остальных абстракций вокруг. И это, внезапно, совсем не помогает бороться со сложностью, а делает только хуже.

На реддите основная претензия к видео – “представьте, что завтра вам надо будет добавить серобуромалиновые полигоны с дырками”. На это я могу ответить только одно: а представьте, что нет. Представьте, что не надо. Или что через 2 года, когда будет надо, текущей абстракции всё равно не хватит и её придётся пересобирать с нуля.

"Clean Code подходы" и прочий сферический SOLID-OOP, как и любой другой инструмент, хорошо работает для одних целей и плохо для других. Но его почему-то воспринимают как нерушимую истину. Из-за этого не следовать ему может быть страшно и даже стыдно. Но самое страшное, что произойдёт – ты поругаешься с ярыми евангелистами, не напишешь FizzBuzzEnterpriseEdition, а где-то далеко горько заплачет один Дядя Боб.

BTW Мне нравится, что люди начали замечать, что книга то – говно.

#performance #architecture
🔥3🌭2
groups – наш пользователь и человек, и пароход, и wheel

Я говорил, что gid у процесса очень похож на uid. Но на самом деле не совсем похож.

gid - такой же произвольный uint32, как и uid. Только uid у твоего процесса один, а gid'ов может быть много. Много групп groups, к которым у процесса есть доступ, и одна активная, собственно, gid.

Увидеть это можно запустив обычный id:

❯ id uid=1000(flapenguin) gid=1001(flapenguin) groups=1001(flapenguin),998(docker)


Но зачем?

Нужна возможность как-то получить процесс, который может и cron настраивать и docker запускать. Т.е. с доступом в разграниченные части fs. И именно groups дают тебе такие права. У процесса таких может быть аж 65536 (2^16).

У отдельного gid'а задача более прозаичная. Он нужен как дефолт для операций типа open(..., O_CREAT) и clone.

И то и другое наследуется при создании процесса. И то и другое может менять только владелец capability CAP_SETGID через setgid и setgroups.

Именно поэтому после добавления себя в группу тебе надо перелогиниться. Перелогинивает тебя сессионный менеджер, у которого этот самый CAP_SETGID есть. Будь то systemd-loging или sshd.

Кстати, в #POSIX'е есть setuid-утилита newgrp, которая за тебя перелогинится насоздаёт лишних процессов. Не рекомендую.

$ ps -ft
PID TTY      STAT   TIME COMMAND
1638690 pts/0    Ss     0:00 -bash
1638804 pts/0    S      0:00  _ newgrp
1638805 pts/0    S      0:00      _ bash
1638815 pts/0    S      0:00          _ newgrp
1638816 pts/0    S      0:00              _ bash
1638829 pts/0    R+     0:00                  _ ps -ft


https://flapenguin.me/bnb/groups
2🍾1
scm_rights - посылаем дескрипторы почтой

Всё это время я тут задвигал про uid'ы и gid'ы. Это неплохая система доступов, позволяющая делать комплексные вещи.

Но бывает, твоя задача не решается в рамках такой системы. Ну вот не лезет и всё тут.

Допустим, тебе надо разделить accept соединения и его обработку. Потому что работа твоего сервера с capability CAP_NET_BIND_SERVICE – принимать соединения. Потому что работать же с этими соединениями можно хоть из под nobody. Потому что тебе хочется как-то понизить права.

Да, ты можешь форкнуться и понизить нового себя в правах. Но наследование дескриптора произойдёт только в момент форка. Так что форкаться тебе придётся на каждое соединение. Очевидно, что быстро работать это не может.

Да, ты можешь поднять внутри своего основного процесса колхозный RPC и перекладывать через него данные. Просто доставать данные из одной трубы, чтобы переложить в другую.

Но зачем всё это, если ты можешь отправлять fd'шки почтой по unix-сокетам.

Буквально, по unix-сокетам можно отправить почти любой открытый дескриптор соседу. Фича называется SCM_RIGHTS, часть socket ancillary data. И это не что-то новое. Фича есть в #POSIX'е минимум 15 лет.

Используется это добро, например, в DRI Xserver'а и в wayland'е, чтобы отдать пользователю преднастроеный дескриптор /dev/video. В nodejs (через поддержку в libuv) используется для ipc и балансировки cluster'ом.

Есть ещё SO_PEERCRED, чтобы узнать uid/gid процесса с другой стороны сокета, и SCM_CREDENTIALS, чтобы отправить любые uid/gid. Которые проверятся ядром, а его не обманешь. Такая бесплатная авторизация в локальных демонах. Зачем при такой фиче нужен kostilique .Xauthority для меня загадка.

https://flapenguin.me/bnb/scm_rights
🔥4🌭1
euid - от кого запустили sudo

Уже достаточно давно я разбирал как запускать всякое от root’а через setuid флажки.

Сегодня расскажу про вторую половинку этого механизма. Без которой ничего, на самом деле, не заведётся.

Потому что мало просто выбросить свою старую идентичность и стать root’ом. sudo нужно знать от кого его запустили, иначе как он будет свой /etc/sudoers читать. А ping было бы неплохо иметь возможность SIGSTOP’нуть или SIGKILL’нуть не из под root’а.

Никакой сложной системы тут нет и решение проблемы поистине дубовое. Старая идентичность при запуске setuid бинарников не выбрасывается, а перекладывается в соседние поля процесса.

sudo знает кто его запустил из saved user id и saved group id.

Ядро знает, кому можно kill’ять ping из real user id и real group id.

Поля эти есть у каждого процесса и у них даже есть описание в man 7 capabilities. Оно старательно пытается выглядеть не как ой-нам-тут-надо-было-прокинуть-получилось-как-получилось, но костыли за широкими мануалами не спрячешь.

Тем не менее:
- euid Effective User ID — это про проверки unix permission в ядре (то, что делает root’а root’ом)
- suid Saved User ID — это способ сообщить setuid процесс’у от чьего имени его запустили
- ruid Real User ID — это разрешение процессам с таким же uid посылать любые сигналы

Посмотреть на всё это добро можно через procfs. Дока последнего говорит, что они лежат в /proc/1337/status в таком формате

Uid | Real, effective, saved set, and file system UIDs
Gid | Real, effective, saved set, and file system GIDs


https://flapenguin.me/bnb/euid
🌭2🤯1
This media is not supported in your browser
VIEW IN TELEGRAM
А вы знали, что для регистрации на гитхабе теперь надо крутить собаку?
🔥4😁2
Почему ON CONFLICT DO NOTHING тебя ненавидит и конфликтует

В стандарте SQL есть такая конструкция INSERT ... ON CONFLICT (constraint) DO UPDATE, также известная как UPSERT. Нужна чтобы добавить или подобновить строку. на И есть её брат-близнец INSERT ... ON CONFLICT (constraint) DO NOTHING, которая просто превращается в no-op, если строка уже есть.

Но DO NOTHING этот работает не совсем интуитивно. По крайней мере, в #postgresql.

Представь, что у тебя есть CREATE TABLE numbers (value INT UNIQUE) и ты выполняешь одновременно два одинаковых запроса в разных сессиях (горизонтальное масштабирование и вот это вот всё): INSERT INTO numbers (value) VALUES (1) ON CONFLICT DO NOTHING.

Одной сессии повезёт больше и она добавит строку, пусть даже в транзакции. Другой сессии же придётся встать в очередь и ждать. Потому что любое предположение о другой транзакции может быть неправильным: строку могут удалить следующим запросом, да всю транзакцию могут отменить, в конце концов.

И вот ты вроде написал ON CONFLICT DO NOTHING, а де-факто в PostgreSQL получил не DO NOTHING, а DO NOTHING NOTHING OR LOCK numbers_value_unique_ix. Насколько долгий lock? На lock_timeout, который ты, конечно же, забыл настроить для своей сессии.

“Ну и что” – скажешь ты, “мы хотели одну строку и получили в итоге одну строку, пусть и подождали немного”. И, действительно, обычно нет ничего страшного если строка одна. Но если ты UPSERT’ишь с сотню одинаковых строк в нескольких сессиях, то конфликтов становится слишком много и ты рискуешь выстроить все свои INSERT’ы в очередь, которая просто не будет успевать разгребаться.

https://flapenguin.me/bnb/psql-on-conflict
🤔3👍1😢1