Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bubblejail and AppImage #8

Open
ls0h opened this issue Feb 3, 2021 · 24 comments
Open

Bubblejail and AppImage #8

ls0h opened this issue Feb 3, 2021 · 24 comments

Comments

@ls0h
Copy link

ls0h commented Feb 3, 2021

Hi again! 😄

I am experimenting with Bubblejail and AppImage. For now it does not work. First of all executable file must be binded inside the sandbox. And, second, there is must be a /dev/fuse available inside. These are not a problems. I wrote a small patch (I am not making an pull request because it is not ready and I want to discuss it). But these things are not enough. It is impossible to mount FUSE inside: mount("appName-v1.2.3.x86_64.AppImage", "/tmp/.mount_appName-v5QFPIr", "fuse.appName-v1.2.3.x86_64.AppImag"..., MS_RDONLY|MS_NOSUID|MS_NODEV, "fd=5,rootmode=40000,user_id=1000"...) = -1 EPERM (Operation not permitted) I think it is a security restriction of bwrap. But I do not know how to bypass it and is it a good idea.

Another way, I think, is to create a helper function, which will mount AppImage before start and unmount it after, outside of the sandbox. This can be done with udisksctl or dbus call to udisk daemon. But there is a disadvantage: mount point will be visible as a disk in /media.

P.S.: I have some more ideas about integration between Bubblejail and AppImage. Where is it convenient for you to discuss this?
P.P.S: Do you speak Russian? Do not get me wrong. I think I saw your nickname somethere as an email address with yandex.ru domain. If so, it would help communicate.

@igo95862
Copy link
Owner

igo95862 commented Feb 3, 2021

FUSE может легко повесить систему так что это правильно что bwrap не дает монтирование.

AppImage можно распаковать и смонтировать а потом пропустить в sandbox. https://docs.appimage.org/user-guide/run-appimages.html#mount-or-extract-appimages

--appimage-mount не рекомендую напрямую использовать так как он испольняет код напрямую из образа.

Вроде как appimagetool может смонтировать образ.

Если добавить опцию чтобы использовать алтенативный /usr то вполне можно использовать AppImage. Даже можно написать скрип чтобы автоматизировать.

@ls0h
Copy link
Author

ls0h commented Feb 3, 2021

FUSE может легко повесить систему

Повесить именно систему или приложения, которые обращаются к точке монтирования? Просто я с таким не сталкивался. Если приложения, то и ладно, т.к. приложение к точке монтирование будет обращаться только одно, собственно что внутри AppImage.

Знаю, что можно распаковать и потом просто запускать AppRun. Но это не очень удобно, т.к. будет занимать дополнительное место и время. Понятно, что средствами самого AppImage-приложения это делать не надо, т.к. теряется смысл в песочнице. А смонтировать можно, вроде бы, и так, указав смещение. Или с помощью squashfuse, тоже со смещением. Смещение в свою очередь можно получить запустив AppImage с опцией --appimage-offset, что, конечно, опять проблема с безопасностью. Или написать небольшую утилиту, которая найдёт его по заголовку squash. Это должно быть несложно.

Если добавить опцию чтобы использовать алтенативный /usr то вполне можно использовать AppImage
Вот это я не очень понял. Какая связь с /usr?

Мне кажется неплохим решением добавить в Bubblejail что-то вроде helper'ов, которые в зависимости от исполняемого файла будут делать что-то до и после запуска. Может быть это оформить как сервис? Насколько я понял из исходников, сервисы в Bubblejail могут что-то делать кроме простого формирования аргументов (не со всеми нюансами я ещё разобрался). И тогда монтировать AppImage куда-нибудь в /tmp средствами системы. Может быть драйвером ядра или через FUSE вариант squashfuse. Если ядром, то так даже быстрее будет работать, чем AppImage сам по себе. Тут правда есть вопрос, может ли специально подготовленный образ squashfs сломать драйвер и как-то навредить системе. Но, это уже всяко за рамками данного проекта. И, если мы считаем squashfs небезопасным, то AppImage ни в каком виде нельзя использовать (за исключением предварительной распаковки).

Почему я вообще стал разбираться с Bubblejail? Я совсем недавно думал о чём-то аналогичным. Правда с упором на запуск именно AppImage, хотя не только. И хотел что-то такое написать, вероятно с использованием bwrap (также думал использовать systemd-nspawn). И тут нашёл Bubblejail, который уже делает многое из планируемого. Так что, если не против, я бы ещё пообсуждал варианты использования Bubblejail для AppImage, может быть какие-то доработки в эту сторону...

@igo95862
Copy link
Owner

igo95862 commented Feb 3, 2021

Если squashfuse или что либо другое может монтировать файловую систему из образа то тогда все просто.

AppImage изображение идет с полноценым /usr (все библиотеки) который надо смонтировать где-то а потом использовать внутри sandbox вместо системного.

Я завтра что-то попробую.

Насколько я понял из исходников, сервисы в Bubblejail могут что-то делать кроме простого формирования аргументов (не со всеми нюансами я ещё разобрался).

Они только форматируют аргументы но могут это делать умно.

@ls0h
Copy link
Author

ls0h commented Feb 3, 2021

Если squashfuse или что либо другое может монтировать файловую систему из образа то тогда все просто.

Это понятно. Я хочу сделать монтирование прозрачным и удобным для пользователя, чтобы не нужно было делать вручную. Поэтому и спросил про сервисы, с точки зрения того, в какую часть Bubblejail лучше добавить функции для AppImage.

с полноценым /usr (все библиотеки) который надо смонтировать где-то а потом использовать внутри sandbox вместо системного.

Не уверен, что из системного /usr вообще ничего не используется. Да и зачем такие сложности? Всё равно образ AppImage монтировать. Вопрос был только в том, как это сделать максимально безопасно и удобно.

AppImage изображение
Я завтра что-то попробую.

Прошу прощения. На русском точно удобно общаться? Мне показалось, что это перевод с помощью переводчика в интернете. Можно вернуться на английский, если на русском сложно. Конечно, при условии, что мой английский достаточно понятен. (знаю, что английский у меня не очень хорош)

@igo95862
Copy link
Owner

igo95862 commented Feb 4, 2021

Я провел исследование почему FUSE не работает. Дело в том что любая FUSE система использует fusermount программу которая является SUID то есть при запуске становится root. bwrap в первую очередь блокирует все SUID программы через PR_SET_NO_NEW_PRIVS. (SUID большой риск)

Это понятно. Я хочу сделать монтирование прозрачным и удобным для пользователя, чтобы не нужно было делать вручную. Поэтому и спросил про сервисы, с точки зрения того, в какую часть Bubblejail лучше добавить функции для AppImage.

Скорее всего это будет в отдельной программе. appimagetool --appimage-mount дает точку для монтирования в stdout (/tmp/.mount*/) которую можно пропустить в sandbox. Потом можно запустить bubblejail с опцией переписывания одного из сервиса. (я ее сегодня добавлю) root_share позволяет пропустить определиный путь. Исполняемый файл будет tmp/.mount*/AppRun

Для пользователя комманда примерно будет bubblejail-appimage-run [instance_name] [AppImageFile].

Прошу прощения. На русском точно удобно общаться? Мне показалось, что это перевод с помощью переводчика в интернете. Можно вернуться на английский, если на русском сложно. Конечно, при условии, что мой английский достаточно понятен. (знаю, что английский у меня не очень хорош)

Я технический жаргон только на англиском знаю так что мне приходится подбирать слова которые не совсем привычными могут быть.

@ls0h
Copy link
Author

ls0h commented Feb 4, 2021

Дело в том что любая FUSE система использует fusermount программу которая является SUID то есть при запуске становится root.

Возможно я недостаточно хорошо знаю, как работает FUSE. Но я попробовал с помощью strace отследить работу squashfuse. Он сам сначала делает системный вызов mount и только после неудачи запускает процесс fusermount, т.е. пробует разные способы. Не обязательно именно squashfuse использовать в Bubblejail, но интересно разобраться как что работает.

SUID большой риск

Насколько я понимаю работу пространств имён в Linux, SUID тут не требуется. Можно создать пространство имён, внутри которого программа будет работать с правами root пользователя, а вне его это будет обычный пользователь или вообще никто (nobody).

Пример того, что fusermount не является необходимым, как и SUID:

lsh@lsh-ubu:~$ squashfuse Applications/qTox-v1.16.3.x86_64.AppImage /tmp/appimage/ -o offset=156768
lsh@lsh-ubu:~$ ls -l /tmp/appimage/
total 2
lrwxrwxrwx 1 root root   14 июл 21  2018 AppRun -> local/bin/qtox
-rw-r--r-- 1 root root  339 июл 21  2018 io.github.qtox.qTox.desktop
drwxr-xr-x 6 root root    0 июл 21  2018 local
-rw-r--r-- 1 root root 1549 июл 21  2018 qtox.png
lsh@lsh-ubu:~$ fusermount -u /tmp/appimage

lsh@lsh-ubu:~$ sudo -i
[sudo] password for lsh:
root@lsh-ubu:~# ls -l /bin/fusermount*
lrwxrwxrwx 1 root root    11 авг 20 16:16 /bin/fusermount -> fusermount3
-rwsr-xr-x 1 root root 39144 авг 20 16:16 /bin/fusermount3
root@lsh-ubu:~# mv /bin/fusermount3 /bin/fusermount3_
root@lsh-ubu:~# exit
logout

lsh@lsh-ubu:~$ squashfuse Applications/qTox-v1.16.3.x86_64.AppImage /tmp/appimage/ -o offset=156768
fuse: failed to exec fusermount: No such file or directory

lsh@lsh-ubu:~$ unshare --user --mount --map-root-user bash
root@lsh-ubu:~# squashfuse Applications/qTox-v1.16.3.x86_64.AppImage /tmp/appimage/ -o offset=156768
root@lsh-ubu:~# ls -l /tmp/appimage/
total 2
lrwxrwxrwx 1 root root   14 июл 21  2018 AppRun -> local/bin/qtox
-rw-r--r-- 1 root root  339 июл 21  2018 io.github.qtox.qTox.desktop
drwxr-xr-x 6 root root    0 июл 21  2018 local
-rw-r--r-- 1 root root 1549 июл 21  2018 qtox.png
root@lsh-ubu:~# umount /tmp/appimage
root@lsh-ubu:~# exit
exit

Возможно ли с помощью bwrap создать пространство имён, где внутри пользователь будет root? Кажется там есть опции для задания uid и gid.

@igo95862
Copy link
Owner

igo95862 commented Feb 4, 2021

Пример того, что fusermount не является необходимым, как и SUID:

Интересно, думаю это добавили в 4.18

https://www.phoronix.com/scan.php?page=news_item&px=Linux-4.18-FUSE

Возможно ли с помощью bwrap создать пространство имён, где внутри пользователь будет root? Кажется там есть опции для задания uid и gid.

Можно но потом не переключишся на обычного пользователя. Я думаю опять изза PR_SET_NO_NEW_PRIVS.

Я почти закончил прототип который монтирует извне.

@igo95862
Copy link
Owner

igo95862 commented Feb 4, 2021

