-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Argomento:
SO2 2023
Domanda:
Si supponga di avere il seguente frammento di codice. Quale dei seguenti frammenti di codice ha lo stesso effetto?
int var = somefunction1();
double var2 = somefunction2();
fprintf(stdout, "%d\n%lf\n", var, var2);Secondo me nessuna delle risposte è da ritenersi corretta, perché:
A:
int var = somefunction1();
double var2 = somefunction2();
char buf[4];
sprintf(buf, "%d", var);
write(1, buf, sizeof(var));
write(1, "\n", 1);
sprintf(buf, "%lf", var2);
write(1, buf, sizeof(var2));
write(1, "\n", 1);A. buf è troppo piccolo, anche senza guardare il resto, var potrebbe avere un intero che richiede più di 3 cifre (+ terminatore), così come var2 potrebbe richiederne più di 3. Al di là del buffer overflow, comunque non produrrebbe l'effetto desiderato perché si limita a stampare 4 cifre.
B:
int var = somefunction1();
double var2 = somefunction2();
char *buf = calloc(sizeof(var2) > sizeof(var) ? sizeof(var2) : sizeof(var), sizeof(char));
sprintf(buf, "%d", var);
write(1, buf, sizeof(var));
write(1, "\n", 1);
sprintf(buf, "%lf", var2);
write(1, buf, sizeof(var2));
write(1, "\n", 1);B. Questa è la risposta corretta nel json. In realtà questa ha gli stessi problemi di A: la riga 3 è semplicemente calloc(8, 1) su x86-64. L'intero massimo rappresentabile è 2147483647, che ha 10 cifre, poi ci sono anche i negativi, -2147483648 ne ha 11. Anche ignorando gli overflow il write stampa comunque solo 4 cifre (su x86), quindi non ha lo stesso effetto dell'originale.
Se proprio vogliamo esagerare, il secondo sprintf potrebbe scrivere anche più di 300 byte secondo questo warning di GCC:
<source>:25:5: warning: 'sprintf' may write a terminating nul past the end of the destination [-Wformat-overflow=]
25 | sprintf(buf, "%lf", var2);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:25:5: note: 'sprintf' output between 4 and 318 bytes into a destination of size 8
C:
int var = somefunction1();
double var2 = somefunction2();
char *buf = calloc(sizeof(var2) > sizeof(var) ? sizeof(var2) : sizeof(var), sizeof(char));
sprintf(buf, "%d\n", var);
write(1, buf, sizeof(var) + 1);
sprintf(buf, "%lf\n", var2);
write(1, buf, sizeof(var2) + 1);C. Lo stesso di B tranne per l'"aggiunta" di un overrun sul write e sullo sprintf (più di prima). Comunque in A, B, C il write scrive anche i byte nulli, che non considererei proprio avere lo stesso effetto di fprintf.
D:
int var = somefunction1();
double var2 = somefunction2();
write(1, (char *)&var, sizeof(var));
write(1, (char *)&var2, sizeof(var2));D. Ironicamente la versione più sicura, però scrive i due numeri in binario mentre fprintf li converte in ASCII, in più mancano i newline.