软件安全作业二——安全的sprintf函数
- 详述函数编写思路和流程
本次实现的sprintf函数需要实现以下功能:
a) 不会产生缓冲区溢出
b) 不能被格式字符串控制输出内容
c) 实现安全的变参
为实现以上功能:
函数首先检查传入的目标缓冲区指针和大小是否有效,若无效则返回 -1 表示参数无效。
使用 va_list 类型的变量 args 来处理可变数量的参数,通过 va_start 宏初始化。
使用一个循环遍历格式化字符串 format,逐个处理其中的字符:
对于普通字符,直接将其复制到目标缓冲区中;对于 %s 格式符,从参数中获取字符串,计算其长度,然后将其复制到目标缓冲区中;对于 %d 格式符,从参数中获取整数,将其转换为字符串,然后复制到目标缓冲区中。
在复制字符串时,会检查缓冲区是否足够大,若不足则返回 -1。
循环结束后,添加字符串终止符 \0 到目标缓冲区的末尾。
最后,释放可变参数列表并返回写入目标缓冲区的字符数量(不包括终止符)。如果参数不匹配或缓冲区不足,也返回 -1。
函数源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
int safe_sprintf(char* buffer, size_t sizeOfBuffer, const char* format, ...) {
if (buffer == NULL || sizeOfBuffer == 0) {
// 参数无效
return -1;
}
va_list args;
va_start(args, format);
size_t i = 0; // 记录写入字符的位置
while (*format != '\0') {
// 处理格式化字符串中的普通字符
if (*format != '%' && i < sizeOfBuffer - 1) {
buffer[i++] = *format;
}
else if (*format == '%' && *(format + 1) == 's')
{
// 处理 %s 格式符
const char* str = va_arg(args, const char*);
size_t len = 0;
// 计算字符串长度
if (str == "-1") {
//参数不匹配
va_end(args);
return -1;
}
while (str[len] != '\0') {
len++;
}
if (i + len < sizeOfBuffer) {
// 复制字符串到缓冲区
size_t j;
for (j = 0; j < len; j++) {
buffer[i + j] = str[j];
}
i += len;
}
else {
// 缓冲区不足
va_end(args);
return -1;
}
// 判断是否能容纳整个字符串
// 移动 format 指针到 %s 后面
format += 1;
}
else if (*format == '%' && *(format + 1) == 'd')
{
// 处理 %d 格式符
long int num = va_arg(args, long int);
// 分配足够的内存来存储整数的字符串表示
char str[200]; // 调整大小以适应你的需要
sprintf_s(str, sizeof(str), " %d", num);
// 检查参数是否匹配
if (str=="-1") {
// 参数不匹配
va_end(args);
return -1;
}
// 计算字符串长度
size_t len = strlen(str);
// 判断是否能容纳整个字符串
if (i + len < sizeOfBuffer) {
// 复制字符串到缓冲区
size_t j;
for (j = 0; j < len; j++) {
buffer[i + j] = str[j];
}
i += len;
}
else {
// 缓冲区不足
va_end(args);
return -1;
}
format += 1;
}
else {
// 未知的格式化符号
va_end(args);
return -1;
}
++format;
}
// 在字符串末尾添加终止符
if (i < sizeOfBuffer) {
buffer[i] = '\0';
}
else {
// 缓冲区不足
va_end(args);
return -1;
}
va_end(args);
return i; // 返回写入字符的数量(不包括终止符)
}
int main() {
char buffer[20];
printf("Normal output example:\n");
int result = safe_sprintf(buffer, sizeof(buffer), "Hello, %s%d!", "world", 1111, "-1"); //正常输出
if (result >= 0) {
printf("Formatted string: %s\n", buffer);
}
else {
printf("Error occurred: %d\n", result);
}
printf("Example of Scalable Buffer:\n");
result = safe_sprintf(buffer, sizeof(buffer), "Helloooooooooooooooooooooooooooooooooooooooo, %s%d!", "world", 11111, "-1");
if (result >= 0) {
printf("Formatted string: %s\n", buffer);
}
else {
printf("Error occurred: %d\n", result);
}
printf("Example of viewing stack content:\n");
result = safe_sprintf(buffer, sizeof(buffer), "Hello, %s%d%s%s%s!", "world", 11111, "-1");
if (result >= 0) {
printf("Formatted string: %s\n", buffer);
}
else {
printf("Error occurred: %d\n", result);
}
printf("Memory Overwrite Example:\n");
result = safe_sprintf(buffer, sizeof(buffer), "Hello, %s%d%s!", "world", 11111, "Memory Overwrite Memory Overwrite Memory Overwrite", "-1");
if (result >= 0) {
printf("Formatted string: %s\n", buffer);
}
else {
printf("Error occurred: %d\n", result);
}
return 0;
}运行结果需要截图证明
运行结果
测试样例
实验中第一次输出成功;
第二次验证可伸展的缓冲区和程序崩溃,报错中断;
第三次验证查看栈内容,报错中断;
第二次验证内容覆写,报错中断;
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ZYH's blog!
评论