S-bit -- životu nebezpečno
                          -=-=-=-=-=-=-=-=-=-=-=-=-=-=

      Unix is a lot more complicated (than CP/M) of course -- the typical
      Unix hacker can never remember what the PRINT command is called this
      week -- but when it gets right down to it, Unix is a glorified video
      game. People don't do serious work on Unix systems; they send jokes
        around the world on USENET or write adventure games and research
                                    papers.
                                   -- E. Post
             "Real Programmers Don't Use Pascal", Datamation, 7/83

                        Pavel Kaňkovský, 3. března 1998

          Mnoho  systémových  programů  v UNIXovém  operačním  systému  --
      Linux  nevyjímaje -- potřebuje  pro  svůj běh  zvláštní  privilegia.
      Příkladem budiž program passwd, který musí být schopen  zapisovat do
      souboru  /etc/passwd resp.  /etc/shadow, aniž by k tomu byl oprávněn
      sám  uživatel  (z  pochopitelných  důvodů).   Nejčastěji  používaným
      mechanismem, který toto umožňuje, je tzv. propůjčení identifikace.

          U programu, jehož spustitelný  soubor má nastavený  aspoň  jeden
      z s-bitů, například takto:

  -r-sr-xr-x  1 root   bin     15796 /usr/bin/passwd

          dojde  po  jeho  spuštění  systémovým  voláním  execve() k tomu,
      že efektivní  vlastník,  efektivní  skupina, případně oboje -- podle
      toho, zda je v přístupových  právech  nastaven  setuid bit ( 04000),
      setgid bit (02000), nebo oba dva -- jsou nastaveny na hodnoty určené
      uživatelem resp. skupinou  vlastnící  soubor.  (Množina  doplňkových
      skupin (supplemental groups) je vždy ponechána beze změny.)  Program
      pak pracuje se stejnými  oprávněními,  jako  kdyby byl spuštěn  svým
      vlastníkem resp. uživatelem v dané skupině.

                             Proč je to nebezpečné?

          Výše popsaný  mechanismus není prost  rizik. Musí být zajištěno,
      aby zvláštní  privilegia  přidělená  programu  nemohla být zneužita.
      Část odpovědnosti  leží na samotném  jádře  systému,  které musí mj.
      zabránit jeho trasování  nebo čtení jeho  paměťového  prostoru  (kde
      se mohou  nacházet  důvěrná  data) ze strany  skutečného  vlastníka,
      ovšem  převážná část spočívá na bedrech  samotného  programu (a také
      jím používaných knihovnách).

          Privilegovaný   proces  musí  pečlivě   kontrolovat   prostředí,
      ve  kterém  je   spuštěn,  a  vstupy,   které   jsou  mu  uživatelem
      předkládány. Naneštěstí mnoho programů (či spíše jejich autorů) toto
      podceňuje a výsledkem jsou závažné bezpečnostní problémy, které jsou
      ještě umocněny tím, že velká část takových  programů  pracuje rovnou
      s nejvyššími možnými privilegii -- s právy superuživatele.

          K nejčastějším  chybám,  které  mají  za  následek  bezpečnostní
      problémy (a které budou detailněji diskutovány níže) patří:

      - přetečení mezí pole
      - neopatrná manipulace se soubory
      - neopatrné spouštění dalších programů

                        Odstrašující příklad -- Xserver

          Jak si asi mnozí, kteří mají na svém Linuxu  nainstalovaný balík
      {\tt XFree86},  všimli, Xserver běží s právy superuživatele. Je tomu
      tak, aby mu bylo dovoleno  přistupovat přímo ke grafickému hardware,
      k jehož  ovládání  neposkytuje  standardní  linuxové jádro prakticky
      žádnou   podporu.  Ovšem  Xserver  je  velmi  komplikovaný   program
      a vyskytují se v něm (nyní bude řeč o aktuální verzi  3.3.1 založené
      na X11R6.3) různé problémy,  dokonce ze všech tří výše vyjmenovaných
      kategorií.  Jedná se o následek  toho, že kód  Xserveru  (a do jisté
      míry to platí o celém  X11) byl většinou  navrhován a implementován,
      aniž byl kladen  dostatečný  důraz na jeho bezpečnost. Svou roli zde
      nepochybně  hraje  velký rozsah a složitost  kódu,  stejně jako jeho
      značná různorodost.

                              Přetečení mezí pole

          Toto   je  snad   nejfrekventovanější   chyba   vyskytující   se
      v céčkových  programech  vůbec.  Poněkud  překvapivě  se však  chyba
      vyskytuje mnohem  častěji  nepřímo -- při použití  některých  funkcí
      standardní  knihovny. Zvláště se jedná o funkce sprintf(),  strcpy()
      a strcat(), které neumožňují explicitně  stanovit  velikost pole, do
      kterého je ukládán výsledek, a bezstarostně  zapisují za jeho konec.
      (Což  vede k otázce,  zda je celková  koncepce  standardní  knihovny
      vhodná.) Podrobněji k této problematice v dalším článku.

          Pomocí vhodně  sestrojených  vstupních dat pak může zlý uživatel
      přepsat  návratovou  adresu na zásobníku  nebo jiné  důležité  údaje
      a donutit  program k vykonání  prakticky  libovolných  akcí -- např.
      ke spuštění privilegovaného shellu. Detailní popis přináší například
      http://www-miaif.ibp.fr/willy/security/.

      Příklad (xc/programs/Xserver/os/access.c):

  void
  ResetHosts (display)
      char *display;
  {
      register HOST  *host;
      char lhostname[120], ohostname[120];
      char *hostname = ohostname;
      char fname[100];
  ...
      strcpy (fname, "/etc/X");
      strcat (fname, display);
      strcat (fname, ".hosts");
      if (fd = fopen (fname, "r"))
  ...
  }

          Uživatel  může  při  spuštění  Xserveru  specifikovat  libovolný
      (i  nesmyslný  a hlavně  libovolně  dlouhý)  název  displaye  pomocí
      parametru  uvozeného  dvojtečkou.  Tato hodnota je předána do funkce
      ResetHosts(),  která ji používá pro konstrukci jména souboru.  Avšak
      příliš dlouhá hodnota způsobí přetečení mezí pole fname.

                        Neopatrná manipulace se soubory

          Většina   programů   pracuje   nějakým  způsobem  se  souborovým
      systémem. Pokud program neprovádí  dostatečné  kontroly jmen souborů
      nebo jiných uživatelem  poskytnutých dat, z nichž jsou jména souborů
      konstruována,  lze  snadno  získat  neoprávněný  přístup k souborům,
      systémovým zvláště.

      Příklad (xc/programs/Xserver/os/utils.c)

  static void
  InsertFileIntoCommandLine(resargc, resargv, prefix_argc, prefix_argv, filename
     suffix_argc, suffix_argv)

      int *resargc;
  ...
  {
      struct stat    st;
      FILE           *f;
      char           *p;
      char           *q;
      int            insert_argc;
      char           *buf;
      int            len;
      int            i;

      f = fopen(filename, "r");
  ...
  } /* end InsertFileIntoCommandLine */

  void
  ExpandCommandLine(pargc, pargv)
      int *pargc;
      char ***pargv;
  {
      int i;
      for (i = 1; i << *pargc; i++)
      {
          if ( (0 == strcmp((*pargv)[i], "-config")) && (i << (*pargc -- 1)) )
          {
              InsertFileIntoCommandLine(pargc, pargv,
                                            i, *pargv,
                                      (*pargv)[i+1], /* filename */
                                      *pargc -- i - 2, *pargv + i + 2);
              i--;
          }
      }
  } /* end ExpandCommandLine */

          Funkce ExpandCommandLine()  interpretuje  parametr  -config tak,
      že  načte  obsah  specifikovaného  souboru  a přidá  ho k parametrům
      zadaným na  příkazové  řádce.  Nicméně  vůbec  nekontroluje,  zda je
      daný soubor uživateli, který Xserver  spustil,  přístupný pro čtení.
      Například, pokud zlý uživatel zadá soubor /etc/shadow, dostane se mu
      chybového hlášení typu:

