[英]How to prevent scanf causing a buffer overflow in C?
我使用這個代碼:
while ( scanf("%s", buf) == 1 ){
防止可能的緩沖區溢出以便可以傳遞隨機長度的字符串的最佳方法是什么?
我知道我可以通過調用來限制輸入字符串,例如:
while ( scanf("%20s", buf) == 1 ){
但我更希望能夠處理用戶輸入的任何內容。 或者這不能使用 scanf 安全地完成,我應該使用 fgets?
Kernighan 和 Pike 在他們的書The Practice of Programming (非常值得一讀)中討論了這個問題,他們通過使用snprintf()
創建具有正確緩沖區大小的字符串以傳遞給scanf()
系列函數來解決這個問題. 有效:
int scanner(const char *data, char *buffer, size_t buflen)
{
char format[32];
if (buflen == 0)
return 0;
snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
return sscanf(data, format, buffer);
}
請注意,這仍然將輸入限制為作為“緩沖區”提供的大小。 如果您需要更多空間,那么您必須進行內存分配,或者使用為您進行內存分配的非標准庫函數。
請注意, POSIX 2008 (2013) 版本的scanf()
系列函數支持用於字符串輸入( %s
、 %c
、 %[
)的格式修飾符m
(賦值分配字符)。 它不采用char *
參數,而是采用char **
參數,並為它讀取的值分配必要的空間:
char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
printf("String is: <<%s>>\n", buffer);
free(buffer);
}
如果sscanf()
函數無法滿足所有轉換規范,則在函數返回之前釋放它為%ms
類的轉換分配的所有內存。
如果您使用的是 gcc,您可以使用 GNU-extension a
說明符讓 scanf() 為您分配內存以保存輸入:
int main()
{
char *str = NULL;
scanf ("%as", &str);
if (str) {
printf("\"%s\"\n", str);
free(str);
}
return 0;
}
編輯:正如喬納森指出的那樣,您應該查閱scanf
手冊頁,因為說明符可能不同( %m
)並且您可能需要在編譯時啟用某些定義。
大多數時候fgets
和sscanf
的組合可以完成這項工作。 另一件事是編寫自己的解析器,如果輸入格式正確。 另請注意,您的第二個示例需要進行一些修改才能安全使用:
#define LENGTH 42
#define str(x) # x
#define xstr(x) str(x)
/* ... */
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array);
以上丟棄了輸入流,但不包括換行符 ( \\n
) 字符。 您需要添加一個getchar()
來使用它。 還要檢查您是否到達了流末尾:
if (!feof(stdin)) { ...
就是這樣。
直接使用scanf(3)
及其變體會帶來許多問題。 通常,用戶和非交互式用例是根據輸入行定義的。 如果沒有找到足夠多的對象,則很少出現這樣的情況,更多的行將解決問題,但這是 scanf 的默認模式。 (如果用戶不知道在第一行輸入數字,則第二行和第三行可能無濟於事。)
至少如果你fgets(3)
你知道你的程序需要多少輸入行,並且你不會有任何緩沖區溢出......
制作一個為字符串分配所需內存的函數並沒有那么多工作。 這是我前段時間寫的一個小c函數,我總是用它來讀取字符串。
它將返回讀取的字符串,或者如果發生內存錯誤則返回 NULL。 但請注意,您必須 free() 您的字符串並始終檢查它的返回值。
#define BUFFER 32
char *readString()
{
char *str = malloc(sizeof(char) * BUFFER), *err;
int pos;
for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++)
{
if(pos % BUFFER == BUFFER - 1)
{
if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL)
free(str);
str = err;
}
}
if(str != NULL)
str[pos] = '\0';
return str;
}
限制輸入的長度肯定更容易。 您可以通過使用循環來接受任意長度的輸入,一次讀取一點,根據需要為字符串重新分配空間......
但這需要大量工作,因此大多數 C 程序員只是以任意長度截斷輸入。 我想您已經知道這一點,但是使用 fgets() 不會允許您接受任意數量的文本 - 您仍然需要設置限制。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.