Оказывается appimagetool не может монтировать изображения так что мне пришлось использовать --appimage-mount самого изображения что не идеально. 😥️ (вроде что-то разработчики планируют AppImage/AppImageKit#830)

В этой branch 3 commit. https://github.com/igo95862/bubblejail/tree/appimage

03a5c65 добавляет специальную переменую.

1015666
cc0fafb сдесь сама программа. (под ./bubblejail-appimage-run.py) Она правда использует файл ./dev.py но можно легко поменять на bubblejail если поставить его с 03a5c65

Вот Subsurface из образа.

Screenshot from 2021-02-04 18-51-58

@ls0h
Copy link
Author

ls0h commented Feb 4, 2021

У меня получилось монтирование средствами самого AppImage внутри песочницы. Для этого я добавил аргументы --cap-add CAP_SYS_ADMIN --uid 0 --gid 0 для bwrap. Монтирование ни на что не влияет вне песочницы. Я думаю, что это безопасно. Смотри раздел Effect of capabilities within a user namespace. Если такой вариант допустим, то остаётся только добавить --ro-bind для самого исполняемого файла AppImage, в случае, если он находится по недоступному пути (примерно, как было в изначальном патче).

@igo95862
Copy link
Owner

igo95862 commented Feb 5, 2021

Я добавил опцию --debug-bwrap-args которая добавляет агументы к bwrap.

Вот как будет выглядить запуск.

bubblejail run --debug-bwrap-args cap-add CAP_SYS_ADMIN --debug-bwrap-args uid 0 --debug-bwrap-args gid 0 --debug-bwrap-args ro-bind $HOME/appimage /appimage -- test2 /appimage

Если такой вариант допустим, то остаётся только добавить --ro-bind для самого исполняемого файла AppImage, в случае, если он находится по недоступному пути (примерно, как было в изначальном патче).

Не рекомендую так как bubblejail маскирует имя пользователя. Домашняя папка имеет имя пользователя. Лучше перемонтировать appimage гдето внути и запустить оттуда.

@ls0h
Copy link
Author

ls0h commented Feb 7, 2021

А мы можем добавить capabilities и режим superuser как стандартные опции? Что-то вроде этого. Опция --debug-bwrap-args это хороший способ отладки. При этом возможность сохранить данные параметры в конфигурационном файле тоже будет полезной, на мой взгляд. Да, список capabilities в диалоге конфигурации может получится длинный, если capabilities много. Думаю, это решаемо, можно сделать новый ServiceOption в виде выбора из множества предопределённых значений. Пользователь нажимает кнопку +, появляется выпадающий список, из которого он выбирает одно из значений. Выбранные значения записываются горизонтально в виде кнопок. При нажатии на кнопку значение убирается из выбранных. Можно на кнопку добавить маленький крестик (x), чтобы было понятно, что это удаление.

Не рекомендую так как bubblejail маскирует имя пользователя. Домашняя папка имеет имя пользователя.

В принципе пользователь с UID 0 не обязательно должен называться root, может как угодно. Насколько я знаю.

Лучше перемонтировать appimage гдето внути и запустить оттуда.

Я согласен с тем, что монтирование AppImage вне песочницы имеет преимущества. Например, можно монтировать с использованием драйвера ядра, что повысит производительность, либо использовать какие-то другие опции. С другой стороны, было бы здорово иметь возможность монтировать средствами самого AppImage внутри песочницы. Почему? На данный момент существует два формата AppImage: suqashfs и страый iso. А если появится ещё какой-то? Придётся добавлять ещё расширения для BubbleJail.

@igo95862
Copy link
Owner

igo95862 commented Feb 9, 2021

Я думаю это крайне специфичные насиройки. Не думаю что будет какое либо приложение которуму потребуется такие-же насиройки и иметь отдельный сервис будет только смущать пользователя. У меня есть план на Debug сервис где можно будет добавлять аргументы к bwrap так же как --debug-bwrap-args работает.

Но это все должно подождать пока я не перепишу сервисы.

@ls0h
Copy link
Author

ls0h commented Feb 9, 2021

Но это все должно подождать пока я не перепишу сервисы.

А какие изменения планируются? Новые сервисы будут несовместимы со старыми? У меня были ещё некоторые идеи. Значит пока стоит их отложить?

@igo95862
Copy link
Owner

igo95862 commented Feb 9, 2021

То что сейчас в .toml файлах будет польностью совместимо. Разница будет в том как графический интерфейс будет подключен. Сейчас он основан на PyQt5 но я планирую использовать мою новую D-Bus библиотеку чтобы подключить графический интерфейс через D-Bus. Как я осазнал когда писал интерфейс надо использовать родной язык toolkit (C для Gtk, C++ для Qt) иначе будеи спагетии.

Еще значения по умолчанию будут None а не какое-то значение. Сейчас если графический интерфейс записал сервис то он напишет мусор из значений по-умочанию а будет ничего не писать если нет заданого значения.

@ls0h
Copy link
Author

ls0h commented Feb 9, 2021

Как я осазнал когда писал интерфейс надо использовать родной язык toolkit (C для Gtk, C++ для Qt) иначе будеи спагетии.

Т.е. графический интерфейс планируется переписать на C++, раз уж он использует Qt? Если так, то очень жаль. Python тут очень хорошо подходит, у него низкий порог вхождения, в приложение просто вносить изменения без перекомпиляци и его производительности хватает, скоростей компилируемого языка не требуется. GTK кстати говоря, отлично работает с Python через bindings. А чтобы не было лапши, можно использовать Glade. Я использовал эту связку в своих проектах.

При разделении на две части (клиент и сервер D-Bus) предполагается что сервер будет всегда висеть в памяти?

@igo95862
Copy link
Owner

igo95862 commented Feb 9, 2021

Т.е. графический интерфейс планируется переписать на C++, раз уж он использует Qt?

Скорее всего в C и Gtk.

GTK кстати говоря, отлично работает с Python через bindings.

Я как раз переписываю потому что они полный кошмар. Никакой документации, нарушают стандарты и аналитические программы типа mypy не совместимы.

PyQt5 только чуть лучше так как я использовал документацию от C++ но у них такие же проблемы.

При разделении на две части (клиент и сервер D-Bus) предполагается что сервер будет всегда висеть в памяти?

Нет. У D-Bus есть опция активации при первом обращении. Потом можно выгрузить сервер при не использовании в течении какого-то времени.

@ls0h
Copy link
Author

ls0h commented Feb 9, 2021

отдельный сервис будет только смущать пользователя.

Сервисов в любом случае станет больше со временем. Я хотел предложить ещё идею. В графическом интерфейсе сделать две вкладки "Enabled services" и "Available services". Когда сервис выключен, он отображается только на вкладке "Available services" и имеет подробное описание (небольшую справку) что он делает. Когда сервис включён, он отображается на вкладке "Enabled services" с кратким описанием и самими параметрами. Так интерфейс не будет перегружен.

Не думаю что будет какое либо приложение которому потребуется такие-же насиройки

Вполне возможно. Приложение может проверять, является ли пользователь root. Для различных экспериментов это определённо будет полезно.

Раз уж речь зашла про сервисы. У меня была ещё одна идея связанная с ними. Сделать настройку для выбора фиктивной домашней директории. Три варианта: 1) Как сейчас, т.е. внутри ~/.local/share/bubblejail/instances/; 2) директория, указанная пользователем; 3) автоматически создаваемая директория рядом с исполняемым файлом. Зачем? Это опять же удобно для AppImage. С вариантом 3 AppImage можно использовать как переносимые приложения (на USB носителе, просто копировать по сети). Работать должно так. При первом запуске рядом с AppImage создаётся директория, которая будет смонтирована внутрь контейнера, например есть приложение /home/real_user/apps/MyApp.AppImage, тогда будет создана директория /home/real_user/apps/.MyApp.AppImage.fakehome/. Данные приложения будет удобно переносить между разными компьютерами вместе с самим приложением. К данным приложения можно будет легко и быстро получить доступ. В реализации этой идеи всё достаточно просто, однако я не нашёл в каком месте лучше добавить код для создания директории и, как внутри сервиса Bind получить путь к исполняемому файлу, чтобы создать директорию рядом с ним.

