dag7dev / UniQuizzes

Learning improving tool by prompting useful questions.
https://dag7dev.github.io/UniQuizzes
GNU General Public License v3.0
17 stars 6 forks source link

[ANS] SO2 2023, reimpl fprintf, nessuna risposta corretta? #41

Open interestingmaneuver opened 5 months ago

interestingmaneuver commented 5 months ago

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.

FeddyLix17 commented 3 months ago

@interestingmaneuver premesso che anche secondo me (per gli stessi motivi che hai scritto) nessun blocco di codice riportato è esattamente equivalente a quello riportato dalla domanda (considerato che la funzione write() non è normalmente predisposta per la stampa di interi composti da più cifre)

tolte le opzioni D ed A (sicuramente diverse), tra l'opzione B e C sceglieri la B in quanto nell'fprintf() il numero e il carattere "\n" vengono stampati "separatamente" e non "seguendo lo stesso flusso dati di un buffer"