Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Poisoning certain API functions (not being maintainer)

Tags:

c++

Related to e.g.: How to poison an identifier in VC++?
What does it mean to "poison a function" in C++?

Is there a way to "properly" poison some functions whose declarations (or implementations) are not under my control?

Specifically, I'm trying to prevent accidential use of certain Windows API functions which may result in surprise results, such as e.g. CreateFileA (where use of the T macro hides the fact that you mix ANSI and Unicode) or SetWindowLong (which will cause your program to fail silently, without error, and without a chance of knowing what went wrong on a 64-bit system).

My definition of poisoning "properly" is that attempting to call the function breaks the build with a readable error (bonus points if the error occurs at the correct location in the source code).
Preferrably, this should be portable, and at the very least it must work with both GCC and clang.

What I've tried / looked at so far:

preprocessor

The simplest, immediately obvious solution would be to exploit the preprocessor:

#define CreateFileA __poison_ANSI_CreateFileA

This works very well for that one particular function as well as a couple of others, working exactly as intended, with a precise error that hints to the problem and points to the correct location in the source:

error: no matching function for call to '__poison_ANSI_CreateFileA'
note: expanded from macro 'CreateFile'
note: expanded from ... (precise location in source)

For different functions, one needs to have a unique name for each to avoid conflicting definition errors, which is tedious but easily done. So far so good, except it does not work for functions where the MinGW-w64 headers themselves tamper names using variadic macros, such as for example CreateWindow or SetWindowLong. No matter what, these compile just fine, poisoned or not.

pragma

#pragma GCC poison, which clang happens to understand as well (and also features its own version), looks and sounds like it should do exactly what I want, and in the most idiomatic way possible. Alas, it does not work. Or rather, it works too well.
Functions poisoned with the pragma directive already trigger an error when the poisoned name appears in a declaration, which means... every time, in every build that includes the header at all. That's not very helpful for writing a program!

attributes

These have the immense disadvantage that you must copy exactly the type definition lest you get "ambiguous function call" errors on legitimate calls on the one hand side. On the other hand side, they don't work. clang complains about __attribute__((__error__)) but ignores gnu::__error__ or gnu::error as well as gnu::deprecated. Further, the standard [[deprecated]] is all nice and well, but seems to do exactly nothing (compiles fine, not even a warning?!).

Is there anything I can do?

like image 222
Damon Avatar asked Jan 25 '17 17:01

Damon


1 Answers

Have found a way to enforce ambiguity by adding another declaration of the same function in a namespace. No pragmas or attrbiutes are used, should be portable to any C++11 compiler:

#include <stdio.h>

#define POISONED_BY_NAME(name) \
    namespace Poisoned \
    { \
        using function_type = decltype(::name); \
        extern function_type* name; \
    } \
    using namespace Poisoned;


POISONED_BY_NAME(puts)

int main()
{
    puts("Hello"); 
}

Visual Studio message:

error C2872: 'puts': ambiguous symbol
1>c:\program files\windows kits\10\include\10.0.17763.0\ucrt\stdio.h(353): note: could be 'int puts(const char *)'
1>c:\test.cpp(43): note: or       'Poisoned::function_type (__cdecl *__cdecl Poisoned::puts)'

gcc message:

main.cpp: In function 'int main()':
main.cpp:16:5: error: reference to 'puts' is ambiguous
     puts("Hello");
     ^~~~
main.cpp:12:22: note: candidates are: 'int (* Poisoned::puts)(const char*)'
     POISONED_BY_NAME(puts)
                      ^~~~
main.cpp:7:35: note: in definition of macro 'POISONED_BY_NAME'
             extern function_type* name; \
                                   ^~~~
In file included from main.cpp:1:
/usr/include/stdio.h:695:12: note:                 'int puts(const char*)'
 extern int puts (const char *__s);
            ^~~~
like image 161
Alex Guteniev Avatar answered Sep 20 '22 23:09

Alex Guteniev