1 of 40

Cursul 2

2

Apeluri de sistem

2 of 40

Apeluri de sistem

  • Apeluri de sistem
  • GAS, stack frame
  • Implementarea apelurilor de sistem în Linux
  • Bibliografie
    • UTLK: capitolul 10
    • LKD: capitolul 5

3 of 40

Apeluri de sistem

Read (fd, buffer, nbytes)

Apelurile de sistem sunt modalitatea prin care aplicaţiile accesează serviciile puse la dispoziţie de kernel.

4 of 40

Exemple

  • Exemple de funcţii ce fac apeluri de sistem:
    • Linux: read, write, etc.
    • Windows: NtReadFile, NtWriteFile, etc.
  • Exemple de funcţii ce nu fac apeluri de sistem:
    • Memcpy, strcpy

5 of 40

Ce este un apel de sistem?

  • O modalitate de a intra în mod controlat în kernel: trap / excepţie / întrerupere sincronă cauzată de execuţia unei instrucţiuni speciale
  • Trap-ul are asociat o rutină de tratare (system call dispatcher)
  • Apelurile de sistem se identifică prin numere
  • Informaţii suplimentare se pot pasa prin parametri

6 of 40

Ce se întâmplă la un apel de sistem?

Un apel de sistem determină comutarea contextului de la aplicaţie la kernel. Procesul curent îşi continuă execuţia în kernel mode şi rulează rutina asociată apelului de sistem.

7 of 40

Ce se întâmplă la un apel de sistem? (2)

  • Se trece din user mode în kernel mode
  • Se ridică restricţiile de accesarea kernel space
  • Se schimbă stiva user cu cea kernel
  • Se verifică şi copiază argumentele din user space în kernel space
  • Se identifică şi rulează rutina asociată cu apelul de sistem
  • Se comută înapoi în user mode şi se continuă execuţia aplicaţiei

8 of 40

Parametrii apelurilor de sistem

  • Întregi de dimensiunea cuvântului maşinii pentru simplitate
  • Sunt stocaţi fie în regiştri, fie pe stiva user; sunt copiaţi pe stiva kernel de către dispatcher
  • Pot conţine pointeri (in/out); accesarea pointerilor se face de către rutina ce implementează apelul de sistem
  • Valorile parametrilor nu pot fi considerate ca fiind valide trebuie verificate
  • Atenţie sporită la pointeri

9 of 40

Pointeri către kernel-space

  • Aplicaţia poate pasa un pointer care să pointeze în kernel-space
    • Pointer de tip in către kernel space: acces la date protejate
    • Pointer de tip out: coruperea memoriei kernel
  • Soluţie: verificarea valorii pointerului şi interzicerea apelului de sistem în cazul în care acesta pointează către kernel space

10 of 40

Pointeri către zone invalide

  • Aplicaţia poate să paseze parametri cu pointeri către zone de memorie nemapate în spaţiul de adresă al procesului
  • Soluţia evidentă: verificarea adresei înainte de orice accesare
    • Costisitoare, necesită căutarea adresei în spaţiul de adresă al procesului

11 of 40

Soluţia eficientă

  • Nu facem verificări explicite
  • Dacă o adresă este invalidă în momentul accesării sale se va genera un page fault
    • Se rulează rutina de tratare a page fault-ului
    • Identificăm faptul că page falt-ul a fost cauzat de o adresă invalidă primită din user-space
    • Întrerupem apelul de sistem cu eroare
  • În cazul în care adresa este corectă nu trebuie să facem verificări suplimentare

12 of 40

Page fault-uri

  • Copy on write, demand paging, swap
    • Identificare: adresa de fault este în user-space şi este validă
  • Kernel bug
    • Identificare: adresa de fault este în user-space şi este invalidă
  • Pointer invalid primit din user-space
    • Identificare: adresa de fault este în user-space şi este invalidă
  • Ambiguitate în cazul ultimelor două situaţii

13 of 40

Exemplu ambiguitate

  • Kernel bug

int *i=NULL;

if (*i != 0)...

  • Pointer invalid pasat din user-space

/* ubuf vine din user-space si e NULL */

memcpy(kbuf, ubuf, len);

14 of 40

Rezolvarea ambiguităţii

  • Secvenţele de cod ce accesează direct spaţiul utilizator sunt marcate special
  • La rezolvarea page fault-ului verifică dacă adresa codului ce a generat page-fault-ul face parte dintr-o zonă special marcată
    • Da -> pointer invalid pasat din user-space
    • Nu -> kernel bug

15 of 40

