此 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,无论任何事情是否成功。

哪些是安全问题?在某些层面上,所有这些。在某些情况下,您可以假设 farrmap 是合法的而不进行检查。不检查读取是否成功,尤其是第一次,是一个严重的问题。 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.hstderr.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