C-Sicherheitsfunktionen
In der Sprache C wurden, um die Code-Sicherheit zu verbessern, insbesondere um häufige Sicherheitsprobleme wie Pufferüberläufe zu verhindern, mit dem C11-Standard einigesichere Funktioneneingeführt, auch bekannt alsAnnex K-Standardbibliotheksfunktionen. Diese sicheren Funktionen sind hauptsächlich verbesserte Versionen von Standard-Zeichenketten- und Speicheroperationsfunktionen und bieten eine bessere Fehlererkennung und -behandlung durch zusätzliche Parameter (wie Puffergröße).
Merkmale von Sicherheitsfunktionen:
- PuffergrößenprüfungAlle sicheren Funktionen erfordern, dass der Größenparameter des Zielpuffers übergeben wird, um Pufferüberläufe zu verhindern.
- Rückgabewertprüfung: Die meisten Funktionen geben zurück
errno_tFehlercodes dieses Typs können überprüft werden, um festzustellen, ob die Funktion erfolgreich ausgeführt wurde. - Bessere Fehlerbehandlung: Wenn die Puffergröße nicht ausreicht oder andere Probleme auftreten, geben diese Funktionen einen Fehlercode zurück und versuchen, den Ausgabepuffer zu löschen oder zu initialisieren.
Sichere Funktionen werden in Compilern wie Visual Studio gut unterstützt, sind aber in einigen älteren Compilerversionen möglicherweise nicht verfügbar, daher muss die Kompatibilität beachtet werden.
Das Folgende ist ein Vergleich gängiger sicherer Funktionen in C und ihrer entsprechenden traditionellen Funktionen:
1. Sicherheitsfunktionen für Zeichenkettenoperationen
strcpy_s: Sicherheitsversion von strcpy, kopiert die Zeichenkette und prüft die Größe des Zielpuffers.
errno_t strcpy_s(char *dest, rsize_t destsz, const char *src);
strcat_sSichere Version von strcat, hängt die Quellzeichenkette an das Ende der Zielzeichenkette an und prüft die Puffergröße.
errno_t strcat_s(char *dest, rsize_t destsz, const char *src);
strncpy_s: Sicherheitsversion von strncpy, kopiert bis zu n Zeichen und prüft die Puffergröße.
errno_t strncpy_s(char *dest, rsize_t destsz, const char *src, rsize_t count);
strncat_sSichere Version von strncat, hängt höchstens n Zeichen an das Ende der Zielzeichenkette an und prüft die Puffergröße.
errno_t strncat_s(char *dest, rsize_t destsz, const char *src, rsize_t count);
strtok_sSichere Version von strtok, führt einen Kontextparameter ein, um Thread-Sicherheitsprobleme zu lösen.
char *strtok_s(char *str, const char *delim, char **context);
2. Sicherheitsfunktionen für formatierte Ausgabe
sprintf_sSichere Version von sprintf, prüft die Puffergröße beim Formatieren der Ausgabe in eine Zeichenkette.
int sprintf_s(char *buffer, rsize_t buffer_size, const char *format, ...);
snprintf_sSichere Version von snprintf, begrenzt die Anzahl der Zeichen und prüft die Puffergröße bei der formatierten Ausgabe.
int snprintf_s(char *buffer, rsize_t buffer_size, const char *format, ...);
vsprintf_sSichere Version von vsprintf, akzeptiert eine va_list-Argumentliste und prüft die Puffergröße.
int vsprintf_s(char *buffer, rsize_t buffer_size, const char *format, va_list argptr);
3. Sicherheitsfunktionen für Speicheroperationen
memcpy_s: Sichere Version von memcpy, prüft die Zielpuffergröße beim Kopieren eines Speicherbereichs.
errno_t memcpy_s(void *dest, rsize_t destsz, const void *src, rsize_t count);
memmove_s: Sichere Version von memmove, kopiert Speicherbereiche, erlaubt Überlappung und prüft die Zielpuffergröße.
errno_t memmove_s(void *dest, rsize_t destsz, const void *src, rsize_t count);
memset_sSichere Version von memset, füllt den Speicherblock mit dem angegebenen Zeichen und prüft die Puffergröße.
errno_t memset_s(void *dest, rsize_t destsz, int ch, rsize_t count);
4. Weitere gängige Sicherheitsfunktionen
_itoa_sund_ultoa_sSichere Version der Ganzzahlkonvertierungsfunktionen, prüft die Zielpuffergröße beim Konvertieren von Ganzzahlen in Zeichenketten.
errno_t _itoa_s(int value, char *buffer, size_t buffer_size, int radix); errno_t _ultoa_s(unsigned long value, char *buffer, size_t buffer_size, int radix);
_strlwr_sund_strupr_sSichere Versionen, die Zeichenketten in Klein- oder Großbuchstaben umwandeln.
errno_t _strlwr_s(char *str, size_t numberOfElements); errno_t _strupr_s(char *str, size_t numberOfElements);
Beispiel
Die folgenden Beispiele zeigen die Verwendung sicherer C-Funktionen für Zeichenketten- und Speicheroperationen und veranschaulichen, wie sie häufige Pufferüberlaufprobleme vermeiden und einen sichereren Programmieransatz bieten.
Beispiel 1: strcpy_s und strcat_s
Beispiel
#include <string.h>
int main() {
char dest[20]; // Die Zielpuffergröße beträgt 20
const char *src = "Hallo, Welt!";
// Verwende strcpy_s, um src nach dest zu kopieren
if (strcpy_s(dest, sizeof(dest), src) != 0) {
printf("strcpy_s fehlgeschlagen!\n");
return 1; // Fehlercode zurückgeben
} else {
printf("Nach strcpy_s: %s\n", dest);
}
// Verwende strcat_s, um " C-Sprache" an dest anzuhängen
const char *appendStr = " C-Sprache";
if (strcat_s(dest, sizeof(dest), appendStr) != 0) {
printf("strcat_s fehlgeschlagen!\n");
return 1; // Fehlercode zurückgeben
} else {
printf("Nach strcat_s: %s\n", dest);
}
return 0;
}
Ausgabe:
After strcpy_s: Hello, World! strcat_s failed!
Im obigen Code hat strcpy_s die Zeichenkette "Hallo, Welt!" erfolgreich nach dest kopiert, aber da dest eine Größe von 20 hat, was nicht ausreicht, um "Hallo, Welt! C-Sprache" aufzunehmen, wird strcat_s feststellen, dass der Puffer nicht ausreicht, und einen Fehlercode zurückgeben.
Beispiel 2: memcpy_s
Beispiel
#include <string.h>
int main() {
char src[] = "Sensitive Data";
char dest[15]; // Größe des Zielpuffers ist 15
// Verwenden Sie memcpy_s, um Daten in dest zu kopieren
if (memcpy_s(dest, sizeof(dest), src, strlen(src) + 1) != 0) {
printf("memcpy_s failed!"\n");
return 1; // Fehlercode zurückgeben
} else {
printf("After memcpy_s: %s\n", dest);
}
return 0;
}
Ausgabe:
After memcpy_s: Sensitive Data
In diesem Beispiel prüft memcpy_s, ob der Zielpuffer dest groß genug ist, um die Daten in src aufzunehmen, einschließlich des Nullzeichens am Ende der Zeichenkette. Wenn destsz kleiner als strlen(src) + 1 ist, gibt die Funktion einen Fehler zurück und führt die Speicherkopie nicht aus.
Beispiel 3: strtok_s
Beispiel
#include <string.h>
int main() {
char str[] = "apple,orange,banana";
char *token;
char *context = NULL;
// Verwende strtok_s, um die Zeichenfolge aufzuteilen
token = strtok_s(str, ",", &context);
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok_s(NULL, ",", &context);
}
return 0;
}
Ausgabe:
Token: apple Token: orange Token: banana
In diesem Beispiel verwendet strtok_s den context-Parameter, um Kontextinformationen beim Aufteilen der Zeichenfolge zu speichern, wodurch das Thread-Unsicherheitsproblem von strtok vermieden wird.
Beispiel 4: sprintf_s
Beispiel
int main() {
char buffer[50];
int num = 42;
const char *str = "Hallo";
// Verwende sprintf_s, um die Zeichenfolge zu formatieren und die Puffergröße zu überprüfen
if (sprintf_s(buffer, sizeof(buffer), "Zahl: %d, Zeichenfolge: %s", num, str) < 0) {
printf("sprintf_s fehlgeschlagen!"\n");
return 1; // Fehlercode zurückgeben
} else {
printf("Formatierte Zeichenfolge: %s\n", buffer);
}
return 0;
}
Ausgabe:
Formatted String: Number: 42, String: Hello
Hier akzeptiert sprintf_s die Puffergröße als Parameter beim Formatieren der Zeichenkette. Wenn die formatierte Zeichenkette die Größe des Puffers überschreitet, gibt die Funktion einen Fehler zurück, wodurch ein Pufferüberlauf vermieden wird.
Die obigen Beispiele zeigen Möglichkeiten, C-Sicherheitsfunktionen zum Kopieren von Zeichenketten, Verketten, Kopieren von Speicher, Aufteilen von Zeichenketten und für formatierte Ausgaben zu verwenden. Diese Funktionen bieten Prüfungen der Puffergröße und verbessern dadurch die Code-Sicherheit erheblich.
Referenzhandbuch
Sichere Funktionen sind hauptsächlich darauf ausgelegt, Pufferüberläufe zu verhindern. Diese Art von Schwachstelle entsteht üblicherweise dadurch, dass Funktionen die Größe des Zielpuffers nicht prüfen.
Die aktuellen vorherrschenden Standards für sichere Funktionen stammen hauptsächlich aus Anhang K (Bounds-checking interfaces) des C11-Standards sowie aus erweiterten Versionen, die von verschiedenen Betriebssystemen bereitgestellt werden (wie MSVC CRT unter Windows).
Die folgende Tabelle zeigt eine Referenztabelle der sicheren C-Funktionen.
1. Zeichenkettenverarbeitung
Dies ist der Bereich, der am anfälligsten für Überläufe ist, und sichere Funktionen erfordern üblicherweise, dass die Größe des Zielpuffers übergeben wird.
| Ursprüngliche Funktion (unsicher) | Sichere Ersatzfunktionen | Wesentliche Verbesserungen |
|---|---|---|
strcpy |
strcpy_s |
Fügt einen Parameter für die Größe des Zielpuffers hinzu; wenn die Quellzeichenkette zu lang ist, wird ein Constraint-Handler aufgerufen. |
strcat |
strcat_s |
Fügt einen Parameter für die Größe des verbleibenden Speicherplatzes des Zielpuffers hinzu, um einen Zugriff außerhalb der Grenzen während der Verkettung zu verhindern. |
strncpy |
strncpy_s |
Stellt sicher, dass die Zielzeichenfolge endet mit\0am Ende, und Laufzeitfehlerprüfung hinzugefügt. |
strtok |
strtok_s |
Führt einen Kontextzeiger ein, wodurch esThread-Sicherheitund robuster wird. |
strlen |
strnlen_s |
Fügt eine maximale Prüflänge hinzu, um Endlosschleifen bei fehlerhaften Daten ohne\0einen Null-Terminator zu verhindern. |
2. Formatierte Ein-/Ausgabe
Unsichere Formatierungsfunktionen können zu Format-String-Schwachstellen oder Pufferüberläufen führen.
| Ursprüngliche Funktion (unsicher) | Sichere Ersatzfunktionen | Wesentliche Verbesserungen |
|---|---|---|
sprintf |
snprintf / sprintf_s |
snprintfBegrenzen Sie die Anzahl der geschriebenen Zeichen;sprintf_sPrüfen Sie die Gültigkeit der Formatzeichenfolge. |
vsprintf |
vsnprintf_s |
Fügt Puffergrößengrenzen hinzu, geeignet für variable Argumentlisten. |
scanf |
scanf_s |
Pair%sund%cKonvertierungsspezifizierer usw.,ErzwingtErfordert die Angabe der Puffergröße. |
fscanf |
fscanf_s |
Längenbegrenzung beim Lesen von Dateieingaben hinzufügen. |
sscanf |
sscanf_s |
Längenbegrenzung beim Lesen aus einer Zeichenkette hinzufügen. |
3. Standardeingabe/-ausgabe
| Ursprüngliche Funktion (unsicher) | Sichere Ersatzfunktionen | Wesentliche Verbesserungen |
|---|---|---|
gets |
gets_s / fgets |
getsIn C11 veraltet.gets_sDie obere Lesegrenze muss angegeben werden. |
tmpnam |
tmpnam_s |
Fügt eine Prüfung der Puffergröße für generierte temporäre Dateinamen hinzu. |
4. Speicheroperationen
| Ursprüngliche Funktion (unsicher) | Sichere Ersatzfunktionen | Wesentliche Verbesserungen |
|---|---|---|
memcpy |
memcpy_s |
Fügt eine Überprüfung der Größe des Zielpuffers hinzu. Bei Überlappung oder Überlauf wird ein Fehler zurückgegeben. |
memmove |
memmove_s |
Fügt eine Prüfung der Größenbegrenzung beim Umgang mit überlappenden Speicherbereichen hinzu. |
memset |
memset_s |
Stellt sicher, dass der Compiler Speicherlöschvorgänge aufgrund von Optimierungen nicht überspringt (wird häufig zum Löschen vertraulicher Daten wie Passwörter verwendet). |