首页 -> 安全研究

安全研究

安全漏洞
FreeBSD strfmon()函数多个整数溢出漏洞

发布日期:2008-03-27
更新日期:2008-04-22

受影响系统:
FreeBSD FreeBSD 7.0
FreeBSD FreeBSD 6.x
NetBSD NetBSD 4.0
描述:
BUGTRAQ  ID: 28479,36443
CVE(CAN) ID: CVE-2008-1391,CVE-2009-4880,CVE-2009-4881

FreeBSD就是一种运行在Intel平台上、可以自由使用的开放源码Unix类系统。

FreeBSD的strfmon()函数使用了GET_NUMBER()宏,该宏没有检查整数溢出便将其值作为参数传送给了memmove()和memset()函数。

在strfmon()函数的源码中:

- ---strfmon()-start---
ssize_t
strfmon(char * __restrict s, size_t maxsize, const char * __restrict format,
    ...)
{
    va_list        ap;
    char         *dst;        /* output destination pointer */
    const char     *fmt;        /* current format poistion pointer */
    struct lconv     *lc;        /* pointer to lconv structure */
    char        *asciivalue;    /* formatted double pointer */

    int        flags;        /* formatting options */
    int        pad_char;    /* padding character */
    int        pad_size;    /* pad size */
    int        width;        /* field width */
    int        left_prec;    /* left precision */
    int        right_prec;    /* right precision */
    double        value;        /* just value */
    char        space_char = ' '; /* space after currency */

    char        cs_precedes,    /* values gathered from struct lconv */
            sep_by_space,
            sign_posn,
            *signstr,
            *currency_symbol;

    char        *tmpptr;    /* temporary vars */
    int        sverrno;

        va_start(ap, format);

    lc = localeconv();
    dst = s;
    fmt = format;
    asciivalue = NULL;
    currency_symbol = NULL;
    pad_size = 0;

    while (*fmt) {
        /* pass nonformating characters AS IS */
        if (*fmt != '%')
            goto literal;

        /* '%' found ! */

        /* "%%" mean just '%' */
        if (*(fmt+1) == '%') {
            fmt++;
    literal:
            PRINT(*fmt++);
            continue;
        }

        /* set up initial values */
        flags = (NEED_GROUPING|LOCALE_POSN);
        pad_char = ' ';        /* padding character is "space" */
        left_prec = -1;        /* no left precision specified */
        right_prec = -1;    /* no right precision specified */
        width = -1;        /* no width specified */
        value = 0;        /* we have no value to print now */

        /* Flags */
        while (1) {
            switch (*++fmt) {
                case '=':    /* fill character */
                    pad_char = *++fmt;
                    if (pad_char == '\0')
                        goto format_error;
                    continue;
                case '^':    /* not group currency  */
                    flags &= ~(NEED_GROUPING);
                    continue;
                case '+':    /* use locale defined signs */
                    if (flags & SIGN_POSN_USED)
                        goto format_error;
                    flags |= (SIGN_POSN_USED|LOCALE_POSN);
                    continue;
                case '(':    /* enclose negatives with () */
                    if (flags & SIGN_POSN_USED)
                        goto format_error;
                    flags |= (SIGN_POSN_USED|PARENTH_POSN);
                    continue;
                case '!':    /* suppress currency symbol */
                    flags |= SUPRESS_CURR_SYMBOL;
                    continue;
                case '-':    /* alignment (left)  */
                    flags |= LEFT_JUSTIFY;
                    continue;
                default:
                    break;
            }
            break;
        }

        /* field Width */
        if (isdigit((unsigned char)*fmt)) {
            GET_NUMBER(width);
            /* Do we have enough space to put number with
             * required width ?
             */
            if (dst + width >= s + maxsize)
                goto e2big_error;
        }

        /* Left precision */
        if (*fmt == '#') {
            if (!isdigit((unsigned char)*++fmt))
                goto format_error;
            GET_NUMBER(left_prec);
        }

        /* Right precision */
        if (*fmt == '.') {
            if (!isdigit((unsigned char)*++fmt))
                goto format_error;
            GET_NUMBER(right_prec);
        }

        /* Conversion Characters */
        switch (*fmt++) {
            case 'i':    /* use internaltion currency format */
                flags |= USE_INTL_CURRENCY;
                break;
            case 'n':    /* use national currency format */
                flags &= ~(USE_INTL_CURRENCY);
                break;
            default:    /* required character is missing or
                       premature EOS */
                goto format_error;
        }

        if (flags & USE_INTL_CURRENCY) {
            currency_symbol = strdup(lc->int_curr_symbol);
            if (currency_symbol != NULL)
                space_char = *(currency_symbol+3);
        } else
            currency_symbol = strdup(lc->currency_symbol);

        if (currency_symbol == NULL)
            goto end_error;            /* ENOMEM. */

        /* value itself */
        value = va_arg(ap, double);

        /* detect sign */
        if (value < 0) {
            flags |= IS_NEGATIVE;
            value = -value;
        }

        /* fill left_prec with amount of padding chars */
        if (left_prec >= 0) {
            pad_size = __calc_left_pad((flags ^ IS_NEGATIVE),
                            currency_symbol) -
                   __calc_left_pad(flags, currency_symbol);
            if (pad_size < 0)
                pad_size = 0;
        }

        asciivalue = __format_grouped_double(value, &flags,
                left_prec, right_prec, pad_char);
        if (asciivalue == NULL)
            goto end_error;        /* errno already set     */
                        /* to ENOMEM by malloc() */

        /* set some variables for later use */
        __setup_vars(flags, &cs_precedes, &sep_by_space,
                &sign_posn, &signstr);

        /*
         * Description of some LC_MONETARY's values:
         *
         * p_cs_precedes & n_cs_precedes
         *
         * = 1 - $currency_symbol precedes the value
         *       for a monetary quantity with a non-negative value
         * = 0 - symbol succeeds the value
         *
         * p_sep_by_space & n_sep_by_space
                 *
         * = 0 - no space separates $currency_symbol
         *       from the value for a monetary quantity with a
         *     non-negative value
         * = 1 - space separates the symbol from the value
         * = 2 - space separates the symbol and the sign string,
         *       if adjacent.
                 *
         * p_sign_posn & n_sign_posn
                 *
         * = 0 - parentheses enclose the quantity and the
         *     $currency_symbol
         * = 1 - the sign string precedes the quantity and the
         *       $currency_symbol
         * = 2 - the sign string succeeds the quantity and the
         *       $currency_symbol
         * = 3 - the sign string precedes the $currency_symbol
         * = 4 - the sign string succeeds the $currency_symbol
                 *
         */

        tmpptr = dst;

        while (pad_size-- > 0)
            PRINT(' ');

        if (sign_posn == 0 && (flags & IS_NEGATIVE))
            PRINT('(');

        if (cs_precedes == 1) {
            if (sign_posn == 1 || sign_posn == 3) {
                PRINTS(signstr);
                if (sep_by_space == 2)        /* XXX: ? */
                    PRINT(' ');
            }

            if (!(flags & SUPRESS_CURR_SYMBOL)) {
                PRINTS(currency_symbol);

                if (sign_posn == 4) {
                    if (sep_by_space == 2)
                        PRINT(space_char);
                    PRINTS(signstr);
                    if (sep_by_space == 1)
                        PRINT(' ');
                } else if (sep_by_space == 1)
                    PRINT(space_char);
            }
        } else if (sign_posn == 1)
            PRINTS(signstr);

        PRINTS(asciivalue);

        if (cs_precedes == 0) {
            if (sign_posn == 3) {
                if (sep_by_space == 1)
                    PRINT(' ');
                PRINTS(signstr);
            }

            if (!(flags & SUPRESS_CURR_SYMBOL)) {
                if ((sign_posn == 3 && sep_by_space == 2)
                    || (sep_by_space == 1
                    && (sign_posn == 0
                    || sign_posn == 1
                    || sign_posn == 2
                    || sign_posn == 4)))
                    PRINT(space_char);
                PRINTS(currency_symbol); /* XXX: len */
                if (sign_posn == 4) {
                    if (sep_by_space == 2)
                        PRINT(' ');
                    PRINTS(signstr);
                }
            }
        }

        if (sign_posn == 2) {
            if (sep_by_space == 2)
                PRINT(' ');
            PRINTS(signstr);
        }

        if (sign_posn == 0 && (flags & IS_NEGATIVE))
            PRINT(')');

        if (dst - tmpptr < width) {
            if (flags & LEFT_JUSTIFY) {
                while (dst - tmpptr < width)
                    PRINT(' ');
            } else {
                pad_size = dst-tmpptr;
                memmove(tmpptr + width-pad_size, tmpptr,
                    pad_size);
                memset(tmpptr, ' ', width-pad_size);
                dst += width-pad_size;
            }
        }
    }

    PRINT('\0');
    va_end(ap);
    free(asciivalue);
    free(currency_symbol);
    return (dst - s - 1);    /* return size of put data except trailing '\0' */

e2big_error:
    errno = E2BIG;
    goto end_error;

format_error:
    errno = EINVAL;

end_error:
    sverrno = errno;
    if (asciivalue != NULL)
        free(asciivalue);
    if (currency_symbol != NULL)
        free(currency_symbol);
    errno = sverrno;
    va_end(ap);
    return (-1);
}
- ---strfmon()-end---

函数GET_NUMBER()可能会导致整数溢出:

- ---GET_NUMBER()-start---
#define GET_NUMBER(VAR)    do {                    
    VAR = 0;                        
    while (isdigit((unsigned char)*fmt)) {            
        VAR *= 10;                    
        VAR += *fmt - '0';                
        fmt++;                        
    }                            
} while (0)
- ---GET_NUMBER()-end---

fmt=2147483647n => GET_NUMBER(2147483647)
fmt=2147483648n => GET_NUMBER(-2147483648)
fmt=2147483649n => GET_NUMBER(-2147483647)
fmt=4294967296n => GET_NUMBER(0)
fmt=4294967297n => GET_NUMBER(1)

此外printf()函数中也存在类似的整数溢出。

<*来源:Maksymilian Arciemowicz (max@jestsuper.pl
  
  链接:http://secunia.com/advisories/29574
        http://marc.info/?l=bugtraq&m=120663280206029&w=2
        ftp://ftp.netbsd.org/pub/NetBSD/security/advisories/NetBSD-SA2008-006.txt.asc
*>

建议:
厂商补丁:

NetBSD
------
目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载:

http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/stdlib/strfmon.c

浏览次数:4206
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障