Analiză

  • Soluţia ineficientă:
    • Cost adresă validă: căutare adresei în spaţiul de adresă al procesului
    • Cost adresă invalidă: căutare adresei în spaţiul de adresă al procesului
  • Soluţia eficientă:
    • Cost adresă validă: zero
    • Cost adresă invalidă: cost page fault + căutare adresei în spaţiul de adresă al procesului + căutarea adresei ce a generat fault-ul

16 of 40

Accesul la spaţiul utilizator

  • Nu se face doar în rutinele ce implementează apelurile de sistem ci şi în ... device drivere
  • Nu accesaţi niciodată direct spaţiul utilizator folosiţi funcţiile / infrastructura pusă la dispoziţie de către kernel

17 of 40

64/32bit user/kernel

  • Datele îşi pot schimba semnificaţia între user space şi kernel space

OS

char

short

int

long

void*

Unix 32bit

1 byte

2 bytes

4 bytes

4 bytes

4 bytes

Windows 32bits

1 byte

2 bytes

4 bytes

4 bytes

4 bytes

Unix 64bits

1 byte

2 bytes

4 bytes

8 bytes

8 bytes

Windows 64bits

1 byte

2 bytes

4 bytes

4 bytes

8 bytes

18 of 40

Exemplu

User-space 32 biţi:

    { .a = 1, .b = 2 };

Kernel space 64 biţi:

    { .a = 8589934593,

     .b = -1075374248 };

struct my_struct {

    long a;

    int b;

}

1

1

2

?

?

?

?

Little endian

2

19 of 40

Apeluri de sistem în Linux

  • Fluxul de execuţie: aplicaţie -> libc -> apel de sistem
  • Generarea trap-ului: int $0x80 sau sysenter (Pentium II+)
    • Numărul apelului de sistem: eax
    • Parametrii: ebx, ecx, edx, esi, edi, ebp
    • Rezultatul: eax

20 of 40

Exemplu de invocare

NAME�       dup, dup2, dup3 - duplicate a file descriptor��SYNOPSIS�       #include <unistd.h>��       int dup(int oldfd);�       int dup2(int oldfd, int newfd);��       #define _GNU_SOURCE�       #include <unistd.h>��       int dup3(int oldfd, int newfd, int flags);�

21 of 40

Exemplu de invocare (2)

000c7590 <__dup2>:

     ...

     c7592: movl 0x8(%esp),%ecx

     c7596: movl 0x4(%esp),%ebx

     c759a: movl $0x3f,%eax

     c759f: int $0x80

     ...

22 of 40

Rutina de dispatch

ENTRY(system_call)

...

SAVE_ALL

...

cmpl $(nr_syscalls),%eax

jae badsys

call *sys_call_table(,%eax,4)

movl %eax,PT_EAX(%esp)

...

ENTRY(sys_call_table)

.long SYMBOL_NAME(sys_ni_syscall)

.long SYMBOL_NAME(sys_exit)

.long SYMBOL_NAME(sys_fork)

23 of 40

Rutina de dispatch (2)

#define SAVE_ALL \

...

push %eax \

push %ebp \

push %edi \

push %esi \

push %edx \

push %ecx \

push %ebx

24 of 40

Implementare apel de sistem

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

asmlinkage 

long sys_open(const char * filename, int flags, int mode)

  • Forţează toţi parametrii pe stivă (altfel compilatorul poate face optimizări)
  • Funcţia poate fi definită cu mai puţin de şase parametri (chiar dacă dispatcher-ul salvează cei şase regiştri pe stivă de fiecare dată)

25 of 40

Exerciţiu

Scrieţi secvenţa de cod ce interceptează apelul de sistem open şi face astfel încât la fiecare invocare să se afişeze numărul şi rezultatul apelului de sistem.

26 of 40

Rezolvare

struct syscall_params {

    long ebx, ecx, edx, esi, edi, ebp, eax;

};

asmlinkage long interceptor(struct syscall_params sp)

{

     int syscall=sp.eax, r=f(sp);

     printk(„%d: %d\n”, syscall, r);

     return r;

}

f=sys_call_table[__NR_open];

sys_call_table[__NR_open]=interceptor;

27 of 40

Rezolvare (2)

  • Este doar un exerciţiu didactic, în practică nu se recomandă interceptarea apelurilor de sistem
    • Probleme: race-uri
      • http://www.watson.org/~robert/2007woot/
    • Folosiţi infrastructura de trace sau LSM
  • Cazuri speciale pentru care această abordare nu funcţionează
    • clone() - se uită şi la alţi regiştri salvaţi
    • exec() trebuie să modifice valoarea EIP-ului salvat de procesor la comutarea contextului

28 of 40

