Die CRT Security Enhancements (C Runtime Library Security Enhancements) sind eine Sammlung von sichereren Funktionsvarianten, die Microsoft in Visual Studio eingeführt hat. Diese Funktionen enden auf _s und sollen Buffer Overflow-Attacken verhindern.
Hauptmerkmale:
Traditionelle C-Funktionen wie strcpy, scanf und sprintf sind anfällig für Buffer Overflow-Fehler, die zu schwerwiegenden Sicherheitslücken führen können.
Probleme mit klassischen Funktionen:
Die _s Funktionen sind NICHT Teil des C-Standards! Sie sind eine Microsoft-spezifische Erweiterung und funktionieren nur in Visual Studio unter Windows.
Nur bei Verwendung von Microsoft Visual Studio unter Windows!
Wenn Sie mit anderen Compilern arbeiten (GCC, Clang), sind die _s Funktionen nicht verfügbar und führen zu Compiler-Fehlern.
Visual Studio definiert automatisch das Makro _MSC_VER. Dies kann verwendet werden, um plattformspezifischen Code zu schreiben.
Visual Studio zeigt standardmäßig Warnungen an, wenn Sie unsichere Funktionen verwenden. Diese Warnungen können deaktiviert werden, aber das wird nicht empfohlen!
Das Deaktivieren dieser Warnungen macht Ihr Programm anfälliger für Sicherheitslücken. Verwenden Sie stattdessen die sicheren _s Funktionen oder schreiben Sie plattformunabhängigen Code!
Der Hauptunterschied liegt in der Buffer-Größenangabe bei Strings. Bei scanf_s muss für jeden String-Parameter die maximale Größe angegeben werden.
#include <stdio.h>
int main() {
char name[50];
int alter;
// Gefährlich: Keine Größenprüfung!
printf("Name: ");
scanf("%s", name);
printf("Alter: ");
scanf("%d", &alter);
printf("Hallo %s, %d Jahre\n",
name, alter);
return 0;
}
#include <stdio.h>
int main() {
char name[50];
int alter;
// Sicher: Größe wird geprüft!
printf("Name: ");
scanf_s("%s", name, 50);
printf("Alter: ");
scanf_s("%d", &alter);
printf("Hallo %s, %d Jahre\n",
name, alter);
return 0;
}
Beispiel 1: Ganzzahl einlesen (keine Änderung)
int zahl;
scanf("%d", &zahl);
int zahl;
scanf_s("%d", &zahl);
Beispiel 2: String einlesen (WICHTIG!)
char name[100];
scanf("%s", name);
char name[100];
scanf_s("%s", name, 100);
Beispiel 3: Einzelnes Zeichen einlesen
char zeichen;
scanf(" %c", &zeichen);
char zeichen;
scanf_s(" %c", &zeichen, 1);
Beispiel 4: Mehrere Werte einlesen
char vorname[50], nachname[50];
int alter;
scanf("%s %s %d",
vorname, nachname, &alter);
char vorname[50], nachname[50];
int alter;
scanf_s("%s %s %d",
vorname, 50, nachname, 50,
&alter);
scanf_s("%s", name) → Compiler-Fehler!scanf_s("%s", name, sizeof(name*)) → Gibt Pointergröße, nicht Array-Größe!scanf_s("%d", &zahl, 4) → Unnötig und falsch!scanf_s("%c", &c, 1) → Liest Newline!Die strcpy Funktion kopiert einen String, ohne die Zielgröße zu prüfen. strcpy_s verhindert Buffer Overflows durch Größenprüfung.
#include <string.h> char ziel[10]; char quelle[] = "Sehr langer Text"; // GEFAHR: quelle ist länger als ziel! strcpy(ziel, quelle); // → Buffer Overflow!
#include <string.h> char ziel[10]; char quelle[] = "Sehr langer Text"; // Sicher: Prüft die Größe! strcpy_s(ziel, 10, quelle); // → Fehler, kein Buffer Overflow!
errno_t strcpy_s(char *ziel, size_t groesse, const char *quelle);
Die strcat Funktion hängt einen String an einen anderen an. strcat_s prüft, ob genug Platz vorhanden ist.
char text[20] = "Hallo";
char zusatz[] = " Welt!";
// Gefahr bei zu kleinem Buffer!
strcat(text, zusatz);
printf("%s\n", text);
// "Hallo Welt!"
char text[20] = "Hallo";
char zusatz[] = " Welt!";
// Sicher: Prüft verfügbaren Platz!
strcat_s(text, 20, zusatz);
printf("%s\n", text);
// "Hallo Welt!"
char vollername[100]; char vorname[] = "Anna"; char nachname[] = "Schmidt"; strcpy(vollername, vorname); strcat(vollername, " "); strcat(vollername, nachname); // "Anna Schmidt"
char vollername[100]; char vorname[] = "Anna"; char nachname[] = "Schmidt"; strcpy_s(vollername, 100, vorname); strcat_s(vollername, 100, " "); strcat_s(vollername, 100, nachname); // "Anna Schmidt"
Die gets Funktion ist extrem unsicher und wurde aus dem C11-Standard entfernt! gets_s ist sicherer, aber fgets ist die beste Wahl.
char text[50]; // EXTREM GEFÄHRLICH! // Keine Größenprüfung möglich! gets(text); // Diese Funktion sollte // NIEMALS verwendet werden!
char text[50]; // Besser, aber immer noch // nicht standardkonform gets_s(text, 50); // Funktioniert nur in VS!
fgets ist die empfohlene Methode für sichere String-Eingabe und funktioniert auf allen Plattformen!
Vorteile von fgets:
Die sprintf Funktion formatiert Daten in einen String. sprintf_s verhindert Buffer Overflows durch Größenprüfung.
char buffer[50];
int zahl = 42;
char text[] = "Antwort";
// Gefahr: Keine Größenprüfung!
sprintf(buffer,
"Die %s ist %d",
text, zahl);
printf("%s\n", buffer);
// "Die Antwort ist 42"
char buffer[50];
int zahl = 42;
char text[] = "Antwort";
// Sicher: Größe wird geprüft!
sprintf_s(buffer, 50,
"Die %s ist %d",
text, zahl);
printf("%s\n", buffer);
// "Die Antwort ist 42"
int sprintf_s(char *buffer, size_t groesse, const char *format, ...);
Ein wichtiger Unterschied zwischen sprintf und sprintf_s liegt im Fehlerverhalten:
char buffer[10];
int result = sprintf(buffer,
"Sehr langer Text");
// result = Anzahl geschriebener
// Zeichen (kann > 10 sein!)
// → Buffer Overflow möglich!
char buffer[10];
int result = sprintf_s(buffer, 10,
"Sehr langer Text");
// result = -1 (Fehler)
// buffer bleibt unverändert
// → Kein Buffer Overflow!
Beispiel 1: Dateiname generieren
char filename[100];
int nummer = 42;
sprintf(filename,
"datei_%03d.txt",
nummer);
// "datei_042.txt"
char filename[100];
int nummer = 42;
sprintf_s(filename, 100,
"datei_%03d.txt",
nummer);
// "datei_042.txt"
Beispiel 2: Benutzerinfo formatieren
char info[200];
char name[] = "Anna";
int alter = 25;
double gehalt = 3500.50;
sprintf(info,
"Name: %s\nAlter: %d\n"
"Gehalt: %.2f EUR",
name, alter, gehalt);
char info[200];
char name[] = "Anna";
int alter = 25;
double gehalt = 3500.50;
sprintf_s(info, 200,
"Name: %s\nAlter: %d\n"
"Gehalt: %.2f EUR",
name, alter, gehalt);
Für plattformunabhängigen Code verwenden Sie snprintf, das seit C99 Standard ist:
Die sscanf Funktion liest formatierte Daten aus einem String. sscanf_s benötigt wie scanf_s zusätzliche Größenangaben für Strings.
char eingabe[] = "Anna 25 Berlin";
char name[50];
int alter;
char stadt[50];
// Keine Größenprüfung!
sscanf(eingabe,
"%s %d %s",
name, &alter, stadt);
printf("%s, %d, %s\n",
name, alter, stadt);
char eingabe[] = "Anna 25 Berlin";
char name[50];
int alter;
char stadt[50];
// Mit Größenprüfung!
sscanf_s(eingabe,
"%s %d %s",
name, 50, &alter, stadt, 50);
printf("%s, %d, %s\n",
name, alter, stadt);
int sscanf_s(const char *string, const char *format, ...);
Beispiel 1: CSV-Zeile parsen
char zeile[] = "12345,Laptop,899.99";
int id;
char produkt[100];
double preis;
sscanf(zeile, "%d,%[^,],%lf",
&id, produkt, &preis);
printf("ID: %d\n", id);
printf("Produkt: %s\n", produkt);
printf("Preis: %.2f\n", preis);
char zeile[] = "12345,Laptop,899.99";
int id;
char produkt[100];
double preis;
sscanf_s(zeile, "%d,%[^,],%lf",
&id, produkt, 100, &preis);
printf("ID: %d\n", id);
printf("Produkt: %s\n", produkt);
printf("Preis: %.2f\n", preis);
Beispiel 2: Datum parsen
char datum[] = "2025-11-27";
int jahr, monat, tag;
sscanf(datum, "%d-%d-%d",
&jahr, &monat, &tag);
printf("%02d.%02d.%d\n",
tag, monat, jahr);
char datum[] = "2025-11-27";
int jahr, monat, tag;
sscanf_s(datum, "%d-%d-%d",
&jahr, &monat, &tag);
printf("%02d.%02d.%d\n",
tag, monat, jahr);
Beispiel 3: IP-Adresse parsen
char ip[] = "192.168.1.1";
int a, b, c, d;
sscanf(ip, "%d.%d.%d.%d",
&a, &b, &c, &d);
printf("IP: %d.%d.%d.%d\n",
a, b, c, d);
char ip[] = "192.168.1.1";
int a, b, c, d;
sscanf_s(ip, "%d.%d.%d.%d",
&a, &b, &c, &d);
printf("IP: %d.%d.%d.%d\n",
a, b, c, d);
Das Format %[^,] liest alle Zeichen bis zum Komma. Bei sscanf_s muss auch hier die Größe angegeben werden:
Der größte Unterschied zwischen fopen und fopen_s liegt in der Fehlerbehandlung und dem Rückgabewert.
#include <stdio.h>
FILE *datei;
// Gibt FILE* zurück
datei = fopen("test.txt", "r");
if (datei == NULL) {
printf("Fehler beim Öffnen!\n");
return 1;
}
// Mit Datei arbeiten...
fclose(datei);
#include <stdio.h>
FILE *datei;
errno_t err;
// Gibt Fehlercode zurück!
err = fopen_s(&datei, "test.txt", "r");
if (err != 0) {
printf("Fehler beim Öffnen!\n");
return 1;
}
// Mit Datei arbeiten...
fclose(datei);
fopen gibt FILE* zurück, fopen_s gibt errno_t (Fehlercode) zurückfopen_s wird der FILE-Pointer als Adresse übergeben (&datei)fopen prüft auf NULL, fopen_s prüft auf != 0fopen_s setzt den Pointer auf NULL bei Fehlererrno_t fopen_s(FILE **dateiptr, const char *dateiname, const char *modus);
#include <stdio.h>
int main() {
FILE *datei = fopen("output.txt", "w");
if (datei == NULL) {
printf("Fehler!\n");
return 1;
}
fprintf(datei, "Hallo Welt!\n");
fprintf(datei, "Zahl: %d\n", 42);
fclose(datei);
return 0;
}
#include <stdio.h>
int main() {
FILE *datei;
errno_t err = fopen_s(&datei, "output.txt", "w");
if (err != 0) {
printf("Fehler!\n");
return 1;
}
fprintf_s(datei, "Hallo Welt!\n");
fprintf_s(datei, "Zahl: %d\n", 42);
fclose(datei);
return 0;
}
Wichtig: Für das Lesen von Dateien ist fgets bereits sicher und benötigt keine _s Version!
Die strtok Funktion zerlegt einen String in Tokens. strtok_s ist threadsicher und verhindert Race Conditions.
#include <string.h>
char text[] = "Hallo,Welt,Test";
char *token;
// Erster Aufruf
token = strtok(text, ",");
while (token != NULL) {
printf("%s\n", token);
token = strtok(NULL, ",");
}
// Ausgabe:
// Hallo
// Welt
// Test
#include <string.h>
char text[] = "Hallo,Welt,Test";
char *token;
char *context = NULL;
// Context-Pointer erforderlich!
token = strtok_s(text, ",", &context);
while (token != NULL) {
printf("%s\n", token);
token = strtok_s(NULL, ",", &context);
}
// Ausgabe:
// Hallo
// Welt
// Test
char* strtok_s(char *string, const char *delimiters, char **context);
strtok_r (POSIX-Standard, auch threadsicher)Die getenv Funktion liest Umgebungsvariablen. getenv_s bietet sichereren Zugriff mit Größenprüfung.
#include <stdlib.h>
#include <stdio.h>
char *wert;
wert = getenv("PATH");
if (wert != NULL) {
printf("PATH: %s\n", wert);
} else {
printf("PATH nicht gefunden\n");
}
#include <stdlib.h>
#include <stdio.h>
char wert[1024];
size_t laenge;
errno_t err = getenv_s(&laenge,
wert, 1024, "PATH");
if (err == 0 && laenge > 0) {
printf("PATH: %s\n", wert);
} else {
printf("PATH nicht gefunden\n");
}
Hier ist eine Übersicht weiterer häufig verwendeter sicherer Funktionen:
| Standard-Funktion | Sichere Alternative (_s) | Beschreibung |
|---|---|---|
strncpy |
strncpy_s |
String mit maximaler Länge kopieren |
strncat |
strncat_s |
String mit maximaler Länge anhängen |
memcpy |
memcpy_s |
Speicherbereich kopieren |
memmove |
memmove_s |
Überlappende Speicherbereiche kopieren |
asctime |
asctime_s |
Zeit in String konvertieren |
ctime |
ctime_s |
Zeitstempel in String konvertieren |
int quelle[5] = {1, 2, 3, 4, 5};
int ziel[5];
// Keine Größenprüfung!
memcpy(ziel, quelle,
5 * sizeof(int));
int quelle[5] = {1, 2, 3, 4, 5};
int ziel[5];
// Mit Größenprüfung!
memcpy_s(ziel, sizeof(ziel),
quelle, 5 * sizeof(int));
Der beste Ansatz ist, Code zu schreiben, der sowohl auf Visual Studio als auch auf GCC/Clang funktioniert. Dies erreichen Sie mit Präprozessor-Direktiven.
Visual Studio definiert automatisch das Makro _MSC_VER. Nutzen Sie dies für bedingte Kompilierung:
Erstellen Sie eigene Makros für plattformunabhängigen Code:
Ein komplettes Beispiel für plattformunabhängige Datei-Operationen:
Wenn Sie Visual Studio verwenden, beachten Sie folgende Tipps:
_s Funktionen, um Buffer Overflow-Schutz zu verstehen#ifdef _MSC_VERfgets und snprintf (C99)_CRT_SECURE_NO_WARNINGS deaktivieren#ifdef _MSC_VER oder Standard-Funktionen_s Funktionen auf Linux_s Funktionen korrekt zu verwenden_s FunktionenDie beste Lösung ist oft, moderne Standard-C-Funktionen zu verwenden, die auf allen Plattformen funktionieren:
fgets + sscanfsnprintf (C99)strncpy mit korrekter Nullterminierungfgets| Kategorie | Standard C | Visual Studio (_s) | Plattformunabhängige Alternative |
|---|---|---|---|
| Eingabe | scanf("%s", str) |
scanf_s("%s", str, size) |
fgets(str, size, stdin) |
| String kopieren | strcpy(dst, src) |
strcpy_s(dst, size, src) |
strncpy(dst, src, size-1)+ Nullterminierung |
| String anhängen | strcat(dst, src) |
strcat_s(dst, size, src) |
strncat(dst, src, size-strlen(dst)-1) |
| Formatierte Ausgabe | sprintf(buf, fmt, ...) |
sprintf_s(buf, size, fmt, ...) |
snprintf(buf, size, fmt, ...) |
| String parsen | sscanf(str, fmt, ...) |
sscanf_s(str, fmt, ...) |
sscanf(str, fmt, ...)mit Feldbreite: %49s |
| Datei öffnen | f = fopen(name, mode) |
fopen_s(&f, name, mode) |
fopen mit NULL-Prüfung |
| String tokenisieren | strtok(str, delim) |
strtok_s(str, delim, &ctx) |
strtok_r(str, delim, &ctx)(POSIX) |
| Zeilenweise lesen | fgets(buf, size, file) |
fgets(buf, size, file) |
fgets(buf, size, file)(bereits sicher!) |
_s Funktionen sind Microsoft-spezifisch, nicht Standard-Cint, double, etc. ändert sich nichtsfopen_s gibt Fehlercode zurück, nicht FILE*#ifdef _MSC_VERsnprintf, fgets)| Situation | Empfehlung |
|---|---|
| Nur Visual Studio verwenden | Verwenden Sie _s Funktionen für maximale Sicherheit |
| Code soll auf mehreren Plattformen laufen | Verwenden Sie #ifdef _MSC_VER oder Standard-Funktionen |
| String-Eingabe von Benutzer | Beste Wahl: fgets (funktioniert überall) |
| Formatierte String-Ausgabe | Beste Wahl: snprintf (C99, funktioniert überall) |
| Strings kopieren/anhängen | Verwenden Sie _s Funktionen in VS oder strncpy/strncat |
| Datei-Operationen | fopen_s in VS, sonst fopen mit NULL-Prüfung |
| Zeilenweise aus Datei lesen | fgets - funktioniert überall und ist bereits sicher! |
<string>_CRT_SECURE_NO_WARNINGS)gets
Die _s Funktionen sind eine Microsoft-spezifische Erweiterung und nicht Teil des C-Standards!
Code, der diese Funktionen verwendet, funktioniert nur in Visual Studio.
Für plattformunabhängigen Code verwenden Sie:
#ifdef _MSC_VER oder moderne Standard-C-Funktionen wie fgets und snprintf.
HTW Berlin • Grundlagen der Programmierung • WiSe 2025/26
Verwenden Sie sichere Funktionen, um Buffer Overflow-Attacken zu verhindern.