C 当您不使用';我不知道缓冲区的大小

C 当您不使用';我不知道缓冲区的大小,c,static-analysis,stdio,cstdio,C,Static Analysis,Stdio,Cstdio,我的问题是,我不能将缓冲区大小参数添加到我的sprintf中,因为它在超过50k个位置使用,我不能用vsprintf_s或vsnprintf替换vsprintf 有没有其他方法可以使上述函数更安全?您在这里要问的是一个(in)著名问题的特殊化:“如果我只有一个指针,如何获得数组的大小?” 无法确定dest指向的对象的大小。你最好的选择可能是咬紧牙关,改变那些50k的位置来通过这个尺寸 #define SPRINTF_TRACE_BUFSIZE 4096 int my_sprintf( char

我的问题是,我不能将缓冲区大小参数添加到我的sprintf中,因为它在超过50k个位置使用,我不能用vsprintf_s或vsnprintf替换vsprintf


有没有其他方法可以使上述函数更安全?

您在这里要问的是一个(in)著名问题的特殊化:“如果我只有一个指针,如何获得数组的大小?”

无法确定
dest
指向的对象的大小。你最好的选择可能是咬紧牙关,改变那些50k的位置来通过这个尺寸

#define SPRINTF_TRACE_BUFSIZE 4096

int my_sprintf( char* dest, const char* fmt, ... )
{
    /* in threaded code use malloc(3) instead */
    static char trace_buf[SPRINTF_TRACE_BUFSIZE];

    va_list va;
    va_start( va, fmt );
    int rc = vsnprintf( trace_buf, SPRINTF_TRACE_BUFSIZE, fmt, va );

    assert( rc != -1 && rc < SPRINTF_TRACE_BUFSIZE );

    memcpy( dest, trace_buf, rc + 1 ); /* +1 for \0 terminator */
    return rc;
}


您的代码中可能有更多您没有告诉我们的内容。例如,在你提到的那些“50k”的地方,大小是已知的吗?如果是这样的话,你可以使用一个肮脏的变量宏,它在幕后使用
sizeof
,然后调用一个带有长度参数的函数。

对不起,这里没有适合你的银弹,正如@cnicutar已经提到的

您可以从限制缓冲区大小和断言溢出开始。比如:

__inline int my_sprintf (char *dest,char *format,...)
{
    va_list va;
    va_start(va,format);
    return vsprintf(dest,format,va);
}
#定义SPRINTF_TRACE_BUFSIZE 4096
int my_sprintf(char*dest,const char*fmt,…)
{
/*在线程化代码中使用malloc(3)*/
静态字符跟踪[SPRINTF\u trace\u BUFSIZE];
va_列表va;
va_启动(va,fmt);
int rc=vsnprintf(trace_buf,SPRINTF_trace_BUFSIZE,fmt,va);
断言(rc!=-1&&rc
然后开始降低跟踪缓冲区大小,直到断言开始触发。在这一点上,您可以找到并修复有问题的呼叫

这当然会降低整个系统的速度,但我们这里不讨论性能


只是强调一下-这是一个快速而肮脏的黑客攻击,用于与大量旧的现有代码库作斗争,不要将其用于新的开发

这一大规模的重构工作需要自动化。更改
my_sprintf
函数本身的行为是微不足道的,因此我将把这个练习留给读者。正如你所指出的,电话是最困难的部分。我假设这些调用的结构如下:

ret=my_sprintf(dest,“%d:%s”,arg1,arg2)

许多IDE/文本编辑器在其搜索/替换选项中支持正则表达式。我的其中一个,SlickEdit,特别好,因为它允许Perl正则表达式。下面是一个将上述调用转换为以下内容的示例:

ret=my\u sprintf(目标,最大大小,“%d:%s”,arg1,arg2)

请注意,已指示SlickEdit将此模式递归应用于我的项目树中的所有C源文件

请注意,对话框条目应相当于
s/(\“+\”)/MAX\u SIZE、$1/
的正则表达式。您的开发环境是否具有类似的功能?很多人都是这样,看看吧

如果没有,还有其他选项,比如独立脚本。例如,假设您有一些类似*nix的shell和Perl可用。Google“perl one liner replace”查找一些很好的示例,如和。将这些与明智地使用
find
命令()结合起来,问题就解决了


*免责声明:我不能保证我的示例regex的优雅。在我的例子中,它是有效的,但regex专家无疑可以改进它。

OP评论说“大量的缓冲区是动态分配的,…”。
malloc()
realloc()
calloc()
free()
等可以使用存储大小的包装函数重新编写

#define SPRINTF_TRACE_BUFSIZE 4096

int my_sprintf( char* dest, const char* fmt, ... )
{
    /* in threaded code use malloc(3) instead */
    static char trace_buf[SPRINTF_TRACE_BUFSIZE];

    va_list va;
    va_start( va, fmt );
    int rc = vsnprintf( trace_buf, SPRINTF_TRACE_BUFSIZE, fmt, va );

    assert( rc != -1 && rc < SPRINTF_TRACE_BUFSIZE );

    memcpy( dest, trace_buf, rc + 1 ); /* +1 for \0 terminator */
    return rc;
}
所有其他*.c文件都使用

typedef union {
  max_align_t align;
  size_t sz;
} my_header;

void* my_malloc(size_t size) {
  my_header *p = malloc(sizeof *p + size);
  if (p) {
    p->sz = size;
    p++;
  }
  return p;
}

size_t my_size(const void *p) {
  if (p) {
    const my_header *head = p;
    return head[-1].sz;
  }
  return 0;
}

void my_free(void *p) {
  if (p) {
    my_header *head = p;
    free(--head);
  }
}
现在,当使用分配的指针调用my_sprintf()

此外,还可以预先设置一个幻数,以帮助识别传递的指针是否真的是
my_allcoating()
1

包装分配函数也是确定各种分配关注点的一种方法:双重空闲、最大使用、所有指针空闲


[编辑]5年后

代码需要确保对齐-代码重新工作


对于C11之前的版本,使用宽类型的并集代替
max\u align\u t

int my_sprintf (char *dest,char *format,...) {
  va_list va;
  va_start(va,format);
  size_t n = my_size(dest);
  return vsnprintf(dest,n,format,va);
}

当您的
my\u sprintf()
被传递的不是真正的
char*
时,您可以使用宏来提供帮助

typedef union {
  double d;
  long l;
  void *p;
  void (*fp)();
  // With C99
  complex long double cld;
  long long ll;

  size_t sz;
} my_header; 
}

是的,它确实计算了
dest
两次,但是在不知道代码库的情况下,我不能说这是否是一个问题。但它至少在使用数组进行调用的情况下会有所帮助


我还郑重建议您看看Purify之类的东西是否值得购买。

大量缓冲区是动态分配的,所以大多数情况下我不能使用Size。。。真是一场噩梦:(@user327843-Hmm.“50k个位置”…那可能需要很长时间。@Eregrith该死的veracode:D我认为我不会在下一个月实现我的目标appraisal@user327843从一个
grep-Ri--include=*.[ch]my|u sprintf | wc-l
开始,了解需要更改多少行:)如果dest太小,memcpy不会导致缓冲区溢出吗?是的,它会。但这不是为了防止坏的上游代码溢出缓冲区,而是为了找到违规者并清理它们。我继承了一个非常庞大的遗留代码(数百万LOC),没有自动的单元测试。在我完成之前,我已经死了。我喜欢这样做,因为这是处理问题规模的简单方法。或者,代码使用BA缓冲区并简单地记录全局变量中使用的最大长度,以便以后进行调试检查。IOWs,显示通常使用的
my\u sprintf()
的上下文。不错,但有点不可靠。。。我认为,如果您首先在我的_标题中添加一些魔力,比如0xC0DE,从而尝试无限制地打印到
#define my_sprintf(dest,format,...) \
    my_sprintf_func( (dest),sizeof(dest), ( format ), __VA_ARGS__ )

__inline int my_sprintf_func(char *dest,size_t size,char *format,...)
{
    va_list va;
    va_start(va,format);
    if ( size == sizeof(dest) )
        return vsprintf(dest,format,va);
    return vsnprintf(dest,size,format,va);