/** * @file

Back-door to private members and methods

*/
#define LANGUAGE_LAWYERS_DAY
#define OBSCURE_LANGUAGE_FEATURES
#define WORLD_UPSIDE_DOWN

Disclaimer

This is not my original idea. I probably stumbled by accident upon work of Johannes Schaub - litb at his personal blog: litb's Blog. Despite it's limited practical value I liked it for the insight and doing the "impossible". In this short article I would try to explain as simple as possible the core idea.

Vulnerability

Critical observation is that access specifiers (e. g. private) are not taken into account during explicit template instantiation. C++ 1998, 2003 and 2011 standards say so (always section 14.7.2 yet different paragraphs).

ISO/EIC 14882:2011 14.7.2 paragraph 12

The usual access checking rules do not apply to names used to specify explicit instantiations. [ Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible. - end note ]

Practical example

Let's show how to exploit this feature in practice on trivial structure with private data member.

#include <iostream>

struct safe {
    private:
        const int data = 42;
};

int main() {
    
    safe the_one;
    
    /* our definitely not noble quest:
    std::cout << the_one.data;
    */
    
    return 0;
}

Corner-stone will be misuse of above mentioned "feature" as it is the only place where private member name can be used legally (AFAIK ATM). Yet clever way to use it without need to mention the member name elsewhere needs to be used.

#include <iostream>

struct safe {
    private:
        const int data = 42;
};

template<const int safe::* MEMBER_INT_PTR> Surprise;

template struct Surprise<&safe::data>;

int main() {
    
    safe the_one;
    
    /* compile time error: 
    the_one.data;
    */
    
    return 0;
}

Just keep in mind that class template definition on it's own does not affect runtime unless compile-time-called. It just describes way how to compile-time-instantiate class definitions. To actually define some class compile-time-instantiation of class template is needed.

Template instantiation (please don't confuse with template specialization) doesn't do much unless you are creative. The trick is to use friend function definition in template class.

Friend function definition

Among other (more common) things friend keyword let you define function inside class declaration that (apart from having access to class private members/methods) belongs to the scope enclosing the class declaration. In other words - the new function is defined outside of the class.

class a {
    friend void my_function() { /* see it is a definition */ }
};

That's it - we are basically done. To define some generic template so that it prints member referred to by pointer parameter is trivial and all that's left is to call it.

All put together it might looks like this:

#include <iostream>

struct safe {
    private:
        const int data = 42;
};

template<const int safe::* MEMBER_INT_PTR>
struct GenerateThiefFunction { 
    friend void steal_from(const safe& victim_object) {
        // dereferencing member pointer on instance - might look exotic but no magic here
        std::cout << victim_object.*MEMBER_INT_PTR << std::endl;
    }
};

// actually defines the class and therefore defines the steal_from() function
template struct GenerateThiefFunction<&safe::data>;

int main() {
    
    safe the_one;
    
    steal_from(the_one);
    
    return 0;
}

There's just one last catch - such function is not used in name lookup unless either:

  1. it is declared outside of the class
  2. non-trivial name lookup is used (think Argument-Dependent-Lookup)

Or from the horse's mouth:

ISO/EIC 14882:2011 7.3.1.2 paragraph 3

If a friend declaration in a non-local class first declares a class or function the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (3.4.1) or by qualified lookup (3.4.3) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship). If a friend function is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2).

Final version

Since I am trying to show as simple proof-of-concept as possible declaration is a winner:

#include <iostream>

struct safe {
    private:
        const int data = 42;
};

template<const int safe::* MEMBER_INT_PTR>
struct GenerateThiefFunction { 
    friend void steal_from(const safe& victim_object) {
        std::cout << victim_object.*MEMBER_INT_PTR << std::endl;
    }
};

template struct GenerateThiefFunction<&safe::data>;

void steal_from(const safe& victim_object);

int main() {
    
    safe the_one;
    
    steal_from(the_one);
    
    return 0;
}

Closing thoughts

If you are to repeat the code for every private member you want to get access to it gets tedious. So if you really need to do sabotage encapsulation this way you might find some inspiration in original article which has some template parameters and ADL on top of the above.

Since this is rather extreme use of obscure language feature I would not be surprised if there's compiler out there that is not compliant to standard regarding disregard for access specifiers. I wouldn't expect this hack to work just because standard demands it.