xsudo.sh – en skriptad X11-wrapper för sudo

För att fira slutet av min exil i Windows skriver jag en följetong om de olika program, inställningar och knep som gör mitt sätt att använda OpenBSD… ja, värt att fira.

Gårdagens inlägg handlade om att byta plats på Escape och Caps lock, och var en uppfräschning av ett tidigare inlägg. I dag är det dags för en uppdatering av ett annat gammalt inlägg, som beskriver hur man kan köra X11-program som andra användare eller root.

Själv använder jag den här metoden för att köra webbläsare som användaren _web med hemkatalogen /home/_web. Därigenom saknar webbläsaren skrivrättigheter under min hemkatalog, och kan inte sabba något.

Eftersom jag dessutom kör med umask 077 i min .profile och med jämna mellanrum gör chmod -R og-rwx på min hemkatalog, kan webbläsarna inte heller läsa mina filer. Den enda nackdelen med det upplägget är inte så blodig: När jag vill ladda upp eller ned något genom webbläsaren måste jag låta filerna ifråga passera /tmp (som för övrigt töms automatiskt av OpenBSD vid uppstart, och dessutom är mountad async på min maskin).

Det här upplägget borde fungera i de allra flesta X11-varianter, exempelvis X.org.

sudo och $SUDO_ASKPASS

Du känner kanske till gksudo och kdesudo, två grafiska sudo-prompter för GNOME respektive KDE? De är smidiga när man vill köra ett X11-program som någon annan användare eller root, men inte vill ta en omväg över xterm.

Men de är utom räckhåll här, eftersom jag inte vill ha vare sig KDE eller GNOME. Som tur är behövs de inte heller, för samma funktionalitet uppstår i det närmaste av sig självt, om man bara har OpenSSH installerat. Det följande är klippt ur sudo (8):

-A    Normally, if sudo requires a password, it will read it from
      the user's terminal.  If the -A (askpass) option is
      specified, a (possibly graphical) helper program is executed
      to read the user's password and output the password to the
      standard output.  If the SUDO_ASKPASS environment variable is
      set, it specifies the path to the helper program.  Otherwise,
      the value specified by the askpass option in sudoers(5) is
      used.  If no askpass program is available, sudo will exit
      with an error.

Vi behöver med andra ord något enkelt program som visar ett fönster, läser in ett lösenord och sedan trycker ut det på standard output. Programmet heter ssh-askpass. Manualsidan ssh-askpass (1) menar att det inte ska köras direkt, men det struntar jag i.

Följande skript skulle alltså kunna fungera:

Men ska det verkligen vara så lätt? Vi provar att köra xcalc som användaren _web:

$ sh bin/xsudo.sh -Hu _web xcalc
No protocol specified
Error: Can't open display: :0

Visst ja – användaren _web har ju ingen åtkomst till min X11-server. Det är nu allt blir krångligt.

Display-åtkomst i X11

Säkerhet och X11 går inte direkt hand i hand, och de säkerhetsmekanismer som faktiskt finns är svåranvända påbyggnader som alltjämt riskerar att rasa samman. När en X11-session startas på din maskin, så är det i själva verket två program som drar i gång. Först en X11-server, och sedan en X11-klient. Medan servern kör alla program är det klienten som direkt kommunicerar med dig via tangentbord, mus och skärm.

En fördel med den modellen är att klient och server inte alls behöver vara på samma maskin. I vanliga fall, t.ex. på hemdatorer, ansluter klienten till servern via loopback-gränssnittet, alltså till adressen localhost eller 127.0.0.1.

Det som i själva verket händer när xcalc försöker komma åt X11 i vårt xsudo-skript ovan, är att servern nekar användaren _web att ansluta. Tre lösningar på det problemet är vanligt förekommande.

  1. xhost kan användas för att ge full åtkomst till din X11-display åt en viss användare på samma maskin. (Programmet kan även ge full åtkomst till alla användare på valfri IP-adress, men det är en dålig idé.)
  2. xauth kan användas för att ge full eller begränsad åtkomst till din X11-display åt en användare eller ett program, vare sig de finns på samma maskin eller någon annan.
  3. ssh kan alternativt köras med X11 forwarding, och ger då begränsad åtkomst till din X11-display.

