Skip to content

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

Description

@interestingmaneuver

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions