C中此代码段背后的安全注意事项

时间:2017-01-07 20:11:58

标签: c security static-analysis

我在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;
}

1 个答案:

答案 0 :(得分:4)

单独考虑,问题包括:

  • 您不会检查f是否为空。
  • 您不会检查arrmap是否为空。
  • 您不会检查第一个fread()是否成功。
  • 您没有完全验证读取的值;负值或零值会使事情变得糟糕。
  • 您不会检查第二个fread()是否成功。
  • 无论是否有效,您都会返回-1

其中哪些是安全问题?在某些层面,所有这些。在某些情况下,您可以假定farrmap是合法的而不进行检查。不检查读取是否成功,尤其是第一次,是一个严重的问题。 n_map的负值将是一个严重的问题。每次读取失败都要求成功是一个问题。

当循环完成时,n_map设置为-1(在递减后它为零)。所以,你在失败或成功时返回-1。那没用。它几乎肯定会返回从文件中读取的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;
}

此版本报告特定错误并在出错时返回。它在输入时产生半适当的断言(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