Vill vi använda begränsad eller full åtkomst? Det beror på. Begränsad åtkomst innebär att bland annat accelererad grafik och direkt åtkomst till X input devices inte fungerar. Grafiskt intensiva program, som exempelvis webbläsare faktiskt är, går därför långsammare med begränsad åtkomst. En annan nackdel är att AltGr kan sluta fungera.

När det är möjligt bör man ändå använda begränsad åtkomst. Det är ganska lätt att visa varför. Om du själv vill prova det följande ska du se till att ha xinput på din maskin.

kaja$ xhost +si:localuser:_web # Släpp in lokal användare '_web' i X11
localuser:_web being added to access control list
kaja$ xinput list # Lista alla input devices
+ Virtual core pointer                  id=2    [master pointer  (3)]
|   + Virtual core XTEST pointer        id=4    [slave  pointer  (2)]
|   + /dev/wsmouse0                     id=7    [slave  pointer  (2)]
|   + /dev/wsmouse                      id=8    [slave  pointer  (2)]
+ Virtual core keyboard                 id=3    [master keyboard (2)]
    + Virtual core XTEST keyboard       id=5    [slave  keyboard (3)]
    + /dev/wskbd                        id=6    [slave  keyboard (3)]
kaja$ sudo -u _web xinput test 6 # Dumpa händelser på device 6
key release 36
key press   33
key release 33
key press   38
key release 38
key press   39
key release 39
key press   39
key release 39
key press   25
key press   32
key release 25
key release 32
key press   27
key release 27
key press   40
key release 40

Vad var det egentligen som hände? Med xinput list listade jag alla aktiva X input devices, och kom fram till att tangentbordet var device nummer 6. Sedan körde jag xinput test 6 som användaren _web, och skrev därefter "password" i ett annat fönster, som jag körde i egenskap av mig själv. Trots att jag skrev i ett fönster som tillhörde användaren jesper, och till råga på allt i ett lösenordsfält, så kunde användaren _web se exakt vilka tangenter jag tryckte ned (och släppte upp, för den delen).

Med andra ord kan vilket program som helst, oavsett vilken användare som kör det, avläsa alla nedslag på tangentbordet, oavsett vilket program som "egentligen" tar emot dem, så länge det har full åtkomst till X11-displayen.

Exceptionellt illa. Så pass illa att om du bara lyckas peta in ett xhost localhost på någon dator du har åtkomst till via ssh, så är det följande en fungerande keylogger:

kaja$ ssh struts xinput test 6

…förutsatt att tangentbordet är device 6 på maskinen struts, då. Detta är förklaringen till varför xhost-autentisering mot någon annan dator än localhost är så dåligt. Med en sådan regel på plats behöver man inte ens ha tillgång till maskinen via exempelvis ssh, utan kan köra sin keylogger direkt mot maskinens IP-adress.

Lätt skrämmande. Med begränsad åtkomst kan vi däremot inte göra några sådana hyss. I exemplet nedan ansluter jag till min egen maskin (som heter kaja) via ssh för att få en session utan full åtkomst.

kaja$ ssh -o ForwardX11=yes localhost
kaja$ xinput list
X Input extension not available.
kaja$ xinput test 6
X Input extension not available.

Mycket bättre, men som sagt också fritt från både grafisk acceleration och AltGr.

Vilken typ av autentisering ska vi då välja?

Alternativ 3, alltså X11 över ssh, är inte intressant för oss, eftersom samma resultat kan uppnås utan att belasta processorn med en krypterad anslutning till localhost.

Alternativ 1, alltså att köra xhost för en lokal användare, är visserligen användbart när vi behöver full åtkomst, men klarar inte att ge begränsad åtkomst. Vi vill kunna välja.

Kvar blir alltså alternativ 2, att använda xauth, som givetvis är krångligast.

xsudo.sh – en skriptad X11-wrapper för sudo