Unrecognized option: root:BFLMpsvzABCD:12345:::::

          a může začít luštit superuživatelovo heslo.

          Zvláštní   kapitolu   představují   dočasné   soubory  vytvářené
      v  oblastech  přístupných  uživateli  --  zvláště v  adresáři  /tmp,
      ale i v domovském  adresáři  uživatele, v aktuálním  adresáři  apod.
      Zlý uživatel může vytvořením (sym)linků nebo přejmenováváním souborů
      lehce přesvědčit naivně implementovaný program, aby pracoval s úplně
      jiným souborem, než zamýšlel. Bohužel tomu opět napomáhá  standardní
      knihovna  --   konkrétně   funkce   mktemp() a  tmpnam(). V  případě
      Xserveru lze problém  tohoto  typu demonstrovat na práci s adresářem
      /tmp/.X11-unix,  i  když  konkrétní   povaha  těchto  rizik  poněkud
      vybočuje ze zaměření tohoto článku.

                      Neopatrné spouštění dalších programů

          Příležitostně  potřebuje  jeden  program  využít  služeb  jiného
      programu.  Pokud  první  program běží se zvláštními  privilegii, pak
      jsou často tato  privilegia  přenášena i na programy z něj spuštěné.
      To pak znamená, že se na jedné  straně  nabízí  možnost  využít  (či
      spíše  zneužít)  nedostatků  jiného  programu, na druhé  straně  pak
      možnost přímo donutit  privilegovaný  proces k spuštění  libovolného
      programu dodaného zlým uživatelem (sem lze zařadit použití dynamicky
      linkované  knihovny podle přání uživatele). Xserver vykazuje vzácnou
      kombinaci obou problémů, jak uvidíme v příkladu.

      Příklad (xc/programs/Xserver/xkb/ddxLoad.c)

  Bool
  XkbDDXCompileKeymapByNames(XkbDescPtr              xkb,
                             XkbComponentNamesPtr    names,
                             unsigned                want,
                             unsigned                need,
                             char *                  nameRtrn,
                             int                     nameRtrnLen)
  {
  ...
      if (XkbBaseDirectory!=NULL) {
  ...
          sprintf(buf,
      "%s/xkbcomp -w %d -R%s -xkm -- -em1 %s -emp %s -eml %s \"%s%s.xkm\"",
                XkbBaseDirectory,
                ((xkbDebugFlags<<2)?1:((xkbDebugFlags>>10)?10:xkbDebugFlags)),
                XkbBaseDirectory,
                PRE_ERROR_MSG,ERROR_PREFIX,POST_ERROR_MSG1,
                xkm_output_dir,keymap);
  ...
      }
      else {
  ...
      }
  ...
      out= popen(buf,"w");
  ...
  }


          Proměnná   XkbBaseDirectory   obsahuje  buď  implicitní  hodnotu
      ( /usr/lib/X11/xkb), nebo hodnotu  specifikovanou  pomocí  parametru
      -xkbdir.  (Z tohoto  důvodu mi není  zcela  jasný  význam  testu  na
      nulovou  hodnotu,  ale to ponechme  stranou.)  Uživatel má nyní  dvě
      možnosti jak tento parametr zneužít: jednak může vytvořit  libovolný
      program pod  jménem  xkbcomp a předat  jméno  adresáře, ve kterém se
      nachází, nebo může využít faktu, že funkce popen() způsobí  spuštění
      shellu, a zadat  hodnotu,  která  obsahuje  znaky  mající  pro shell
      zvláštní význam (např. mezery, obrácené apostrofy aj.).

                               Východiska z nouze

          Rozsáhlé a těžko prověřitelné  programy (jako  zmíněný  Xserver)
      neměly být nikdy  spouštěny se zvláštními  privilegii.  Principiálně
      vzato by těžko  prověřitelné  programy  neměly vůbec  vznikat. Často
      používané  bezpečnostní  obálky  (wrappers) a podobná  opatření jsou
      pouze  nápravou  následků,  nikoli  příčin, které spočívají v špatné
      konstrukci  samotných  programů, i když za určitých  okolností mohou
      mít své výhody. Návod ke správné konstrukci bezpečných  programů lze
      hledat ve dvou "základních bezpečnostních pravidlech", která zní:

      - Nikdy nikomu nevěř.
      - Co není dovoleno, je zakázáno.

          První pravidlo říká, že v programu  nesmí být chyby, ale navržen
      musí být s ohledem na to, že tam chyby budou. To například  znamená,
      že ty části programů, které zvláštní privilegia  vyžadují, je vhodné
      separovat  do  malých  samostatných  programů  s přesně  definovaným
      rozhraním,  jejichž  správnou  funkci  bude možno  ověřit -- ideálně
      formálním důkazem.


          Druhé   pravidlo   pak   doporučuje,  aby  každý   program   měl
      přidělena   pouze  ta  privilegia,  která  ke  své  práci  potřebuje
      a pokud  možno  žádná  jiná.  To  bohužel  často  naráží  na omezení
      operačního   systému  --  zvláště v  UNIXovém   světě   (Linux  opět
      nevyjímaje),  kde  vládne   silná  dichotomie:  vše  (superuživatel)
      nebo  nic  (ostatní   uživatelé).  Není-li  možno  dostatečně  jemně
      rozlišovat   privilegia,   dochází  k  tomu,  že  jsou   přidělována
      širší  oprávnění,  než  je  zapotřebí,  a  otevírá  se  prostor  pro
      jejich  zneužití.  Jediným  skutečným  řešením  tohoto  problému  je
      upravit  operační  systém  tak,  aby  programy  nemusely  mít  větší
      oprávnění  než  je  nutno.  Komplexnější  projekty  jako  LinuxPrivs
      http://www.kernel.org/pub/linux/libs/security/linux-privs/
          (implementace   privilegií   podle   POSIX.1e)   jsou  vykročení
      správným  směrem, ale  existují i částečná  řešení,  jako  například
      patch  http://www.phrack.com/52/P52-06,  který mj.  eliminuje  dosti
      nesmyslnou  (v dnešní  době)  potřebu  superuživatelských  práv  pro
      některé síťové služby.

          Poněkud paradoxní je, že popisovaná opatření (pomineme-li obecné
      doporučení, že v programech  nemají být chyby)  nejsou příliš vhodná
      pro náš Xserver. Ten totiž ve skutečnosti žádná speciální privilegia
      nepotřebuje,  pouze  musí být  schopen  ovládat  grafický  hardware.
      Stačí,  aby  pro to  byla v jádře  dostatečná  podpora  --  jako  jí
      poskytuje například GGI http://www.ggi-project.org/.

                                     Závěr

          Tento článek  popisuje pouze malou část z rozsáhlého  repertoáru
      možných  rizik:  zmiňované  problémy  (nebo  problémy  jim  podobné)
      mohou   vznikat  (a  bohužel  také  vznikají)   všude,  kde  dochází
      k  interakci  mezi   procesy   pracujícími s  různými   oprávněními,
      ať už  jsou  to   diskutované   programy   využívající   mechanismus
      propůjčení identifikace, programy komunikující po síti (na serverové
      i na klientské  straně), nebo dokonce uživatelské  aplikace, jsou-li
      jim  předloženy  vstupy  pocházející z  potenciálně  nedůvěryhodného
      zdroje. Proto nelze než doporučit, aby tvůrce  libovolného  programu
      zvážil  při jeho  návrhu a implementaci  možná  bezpečnostní  rizika
      a snažil se je úplně eliminovat, nebo alespoň maximálně omezit.


            výheň