Warum wird strncpy nicht null beendet?

76

strncpy()schützt angeblich vor Pufferüberläufen. Wenn jedoch ein Überlauf ohne Nullbeendigung verhindert wird, wird aller Wahrscheinlichkeit nach eine nachfolgende Zeichenfolgenoperation überlaufen. Um mich davor zu schützen, mache ich Folgendes:

strncpy( dest, src, LEN );
dest[LEN - 1] = '\0';

man strncpy gibt:

Die strncpy()Funktion ist ähnlich, außer dass nicht mehr als nBytes von srckopiert werden. Wenn also kein Nullbyte unter den ersten nBytes von vorhanden ist src, wird das Ergebnis nicht nullterminiert.

Ohne null etwas scheinbar Unschuldiges zu beenden wie:

   printf( "FOO: %s\n", dest );

... könnte abstürzen.


Gibt es bessere und sicherere Alternativen zu strncpy()?

Timothy Pratley
quelle
1
Beachten Sie, dass unter MacOS X (BSD) auf der Manpage (von ' extern char *strncpy(char * restrict s1, const char * restrict s2, size_t n);') steht: Die Funktion strncpy () kopiert höchstens n Zeichen von s2 nach s1. Wenn s2 weniger als n Zeichen lang ist, wird der Rest von s1 mit "\ 0" Zeichen gefüllt. Andernfalls wird s1 nicht beendet.
Jonathan Leffler
Sollte es nicht dest sein [LEN-1] = '\ 0'; ?
CodeObserver
1
So würde ich denken, wir würden eine Kopie der Zeichenfolge erstellen: int LEN = src.len; str * dest = neues Zeichen [LEN + 1]; strncpy (dest, src, LEN); dest [LEN] = '\ 0';
CodeObserver
Die Verwendung von memset für die Zielzeichenfolge ist der sicherste Ansatz, wenn Sie sicher sind, dass die Größe der Zeichenfolge die Zielpufferlänge nicht überschreitet.
Koolvcvc
Schreiben Sie Ihre eigene Funktion, ich denke nicht, dass das eine schwierige Aufgabe sein sollte
Megharaj

Antworten:

45

strncpy()ist nicht als sicherer gedacht strcpy(), sondern soll verwendet werden, um eine Zeichenfolge in die Mitte einer anderen einzufügen.

Alle diese "sicheren" Zeichenfolgenbehandlungsfunktionen wie snprintf()und vsnprintf()sind Korrekturen, die in späteren Standards hinzugefügt wurden, um Pufferüberlauf-Exploits usw. zu verringern.

Wikipedia erwähnt strncat()als Alternative zum Schreiben Ihres eigenen Safes strncpy():

*dst = '\0';
strncat(dst, src, LEN);

BEARBEITEN

Ich habe übersehen, dass strncat()LEN-Zeichen überschritten werden, wenn die Zeichenfolge mit Null beendet wird, wenn sie länger oder gleich LEN-Zeichen ist.

Wie auch immer, der Sinn der Verwendung strncat()anstelle einer selbst entwickelten Lösung wie memcpy(..., strlen(...))/ was auch immer ist, dass die Implementierung von strncat()Ziel / Plattform in der Bibliothek optimiert sein könnte.

Natürlich müssen Sie überprüfen, ob dst mindestens das Nullzeichen enthält, damit die korrekte Verwendung von ungefähr so ​​lautet strncat():

if (LEN) {
    *dst = '\0'; strncat(dst, src, LEN-1);
}

Ich gebe auch zu, dass dies strncpy()nicht sehr nützlich ist, um einen Teilstring in einen anderen String zu kopieren. Wenn der src kürzer als n Zeichen ist, wird der Zielstring abgeschnitten.