В принципе я мог бы реализовать эти идеи. Однако я хочу, чтобы дополнения гармонично вписывались в проект, не были чужеродными. Поэтому сначала описываю идею здесь, в issue.

Вообще, нужна ли проекту помощь? И имеет ли смысл предлагать идеи и код?
Я знаю Python, использовал его с GTK для создания GUI. Достаточно давно на этой связке написал док, который потом стал известен как DockbarX (форк первоначального Dockbar). Но давно уже не развиваю проект. Более-менее понимаю, как работают контейнеры и пространства имён. На C не пишу, к сожалению.

@igo95862
Copy link
Owner

igo95862 commented Feb 9, 2021

Сервисов в любом случае станет больше со временем. Я хотел предложить ещё идею. В графическом интерфейсе сделать две вкладки "Enabled services" и "Available services". Когда сервис выключен, он отображается только на вкладке "Available services" и имеет подробное описание (небольшую справку) что он делает. Когда сервис включён, он отображается на вкладке "Enabled services" с кратким описанием и самими параметрами. Так интерфейс не будет перегружен.

Да это хорошая идея.

Вполне возможно. Приложение может проверять, является ли пользователь root. Для различных экспериментов это определённо будет полезно.

В сервисе Debug это будет уместно.

Раз уж речь зашла про сервисы. У меня была ещё одна идея связанная с ними. Сделать настройку для выбора фиктивной домашней директории. Три варианта: 1) Как сейчас, т.е. внутри ~/.local/share/bubblejail/instances/; 2) директория, указанная пользователем;

