描述:
我創建了一個小程式,用于將檔案的名稱和校驗和存盤在結構中,用于目錄中的每個檔案。當使用 printf 將輸出寫入 stdout 時,一切似乎都很好,但是如果我們使用fputs或寫入檔案fprintf,值會被覆寫,可能是因為某些緩沖區溢位?
帶有列印的 main 輸出。
Name: 2.txt. Checksum: fc769d448ed4e08bd855927bad2c8e43efdf5315a6daa9f28577758786d52eaf
Name: 1.txt. Checksum: 2d46cffd0302c5537ddb4952a9cca7d66060dafecd56fe3a7fe8e5e5cabbbbf9
Name: 3.txt. Checksum: 37bb2e5563e94eee68fac6b07501c44f018599482e897a626a94dd88053b4b7e
但是,如果我們將 的值列印checksumMaps[0]到檔案中,則該值checksumMaps[0].filename將被覆寫(使用校驗和字串的最后 2 個位元組),如下所示:
FILE *fp = fopen("mychecksums.txt", "w");
char formatted_bytes[32*2 1];
char *filename = checksumMaps[0].filename;
format_bytes(formatted_bytes, checksumMaps[0].checksum);
fputs(filename, fp);
fputs(formatted_bytes, fp);
// We print the value of `filename` again in order to see that it has been overwritten.
printf("%s \n", filename);
fclose(fp);
該程式寫入aftxtstdout 而不是2.txt. 使用gdb,我可以看到行之后的值filename從2.txt變為。這可能是什么原因?aftxtfputs(formatted_bytes, fp);
最小可重復示例
存檔檔案.h
typedef struct ArchiveFile{
char *uuid;
char *checksum;
char *relative_path;
int is_binary;
} ArchiveFile;
typedef struct file_content{
unsigned char* bytes;
unsigned long file_size;
} file_content;
void set_uuid(ArchiveFile *file, char* uuid);
char* get_absolute_path(ArchiveFile *file, char* root);
char* get_file_text(ArchiveFile *file, char* root);
void get_bytes(ArchiveFile *file, char* root, unsigned char *buffer, size_t fsize);
long get_file_size(ArchiveFile *file, char *root);
存檔檔案
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include "ArchiveFile.h"
#include <string.h>
void set_uuid(ArchiveFile* file, char* uuid){
file->uuid = uuid;
}
char* get_absolute_path(ArchiveFile *file, char* root){
/* Allocate space according to the relative path
the root path null terminating byte.*/
char* absolute_path = malloc(strlen(file->relative_path) strlen(root) 1);
// Add the root path.
strcpy(absolute_path, root);
// Concatonate the root with the rest of the path.
strcat(absolute_path, file->relative_path);
return absolute_path;
}
char* get_file_text(ArchiveFile *file, char* root){
char* absolute_path = get_absolute_path(file, root);
FILE *fp = fopen(absolute_path, "r");
if(fp == NULL)
printf("Could not open file %s \n", absolute_path);
// Platform independent way of getting the file size in bytes.
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
fseek(fp, 0, SEEK_SET); /* same as rewind(f); */
char *buffer = malloc(fsize);
if(fp){
fread(buffer, sizeof(char), fsize, fp);
}
fclose(fp);
free(absolute_path);
return buffer;
}
void print_bytes2(unsigned char* md, size_t size){
for (size_t i = 0; i < size; i ) {
printf("x ", md[i]);
}
printf("\n");
}
void get_bytes(ArchiveFile *file, char *root, unsigned char *buffer, size_t fsize){
char* absolute_path = get_absolute_path(file, root);
FILE *fp = fopen(absolute_path, "rb");
if(fp){
fread(buffer, 1, fsize, fp);
}
free(absolute_path);
fclose(fp);
}
long get_file_size(ArchiveFile *file, char *root){
char* filepath = get_absolute_path(file, root);
FILE *fp = fopen(filepath, "rb");
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
fseek(fp, 0, SEEK_SET); /* same as rewind(f); */
free(filepath);
fclose(fp);
return fsize;
}
校驗和/校驗和.h
// Used to store information about filename and checksum.
typedef struct ChecksumMap{
char* filename;
unsigned char checksum [32];
} ChecksumMap;
int calculate_checksum(void* input, unsigned long length, unsigned char* md);
校驗和/校驗和.h
#include <stdio.h>
#include <openssl/sha.h>
#include "checksum.h"
int calculate_checksum(void* input, unsigned long length, unsigned char* md){
SHA256_CTX context;
if(!SHA256_Init(&context))
return 0;
if(!SHA256_Update(&context, (unsigned char*)input, length))
return 0;
if(!SHA256_Final(md, &context))
return 0;
return 1;
}
主檔案
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include "ArchiveFile.h"
#include "checksum/checksum.h"
void format_bytes(char* buffer, unsigned char* md){
for (int i = 0; i < 32; i ) {
sprintf(&buffer[i*2], "x", md[i]);
}
buffer[32*2] = '\0';
}
void *listdir(char *name, int count, ChecksumMap *checksumMaps)
{
DIR *dir;
struct dirent *direntry;
if (!(dir = opendir(name)))
return NULL;
while ((direntry = readdir(dir)) != NULL) {
// If we reach a directory (that is not . or ..) then recursive step.
if (direntry->d_type == DT_DIR) {
char path[1024];
if (strcmp(direntry->d_name, ".") == 0 || strcmp(direntry->d_name, "..") == 0)
continue;
snprintf(path, sizeof(path), "%s/%s", name, direntry->d_name);
listdir(path, count, checksumMaps);
} else {
unsigned char md[32];
ArchiveFile file;
file.relative_path = direntry->d_name;
// Get the full path of the file:
char parent_name[strlen(name) 1];
memset(&parent_name[0], 0, sizeof(parent_name));
strcat(parent_name, name);
strcat(parent_name, "/");
size_t fsize = get_file_size(&file, parent_name);
unsigned char *bytes = malloc(sizeof(char) * fsize);
get_bytes(&file, parent_name, bytes, fsize);
calculate_checksum((void*) bytes, fsize, md);
ChecksumMap checksumMap = {.filename=file.relative_path};
memcpy(checksumMap.checksum, md,
sizeof(checksumMap.checksum));
free(bytes);
}
}
closedir(dir);
return NULL;
}
int main(int argc, char const *argv[]) {
FILE *fp = fopen("mychecksums.txt", "w");
char formatted_bytes[32*2 1];
char *filename = checksumMaps[0].filename;
format_bytes(formatted_bytes, checksumMaps[0].checksum);
fputs(filename, fp);
fputs(formatted_bytes, fp);
// We print the value of `filename` again in order to see that it has been overwritten.
printf("%s \n", filename);
fclose(fp);
}
用 gcc 編譯:
gcc -Wall -Wextra main.c ArchiveFile.c checksum/checksum.c -lcrypto
uj5u.com熱心網友回復:
程式將 aftxt 寫入 stdout 而不是 2.txt。使用gdb,我可以看到filename 的值在行之后從2.txt 變為aftxt
fputs(formatted_bytes, fp);。這可能是什么原因?
很難說,因為我們處于 UB(未定義行為)的領域。但這里有兩個明顯的候選人。
formatted_bytes未正確終止,導致fputs讀取陣列,呼叫 UB。fp不是有效的流。原因可能是它沒有初始化或更改,或者流已關閉或其他原因。
啟用-Wall -Wextra -fsanitize=address。你也可以試試-fsanitize=undefined。
檢查所有回傳值。malloc,fopen并fputs回傳一個可用于錯誤檢查的值。
替換formatted_bytes為具有您認為具有的值的硬編碼字串。
了解如何創建最小的、可重現的示例以及如何除錯小型 c 程式。這是我前段時間寫的指南。
uj5u.com熱心網友回復:
更新
代碼似乎存在一些不同的問題。首先要注意的是file.relative_path = direntry->d_name;,direntry 指向的值在每次迭代中都會發生變化,因此file.relative_path指向的值也會發生變化。此外,file.relative_path 中存盤的字串的大小從未指定,如果我們使用 strcpy,這將是一個問題。
解決方案是指定大小file.relative_path并用于strcpy復制 的值direntry->d_name。此外,不需要 checksumMap 結構,因為 ArchiveFile 已經可以存盤相同的資訊(再次指定校驗和的大小)。
在 C 中使用字串、緩沖區、陣列時要記住的事情:
請記住,C 中的字串基于字符陣列,它們本身基于指向第一個元素的指標。當您實際上想要復制字串的值而不是第一個元素的地址時,將一個字串的值分配給另一個字串可能會出現意外行為。
uj5u.com熱心網友回復:
這里有一個錯誤:
char parent_name[strlen(name) 1];
memset(&parent_name[0], 0, sizeof(parent_name)); // could have been parent_name[0]='\0'; instead
strcat(parent_name, name); // Now the parent_name buffer is full and null terminated.
strcat(parent_name, "/"); // this overwrites the null terminator and writes a new one out-of-bounds
你應該做這樣的事情:
size_t length = strlen(name);
char parent_name[length 1 1];
memcpy(parent_name, name, length); // copies characters only (fast) but not the null term
parent_name[length] = '/'; // append this single character 1 symbol past "name" string
parent_name[length 1] = '\0'; // add manual null termination
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/383181.html
