2013-07-14 3 views
0

я получил следующий код (часть log функции):Почему этот стек разбит?

/* set to 32 on purpose */ 
#define MAX_LOG_MSG_SZ 32 

void log(const char *fmt, ...) { 
    .... 

    char msg[MAX_LOG_MSG_SZ] = {0}; 
    int nb_bytes = 0; 

    /* get current time */ 
    time_t now = time(NULL); 

    char time_buf[32] = {0}; 

    /* format time as `14 Jul 20:00:08`, and exactly 16 bytes */ 
    strftime(time_buf, sizeof(time_buf), "%d %b %H:%M:%S", localtime(&now)); 

    nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf); 

    va_list ap; 
    va_start(ap, fmt); 
    vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap); 
    va_end(ap); 

    .... 
} 

Хитрость вещей при прохождении длинных параметров (сделать его больше, чем 32 байта) и изменить time_buf на другое значение меньше, чем 32 (больше 16, например, 31), этот код приведет к разрыву стека. После минуты отладки, я изменил vsnprintf вызывающую строку, как

vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ - nb_bytes, fmt, ap); 

и стек Smashing ушел, я думаю, что проблема исправлена.

НО: На time_buf[32] (или другого большего размера), поэтому ошибка вызывающему

vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap); 
не

бросить стек разящий? Точнее, почему msg 's разбивка стека, связанная с этим несвязанный stack (time_buf) space?

UPDATE: это мой uname -a выход:

Linux coanor 3.5.0-34-generiC#55-Ubuntu SMP Thu Jun 6 20:20:19 UTC 2013 i686 i686 i686 GNU/Linux 

ответ

1
char time_buf[32] = {0}; 
/* format time as `14 Jul 20:00:08`, and exactly 16 bytes */ 
strftime(time_buf, sizeof(time_buf), "%d %b %H:%M:%S", localtime(&now)); 

nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf); 

Так эффективно time_buf и сообщ содержат одни и те же данные. snprintf возвращает количество символов, которые были бы успешно записаны на msg, не считая нулевого символа.

vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap); 

Вы пытаетесь написать из адреса, указанного msg+nb_bytes. У вас было 16 символов в msg. Но вы утверждаете, что у вас есть MAX_LOG_MSG_SZ, который составляет 32 символа. Вы пытаетесь написать до конца строки. Возможно, fmt содержит более 15 символов.

vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ - nb_bytes, fmt, ap); 

На этот раз вы правильно вычитать символы, которые уже были записаны в сообщ и дают 16 символов для записи в vsnprintf. Он подчиняется и не пишет за пределами массива символов.

+0

фактически, вы ошибаетесь.snprintf возвращает количество символов, которые ** были бы написаны **. Не количество успешно записанных байтов. Эта ошибка может привести к серьезным неприятностям в этом коде. (Предположим, что результирующая строка будет больше 32 и рассмотрит следующий вызов snprintf с 'MAX_LOG_MSG_SZ - nb_bytes') – junix

+0

Я предполагаю, что они здесь одинаковы. OP копирует time_buf в msg. – bsd

+0

Скорее всего, это верно для этого конкретного случая. Но когда я читаю ваше заявление, мне кажется, что это всегда будет так, а это не так. Вот почему я хотел отметить свой комментарий. – junix

1

Использование буферов на основе стека, вы должны заботиться о том, чтобы не перекрывать выделенный буфер. Как вы выяснили, вы ввели возможное переполнение буфера с помощью vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap);. Это потому, что вы скажете vsnprintf, есть больше свободного места, чем есть (потому что nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf); уже написал несколько байтов в буфер).

Следовательно, ваше исправление, пройти MAX_LOG_MSG_SZ - nb_bytes вместо MAX_LOG_MSG_SIZE, является правильным, чтобы избежать этого эффекта.

Это также важно знать, что snprintf и это варианты всегда возвращает количество байт, которое было бы написано, независимо от количества байт, записанных в буфер.

РЕДАКТИРОВАТЬ: Таким образом, в вашем случае вы должны отслеживать общую длину строки во время ее композиции, чтобы убедиться, что вы не переполняете всю длину буфера сообщений.

+0

+1 при упоминании тонкости возвращаемого значения snprintf –

Смежные вопросы