printf-format-fix.c revision c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387
6781N/A/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
6781N/A
6781N/A#include "lib.h"
6781N/A#include "printf-format-fix.h"
6781N/A
6781N/Astatic const char *
6781N/Afix_format_real(const char *fmt, const char *p, size_t *len_r)
6781N/A{
6781N/A const char *errstr;
6781N/A char *buf;
6781N/A size_t len1, len2, len3;
6781N/A
6781N/A i_assert((size_t)(p - fmt) < INT_MAX);
6781N/A i_assert(p[0] == '%' && p[1] == 'm');
6781N/A
6781N/A errstr = strerror(errno);
6781N/A
6781N/A /* we'll assume that there's only one %m in the format string.
6781N/A this simplifies the code and there's really no good reason to have
6781N/A it multiple times. Callers can trap this case themselves. */
6781N/A len1 = p - fmt;
6781N/A len2 = strlen(errstr);
6781N/A len3 = strlen(p + 2);
6781N/A
6781N/A /* @UNSAFE */
6781N/A buf = t_buffer_get(len1 + len2 + len3 + 1);
6781N/A memcpy(buf, fmt, len1);
6781N/A memcpy(buf + len1, errstr, len2);
6781N/A memcpy(buf + len1 + len2, p + 2, len3 + 1);
6781N/A
6781N/A *len_r = len1 + len2 + len3;
6781N/A return buf;
6781N/A}
6781N/A
6781N/Astatic const char *
6781N/Aprintf_format_fix_noalloc(const char *format, size_t *len_r)
6781N/A{
6781N/A /* NOTE: This function is overly strict in what it accepts. Some
6781N/A format strings that are valid (and safe) in C99 will cause a panic
6781N/A here. This is because we don't really need to support the weirdest
6781N/A special cases, and we're also being extra careful not to pass
6781N/A anything to the underlying libc printf, which might treat the string
6781N/A differently than us and unexpectedly handling it as %n. For example
6781N/A "%**%n" with glibc. */
6781N/A
6781N/A /* Allow only the standard C99 flags. There are also <'> and <I> flags,
6781N/A but we don't really need them. And at worst if they're not supported
6781N/A by the underlying printf, they could potentially be used to work
6781N/A around our restrictions. */
6781N/A const char printf_flags[] = "#0- +";
6781N/A /* As a tiny optimization keep the most commonly used conversion
6781N/A specifiers first, so strchr() stops early. */
6781N/A static const char *printf_specifiers = "sudcixXpoeEfFgGaA";
6781N/A const char *ret, *p, *p2;
6781N/A char *flag;
6781N/A
6781N/A p = ret = format;
6781N/A while ((p2 = strchr(p, '%')) != NULL) {
6781N/A const unsigned int start_pos = p2 - format;
6781N/A
6781N/A p = p2+1;
6781N/A if (*p == '%') {
6781N/A /* we'll be strict and allow %% only when there are no
6781N/A optinal flags or modifiers. */
6781N/A p++;
6781N/A continue;
6781N/A }
6781N/A /* 1) zero or more flags. We'll add a further restriction that
6781N/A each flag can be used only once, since there's no need to
6781N/A use them more than once, and some implementations might
6781N/A add their own limits. */
6781N/A bool printf_flags_seen[N_ELEMENTS(printf_flags)] = { FALSE, };
6781N/A while (*p != '\0' &&
6781N/A (flag = strchr(printf_flags, *p)) != NULL) {
6781N/A unsigned int flag_idx = flag - printf_flags;
6781N/A
6781N/A if (printf_flags_seen[flag_idx]) {
6781N/A i_panic("Duplicate %% flag '%c' starting at #%u in '%s'",
6781N/A *p, start_pos, format);
6781N/A }
6781N/A printf_flags_seen[flag_idx] = TRUE;
6781N/A p++;
6781N/A }
6781N/A
6781N/A /* 2) Optional minimum field width */
6781N/A if (*p == '*') {
6781N/A /* We don't bother supporting "*m$" - it's not used
6781N/A anywhere and seems a bit dangerous. */
6781N/A p++;
6781N/A } else if (*p >= '1' && *p <= '9') {
6781N/A /* Limit to 4 digits - we'll never want more than that.
6781N/A Some implementations might not handle long digits
6781N/A correctly, or maybe even could be used for DoS due
6781N/A to using too much CPU. */
6781N/A unsigned int i = 0;
6781N/A do {
6781N/A p++;
6781N/A if (++i > 4) {
6781N/A i_panic("Too large minimum field width starting at #%u in '%s'",
6781N/A start_pos, format);
6781N/A }
6781N/A } while (*p >= '0' && *p <= '9');
6781N/A }
6781N/A
6781N/A /* 3) Optional precision */
6781N/A if (*p == '.') {
6781N/A /* We don't bother supporting anything but numbers
6781N/A here. 999 should be long enough precision. */
6781N/A unsigned int i = 0;
6781N/A p++;
6781N/A while (*p >= '0' && *p <= '9') {
6781N/A if (++i > 3) {
6781N/A i_panic("Too large precision starting at #%u in '%s'",
6781N/A start_pos, format);
6781N/A }
6781N/A p++;
6781N/A }
6781N/A }
6781N/A
6781N/A /* 4) Optional length modifier */
6781N/A switch (*p) {
6781N/A case 'h':
6781N/A if (*++p == 'h')
6781N/A p++;
6781N/A break;
6781N/A case 'l':
6781N/A if (*++p == 'l')
6781N/A p++;
6781N/A break;
6781N/A case 'L':
6781N/A case 'j':
6781N/A case 'z':
6781N/A case 't':
6781N/A p++;
6781N/A break;
6781N/A }
6781N/A
6781N/A /* 5) conversion specifier */
6781N/A if (*p == '\0' || strchr(printf_specifiers, *p) == NULL) {
6781N/A switch (*p) {
6781N/A case 'n':
6781N/A i_panic("%%n modifier used");
6781N/A case 'm':
6781N/A if (ret != format)
6781N/A i_panic("%%m used twice");
6781N/A ret = fix_format_real(format, p-1, len_r);
6781N/A break;
6781N/A case '\0':
6781N/A i_panic("Missing %% specifier starting at #%u in '%s'",
6781N/A start_pos, format);
6781N/A default:
6781N/A i_panic("Unsupported 0x%02x specifier starting at #%u in '%s'",
6781N/A *p, start_pos, format);
6781N/A }
6781N/A }
6781N/A p++;
6781N/A }
6781N/A
6781N/A if (ret == format)
6781N/A *len_r = p - format + strlen(p);
6781N/A return ret;
6781N/A}
6781N/A
6781N/Aconst char *printf_format_fix_get_len(const char *format, size_t *len_r)
6781N/A{
6781N/A const char *ret;
6781N/A
6781N/A ret = printf_format_fix_noalloc(format, len_r);
6781N/A if (ret != format)
6781N/A t_buffer_alloc(*len_r + 1);
6781N/A return ret;
6781N/A}
6781N/A
6781N/Aconst char *printf_format_fix(const char *format)
6781N/A{
6781N/A const char *ret;
6781N/A size_t len;
6781N/A
6781N/A ret = printf_format_fix_noalloc(format, &len);
6781N/A if (ret != format)
6781N/A t_buffer_alloc(len + 1);
6781N/A return ret;
6781N/A}
6781N/A
6781N/Aconst char *printf_format_fix_unsafe(const char *format)
6781N/A{
6781N/A size_t len;
6781N/A
6781N/A return printf_format_fix_noalloc(format, &len);
6781N/A}
6781N/A