Skip to content

winapi: simplify curlx_get_winapi_error() function#20261

Closed
dEajL3kA wants to merge 1 commit intocurl:masterfrom
dEajL3kA:master
Closed

winapi: simplify curlx_get_winapi_error() function#20261
dEajL3kA wants to merge 1 commit intocurl:masterfrom
dEajL3kA:master

Conversation

@dEajL3kA
Copy link
Contributor

@dEajL3kA dEajL3kA commented Jan 12, 2026

Use FormatMessageA() to get the error message as multibyte-character string (local codepage) right away, instead of using FormatMessageW() and then converting from Unicode (UTF-16) string to multibyte-character string (local codepage) manually via wcstombs() function.

Ref: a326877 #6065

@dEajL3kA dEajL3kA force-pushed the master branch 3 times, most recently from 60701de to 744d4fc Compare January 12, 2026 11:03
@vszakats vszakats changed the title Simplified curlx_get_winapi_error() function winapi: simplify curlx_get_winapi_error() function Jan 12, 2026
if(!buflen)
/* The output buffer cannot be larger than 64K bytes. */
if(!buf || !buflen || buflen > 0xFFFF)
return NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would FormatMessageA() return a failure if the passed buflen exceeds 64K?

Copy link
Contributor Author

@dEajL3kA dEajL3kA Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Microsoft says:

The output buffer cannot be larger than 64K bytes.

Wine does this:

    if (size >= 32768)
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return 0;
    }

Maybe we should check for 32768 then? 🤔

Copy link
Contributor Author

@dEajL3kA dEajL3kA Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows 11, size greater than 0xFFFF fails with ERROR_INSUFFICIENT_BUFFER.

Don't know if limit maybe was 0x7FFF on older versions ?!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about doing it like before, omit the the manual check, and let the API return an error as necessary? it sees the size and can (and does) act upon it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about limiting the size to 0x7FFF?

If caller offers a bigger buffer, we can simply pretend the buffer is only 0x7FFF in size, so that the API won't fail.

Copy link
Contributor Author

@dEajL3kA dEajL3kA Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the "old" version, buflen was not passed to FormatMessage(), since a local wide-char buffer was used.

Now that we pass buf and buflen to FormatMessage() directly, we have to cast buflen from size_t to DWORD.

This creates a possible overflow that did not exist before.

To avoid overflow, we need to bound buflen to MAXDWORD. But if we do that, we could simply bound to 0x7FFF, because that not only avoids the possible overflow but also fixes the possible API failure at the same time.

Not bounding it at all, just hoping caller will never pass a value that would overflow, is precarious, in my opinion.


Goal of this PR is to get rid of an unnecessary character set conversion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the overflow, if passing 256 bytes max throughout the codebase?
Possible API failures are handled by checking the return value.

Why not keep it simple?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could potentially add an assert for it to catch ourselves if we ever change the usage in a future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not keep it simple?

curlx_get_winapi_error() is is used in curlx_strerr(), which passes through the buflen parameter. And curlx_strerr() is used in many places. On a quick search, it appears that all curlx_strerr() invocations are "internal" calls with a fixed-size buffer. But this is not trivial to verify. And even if it is true now, it could change in the future. So I'd say, better safe than sorry. After all, clamping buflen to the range that FormatMessage() actually supports does not add that much of "complexity". Especially considered that we saved the entire string conversion.

Copy link
Member

@vszakats vszakats Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that adding magic numbers like 0xFFFF, 0x7FFF and duplicating the API's work is completely overkill.
We're checking for the API result and fail if it fails. It's known to fail if the passed size is too large.

casting size_t to DWORD is done elsewhere in the code and will truncate the value to DWORD_MAX, and make the function fail.

curlx_strerr() all follow the same pattern throughout the codebase, and even a call accidentally passed a 1GB or 4GB buffer to it, FormatMessage() errors out.

@dEajL3kA dEajL3kA force-pushed the master branch 4 times, most recently from 8f7d2a8 to 2ee0229 Compare January 12, 2026 15:40
Copy link
Contributor Author

@dEajL3kA dEajL3kA left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added DEBUGASSERT, as suggested.

if(!buflen)
/* The output buffer cannot be larger than 64K bytes. */
if(!buf || !buflen || buflen > 0xFFFF)
return NULL;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not keep it simple?

curlx_get_winapi_error() is is used in curlx_strerr(), which passes through the buflen parameter. And curlx_strerr() is used in many places. On a quick search, it appears that all curlx_strerr() invocations are "internal" calls with a fixed-size buffer. But this is not trivial to verify. And even if it is true now, it could change in the future. So I'd say, better safe than sorry. After all, clamping buflen to the range that FormatMessage() actually supports does not add that much of "complexity". Especially considered that we saved the entire string conversion.

@jay
Copy link
Member

jay commented Jan 12, 2026

This function originally used FormatMessage for Windows CE and FormatMessageA for all other Windows. Some versions of Windows CE did not have FormatMessageA and always defined FormatMessage as FormatMessageW (or maybe just had a FormatMessage with wide characters I don't remember exactly).

Switching to FormatMessageA should be fine since we recently ended Windows CE support.

…string (local codepage) directly, instead of using FormatMessageW() and then convert the string from Unicode (UTF-16) to multi-byte (local codepage) manually.
@dEajL3kA
Copy link
Contributor Author

dEajL3kA commented Jan 16, 2026

Is anything else needed?

@jay
Copy link
Member

jay commented Jan 20, 2026

Is anything else needed?

Looks ok to me. I don't think the misc checks CI failure is related so I set it to run again. I'd get rid of the WINE URL. The assert is not strictly needed but I'm fine with it. What do you think Viktor?

@vszakats
Copy link
Member

vszakats commented Jan 20, 2026

Is anything else needed?

Looks ok to me. I don't think the misc checks CI failure is related so I set it to run again. I'd get rid of the WINE URL. The assert is not strictly needed but I'm fine with it. What do you think Viktor?

I'd also get rid of the long URL, and remove DEBUGASSERT,
since the API already does this check for us. If we want to keep it
to verify our own code, it'd be more useful IMO to make it check for
STRERROR_LEN or a reasonable range we'd actually use in internal
calls, like > 64 && < 1024, instead of hard-coding an internal limit
of a particular revision of the WINE implementation.

@jay jay closed this in 4890074 Jan 21, 2026
@jay
Copy link
Member

jay commented Jan 21, 2026

Landed without the assert. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants