c - 带有自定义注册说明符的 GCC snprintf

标签 c gcc printf buffer-overflow

当我将 snprintf() 与自定义说明符(例如 %b 用于二进制)一起使用时,GCC 的行为是什么?

这段代码安全吗?:

alx_printf_b_init();
snprintf(str, 2, "%b", 5);

我认为这是不安全的,因为负责打印二进制数的代码不知道缓冲区的大小。也许有一种方法可以在注册说明符的代码中接收缓冲区的长度。

注册说明符的代码如下(来源:libalx,我写的):

/******************************************************************************
 ******* headers **************************************************************
 ******************************************************************************/
#include "libalx/base/stdio/printf/b.h"

#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <printf.h>


/******************************************************************************
 ******* macros ***************************************************************
 ******************************************************************************/
#define BIN_REPR_BUFSIZ (sizeof(uintmax_t) * CHAR_BIT)


/******************************************************************************
 ******* enums ****************************************************************
 ******************************************************************************/


/******************************************************************************
 ******* structs / unions *****************************************************
 ******************************************************************************/
struct  Printf_Pad {
        char    ch;
        int     len;
};


/******************************************************************************
 ******* static functions (prototypes) ****************************************
 ******************************************************************************/
static  int     printf_b_output     (FILE *stream,
                                     const struct printf_info *info,
                                     const void *const args[]);
static  int     printf_b_arginf_sz  (const struct printf_info *info,
                                     size_t n, int *argtypes, int *size);

static  uintmax_t printf_b_value    (const struct printf_info *info,
                                     const void *arg);
static  int     printf_b_bin_repr   (bool bin[BIN_REPR_BUFSIZ],
                                     const struct printf_info *info,
                                     const void *arg);
static  int     printf_b_bin_len    (const struct printf_info *info,
                                     int min_len);
static  int     printf_b_pad_len    (const struct printf_info *info,
                                     int bin_len);
static  int     printf_b_print_prefix   (FILE *stream,
                                     const struct printf_info *info);
static  int     printf_b_pad_zeros  (FILE *stream,
                                     const struct printf_info *info,
                                     int min_len);
static  int     printf_b_print_number   (FILE *stream,
                                     const struct printf_info *info,
                                     bool bin[BIN_REPR_BUFSIZ],
                                     int min_len, int bin_len);
static  char    printf_pad_ch       (const struct printf_info *info);
static  int     printf_pad_spaces   (FILE *stream, int pad_len);




/******************************************************************************
 ******* global functions *****************************************************
 ******************************************************************************/
int     alx_printf_b_init   (void)
{

    if (register_printf_specifier('b', printf_b_output, printf_b_arginf_sz))
        return  -1;
    if (register_printf_specifier('B', printf_b_output, printf_b_arginf_sz))
        return  -1;

    return  0;
}


/******************************************************************************
 ******* static functions (definitions) ***************************************
 ******************************************************************************/
static
int     printf_b_output     (FILE *stream,
                             const struct printf_info *info,
                             const void *const args[])
{
    struct  Printf_Pad  pad = {0};
    bool    bin[BIN_REPR_BUFSIZ];
    int     min_len;
    int     bin_len;
    int     len;
    int     tmp;

    len = 0;

    min_len = printf_b_bin_repr(bin, info, args[0]);
    bin_len = printf_b_bin_len(info, min_len);

    pad.ch = printf_pad_ch(info);
    if (pad.ch == ' ')
        pad.len = printf_b_pad_len(info, bin_len);

    /* Padding with ' ' (right aligned) */
    if ((pad.ch == ' ')  &&  !info->left) {
        tmp = printf_pad_spaces(stream, pad.len);
        if (tmp == EOF)
            return  EOF;
        len += tmp;
    }

    /* "0b"/"0B" prefix */
    if (info->alt) {
        tmp = printf_b_print_prefix(stream, info);
        if (tmp == EOF)
            return  EOF;
        len += tmp;
    }

    /* Padding with '0' */
    if (pad.ch == '0') {
        tmp = printf_b_pad_zeros(stream, info, min_len);
        if (tmp == EOF)
            return  EOF;
        len += tmp;
    }

    /* Print number (including leading 0s to fill precission) */
    tmp = printf_b_print_number(stream, info, bin, min_len, bin_len);
    if (tmp == EOF)
        return  EOF;
    len += tmp;

    /* Padding with ' ' (left aligned) */
    if (info->left) {
        tmp = printf_pad_spaces(stream, pad.len);
        if (tmp == EOF)
            return  EOF;
        len += tmp;
    }

    return  len;
}

static
int     printf_b_arginf_sz  (const struct printf_info *info,
                             size_t n, int *argtypes, int *size)
{

    (void)info;
    (void)size;

    if (n > 0)
        argtypes[0] = PA_INT;

    return 1;
}