Efter en blick (eller tjugotre) i xauth (1) verkar följande ordning gångbar:

  1. Skapa en tom, privat och tillfällig fil i /tmp.
  2. Be X11-servern om en MIT-cookie (som är trusted eller untrusted beroende på om vi vill ha full eller begränsad åtkomst) och placera denna i den tillfälliga filen.
  3. För över ägandet av den tillfälliga filen till användaren vi vill bli.
  4. Kör sudo för användaren vi vill bli, med XAUTHORITY satt till vår temporära fil och DISPLAY till den X11-display som kakan vi skapat gäller för.
  5. Ta bort den tillfälliga filen när sudo (och det program som sudo startat) avslutats.

Bäst att ge ett praktiskt exempel. Nedan körs xcalc som _web med begränsad åtkomst.

$ umask 077                                 # Bli privat
$ mktemp                                    # Skaffa en tempfil
/tmp/tmp.OVZCpcwL4A
$ xauth -f /tmp/tmp.OVZCpcwL4A generate \   # Skapa cookie i filen...
    $DISPLAY \                              # ...för DISPLAY
    . \                                     # ...av MIT-COOKIE-typ
    untrusted                               # ...med begränsad åtkomst
$ sudo chown _web._web /tmp/tmp.OVZCpcwL4A  # Ge ägande till _web
$ sudo -AHu _web \                          # Bli _web...
    env DISPLAY=$DISPLAY \                  # ...med rätt X11-display
    env XAUTHORITY=/tmp/tmp.OVZCpcwL4A \    # ...och rätt MIT cookie
    xcalc                                   # ...och kör xcalc
$ rm -f /tmp/tmp.OVZCpcwL4A                 # Ta bort tempfilen

Och för att hoppa över ett närmast oändligt antal steg: Så här ser ett skript som automatiserar processen ut:

(Givetvis kan du även läsa den senaste versionen av skriptet.)

Hur använder man då skriptet? Vi kan ju börja med att testa det:

kaja$ sh /usr/opt/bin/xsudo.sh whoami  
root
kaja$ sh /usr/opt/bin/xsudo.sh -u _web whoami
_web
kaja$ sh /usr/opt/bin/xsudo.sh -tu _web xinput list
+ Virtual core pointer                  id=2    [master pointer  (3)]
|   + Virtual core XTEST pointer        id=4    [slave  pointer  (2)]
|   + /dev/wsmouse0                     id=7    [slave  pointer  (2)]
|   + /dev/wsmouse                      id=8    [slave  pointer  (2)]
+ Virtual core keyboard                 id=3    [master keyboard (2)]
    + Virtual core XTEST keyboard       id=5    [slave  keyboard (3)]
    + /dev/wskbd                        id=6    [slave  keyboard (3)]
kaja$ sh /usr/opt/bin/xsudo.sh xinput list        
X Input extension not available.

Lägg märke till hur flaggan -t ger sådan där otäck åtkomst till tangentbordet.

Lägg även märke till att jag lagt skriptet i /usr/opt/bin/ i stället för under min hemkatalog; skript som kör sudo ska ägas av root och vara oskrivbara för andra användare.

För att starta Chromium med xsudo.sh har jag kopplat ett menyalternativ i fönsterhanteraren till följande formel:

/bin/sh /usr/opt/bin/xsudo.sh -tu _web /usr/local/bin/chrome

Trots X11:s skrämmande brist på isolering mellan program kör jag alltså webbläsaren med full åtkomst, dels för att det går långsamt utan acceleration och dels för att det är tråkigt att behöva kopiera in tecken som @ och $ från något annat fönster – AltGr fungerar ju som sagt inte med begränsad åtkomst. Det får räcka med att webbläsaren inte kommer åt filerna under min hemkatalog.

Pidgin klarar sig däremot utan sådana finesser:

/bin/sh /usr/opt/bin/xsudo.sh -u _web /usr/local/bin/pidgin

Och tar man även bort "-u _web" ur ovanstående så körs pidgin förstås som root.

Om du nu vill börja köra något program som en annan användare, så vill du antagligen även flytta över programmets dotfiles till den andra användarens hemkatalog, innan du drar i gång programmet.

För Chromium och användaren _web ser processen ut så här:

kaja$ umask 077
kaja$ sudo -u _web mkdir -p ~_web/.config
kaja$ sudo cp -r ~/.config/chromium ~_web/.config/
kaja$ sudo chown -R _web._web ~_web/.config/chromium

Det var det hela. Skriptet kommer bo och uppdateras på diskussionsforumet.