未知的 C 字符串 Truncation/Overwrite
Unknown C String Truncation/Overwrite
我在简单的字符串操作中遇到了一个有趣的内存问题。问题本身实际上不在字符串的读取中,而是在我尝试调用字符串时就在它之前。
char *removeInvalid(char *token){
fprintf(stderr," Before: %s \n", token);
char *newToken = malloc(sizeof(100) + 1);
fprintf(stderr," After: %s \n", token);
}
每当我 运行 this 时,字符串 if t运行 cated 就在 char *newToken 之后被 malloc。所以这个结果的打印输出是
Before: Willy Wanka's Chochlate Factory
After: Will Wanka's Chochlate F!
有人知道这是什么吗?我查看了 malloc 的其他示例,但无法弄清楚这里是怎么出错的。
编辑:完整代码如下。请注意,我是一名刚开始学习 C 的大学生,所以它无论如何都不是完美的。但它一直有效,直到出现此错误。
函数调用如下。 Main->initialReadAVL(这部分工作完美)
然后在调用 commandReadAVL 之后,调用 commandReadAVL->ReadHelper(再次在这里工作正常。
然后 CleanUpString->removeSpaces(工作正常)
然后 CleanUpString->removeInvalid(这是错误的地方)
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "node.h"
#include "avl.h"
#include "scanner.h"
#include "bst.h"
/* Options */
int avlSwitch = 0;
int bstSwitch = 0;
int insertSwitch = 0;
int deleteSwitch = 0;
int frequencySwitch = 0;
int displaySwitch = 0;
int statisticSwitch = 0;
int ProcessOptions(int argc, char **argv);
char *cleanUpString(char *token);
char *turnToLowerCase(char *token);
char *removeSpaces(char *token);
char *removeInvalid(char *token);
char *readHelper(FILE *in);
void Fatal(char *fmt, ...);
void preOrder(struct node *root);
void initialReadAVL(avl *mainAVL, FILE *in);
void initialReadBST(bst *mainBST, FILE *in);
void commandReadBST(bst *mainBST, FILE *commandList);
void commandReadAVL(avl *mainAVL, FILE *commandList);
int main(int argc, char **argv) {
struct avl *mainAVL;
struct bst *mainBST;
FILE *text;
FILE *commandList;
if(argc != 4){
Fatal("There must be 4 arguments of form 'trees -b corpus commands' \n");
}
int argIndex = ProcessOptions(argc,argv);
text = fopen(argv[2], "r");
commandList = fopen(argv[3], "r");
//Protect against an empty file.
if (text == NULL){
fprintf(stderr,"file %s could not be opened for reading\n", argv[2]);
exit(1);
}
if (commandList == NULL){
fprintf(stderr,"file %s could not be opened for reading\n", argv[3]);
exit(1);
}
if (avlSwitch){
mainAVL = newAVL();
initialReadAVL(mainAVL, text);
preOrder(mainAVL->root);
fprintf(stderr,"\n");
commandReadAVL(mainAVL, commandList);
preOrder(mainAVL->root);
fprintf(stderr,"\n");
}
else if (bstSwitch){
mainBST = newBST();
initialReadBST(mainBST, text);
preOrder(mainBST->root);
commandReadBST(mainBST, commandList);
preOrder(mainBST->root);
}
return 0;
}
void commandReadAVL(avl *mainAVL, FILE *commandList){
char *command;
char *textSnip;
while(!feof(commandList)){
command = readHelper(commandList);
textSnip = readHelper(commandList);
textSnip = cleanUpString(textSnip);
if(command != NULL){
switch (command[0]) {
case 'i':
fprintf(stderr,"%s \n", textSnip);
insertAVL(mainAVL, textSnip);
break;
case 'd':
deleteAVL(mainAVL, textSnip);
break;
case 'f':
break;
case 's':
break;
case 'r':
break;
default:
Fatal("option %s not understood\n",command);
}
}
}
}
void commandReadBST(bst *mainBST, FILE *commandList){
char *command;
char *textSnip;
while(!feof(commandList)){
command = readHelper(commandList);
textSnip = readHelper(commandList);
textSnip = cleanUpString(textSnip);
if(command != NULL){
switch (command[0]) {
case 'i':
insertBST(mainBST, textSnip);
break;
case 'd':
deleteBST(mainBST, textSnip);
break;
case 'f':
break;
case 's':
break;
case 'r':
break;
default:
Fatal("option %s not understood\n",command);
}
}
}
}
char *readHelper(FILE *in){
char *token;
if (stringPending(in)){
token = readString(in);
}
else {
token = readToken(in);
}
return token;
}
void initialReadBST(bst *mainBST, FILE *in){
char *token;
while(!feof(in)){
token = readHelper(in);
token = cleanUpString(token);
if (token != NULL){
insertBST(mainBST, token);
}
}
}
void initialReadAVL(avl *mainAVL, FILE *in){
char *token;
while(!feof(in)){
token = readHelper(in);
token = cleanUpString(token);
if (token != NULL){
insertAVL(mainAVL, token);
}
}
}
//Helper Function to clean up a string using all the prerequisites.
char *cleanUpString(char *token){
char *output = malloc(sizeof(*token)+ 1);
if (token != NULL){
output = removeSpaces(token);
fprintf(stderr,"before : %s \n", output);
output = removeInvalid(output);
fprintf(stderr,"%s \n", output);
output = turnToLowerCase(output);
return output;
}
return NULL;
}
//Helper function to turn the given string into lower case letters
char *turnToLowerCase(char *token){
char *output = malloc(sizeof(*token) + 1);
for (int x = 0; x < strlen(token); x++){
output[x] = tolower(token[x]);
}
return output;
}
//Helper function to remove redundent spaces in a string.
char *removeSpaces(char *token){
char *output;
int x = 0;
int y = 0;
while (x < strlen(token)){
if (token[x]== ' ' && x < strlen(token)){
while(token[x] == ' '){
x++;
}
output[y] = ' ';
y++;
output[y] = token[x];
y++;
x++;
}
else {
output[y] = token[x];
y++;
x++;
}
}
return output;
}
char *removeInvalid(char *token){
fprintf(stderr," Before: %s \n", token);
char *newToken = malloc(sizeof(* token)+ 1);
fprintf(stderr," After: %s \n", token);
int x = 0;
int y = 0;
while (x < strlen(token)){
if (!isalpha(token[x]) && token[x] != ' '){
x++;
}
else {
newToken[y] = token[x];
y++;
x++;
}
}
return newToken;
}
//Processes a system ending error.
void Fatal(char *fmt, ...) {
va_list ap;
fprintf(stderr,"An error occured: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
exit(-1);
}
//Processes the options needed to be executed from the command line
int ProcessOptions(int argc, char **argv) {
int argIndex;
int argUsed;
int separateArg;
argIndex = 1;
while (argIndex < argc && *argv[argIndex] == '-')
{
/* check if stdin, represented by "-" is an argument */
/* if so, the end of options has been reached */
if (argv[argIndex][1] == '[=13=]') return argIndex;
separateArg = 0;
argUsed = 0;
if (argv[argIndex][2] == '[=13=]')
{
separateArg = 1;
}
switch (argv[argIndex][1])
{
case 'b':
bstSwitch = 1;
break;
case 'a':
avlSwitch = 1;
break;
default:
Fatal("option %s not understood\n",argv[argIndex]);
}
if (separateArg && argUsed)
++argIndex;
++argIndex;
}
return argIndex;
}
void preOrder(struct node *root) {
if(root != NULL)
{
fprintf(stderr,"%s ", root->key);
preOrder(root->lChild);
preOrder(root->rChild);
}
}
ReadString()
char *
readString(FILE *fp)
{
int ch,index;
char *buffer;
int size = 512;
/* advance to the double quote */
skipWhiteSpace(fp);
if (feof(fp)) return 0;
ch = fgetc(fp);
if (ch == EOF) return 0;
/* allocate the buffer */
buffer = allocateMsg(size,"readString");
if (ch != '\"')
{
fprintf(stderr,"SCAN ERROR: attempt to read a string failed\n");
fprintf(stderr,"first character was <%c>\n",ch);
exit(4);
}
/* toss the double quote, skip to the next character */
ch = fgetc(fp);
/* initialize the buffer index */
index = 0;
/* collect characters until the closing double quote */
while (ch != '\"')
{
if (ch == EOF)
{
fprintf(stderr,"SCAN ERROR: attempt to read a string failed\n");
fprintf(stderr,"no closing double quote\n");
exit(6);
}
if (index > size - 2)
{
++size;
buffer = reallocateMsg(buffer,size,"readString");
}
if (ch == '\')
{
ch = fgetc(fp);
if (ch == EOF)
{
fprintf(stderr,"SCAN ERROR: attempt to read a string failed\n");
fprintf(stderr,"escaped character missing\n");
exit(6);
}
buffer[index] = convertEscapedChar(ch);
}
else
buffer[index] = ch;
++index;
ch = fgetc(fp);
}
buffer[index] = '[=14=]';
return buffer;
}
输入:Commands.txt
i "Willy Wonka's Chochlate Factory"
输入testFile.txt
a b c d e f g h i j k l m n o p q r s t u v w x y z
谢谢!
您几乎可以肯定在您没有向我们展示的某些代码部分存在缓冲区溢出。如果我猜的话,我会说您为 token
分配的存储空间太少,无法包含您首先写入其中的完整字符串。
您是否偶然使用与 removeInvalid()
:
中相同的错误代码分配 token
malloc(sizeof(100) + 1);
^^^^^^^^^^^ this doesn't allocate 101 characters, it allocates sizeof(int)+1
char *readHelper(FILE *in){
char * token = malloc(sizeof(char *) + 1);
if (stringPending(in)){
token = readString(in);
}
else {
token = readToken(in);
}
return token;
}
如果不能看到 readString
或 readToken
,很难理解这一点,但这不可能是正确的。
首先,您分配的字节比指向一个或多个字符的指针所需的多一个字节。这样的东西有什么用?如果您不存储指向一个或多个字符的指针,为什么要使用 sizeof(char *)
?如果要存储指向一个或多个字符的指针,为什么要添加一个?很难想象导致那行代码的原因。
然后,在 if
中,您会立即丢失从 malloc
返回的值,因为您通过使用它来存储其他内容来覆盖 token
。如果您不打算使用分配给 token
的值,您为什么要分配它?
坦率地说,这段代码中的很多内容根本没有任何意义。没有评论,很难理解推理,所以我们可以指出它有什么问题。
要么那行代码背后有推理,在这种情况下,它只是完全错误的推理。或者更糟的是,添加这行代码时没有任何理由希望它能以某种方式工作。这两种方法都不会生成工作代码。
当您尝试调试代码时,请先删除您实验性添加的或您不理解的任何内容。如果您理解malloc(sizeof(char *) + 1)
,那么请解释您认为它的作用,以便纠正您的理解。
为什么您认为您需要一个比指向一个或多个字符的指针大小大一个字节的缓冲区?
char *turnToLowerCase(char *token){
char *output = malloc(sizeof(*token) + 1);
for (int x = 0; x < strlen(token); x++){
output[x] = tolower(token[x]);
}
return output;
}
这可能是您的主要问题。您为两个字符分配了足够的 space,然后继续存储更多。你可能想要:
char *output = malloc(strlen(token) + 1);
因为 token
是 char*
,*token
是 char
。所以 sizeof(*token)
是 sizeof(char)
-- 绝对不是你想要的。
在 David Schwartz 和其他发帖人的帮助下,我找到了问题中的错误。当我为我的 token/output 分配内存时,我没有分配足够的 space.. 使用
的错误代码
malloc(sizeof(100) + 1);
和
malloc(sizeof(*token) + 1);
两者都只产生了几个要分配的字节。这导致缓冲区问题导致随机字母和数字/截断发生。第一个导致 space 相当于 int + 1,第二个导致 char + 1。(因为我使用的是 sizeof token,这只是它最初开始的大小,一个 char )
为了解决这个问题,我将令牌变量的分配更改为
malloc(strlen(token) + 1);
这分配了一个 space 相当于令牌的 "string" 长度 + 1。允许适当的 space 解决我的问题,最终会得到 space of < = 令牌。
我在简单的字符串操作中遇到了一个有趣的内存问题。问题本身实际上不在字符串的读取中,而是在我尝试调用字符串时就在它之前。
char *removeInvalid(char *token){
fprintf(stderr," Before: %s \n", token);
char *newToken = malloc(sizeof(100) + 1);
fprintf(stderr," After: %s \n", token);
}
每当我 运行 this 时,字符串 if t运行 cated 就在 char *newToken 之后被 malloc。所以这个结果的打印输出是
Before: Willy Wanka's Chochlate Factory
After: Will Wanka's Chochlate F!
有人知道这是什么吗?我查看了 malloc 的其他示例,但无法弄清楚这里是怎么出错的。
编辑:完整代码如下。请注意,我是一名刚开始学习 C 的大学生,所以它无论如何都不是完美的。但它一直有效,直到出现此错误。
函数调用如下。 Main->initialReadAVL(这部分工作完美) 然后在调用 commandReadAVL 之后,调用 commandReadAVL->ReadHelper(再次在这里工作正常。 然后 CleanUpString->removeSpaces(工作正常) 然后 CleanUpString->removeInvalid(这是错误的地方)
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "node.h"
#include "avl.h"
#include "scanner.h"
#include "bst.h"
/* Options */
int avlSwitch = 0;
int bstSwitch = 0;
int insertSwitch = 0;
int deleteSwitch = 0;
int frequencySwitch = 0;
int displaySwitch = 0;
int statisticSwitch = 0;
int ProcessOptions(int argc, char **argv);
char *cleanUpString(char *token);
char *turnToLowerCase(char *token);
char *removeSpaces(char *token);
char *removeInvalid(char *token);
char *readHelper(FILE *in);
void Fatal(char *fmt, ...);
void preOrder(struct node *root);
void initialReadAVL(avl *mainAVL, FILE *in);
void initialReadBST(bst *mainBST, FILE *in);
void commandReadBST(bst *mainBST, FILE *commandList);
void commandReadAVL(avl *mainAVL, FILE *commandList);
int main(int argc, char **argv) {
struct avl *mainAVL;
struct bst *mainBST;
FILE *text;
FILE *commandList;
if(argc != 4){
Fatal("There must be 4 arguments of form 'trees -b corpus commands' \n");
}
int argIndex = ProcessOptions(argc,argv);
text = fopen(argv[2], "r");
commandList = fopen(argv[3], "r");
//Protect against an empty file.
if (text == NULL){
fprintf(stderr,"file %s could not be opened for reading\n", argv[2]);
exit(1);
}
if (commandList == NULL){
fprintf(stderr,"file %s could not be opened for reading\n", argv[3]);
exit(1);
}
if (avlSwitch){
mainAVL = newAVL();
initialReadAVL(mainAVL, text);
preOrder(mainAVL->root);
fprintf(stderr,"\n");
commandReadAVL(mainAVL, commandList);
preOrder(mainAVL->root);
fprintf(stderr,"\n");
}
else if (bstSwitch){
mainBST = newBST();
initialReadBST(mainBST, text);
preOrder(mainBST->root);
commandReadBST(mainBST, commandList);
preOrder(mainBST->root);
}
return 0;
}
void commandReadAVL(avl *mainAVL, FILE *commandList){
char *command;
char *textSnip;
while(!feof(commandList)){
command = readHelper(commandList);
textSnip = readHelper(commandList);
textSnip = cleanUpString(textSnip);
if(command != NULL){
switch (command[0]) {
case 'i':
fprintf(stderr,"%s \n", textSnip);
insertAVL(mainAVL, textSnip);
break;
case 'd':
deleteAVL(mainAVL, textSnip);
break;
case 'f':
break;
case 's':
break;
case 'r':
break;
default:
Fatal("option %s not understood\n",command);
}
}
}
}
void commandReadBST(bst *mainBST, FILE *commandList){
char *command;
char *textSnip;
while(!feof(commandList)){
command = readHelper(commandList);
textSnip = readHelper(commandList);
textSnip = cleanUpString(textSnip);
if(command != NULL){
switch (command[0]) {
case 'i':
insertBST(mainBST, textSnip);
break;
case 'd':
deleteBST(mainBST, textSnip);
break;
case 'f':
break;
case 's':
break;
case 'r':
break;
default:
Fatal("option %s not understood\n",command);
}
}
}
}
char *readHelper(FILE *in){
char *token;
if (stringPending(in)){
token = readString(in);
}
else {
token = readToken(in);
}
return token;
}
void initialReadBST(bst *mainBST, FILE *in){
char *token;
while(!feof(in)){
token = readHelper(in);
token = cleanUpString(token);
if (token != NULL){
insertBST(mainBST, token);
}
}
}
void initialReadAVL(avl *mainAVL, FILE *in){
char *token;
while(!feof(in)){
token = readHelper(in);
token = cleanUpString(token);
if (token != NULL){
insertAVL(mainAVL, token);
}
}
}
//Helper Function to clean up a string using all the prerequisites.
char *cleanUpString(char *token){
char *output = malloc(sizeof(*token)+ 1);
if (token != NULL){
output = removeSpaces(token);
fprintf(stderr,"before : %s \n", output);
output = removeInvalid(output);
fprintf(stderr,"%s \n", output);
output = turnToLowerCase(output);
return output;
}
return NULL;
}
//Helper function to turn the given string into lower case letters
char *turnToLowerCase(char *token){
char *output = malloc(sizeof(*token) + 1);
for (int x = 0; x < strlen(token); x++){
output[x] = tolower(token[x]);
}
return output;
}
//Helper function to remove redundent spaces in a string.
char *removeSpaces(char *token){
char *output;
int x = 0;
int y = 0;
while (x < strlen(token)){
if (token[x]== ' ' && x < strlen(token)){
while(token[x] == ' '){
x++;
}
output[y] = ' ';
y++;
output[y] = token[x];
y++;
x++;
}
else {
output[y] = token[x];
y++;
x++;
}
}
return output;
}
char *removeInvalid(char *token){
fprintf(stderr," Before: %s \n", token);
char *newToken = malloc(sizeof(* token)+ 1);
fprintf(stderr," After: %s \n", token);
int x = 0;
int y = 0;
while (x < strlen(token)){
if (!isalpha(token[x]) && token[x] != ' '){
x++;
}
else {
newToken[y] = token[x];
y++;
x++;
}
}
return newToken;
}
//Processes a system ending error.
void Fatal(char *fmt, ...) {
va_list ap;
fprintf(stderr,"An error occured: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
exit(-1);
}
//Processes the options needed to be executed from the command line
int ProcessOptions(int argc, char **argv) {
int argIndex;
int argUsed;
int separateArg;
argIndex = 1;
while (argIndex < argc && *argv[argIndex] == '-')
{
/* check if stdin, represented by "-" is an argument */
/* if so, the end of options has been reached */
if (argv[argIndex][1] == '[=13=]') return argIndex;
separateArg = 0;
argUsed = 0;
if (argv[argIndex][2] == '[=13=]')
{
separateArg = 1;
}
switch (argv[argIndex][1])
{
case 'b':
bstSwitch = 1;
break;
case 'a':
avlSwitch = 1;
break;
default:
Fatal("option %s not understood\n",argv[argIndex]);
}
if (separateArg && argUsed)
++argIndex;
++argIndex;
}
return argIndex;
}
void preOrder(struct node *root) {
if(root != NULL)
{
fprintf(stderr,"%s ", root->key);
preOrder(root->lChild);
preOrder(root->rChild);
}
}
ReadString()
char *
readString(FILE *fp)
{
int ch,index;
char *buffer;
int size = 512;
/* advance to the double quote */
skipWhiteSpace(fp);
if (feof(fp)) return 0;
ch = fgetc(fp);
if (ch == EOF) return 0;
/* allocate the buffer */
buffer = allocateMsg(size,"readString");
if (ch != '\"')
{
fprintf(stderr,"SCAN ERROR: attempt to read a string failed\n");
fprintf(stderr,"first character was <%c>\n",ch);
exit(4);
}
/* toss the double quote, skip to the next character */
ch = fgetc(fp);
/* initialize the buffer index */
index = 0;
/* collect characters until the closing double quote */
while (ch != '\"')
{
if (ch == EOF)
{
fprintf(stderr,"SCAN ERROR: attempt to read a string failed\n");
fprintf(stderr,"no closing double quote\n");
exit(6);
}
if (index > size - 2)
{
++size;
buffer = reallocateMsg(buffer,size,"readString");
}
if (ch == '\')
{
ch = fgetc(fp);
if (ch == EOF)
{
fprintf(stderr,"SCAN ERROR: attempt to read a string failed\n");
fprintf(stderr,"escaped character missing\n");
exit(6);
}
buffer[index] = convertEscapedChar(ch);
}
else
buffer[index] = ch;
++index;
ch = fgetc(fp);
}
buffer[index] = '[=14=]';
return buffer;
}
输入:Commands.txt
i "Willy Wonka's Chochlate Factory"
输入testFile.txt
a b c d e f g h i j k l m n o p q r s t u v w x y z
谢谢!
您几乎可以肯定在您没有向我们展示的某些代码部分存在缓冲区溢出。如果我猜的话,我会说您为 token
分配的存储空间太少,无法包含您首先写入其中的完整字符串。
您是否偶然使用与 removeInvalid()
:
token
malloc(sizeof(100) + 1);
^^^^^^^^^^^ this doesn't allocate 101 characters, it allocates sizeof(int)+1
char *readHelper(FILE *in){
char * token = malloc(sizeof(char *) + 1);
if (stringPending(in)){
token = readString(in);
}
else {
token = readToken(in);
}
return token;
}
如果不能看到 readString
或 readToken
,很难理解这一点,但这不可能是正确的。
首先,您分配的字节比指向一个或多个字符的指针所需的多一个字节。这样的东西有什么用?如果您不存储指向一个或多个字符的指针,为什么要使用 sizeof(char *)
?如果要存储指向一个或多个字符的指针,为什么要添加一个?很难想象导致那行代码的原因。
然后,在 if
中,您会立即丢失从 malloc
返回的值,因为您通过使用它来存储其他内容来覆盖 token
。如果您不打算使用分配给 token
的值,您为什么要分配它?
坦率地说,这段代码中的很多内容根本没有任何意义。没有评论,很难理解推理,所以我们可以指出它有什么问题。
要么那行代码背后有推理,在这种情况下,它只是完全错误的推理。或者更糟的是,添加这行代码时没有任何理由希望它能以某种方式工作。这两种方法都不会生成工作代码。
当您尝试调试代码时,请先删除您实验性添加的或您不理解的任何内容。如果您理解malloc(sizeof(char *) + 1)
,那么请解释您认为它的作用,以便纠正您的理解。
为什么您认为您需要一个比指向一个或多个字符的指针大小大一个字节的缓冲区?
char *turnToLowerCase(char *token){
char *output = malloc(sizeof(*token) + 1);
for (int x = 0; x < strlen(token); x++){
output[x] = tolower(token[x]);
}
return output;
}
这可能是您的主要问题。您为两个字符分配了足够的 space,然后继续存储更多。你可能想要:
char *output = malloc(strlen(token) + 1);
因为 token
是 char*
,*token
是 char
。所以 sizeof(*token)
是 sizeof(char)
-- 绝对不是你想要的。
在 David Schwartz 和其他发帖人的帮助下,我找到了问题中的错误。当我为我的 token/output 分配内存时,我没有分配足够的 space.. 使用
的错误代码malloc(sizeof(100) + 1);
和
malloc(sizeof(*token) + 1);
两者都只产生了几个要分配的字节。这导致缓冲区问题导致随机字母和数字/截断发生。第一个导致 space 相当于 int + 1,第二个导致 char + 1。(因为我使用的是 sizeof token,这只是它最初开始的大小,一个 char )
为了解决这个问题,我将令牌变量的分配更改为
malloc(strlen(token) + 1);
这分配了一个 space 相当于令牌的 "string" 长度 + 1。允许适当的 space 解决我的问题,最终会得到 space of < = 令牌。