Getting required buffer length with secure _vsnprintf_s
Asked Answered
D

2

7

I'm trying to update some "legacy" code to comply with the latest security updates to MSVC, and am facing some trouble migrating from _vsnprintf to _vsnprintf_s.

In particular, I was calling _vsnprintf with a null buffer and zero for the count/length, getting the result, allocating a buffer of the needed size (return value + 1), and then calling _vsnprintf again with the newly-allocated buffer and known-correct size:

size_t length = _vsntprintf(nullptr, 0, mask, params);
TCHAR *final = new TCHAR [length + 1];
_vsntprintf(final, length + 1, mask, params);

This behavior is documented on MSDN:

If the buffer size specified by count is not sufficiently large to contain the output specified by format and argptr, the return value of vsnprintf is the number of characters that would be written if count were sufficiently large. If the return value is greater than count - 1, the output has been truncated.

I'm trying to do the same with _vsnprintf_s, but its documentation does not contain the same. It instead says

If the storage required to store the data and a terminating null exceeds sizeOfBuffer, the invalid parameter handler is invoked, as described in Parameter Validation, unless count is _TRUNCATE, in which case as much of the string as will fit in buffer is written and -1 returned.

Trying it out anyway with the following:

size_t length = _vsntprintf_s(nullptr, 0, 0, mask, params);

This results in a "length" of zero. If you pass in _TRUNCATE (-1) as the count instead, the following assertion fails:

Expression: buffer != nullptr && buffer_count > 0

I presume it is possible to override _set_invalid_parameter_handler and somehow find out what the length should be, but there has to be an easier way?

Dynamism answered 23/3, 2016 at 22:16 Comment(9)
This is no C code.Moonseed
@Olaf Sorry, that should have been C++. I was fretting about the security-enhanced-crt tag that I missed that typo. Do you really think that was worth downvoting, though??Dynamism
In the line size_t length = _vsntprintf(nullptr, 0, 0, mask, params);, did you mean _vsntprintf_s?Avebury
@Avebury I fixed that almost immediately, but I wasn't fast enough for you :)Dynamism
I don't see a need [or desire--since it doesn't work] to replace both. Just do vsnprintf(nullptr,0,...) to get the size and then do vsnprintf_s(final,length + 1,...)Antitragus
@CraigEstey unfortunately MSVC requires jumping through #define and project settings hoops to compile with non-secure functions.Dynamism
vscprintf?Ombudsman
@AlgirdasPreidžius I don't know if you or TriskalJM came up with that first, but it is perfect. If it was you, I'd accept it as the answer if you were to post it.Dynamism
@MahmoudAl-Qudsi Well, when I posted it, there was no answer, and honestly, all it was, was just 2 minutes of Google. And, most often than not, I just am too lazy to post full answer, since you need to put more effort into it, than a comment.Ombudsman
A
11
size_t length = _vscprintf(mask, va_list);
TCHAR *final = new TCHAR [length + 1];
_vsntprintf_s(final, length, _TRUNCATE, mask, va_list);
Avebury answered 23/3, 2016 at 22:42 Comment(1)
That is perfect. A nice, clean solution. _vscprintf should be documented on the the _vsnprintf_s page, imho.Dynamism
A
-1

How about rolling your own vsnprintf variant that doesn't "violate the rules" to get the length:

int
printf_size(const char *fmt,int count,va_list ap)
{
    char buf[2000000];
    int len;

    len = vsnprintf_s(buf,sizeof(buf),count,fmt,ap);

    return len;
}

Since the returned will [most likely] be less than sizeof(buf) you should be fine.

Or, do:

int
printf_size(const char *fmt,int count,va_list ap)
{
    char *buf;
    int siz;
    int len;

    for (siz = 2000000;  ;  siz <<= 1) {
        buf = malloc(siz);
        len = vsnprintf_s(buf,siz,count,fmt,ap);
        free(buf);
        if (len < siz)
            break;
    }

    return len;
}

Or, doing a one stop shop function:

int
sprintf_secure(char **buf,const char *fmt,int count,va_list ap)
{
    char *bp;
    int siz;
    int len;

    for (siz = 2000000;  ;  siz <<= 1) {
        bp = malloc(siz);
        len = vsnprintf_s(bp,siz,count,fmt,ap);
        if (len < siz)
            break;
    }

    bp = realloc(bp,len + 1);

    *buf = bp;

    return len;
}
Antitragus answered 23/3, 2016 at 23:0 Comment(3)
Thanks for the suggestion, but randomly allocating 4MiB of data each time someone calls my function is kind of overkill.Dynamism
Well, just use the [first] stack version. There's no allocation. With a 2MB buffer [10,000 should be plenty], it shouldn't overflow the maximum stack limit. Or, the sprintf_secure but start with 1000 (or 100)--it's a tuning parameter that can adjusted dynamically by watching the actual length used. BTW, based on your comment, I just assumed that the *cprintf would be unusable for the same reasons, otherwise I probably wouldn't have posted the answer.Antitragus
No, the secure crt functions only replace operations that involve writing to a pre-allocated buffer. A function like vscprintf which returns an integer would not be affected.Dynamism

© 2022 - 2024 — McMap. All rights reserved.