此 C 代码片段背后的安全考虑
Security considerations behind this code snippet in C
我在 git 上找到了这段代码并想使用它,但有人评论说其中存在安全漏洞。我好像没认出来:
int32_t read_arrbuff(FILE *f, uint32_t *arrmap) {
int32_t i = 0, n_map;
fread(&n_map, sizeof(n_map), 1, f);
if (n_map > 256)
return -1;
while (n_map--) {
fread(&arrmap[i++], sizeof(uint32_t), 1, f);
}
return n_map;
}
孤立来看,问题包括:
- 您没有检查
f
是否为 NULL。
- 您没有检查
arrmap
是否为 NULL。
- 您没有检查第一个
fread()
是否成功。
- 您没有完全验证读取的值;负值或零值会使事情变得糟糕。
- 您没有检查第二个
fread()
是否成功。
- 你总是 return
-1
,无论任何事情是否成功。
哪些是安全问题?在某些层面上,所有这些。在某些情况下,您可以假设 f
和 arrmap
是合法的而不进行检查。不检查读取是否成功,尤其是第一次,是一个严重的问题。 n_map
的负值将是一个严重的问题。当每次读取都失败时声称成功将是一个问题。
当循环完成时,n_map
设置为 -1
(在 post 减量之前为零)。所以,你 return -1 失败或成功。那没有用。它几乎肯定 return 是从文件中读取的 n_map
的值,因此调用者可以知道数组中有多少个值。
通常最好不要将 256
之类的大小限制硬编码到程序中。该接口应包括数组大小,您应检查传递的数组大小。
使用原始界面,您可以使用:
#include "stderr.h"
#include <stdio.h>
#include <inttypes.h>
extern int32_t read_arrbuff(FILE *f, uint32_t *arrmap);
int32_t read_arrbuff(FILE *f, uint32_t *arrmap)
{
int32_t n_map;
if (fread(&n_map, sizeof(n_map), 1, f) != 1)
err_syserr("failed to read data (count)\n");
if (n_map > 256 || n_map <= 0)
return -1;
for (int32_t i = 0; i < n_map; i++)
{
if (fread(&arrmap[i], sizeof(uint32_t), 1, f) != 1)
err_syserr("failed to read data (value %" PRId32 ")\n", i);
}
return n_map;
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc != 1)
err_usage("");
char file[] = "data";
/* Create data file */
FILE *fp = fopen(file, "wb");
if (fp == NULL)
err_syserr("failed to open file '%s' for writing\n", file);
int32_t nmap = 32;
if (fwrite(&nmap, sizeof(nmap), 1, fp) != 1)
err_syserr("failed to write to file '%s'\n", file);
for (int32_t i = 0; i < nmap; i++)
{
if (fwrite(&i, sizeof(i), 1, fp) != 1)
err_syserr("failed to write to file '%s'\n", file);
}
fclose(fp);
/* Read data file */
fp = fopen(file, "rb");
if (fp == NULL)
err_syserr("failed to open file '%s' for reading\n", file);
uint32_t amap[256];
int32_t rc = read_arrbuff(fp, amap);
printf("rc = %" PRId32 "\n", rc);
for (int32_t i = 0; i < rc; i++)
printf("%3" PRId32 " = %3" PRId32 "\n", i, amap[i]);
fclose(fp);
return 0;
}
你可以争论err_syserr()
单方面退出是否合适。 (err_*()
函数的声明和源代码在 stderr.h
和 stderr.c
中,可从 GitHub 获得。)
从函数参数获取最大数组大小的替代版本是:
#include "stderr.h"
#include <assert.h>
#include <stdio.h>
#include <inttypes.h>
extern int32_t read_arrbuff(FILE *f, int32_t a_size, uint32_t arrmap[a_size]);
int32_t read_arrbuff(FILE *f, int32_t a_size, uint32_t arrmap[a_size])
{
int32_t n_map;
assert(f != NULL && arrmap != NULL && a_size > 0 && a_size <= 256);
if (fread(&n_map, sizeof(n_map), 1, f) != 1)
{
err_sysrem("failed to read data (count)\n");
return -1;
}
if (n_map > a_size || n_map <= 0)
{
err_sysrem("count %" PRId32 " is out of range 1..%" PRId32 "\n",
n_map, a_size);
return -1;
}
for (int32_t i = 0; i < n_map; i++)
{
if (fread(&arrmap[i], sizeof(uint32_t), 1, f) != 1)
{
err_syserr("failed to read data (value %" PRId32 " of %" PRId32 ")\n",
i, n_map);
}
}
return n_map;
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc != 1)
err_usage("");
char file[] = "data";
/* Create data file */
FILE *fp = fopen(file, "wb");
if (fp == NULL)
err_syserr("failed to open file '%s' for writing\n", file);
int32_t nmap = 32;
if (fwrite(&nmap, sizeof(nmap), 1, fp) != 1)
err_syserr("failed to write to file '%s'\n", file);
for (int32_t i = 0; i < nmap; i++)
{
if (fwrite(&i, sizeof(i), 1, fp) != 1)
err_syserr("failed to write to file '%s'\n", file);
}
fclose(fp);
/* Read data file */
fp = fopen(file, "rb");
if (fp == NULL)
err_syserr("failed to open file '%s' for reading\n", file);
enum { AMAP_SIZE = 256 };
uint32_t amap[AMAP_SIZE];
int32_t rc = read_arrbuff(fp, AMAP_SIZE, amap);
printf("rc = %" PRId32 "\n", rc);
for (int32_t i = 0; i < rc; i++)
printf("%3" PRId32 " = %3" PRId32 "\n", i, amap[i]);
fclose(fp);
return 0;
}
此版本报告了一个特定的错误,并且 returns 出现了错误。它在入口上做出半适当的断言(256 限制不一定适当,但与原始代码一致)。
输出平淡无奇(两者相同):
rc = 32
0 = 0
1 = 1
2 = 2
3 = 3
4 = 4
5 = 5
6 = 6
7 = 7
8 = 8
9 = 9
10 = 10
11 = 11
12 = 12
13 = 13
14 = 14
15 = 15
16 = 16
17 = 17
18 = 18
19 = 19
20 = 20
21 = 21
22 = 22
23 = 23
24 = 24
25 = 25
26 = 26
27 = 27
28 = 28
29 = 29
30 = 30
31 = 31
我在 git 上找到了这段代码并想使用它,但有人评论说其中存在安全漏洞。我好像没认出来:
int32_t read_arrbuff(FILE *f, uint32_t *arrmap) {
int32_t i = 0, n_map;
fread(&n_map, sizeof(n_map), 1, f);
if (n_map > 256)
return -1;
while (n_map--) {
fread(&arrmap[i++], sizeof(uint32_t), 1, f);
}
return n_map;
}
孤立来看,问题包括:
- 您没有检查
f
是否为 NULL。 - 您没有检查
arrmap
是否为 NULL。 - 您没有检查第一个
fread()
是否成功。 - 您没有完全验证读取的值;负值或零值会使事情变得糟糕。
- 您没有检查第二个
fread()
是否成功。 - 你总是 return
-1
,无论任何事情是否成功。
哪些是安全问题?在某些层面上,所有这些。在某些情况下,您可以假设 f
和 arrmap
是合法的而不进行检查。不检查读取是否成功,尤其是第一次,是一个严重的问题。 n_map
的负值将是一个严重的问题。当每次读取都失败时声称成功将是一个问题。
当循环完成时,n_map
设置为 -1
(在 post 减量之前为零)。所以,你 return -1 失败或成功。那没有用。它几乎肯定 return 是从文件中读取的 n_map
的值,因此调用者可以知道数组中有多少个值。
通常最好不要将 256
之类的大小限制硬编码到程序中。该接口应包括数组大小,您应检查传递的数组大小。
使用原始界面,您可以使用:
#include "stderr.h"
#include <stdio.h>
#include <inttypes.h>
extern int32_t read_arrbuff(FILE *f, uint32_t *arrmap);
int32_t read_arrbuff(FILE *f, uint32_t *arrmap)
{
int32_t n_map;
if (fread(&n_map, sizeof(n_map), 1, f) != 1)
err_syserr("failed to read data (count)\n");
if (n_map > 256 || n_map <= 0)
return -1;
for (int32_t i = 0; i < n_map; i++)
{
if (fread(&arrmap[i], sizeof(uint32_t), 1, f) != 1)
err_syserr("failed to read data (value %" PRId32 ")\n", i);
}
return n_map;
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc != 1)
err_usage("");
char file[] = "data";
/* Create data file */
FILE *fp = fopen(file, "wb");
if (fp == NULL)
err_syserr("failed to open file '%s' for writing\n", file);
int32_t nmap = 32;
if (fwrite(&nmap, sizeof(nmap), 1, fp) != 1)
err_syserr("failed to write to file '%s'\n", file);
for (int32_t i = 0; i < nmap; i++)
{
if (fwrite(&i, sizeof(i), 1, fp) != 1)
err_syserr("failed to write to file '%s'\n", file);
}
fclose(fp);
/* Read data file */
fp = fopen(file, "rb");
if (fp == NULL)
err_syserr("failed to open file '%s' for reading\n", file);
uint32_t amap[256];
int32_t rc = read_arrbuff(fp, amap);
printf("rc = %" PRId32 "\n", rc);
for (int32_t i = 0; i < rc; i++)
printf("%3" PRId32 " = %3" PRId32 "\n", i, amap[i]);
fclose(fp);
return 0;
}
你可以争论err_syserr()
单方面退出是否合适。 (err_*()
函数的声明和源代码在 stderr.h
和 stderr.c
中,可从 GitHub 获得。)
从函数参数获取最大数组大小的替代版本是:
#include "stderr.h"
#include <assert.h>
#include <stdio.h>
#include <inttypes.h>
extern int32_t read_arrbuff(FILE *f, int32_t a_size, uint32_t arrmap[a_size]);
int32_t read_arrbuff(FILE *f, int32_t a_size, uint32_t arrmap[a_size])
{
int32_t n_map;
assert(f != NULL && arrmap != NULL && a_size > 0 && a_size <= 256);
if (fread(&n_map, sizeof(n_map), 1, f) != 1)
{
err_sysrem("failed to read data (count)\n");
return -1;
}
if (n_map > a_size || n_map <= 0)
{
err_sysrem("count %" PRId32 " is out of range 1..%" PRId32 "\n",
n_map, a_size);
return -1;
}
for (int32_t i = 0; i < n_map; i++)
{
if (fread(&arrmap[i], sizeof(uint32_t), 1, f) != 1)
{
err_syserr("failed to read data (value %" PRId32 " of %" PRId32 ")\n",
i, n_map);
}
}
return n_map;
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc != 1)
err_usage("");
char file[] = "data";
/* Create data file */
FILE *fp = fopen(file, "wb");
if (fp == NULL)
err_syserr("failed to open file '%s' for writing\n", file);
int32_t nmap = 32;
if (fwrite(&nmap, sizeof(nmap), 1, fp) != 1)
err_syserr("failed to write to file '%s'\n", file);
for (int32_t i = 0; i < nmap; i++)
{
if (fwrite(&i, sizeof(i), 1, fp) != 1)
err_syserr("failed to write to file '%s'\n", file);
}
fclose(fp);
/* Read data file */
fp = fopen(file, "rb");
if (fp == NULL)
err_syserr("failed to open file '%s' for reading\n", file);
enum { AMAP_SIZE = 256 };
uint32_t amap[AMAP_SIZE];
int32_t rc = read_arrbuff(fp, AMAP_SIZE, amap);
printf("rc = %" PRId32 "\n", rc);
for (int32_t i = 0; i < rc; i++)
printf("%3" PRId32 " = %3" PRId32 "\n", i, amap[i]);
fclose(fp);
return 0;
}
此版本报告了一个特定的错误,并且 returns 出现了错误。它在入口上做出半适当的断言(256 限制不一定适当,但与原始代码一致)。
输出平淡无奇(两者相同):
rc = 32
0 = 0
1 = 1
2 = 2
3 = 3
4 = 4
5 = 5
6 = 6
7 = 7
8 = 8
9 = 9
10 = 10
11 = 11
12 = 12
13 = 13
14 = 14
15 = 15
16 = 16
17 = 17
18 = 18
19 = 19
20 = 20
21 = 21
22 = 22
23 = 23
24 = 24
25 = 25
26 = 26
27 = 27
28 = 28
29 = 29
30 = 30
31 = 31