static
uintmax_t printf_b_value    (const struct printf_info *info,
                             const void *arg)
{

    if (info->is_long_double)
        return  *(unsigned long long *)arg;
    if (info->is_long)
        return  *(unsigned long *)arg;
    if (info->is_char)
        return  *(unsigned char *)arg;
    if (info->is_short)
        return  *(unsigned short *)arg;
    return  *(unsigned *)arg;
}

static
int     printf_b_bin_repr   (bool bin[BIN_REPR_BUFSIZ],
                             const struct printf_info *info,
                             const void *arg)
{
    uintmax_t   val;
    int     min_len;

    val = printf_b_value(info, arg);

    memset(bin, 0, sizeof(bin[0]) * BIN_REPR_BUFSIZ);
    for (min_len = 0; val; min_len++) {
        if (val % 2)
            bin[min_len]    = 1;
        val >>= 1;
    }

    if (!min_len)
        return  1;
    return  min_len;
}

static
int     printf_b_bin_len    (const struct printf_info *info,
                             int min_len)
{

    if (info->prec > min_len)
        return  info->prec;
    return  min_len;
}

static
int     printf_b_pad_len    (const struct printf_info *info,
                             int bin_len)
{
    int pad_len;

    pad_len = info->width - bin_len;
    if (info->alt)
        pad_len -= 2;
    if (info->group)
        pad_len -= (bin_len - 1) / 4;
    if (pad_len < 0)
        pad_len = 0;

    return  pad_len;
}

static
int     printf_b_print_prefix   (FILE *stream,
                                 const struct printf_info *info)
{
    int len;

    len = 0;
    if (fputc('0', stream) == EOF)
        return  EOF;
    len++;
    if (fputc(info->spec, stream) == EOF)
        return  EOF;
    len++;

    return  len;
}

static
int     printf_b_pad_zeros  (FILE *stream,
                             const struct printf_info *info,
                             int min_len)
{
    int len;
    int tmp;

    len = 0;
    tmp = info->width - (info->alt * 2);
    if (info->group)
        tmp -= tmp / 5 - !(tmp % 5);
    for (int i = tmp - 1; i > min_len - 1; i--) {
        if (fputc('0', stream) == EOF)
            return  EOF;
        len++;
        if (info->group  &&  !(i % 4)) {
            if (fputc('_', stream) == EOF)
                return  EOF;
            len++;
        }
    }

    return  len;
}

static
int     printf_b_print_number   (FILE *stream,
                                 const struct printf_info *info,
                                 bool bin[sizeof(uintmax_t) * CHAR_BIT],
                                 int min_len, int bin_len)
{
    int len;

    len = 0;

    /* Print leading zeros to fill precission */
    for (int i = bin_len - 1; i > min_len - 1; i--) {
        if (fputc('0', stream) == EOF)
            return  EOF;
        len++;
        if (info->group  &&  !(i % 4)) {
            if (fputc('_', stream) == EOF)
                return  EOF;
            len++;
        }
    }

    /* Print number */
    for (int i = min_len - 1; i; i--) {
        if (fputc('0' + bin[i], stream) == EOF)
            return  EOF;
        len++;
        if (info->group  &&  !(i % 4)) {
            if (fputc('_', stream) == EOF)
                return  EOF;
            len++;
        }
    }
    if (fputc('0' + bin[0], stream) == EOF)
        return  EOF;
    len++;

    return  len;
}

static
char    printf_pad_ch       (const struct printf_info *info)
{

    if ((info->prec != -1)  ||  (info->pad == ' ')  ||  info->left)
        return  ' ';
    return  '0';
}

static
int     printf_pad_spaces   (FILE *stream, int pad_len)
{
    int len;

    len = 0;
    for (int i = pad_len; i; i--) {
        if (fputc(' ', stream) == EOF)
            return  EOF;
        len++;
    }

    return  len;
}


/******************************************************************************
 ******* end of file **********************************************************
 ******************************************************************************/

最佳答案

您提供给 glibc printf 自定义说明符功能的回调根本不涉及缓冲区。您编写的自定义处理程序负责将输出写入它作为参数接收的 stdio FILE * 流,它可能是一种特殊类型的流,指的是字符串缓冲区或许多其他缓冲区之一特殊类型。您的代码不应该关心。

此代码中唯一的缓冲区是在内部定义的(不是作为扩展 API 接口(interface)的一部分),其大小为:

#define BIN_REPR_BUFSIZ (sizeof(uintmax_t) * CHAR_BIT)

这是表示任何整数类型所需的二进制位数的硬性上限。

关于c - 带有自定义注册说明符的 GCC snprintf,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57315995/

相关文章:

c++ - 计算多边形的最小面积矩形

c - 是否可以在不编译的情况下包含 .c 文件中的函数?

c - 为什么这个工具不打印字符串?

C - (Printf) 强制显示零

c - `*((char*)ptr+4))` 在做什么?

c - 输出不正确,无法发现代码中的错误。 kochans 的 C 书

c - Haskell - 相当于 for 循环?

c++ - malloc_info() 是如何工作的?

c++ - 使用RowMajor和ColMajor数据排列的矩阵行求和的奇怪性能差异

java - 如何让输出显示尾随零?