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