Я добавлял environment variable BUBBLEJAIL_DATADIRS по которой ишутся Instances. Код сдесь:

data_directories = environ['BUBBLEJAIL_DATADIRS']

Это правда не закончено так как оно переписывает папки поиска а не добавляет.

  1. автоматически создаваемая директория рядом с исполняемым файлом. Зачем? Это опять же удобно для AppImage. С вариантом 3 AppImage можно использовать как переносимые приложения (на USB носителе, просто копировать по сети). Работать должно так. При первом запуске рядом с AppImage создаётся директория, которая будет смонтирована внутрь контейнера, например есть приложение /home/real_user/apps/MyApp.AppImage, тогда будет создана директория /home/real_user/apps/.MyApp.AppImage.fakehome/. Данные приложения будет удобно переносить между разными компьютерами вместе с самим приложением. К данным приложения можно будет легко и быстро получить доступ. В реализации этой идеи всё достаточно просто, однако я не нашёл в каком месте лучше добавить код для создания директории и, как внутри сервиса Bind получить путь к исполняемому файлу, чтобы создать директорию рядом с ним.

Я думаю этому место в отдельной утилите. В исходном коде много модулярности и у меня были планы на дополнительные утилиты.

Вообще, нужна ли проекту помощь? И имеет ли смысл предлагать идеи и код?
Я знаю Python, использовал его с GTK для создания GUI. Достаточно давно на этой связке написал док, который потом стал известен как DockbarX (форк первоначального Dockbar). Но давно уже не развиваю проект. Более-менее понимаю, как работают контейнеры и пространства имён. На C не пишу, к сожалению.

Можно сделать наброски интерфейса в Glade так как у тебя есть опыт с этим.

Еще у меня есть файл в котором я описал нерешеные проблемы (на англиском правда): https://github.com/igo95862/bubblejail/blob/master/docs/TODO.md

@mxvin
Copy link

mxvin commented Aug 19, 2022

Bumping. So what would be the solution from yall guys? OOB support from bjail for running appimage would be great, rely on appimagetool as igo said since we don't trust users appimage binary. But this feature may be blocking that said effort?: AppImage/AppImageKit#981

@igo95862
Copy link
Owner

AppImages are probably better served by a specialized tool. The SUID requirement to run them is directly opposed to bubblewrap security model.

For example: https://github.com/mgord9518/aisap

(I think there was a sandboxing launcher but I can't find it)

@mxvin
Copy link

mxvin commented Aug 20, 2022

As I roughly scan through aisap codes, they seems do mount the appimage and then runs inside it.
Maybe reimplement it using python here (mounting squashfs)
Edit: appimagetool can't mount other appimage, mount command must invoked from inside appimagetool itself

@igo95862
Copy link
Owner

igo95862 commented Aug 20, 2022

Mounting squashfs as unprivileged user would need a FUSE driver. (because it is not marked with FS_USERNS_MOUNT flag) It seems like someone is already created one and it is in Arch repositories. (extra/squashfuse)

@mxvin
Copy link

mxvin commented Aug 21, 2022

Yes, but beforehand we must know where to seek (offset) and pass it to squashfuse. That thing we need to do.
aisap/helper/offset also aisap/mount
Did we want to depends on user installed squashfuse or do ffi instead? 😄

Edit: Quick search on GH seems nobody create appimage management/manipulation on python. aisap using go, so prob recreate their header detection logic on py.

@mgord9518
Copy link

@mxvin Finding the offset is pretty easy. I've actually even implemented it in in posix shell script.

Should be quite easy in Python as I assume there is ELF parsing in the stdlib. All you have to do is multiply e_shnum and e_shentsize, then add e_shoff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants