Xorg від непривілейованого користувача

Абстрактно.
Є якийсь софт, якому потрібні ікси.
Здавалося б, завантажив, встановив, запустив – користуйся.
Але ось, проблема, подібний софт (для мене подібний - абсолютно весь, який не входить у штатний репозиторій Debian) я не хочу запускати:

  1. На своєму хості.
  2. Від мого користувача.
  3. Під Xorg мого користувача.
  4. Допускати у мої мережі, у тому числі 127.0.0.0

Крім того, той же браузер для “полазити” по сайтах і браузер для клієнт-банку, це не той же самий браузер, користувач і, іноді, система.
Пункти 1, 2, 4, зараз не розглядаємо, мова йтиме про X.

У Debian при стандартних налаштуваннях системи як display-manager використовується LightDM.
У ньому можна увімкнути listen tcp, але процеси Xorg він запускає від root.
У gdm3 навпаки, за умовчанням, він запускає Xorg від користувача, який логіниться в оточення, але в ньому зламали можливість увімкнути listen tcp.
Точніше залишили можливість вимкнути nolisten tcp,
але не увімкнути listen tcp.

Для цього потрібно відредагувати обгортку над X.

Спочатку встановимо gdm3 як DM за умовчанням, якщо його немає, то встановимо через apt.
dpkg-reconfigure gdm3

Дозволяємо підключення через tcp


Файл /etc/gdm3/custom.conf, якщо ні, створюємо

1
2
3
4
5
6
[security]
DisallowTCP=false
AllowTCP=true

[xdmcp]
ServerArguments=-listen tcp


До /etc/gdm3/daemon.conf додамо рядки

1
2
3
4
5
6
[security]
DisallowTCP=false
AllowTCP=true

[xdmcp]
ServerArguments=-listen tcp


DisallowTCP=false дійсно відключить ключ -nolisten tcp,
але ні AllowTCP=true,
ні ServerArguments не увімкнуть -listen tcp

Для цього редагуємо скрипт-обгортку /usr/bin/X, «Це безпечно, запевняю».
Однак, при оновленні пакета xorg цей файл буде перезаписано.
І крім потрібного -listen tcp, додамо трохи коду.

Debian “bookworm”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/sh
#
# Execute Xorg.wrap if it exists otherwise execute Xorg directly.
# This allows distros to put the suid wrapper in a separate package.

basedir=/usr/lib/xorg
runuser=`/usr/bin/whoami`
if [ -x "$basedir"/Xorg.wrap ]; then
    if [ "$runuser" = "myuser" ]; then
        exec "$basedir"/Xorg.wrap -listen tcp :0 "$@"
    elif [ "$runuser" = "vmuser" ]; then
        exec "$basedir"/Xorg.wrap -listen tcp :5 "$@"
    elif [ "$runuser" = "unprivilegeduser" ]; then
        exec "$basedir"/Xorg.wrap -listen tcp :25 "$@"
    elif [ "$runuser" = "anotheruser" ]; then
        exec "$basedir"/Xorg.wrap -listen tcp :325 "$@"
    else
        exec "$basedir"/Xorg.wrap -listen tcp "$@"
    fi
else
    exec "$basedir"/Xorg -listen tcp "$@"
fi

Debian “trixie”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/sh
#
# Execute Xorg.wrap if it exists otherwise execute Xorg directly.
# This allows distros to put the suid wrapper in a separate package.

basedir=/usr/lib/xorg
runuser=`/usr/bin/whoami`
if [ -x "$basedir"/Xorg.wrap ]; then
    if [ "$runuser" = "myuser" ]; then
        exec "$basedir"/Xorg.wrap "$@" -listen tcp ':0'
    elif [ "$runuser" = "vmuser" ]; then
        exec "$basedir"/Xorg.wrap "$@" -listen tcp ':5'
    elif [ "$runuser" = "unprivilegeduser" ]; then
        exec "$basedir"/Xorg.wrap "$@" -listen tcp ':25'
    elif [ "$runuser" = "anotheruser" ]; then
        exec "$basedir"/Xorg.wrap "$@" -listen tcp ':325'
    else
        exec "$basedir"/Xorg.wrap "$@" -listen tcp
    fi
else
    exec "$basedir"/Xorg "$@" -listen tcp
fi