Ernelli
quelle
28
"Es soll verwendet werden, um eine Zeichenfolge in die Mitte einer anderen einzufügen" - nein, es ist beabsichtigt, eine Zeichenfolge in ein Feld mit fester Breite zu schreiben, z. B. in einen Verzeichniseintrag. Aus diesem Grund wird der Ausgabepuffer mit NUL aufgefüllt, wenn (und nur wenn) die Quellzeichenfolge zu kurz ist.
Steve Jessop
3
Wie macht das Setzen von * dst = '\ 0' dies sicherer? Es besteht immer noch das ursprüngliche Problem, dass Sie über das Ende des Zielpuffers hinaus schreiben können.
Adam Liss
3
klingt gut, aber sollte es nicht strncat (dst, src, LEN-1) sein, da es ein zusätzliches Zeichen schreiben wird?
Timothy Pratley
3
@ Jonathan: Eigentlich sicher wäre ein Datentyp, der einen Zeiger auf einen Zeichenpuffer mit der Länge dieses Puffers kombiniert. Aber wir alle wissen, dass das nicht passieren wird. Persönlich bin ich all dieser Bemühungen überdrüssig, etwas, das von Natur aus unsicher ist (Programmierer, die versuchen, die Länge eines Puffers genau zu berücksichtigen), einen Bruchteil sicherer zu machen. Es ist nicht so, dass wir derzeit 50% zu viele Pufferüberläufe haben. Wenn wir also nur die String-Handhabung 50% sicherer machen könnten, wäre alles in Ordnung :-(
Steve Jessop
1
+1 für das Nicht-Wiederholen des Mülls, dass strncpy irgendwie eine sichere Version von strcpy ist - ersteres hat seine eigenen Probleme.
Paxdiablo
25

Ursprünglich hatte das UNIX- Dateisystem der 7. Edition (siehe DIR (5)) Verzeichniseinträge, die die Dateinamen auf 14 Byte beschränkten. Jeder Eintrag in einem Verzeichnis bestand aus 2 Bytes für die Inode-Nummer plus 14 Bytes für den Namen, null auf 14 Zeichen aufgefüllt, aber nicht unbedingt nullterminiert. Ich bin davon überzeugt, dass strncpy()es für diese Verzeichnisstrukturen entwickelt wurde - oder zumindest perfekt für diese Struktur.

Erwägen:

  • Ein 14-stelliger Dateiname wurde nicht mit Null beendet.
  • Wenn der Name kürzer als 14 Byte war, wurde er auf volle Länge (14 Byte) null aufgefüllt.

Dies ist genau das, was erreicht werden würde durch:

strncpy(inode->d_name, filename, 14);

So strncpy()wurde ideal zu seiner ursprünglichen Nischenanwendung angepasst. Es ging nur zufällig darum, Überläufe von nullterminierten Zeichenfolgen zu verhindern.

(Beachten Sie, dass das Auffüllen von Nullen bis zur Länge 14 kein schwerwiegender Aufwand ist. Wenn die Länge des Puffers 4 KB beträgt und Sie nur 20 Zeichen sicher in den Puffer kopieren möchten, sind die zusätzlichen 4075-Nullen ein schwerwiegender Overkill und können leicht auftreten führen zu quadratischem Verhalten, wenn Sie wiederholt Material zu einem langen Puffer hinzufügen.)

Jonathan Leffler
quelle
2
Diese besondere Situation mag dunkel sein, aber es ist nicht ungewöhnlich, Datenstrukturen mit Zeichenfolgenfeldern fester Länge zu haben, die mit Nullen aufgefüllt, aber nicht mit Nullen abgeschlossen sind. Wenn Daten mit festem Format gespeichert werden, ist dies häufig die effizienteste Methode.
Supercat
24

Es gibt bereits Open Source-Implementierungen wie strlcpy , die sicher kopieren.

http://en.wikipedia.org/wiki/Strlcpy

In den Referenzen gibt es Links zu den Quellen.

StampedeXV
quelle
1
Ganz zu schweigen von tragbar, schnell und zuverlässig. Sie können es immer noch missbrauchen, aber das Risiko ist um Größenordnungen geringer. IMO, strncpy sollte veraltet sein und durch dieselbe Funktion namens dirnamecpy oder ähnliches ersetzt werden. strncpy ist keine sichere String-Kopie und war es noch nie.
9

Strncpy ist sicherer gegen Stapelüberlaufangriffe durch den Benutzer Ihres Programms. Es schützt Sie nicht vor Fehlern , die der Programmierer macht, wie z. B. das Drucken einer nicht nullterminierten Zeichenfolge, wie Sie es beschrieben haben.

Sie können einen Absturz aufgrund des von Ihnen beschriebenen Problems vermeiden, indem Sie die Anzahl der von printf gedruckten Zeichen begrenzen:

char my_string[10];
//other code here
printf("%.9s",my_string); //limit the number of chars to be printed to 9
Liran Orevi
quelle
Die Verwendung des Präzisionsfeldes zur Begrenzung der Anzahl der von gedruckten Zeichen %smuss eines der dunkelsten Merkmale von C. sein
David Thornley
@ DavidThornley Es ist sehr klar in K & R unter Sprintf dokumentiert.
Weston
@weston: Und in Harbison & Steele, was ich hier bei der Arbeit habe. In welchen anderen populären C-Büchern als diesen beiden wird dies erwähnt? Jedes Merkmal sollte in K & R und H & S erwähnt werden (und wird im Standard erwähnt). Wenn dies also der Standard der Dunkelheit ist, gibt es keine dunklen Merkmale.
David Thornley
@DavidThornley Ich wollte nur Ihren Kommentar ausgleichen, weil durch das Setzen von "einer der dunkelsten Funktionen" diese Antwort schlecht aussieht und die Leute davon abgehalten werden könnten, sie zu verwenden. Was falsch ist, da es sich um eine vollkommen gültige, gut dokumentierte Funktion handelt, die ebenso gut dokumentiert ist wie jede andere Verwendung des Präzisionsfeldes. "Obscure" scheint Ansichtssache zu sein, da ich persönlich sehe, dass dies viel benutzt wird.
Weston
8

Einige neue Alternativen sind in ISO / IEC TR 24731 angegeben ( Informationen finden Sie unter https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/coding/317-BSI.html ). Die meisten dieser Funktionen verwenden einen zusätzlichen Parameter, der die maximale Länge der Zielvariablen angibt, sicherstellt, dass alle Zeichenfolgen nullterminiert sind und Namen haben, die auf _s(für "sicher"?) Enden , um sie von ihren früheren "unsicheren" Versionen zu unterscheiden . 1

Leider erhalten sie immer noch Unterstützung und sind möglicherweise nicht mit Ihrem speziellen Tool-Set verfügbar. In späteren Versionen von Visual Studio werden Warnungen ausgegeben, wenn Sie die alten unsicheren Funktionen verwenden.

Wenn Ihre Tools die neuen Funktionen nicht unterstützen, sollte es ziemlich einfach sein, eigene Wrapper für die alten Funktionen zu erstellen. Hier ist ein Beispiel:

errCode_t strncpy_safe(char *sDst, size_t lenDst,
                       const char *sSrc, size_t count)
{
    // No NULLs allowed.
    if (sDst == NULL  ||  sSrc == NULL)
        return ERR_INVALID_ARGUMENT;

   // Validate buffer space.
   if (count >= lenDst)
        return ERR_BUFFER_OVERFLOW;

   // Copy and always null-terminate
   memcpy(sDst, sSrc, count);
   *(sDst + count) = '\0';

   return OK;
}

Sie können die Funktion an Ihre Bedürfnisse anpassen, um beispielsweise immer so viel wie möglich von der Zeichenfolge zu kopieren, ohne überzulaufen. Tatsächlich kann die VC ++ - Implementierung dies tun, wenn Sie _TRUNCATEals übergeben count.




1 Natürlich müssen Sie die Größe des Zielpuffers genau bestimmen: Wenn Sie einen 3-Zeichen-Puffer strcpy_s()angeben, aber angeben, dass er Platz für 25 Zeichen bietet, sind Sie immer noch in Schwierigkeiten.

Adam Liss
quelle
Sie können eine Funktion, deren Name mit str * beginnt, nicht legal definieren. Dieser "Namespace" ist in C reserviert.
Abwickeln
2
Aber das ISO C-Komitee kann - und hat es getan. Siehe auch: stackoverflow.com/questions/372980/…
Jonathan Leffler
@ Jonathan: Vielen Dank für den Querverweis auf Ihre eigene Frage, die viele zusätzliche hilfreiche Informationen enthält.
Adam Liss
5

Verwendung strlcpy(), hier angegeben:http://www.courtesan.com/todd/papers/strlcpy.html

Wenn Ihre libc keine Implementierung hat, versuchen Sie diese:

size_t strlcpy(char* dst, const char* src, size_t bufsize)
{
  size_t srclen =strlen(src);
  size_t result =srclen; /* Result is always the length of the src string */
  if(bufsize>0)
  {
    if(srclen>=bufsize)
       srclen=bufsize-1;
    if(srclen>0)
       memcpy(dst,src,srclen);
    dst[srclen]='\0';
  }
  return result;
}

(Geschrieben von mir im Jahr 2004 - für den öffentlichen Bereich gewidmet.)

Alex Kribbeln
quelle
Bitte klären Sie mich auf, warum möchten Sie, dass das Ergebnis immer die Länge des src-Strings ist? Meiner Meinung nach ist die Rückkehr srclenbesser, da wir wissen würden, wie viele Zeichen wirklich kopiert werden.
Lê Quang Duy
@ LêQuangDuy, es entspricht der Spezifikation ( freebsd.org/cgi/man.cgi?query=strlcpy&sektion=3#end ): Wie snprintf , strlcat gibt es die Größe des Strings zurück, den es zu schreiben versucht hat , damit der Aufrufer dies kann Stellen Sie einen größeren Puffer bereit und rufen Sie die Funktion erneut auf, um alles zu speichern.
Jonathan Lidbeck
3

strncpy arbeitet direkt mit den verfügbaren Zeichenfolgenpuffern. Wenn Sie direkt mit Ihrem Speicher arbeiten, MÜSSEN Sie jetzt die Größe puffern und Sie können die '\ 0' manuell einstellen.

Ich glaube, es gibt keine bessere Alternative in C, aber es ist nicht wirklich so schlimm, wenn Sie so vorsichtig sind, wie Sie es sollten, wenn Sie mit rohem Gedächtnis spielen.

Arkaitz Jimenez
quelle
3

Stattdessen strncpy()könnten Sie verwenden

snprintf(buffer, BUFFER_SIZE, "%s", src);

Hier ist ein Einzeiler, der höchstens size-1Nicht-Null-Zeichen von srcbis kopiert destund einen Null-Terminator hinzufügt:

static inline void cpystr(char *dest, const char *src, size_t size)
{ if(size) while((*dest++ = --size ? *src++ : 0)); }
Christoph
quelle
Wir verwenden ein Makro, das äquivalent zu ist snprintf(buffer, sizeof(buffer), "%s", src). Funktioniert gut, solange du nie daran erinnern verwenden , um auf char * Ziele
che
3

Ich habe es immer vorgezogen:

 memset(dest, 0, LEN);
 strncpy(dest, src, LEN - 1);

um das Problem danach zu beheben, aber das ist wirklich nur eine Frage der Präferenz.

Steinmetall
quelle
1
Ob alle Puffer auf Null initialisiert werden sollen, ist ein eigenständiges Diskussionsthema. Persönlich bevorzuge ich dies während der Entwicklung / des Debuggens, da dies dazu führt, dass Fehler offensichtlicher werden, aber es gibt viele andere ("billigere") Optionen.
Adam Liss
7
Sie müssen nur einstellen dest[LEN-1]auf 0- die anderen Bytes werden bei Bedarf mit gefüllt strncpy()(denken Sie daran: strncpy(s,d,n)schreibt IMMER nBytes!)
Christoph
2

Diese Funktionen haben sich mehr entwickelt als entworfen, daher gibt es wirklich kein "Warum". Sie müssen nur lernen, "wie". Leider enthalten die Linux-Manpages zumindest keine allgemeinen Anwendungsbeispiele für diese Funktionen, und ich habe in dem von mir überprüften Code viele Missbräuche festgestellt . Ich habe hier einige Notizen gemacht: http://www.pixelbeat.org/programming/gcc/string_buffers.html

Pixelbeat
quelle
Ähm, warum ist das _ in der obigen URL auf% 5F verstümmelt? Unterstrich ist in Ordnung gemäß RFC 3548.
Pixelbeat
Wenn strncpy()es vorhanden ist, kann man die Nullterminierung erzwingen, indem man manuell ein Nullbyte am Ende des Puffers schreibt. Wenn strncpy () darauf bestand, immer ein Null-Byte nach dem letzten nützlichen Speicherort zu schreiben, kann ich mir keine effiziente Methode zum Aktualisieren von null-aufgefüllten (nicht terminierten) Zeichenfolgen vorstellen. Beachten Sie, dass mit Nullen aufgefüllte Zeichenfolgen mit bekannter fester Länge ein zeiteffizientes Mittel zum Speichern von Daten auf der Festplatte waren und sind. Das Speichern von Informationen im RAM im selben Format wie auf der Festplatte kann ebenfalls die Leistung steigern.
Supercat
2

Ohne mich auf neuere Erweiterungen zu verlassen, habe ich in der Vergangenheit so etwas getan:

/* copy N "visible" chars, adding a null in the position just beyond them */
#define MSTRNCPY( dst, src, len) ( strncpy( (dst), (src), (len)), (dst)[ (len) ] = '\0')

und vielleicht sogar:

/* pull up to size - 1 "visible" characters into a fixed size buffer of known size */
#define MFBCPY( dst, src) MSTRNCPY( (dst), (src), sizeof( dst) - 1)

Warum funktionieren die Makros anstelle neuerer "eingebauter" (?) Funktionen? Weil es früher einige verschiedene Unices gab, sowie andere Nicht-Unix-Umgebungen (Nicht-Windows-Umgebungen), auf die ich portieren musste, wenn ich täglich C machte.

Roboprog
quelle