VDSO

  • Problema: folosirea int 0x80/sysenter depinde de versiunea procesorului, versiunea kernelului şi versiunea libc
  • Soluţia: Virtual Dynamic Shared Object:
    • kernelul decide ce instrucţiune de trap să se folosească
    • Pentru genericitate, kernelul va genera chiar el secvenţa de instrucţiuni ce trebuie folosită
    • Respectiva secvenţă va fi mapată în spaţiul utilizator în zona VDSO

29 of 40

VDSO (2)

$ cat /proc/$$/maps

...

bfac5000-bfada000 rw-p bffeb000 00:00 0 [stack]

ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

$ dd if=/proc/self/mem of=linux-gate.so bs=4096 \

  skip=$[0xffffe] count=1

30 of 40

VDSO (3)

$ objdump -d linux-gate.so –no-show-raw-insn

ffffe400 <__kernel_vsyscall>:

ffffe400: push %ecx

ffffe401: push %edx

ffffe402: push %ebp

ffffe403: mov %esp,%ebp

ffffe405: sysenter

ffffe407: nop

31 of 40

Vsyscalls (virtual system calls)

  • Apeluri de sistem care rulează direct din user-space
  • În zone VDSO kernelul mapează cod ce rulează direct din user-space şi accesează direct date kernel, date mapate şi ele în VDSO (dar R/O)
  • Vgettimeofday, Vgetcpu (x86_64)

32 of 40

Accesarea spaţiului utilizator

  • Primitive specializate: get_user, put_user,copy_from_user, copy_to_user
  • Verificarea pointerului şi tratarea eventualului page fault la acces invalid se face intern

/* Dacă s-a generat un page fault datorită unui access

* invalid primitivele întorc o valoare diferită de zero */

 

if (copy_from_user(kernel_buffer, user_buffer, size))

    return -EFAULT;

33 of 40

get_user

#define get_user(x,ptr) \

({ \

    int __ret_gu; \

    unsigned long __val_gu; \

    switch(sizeof (*(ptr))) { \

    case 1:

        __get_user_x(1,__ret_gu,__val_gu,ptr); \

        break; \

    case 2: \

        __get_user_x(2,__ret_gu,__val_gu,ptr); \

        break; \

    ...

    __ret_gu; \

})

34 of 40

__get_user_x

#define __get_user_x(size,ret,x,ptr) \

    __asm__ __volatile__( \

     "call __get_user_" #size \

     :"=a" (ret),"=d" (x) :"0" (ptr) \

    )

ENTRY(__get_user_1)

    GET_THREAD_INFO(%edx)

    cmpl TI_addr_limit(%edx),%eax

    jae bad_get_user

    1: movzbl (%eax),%edx

    xorl %eax,%eax

    ret

ENDPROC(__get_user_1)

35 of 40

__get_user_x

#define __get_user_x(size,ret,x,ptr) \

    __asm__ __volatile__( \

    "call __get_user_" #size \

    :"=a" (ret),"=d" (x) :"0" (ptr) \

)

ENTRY(__get_user_1)

    GET_THREAD_INFO(%edx)

    cmpl TI_addr_limit(%edx),%eax

    jae bad_get_user

    1: movzbl (%eax),%edx

    xorl %eax,%eax

    ret

ENDPROC(__get_user_1)

Este un pointer către userspace?

Instrucţiunea de copiere şi adresa ei

36 of 40

__get_user_x (2)

bad_get_user:

     xorl %edx,%edx

     movl $-14,%eax

     ret

END(bad_get_user)

.section __ex_table,"a"

.long 1b,bad_get_user

.long 2b,bad_get_user

.long 3b,bad_get_user

.previous

Adresa instrucţiunii ce face accesul în userspace din cadrul __get_user_1

37 of 40

Tabela de excepţii

  • Un vector de perechi (fault instruction address, fix-up code address)
  • Generat la compilare în cadrul secţiunii __ex_table
  • Rutina de tratare a page fault-ului va căuta adresa instrucţiunii ce a generat fault-ul în tabelă, şi dacă o găseşte va sări la adresa asociată

38 of 40

Accesul la datele din secţiunile speciale

  • Scriptul de link editare (arch/*/kernel/*.lds.S) gardează secţiunea cu simboluri de genul __start / __stop
  • Exemplu:

    . = ALIGN(16); /* Exception table */

    __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {

     __start___ex_table = .;

     *(__ex_table)

     __stop___ex_table = .;

    }

39 of 40

fixup_exception

int fixup_exception(struct pt_regs *regs)

{

     const struct exception_table_entry *fixup;

     fixup = search_exception_tables(regs->eip);

     if (fixup) {

         regs->eip = fixup->fixup;

         return 1;

     }

     return 0;

}

Setăm instrucţiunea de la care

se continuă execuţia după ce ieşim

din handler-ul de tratare al

page-fault-ului

40 of 40

Întrebări

?