Думаю зрозуміло, мій користувач, myuser, отримує -listen tcp :0 - стандартний порт :0
Тут варто уточнити, якщо у вас, наприклад, три користувача, основний, який завжди у системі.

То, при стандартному налаштуванні, основний отримає порт :0, наступний хто залогіниться, наприклад user2 - отримає xorg приймаючий підключення на :1 (:6001), наступний user3 :2 (:6002), але, якщо user2 (:1) розлогініться, і залогиниться знову - xorg отримає наступний вільний порт :3 .
Тобто черговість портів буде випадковим і доведеться постійно вказувати потрібний порт під час запуску потрібної програми.

А так ми добиваємось для кожного конкретного користувача запуску xorg з -listen tcp на його власному порту.

  1. myuser - :0
  2. vmuser - :5
  3. unprivilegeduser - :25
  4. anotheruser - :325
  5. інші - -listen tcp, і наступний вільний порт, тобто 1,2,3,4,6, тощо.


Сенс у тому, що в kvm, в якій налаштовано середовище, тепер можна запустити програму і вона підключиться саме до того X, до якого потрібно.
Наприклад: DISPLAY=10.1.1.1:25.0 /usr/bin/gedit
Доступ контролюється на рівні iptables, та X конкретного користувача, наприклад, для unprivilegeduser це 6025 порт.

1
  iptables -A INPUT   -s 10.1.1.125/32 -p tcp -m tcp --dport 6025 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT    # Xorg access


Крім iptables, у сесії користувача необхідно дозволити підключення
/usr/bin/xhost + 10.1.1.125, можете автоматизувати як захочете або через ~/.profile, у мене це ~/.config/autostart/init.sh.desktop

1
2
3
4
5
[Desktop Entry]
Exec=/usr/local/bin/unprivilegeduser-init.sh
Name=unprivilegeduser-init.sh
Terminal=False
Type=Application


Скрипт unprivilegeduser-init.sh містить усі необхідні команди,
в тому числі xhost + 10.1.1.125
У kvm, в .bash_profile потрібного користувача додаємо рядок
export DISPLAY=10.1.1.1:25.0
Відтепер, при запуску програми в kvm, вона відображатиметься у X-сесії користувача unprivilegeduser.

Мало? Заборонимо всі мережі для цього користувача


1
2
3
4
5
6
7
8
9
10
11
12
13
id unprivilegeduser
uid=12345(unprivilegeduser) gid=12345(unprivilegeduser) группы=12345(unprivilegeduser)

  # 12345 unprivilegeduser
  iptables -I OUTPUT -o tun11   --match owner --uid-owner 12345 -j DROP
  iptables -I OUTPUT -o tun12   --match owner --uid-owner 12345 -j DROP
  iptables -I OUTPUT -o tun15   --match owner --uid-owner 12345 -j DROP
  iptables -I OUTPUT -o kvmbr0  --match owner --uid-owner 12345 -j ACCEPT               # xorg
  iptables -I OUTPUT -o wlan0   --match owner --uid-owner 12345 -j DROP
  iptables -I OUTPUT -o eth0    --match owner --uid-owner 12345 -j DROP
  iptables -I OUTPUT -o eth1    --match owner --uid-owner 12345 -j DROP
  iptables -I OUTPUT -o wg99    --match owner --uid-owner 12345 -j DROP
  iptables -I OUTPUT -o lo      --match owner --uid-owner 12345 -j DROP


Важливі зауваження


Нагадаю, у зоні вашої уваги зараз два користувача:
unprivilegeduser - на рівні хосту, від якого запущений Xorg, який не повинен мати доступу до жодних мереж, крім kvmbr0.
І деякий користувач, наприклад, insidekvmuser від якого запускається gedit у kvm.
Далі, для перенаправлення всього трафіку в тунель необхідно керувати мережами саме insidekvmuser.

Цей приклад не гарантує повної ізоляції процесів цих користувачів, однак дозволяє не перейматися про clipboard-буфер, інформацію на дисплеї з інших програм та доступ до системних та користувальницьких директорій.

Чому не використовувати vncviewer/virt-viewer/spice?


Так, ці варіанти виключають хост, залишаючи всі процеси у віртуальному середовищі kvm/lxc, проте швидкість відтворення графіки vncviewer або spice не може зрівнятися з підключенням по мережі до xorg, він хіба трохи відстає в швидкості від нативного. Але це вже вам вирішувати.