27

I have always wondered whether public, protected, and private has security implications post compilation.

Hypothetically:

class Foo 
{
public:
    int m_Foo; // Completely vulnerable and dangerous
    
protected:
    int m_Bar; // Possible attack vector from subclasses
    
private:
    int m_FooBar; // Totally secure
};

public members [by terminology alone] suggest that they are more vulnerable than private members, but I can not imagine how this could be taken advantage of post compilation in something like a proprietary program.

Questions:

  1. Pre-Compilation, are members unneccessarily left in public, a security concern?

  2. Why or why not?

  3. Post-Compilation, are members unneccessarily left in public, a security concern?

  4. Why or why not?

  5. Are there any historical or hypothetical examples of attacks which used public designated members as an attack vector?

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
Anon
  • 3,565
  • 3
  • 27
  • 45
  • 2
    I note that your question is reasonable in the context of redistributable ([and _licensed_](https://docs.microsoft.com/en-us/windows/win32/com/licensing-and-iclassfactory2)) COM objects (and their IDL), as a COM object _could_ retain sensitive license information in a private field that _should_ not be exposed via a public property - for example. – Dai Nov 14 '21 at 07:25
  • @Dai COM? IDL?? – Anon Nov 14 '21 at 11:36
  • 1
    IDL is a language for describing object interfaces (like a C++ `class` declaration in a header file, but it's language-agnostic), and COM is... hard to explain, but I guess I'd explain COM as a way to "export" classes and other types from a Windows DLL so they can be reused by other applications without needing to statically-link anything. It's how OLE-DB, ActiveX, OLE, WinRT, VB6, VBA, Office Automation and more... _just work_. Because COM _basically_ lets you export a C++ `class` so that a VBScript or Word document can use _it matters_ how you design it, hence my link to COM licenses. – Dai Nov 14 '21 at 11:39
  • 2
    @Dai while licensing may feel "important" to a licensed library developer, I would not say that licenses are "security" topics. If there was a password stored in a private field in a COM object, I am pretty sure that somebody could use C++'s weak memory safety to "find" it. That may or may not apply to DCOM, I'm not sure. Most of the time licenses from my experience are not meant to be "securely" **impossible to find**, just something **keep people honest** and encourage people to pay as they should. Licenses are always merely obfuscation, because the program needs to access the license. – jrh Nov 14 '21 at 16:04
  • 2
    And to extend that, if hypothetically somebody trusted a "private" anything to be "secure" (e.g., by having some kind of "authentication" that assumed that private was "trusted" and public was "untrusted") **that** would be a severe security breach, because I could probably with enough effort bypass the public API and poke or call the private fields/methods directly, skipping your "authentication". Private is pretty much just for library devs to say "do not use this because it might change in future versions or isn't documented". – jrh Nov 14 '21 at 16:09
  • What about using pimpl ideom, but only use the pointer xored with some secret? – lalala Nov 15 '21 at 17:39
  • The very short answer is 'no' because there is a standard-compliant way to access private members without invoking UB. See https://stackoverflow.com/questions/54907138/is-it-really-a-good-technique-to-work-with-legacy-code-with-reinterpret-cast/54907320#54907320. – Chuu Nov 15 '21 at 21:42
  • @lalala no, because you have to store the "expected" secret somewhere in memory in the program (i.e., when you allocated the memory). It would still be obfuscation, not security. Also remember that an incorrect secret would cause UB, which is probably not a good response to incorrect credentials – jrh Nov 15 '21 at 21:57
  • 1
    @Chuu `reinterpret_cast` does not work with DCOM. In fact, DCOM _will be secure and trusted_ provided the remoted object's interface _doesn't_ expose secrets via public properties. – Dai Nov 15 '21 at 21:58
  • @Dai The question I linked is answered with a technique to get access to private members without reinterpret_cast. Given how non-standard a lot of Microsoft C++ was in the COM era though I admit I do not know if the techniques will actually work, even if they are supposed to. – Chuu Nov 15 '21 at 22:01
  • 1
    @Chuu In DCOM the object _does not exist in memory_ on the local computer, that's why. The C++ class that represents the COM interface on the local computer is a proxy object that reimplements every method into network IO (yes, that's as horrible as it sounds - I think you can even run Direct3D (which is COM based) over DCOM...) – Dai Nov 15 '21 at 22:01
  • @dai I honestly don't know why we are focusing so much on DCOM. Assuming standards compliant C++ the answer is clearly 'no'. The fact that there exists non-compliant C++ and specific cases where the answer is 'yes' doesn't change the general answer. – Chuu Nov 15 '21 at 22:03
  • 1
    @Chuu COM and DCOM can be used with standards-compliant C++. And I'm talking about it because it's a _real world_ production case of potential information leakage through OOP class/interface members, which is what the question is about, so it's on-topic. – Dai Nov 15 '21 at 22:05
  • @Chuu FWIW I found the DCOM tangent interesting; DCOM has classes, and the notion of public/private, and it can be done in C++. It is true that DCOM classes aren't created the "normal" C++ way, but from a high level they are still objects. – jrh Nov 15 '21 at 22:06
  • When you say, "...are members unneccessarily left in `public`, a security concern?", should the comma after the word "public" be removed? Its existence changes the meaning of the phrase and of the overall sentences where it's being used, but I think if the comma were removed, the meaning would be closer to what you intend. – Panzercrisis Nov 15 '21 at 22:57
  • As many have pointed out, public/protective/private in C++ and similar languages are not enforce that run time. However, as a practitioner of research and development of capability computer architectures, one of my goals has been to enable HW and runtime enforcement of such. Capability architecture’s that have structure descriptors can provide such to separate fields of a strict or object, but since structure descriptors are variable size they are hard to implement in HW - and enforcement may be limited to pointer dereferencing. – Krazy Glew Nov 16 '21 at 13:11

9 Answers9

98

Access modifiers like public/private/protected are not intended as a security boundary. And since C++ is not a memory-safe language, this cannot be a security boundary.

The laziest “attack” to access private members would be to reinterpret-cast the value to a struct with equivalent layout:

struct PublicFoo {
    int m_Foo;
    int m_Bar;
    int m_FooBar;
};

PublicFoo* attack(Foo* supposedly_secure) {
  return reinterpret_cast<PublicFoo*>(supposedly_secure);
}

In some cases, I have manually calculated offsets in order to access fields for objects that were created by a different library, e.g. to pick out the m_Bar field:

int attack_bar(Foo const* supposedly_secure) {
  const auto start_of_the_object = reinterpret_cast<char*>(supposedly_secure);
  const auto offset = sizeof(int);  // skip over m_Foo field
  const auto location = start_of_the_object + offset;
  return *reinterpret_cast<int*>(location);
}

So what are access modifiers for? They just help you to manage the data flows in your code. While you can circumvent access modifiers, you typically don't try to sabotage yourself. So if the field m_Foo must guarantee certain invariants, you want all modifications to that value to go through a method of your class. If you declare it private, then attempts to directly access this field will generate a helpful compiler error. This encapsulation helps you build more robust systems, which is especially helpful for larger projects or libraries.

In other languages like Java, access modifiers can sometimes serve as a security boundary. But that only works because Java is a memory-safe language with an explicit security model, so tricks like reinterpret-casting do not work (and reflection can be prevented). But in general, you should not trust language constructs to guarantee security. You would want real sandboxing technology for security boundaries, e.g. Linux Containers.

amon
  • 132,749
  • 27
  • 279
  • 375
  • 35
    Yep. In short, access modifiers are for safety, not for security. – Doc Brown Nov 13 '21 at 12:24
  • The alternative struct will work 99.99% in practice, but not in theory. A C++ compiler must follow the C rules for structs that are C compatible. As soon as there is a “private” for example, the C++ compiler is allowed to use a different layout, violating C rules, possibly more optimised. But there are plenty of ways to access private fields. – gnasher729 Nov 13 '21 at 13:09
  • 18
    @gnasher729 Yes, such memory safety violations are all UB. But C++ compilers don't calculate class layout via rand(). The layout of a particular compiler is quite predictable in practice. Here we have three int members so that there are no alignment/padding issues, but even if we had those that could be handled. – amon Nov 13 '21 at 14:14
  • 6
    *Java is a memory-safe language* JNI. That's ***my*** address space the JVM is running in. – Andrew Henle Nov 13 '21 at 19:06
  • I feel like it would be worth asking the question in context to Java, and perhaps Rust as well, and to understand howto attack memory safe models via their access modifiers. – Anon Nov 13 '21 at 21:00
  • 8
    @Anon In Rust, the same things are possible as in C++, I just need an `unsafe{…}` block to call `std::mem::transmute()` or to do pointer arithmetic. There is no “private” at run time. In Java, the equivalent “attack” would typically use reflection though there are security limits, especially since Java 9 modules. The JVM was designed to work as a sandbox that can run untrusted code, but it was never particularly reliable in this role compared e.g. with VMs for JavaScript. We'd need to agree on a threat model to analyze this in detail, e.g. does the attacker control the runtime environment. – amon Nov 13 '21 at 21:56
  • @gnasher729 I'd go one step further. While the alternative struct pattern does not work in theory, in practice I think it works on 100% of compilers, not just 99.99%. The only thing I can think of is there might be a compiler out there whose sole reason for existence was to break the alternative struct pattern while still being compliant with the C++ spec. – Cort Ammon Nov 13 '21 at 23:25
  • 1
    `#define private public` ; sure the standard says it doesn't work, but it does work. @CortAmmon: C++/clr does funny stuffs; the struct pattern won't work on native classes that inherit from managed classes. – Joshua Nov 14 '21 at 00:34
  • Cort, it a struct contains short/int/short and int requires 4 byte alignment, you’d want the members reordered to save time and space. But you can’t in C sue to the language rules. – gnasher729 Nov 15 '21 at 08:57
  • 7
    Worth noting that Java's security model has failed. That's why applets aren't allowed any more. – user253751 Nov 15 '21 at 10:10
  • 1
    There is a standards-compliant way to get access to private members of a class that does not invoke UB. See this question: https://stackoverflow.com/questions/54907138/is-it-really-a-good-technique-to-work-with-legacy-code-with-reinterpret-cast/54907320#54907320 – Chuu Nov 15 '21 at 21:37
  • As near as I'm aware, there's no way for a Java library writer to prevent their type from being inspected via reflection (and precompiled code is trivially disassembled for Java anyways) - and if they did they should be spindled/folded/mutilated. Reflection is sometimes disallowed by the runtime, but that's not something a library would control. – Clockwork-Muse Nov 16 '21 at 19:34
14

Using public and private correctly (and following good practices in general) helps you write better code with fewer bugs, and code with fewer bugs is typically harder for an attacker to exploit. However, private is not a security boundary, as other answers have already explained.

The same thing is true with many other aspects of a programming language, like excessive use of global variables. While global variables do not create security risks on their own, you're more likely to introduce buggy (and potentially exploitable) code when you use them excessively.

forest
  • 414
  • 2
  • 9
  • That's what I'm thinking, too. It's a way to enforce what your code should be changing and when. So, let's say I'm stupid enough to attempt something like: int someVariable; cin << someVariable; if (object.thingThatShouldNotBeChanged = someVariable && someVariable == 8008) doSomethingThatEndUserShouldNeverBeAbleToDo(); I've just thrown the door wide open for a malicious actor to come in and mess things up. – moonman239 Nov 16 '21 at 19:58
8

Private and protected do not provide any security at all. Why? Because they are in your source code. They only affect code written by someone with access to your source code.

If I have access to your source code that includes “private” and “protected” then I just can change them both to “public”. Any possible protection is gone.

They protect you from your own or someone else’s stupidity. They don’t protect you from sabotage or attacks.

(They also encourage a better programming style, and serve as documentation- “private” means you shouldn’t touch this without a very good reason, or perhaps discussing with the original developer).

PS. You can override “final” functions easily - just edit the source code and remove the “final”. Same principle.

gnasher729
  • 42,090
  • 4
  • 59
  • 119
  • 7
    Ah yes, the classic `#define private public` before including a header file :) – amon Nov 13 '21 at 14:15
  • Amon, I wouldn’t be _that_ evil. I’d open the header file and physically remove the “private”. Yes, same principle. Just realise that also works in Java and removes JVM protections. – gnasher729 Nov 13 '21 at 20:24
  • 3
    "*They protect you from your own or someone else’s stupidity.*" - which very much has security implications. Most security vulnerabilities stem from stupidity (or negligence), not from sabotage. So while an unnecessary `public` or missing `private` is not a vulnerability in its own, it does increase the likelihood of issues. – Bergi Nov 13 '21 at 22:01
  • 1
    I've never seen a working protection against stupidity. IMO access modifiers prevent accidents, but you still need to be smart enough not to try and circumvent them, but instead stop and ask yourself - "what is the _right and intended_ way to do this?" – Vilx- Nov 13 '21 at 22:02
  • 3
    @Amon I do believe you have earned the association with your namesake from Starcraft 2, which sought to unravel all of reality for all time. – Cort Ammon Nov 13 '21 at 23:26
  • @gnasher729 I believe the context where JVM protections are intended to work is if your code is running as a plug-in/app within a someone else's platform running on a device that you don't own or control. In that context you wouldn't be able to physically edit the 'private' to make it 'public'. – bdsl Nov 14 '21 at 12:09
  • But I also see that the Java Security Manager never had much adoption beyond browser applets, and it's been deprecated with Java 17. – bdsl Nov 14 '21 at 12:15
  • "private means you shouldn’t touch this without a very good reason, or perhaps discussing with the original developer" That's really interesting. I'd tend to think the opposite : if you modify something private, the corresponding scope is clear and small. As long as you don't change the external behaviour, it should be okay to modify the implementation. If you modify something which is public, you might break stuff in completely different classes, possibly on another computer. – Eric Duminil Nov 14 '21 at 22:34
  • 1
    Eric: if something is private in class A and class B thinks it should be public and modifiable, and the developer CAN change A and make things public, THAT is when you should think very hard about it. – gnasher729 Nov 15 '21 at 09:01
  • @gnasher729: Sorry, I really didn't get your last comment. – Eric Duminil Nov 15 '21 at 10:32
  • 1
    @gnasher729: Do you mean that it's a bad idea to change the visibility of a method from private to public? Probably, yes. But changing the implementation of a private method, while still leaving it private and keeping the same behavior should be okay, right? – Eric Duminil Nov 15 '21 at 13:16
  • @Eric If you are responsible for class A, you can add, remove, rename, change parameters, change implementation of private functions as much as you like (obviously don't break the class). If you are responsible for class B, then changing a function in class A from private to public can be very dangerous. Whowever is responsible for class A didn't want you to use the function, and they probably know better than you do. Even if both people are you on different days :-) – gnasher729 Nov 15 '21 at 18:02
  • @gnasher729: Okay, sure. We totally agree. – Eric Duminil Nov 15 '21 at 18:44
7

I have always wondered whether public, protected, and private has security implications post compilation.

Since you're asking about C++, I'd say the answer is "not per se" (unless this leads you to shoot yourself in the foot down the line somehow). The reason (and this is something I'd really like to emphasize compared to the other explanations) is that in C++, almost all type information exists only at compile time. The compiler may include a little bit of type information in the binary to facilitate stuff like dynamic_cast, but otherwise it's done away with by and large even by the time the compiler outputs assembly language, let alone machine code. This is true even for C; a typical CPU has no idea if the address you're telling it to jump to truly holds the entry point of a function that takes and returns a double or if it actually holds a char in a list of rutabaga varieties—it just jumps and attempts to treat the next series of bits it encounters as an instruction, even if this leads to disaster. Likewise, no model of CPU I've ever heard of understands what a C++ class is, let alone varying degrees of class member access.

When people talk about security vulnerabilities in a programing context, they generally mean ways to get the program to perform other-than-intended behavior from the outside at runtime. A classic example is failing to check that user input can fit into a fixed-size buffer before storing it there: too-large input can cause your program to write the input past the end of the buffer in memory. With sufficient knowledge, an attacker might be able to craft input to that part of the program that will be written to a location the processor will try to proceed from later, allowing them to take control of the process.

Since member access specifiers like private and protected aren't represented directly in the binary, they wouldn't be said to have "security implications" in that sense. Rather, private and protected are to shield users of a public interface you're designing from depending on parts of the code that you imagine they'd rather not depend on, generally because you don't want to make the same guarantees about them that you do for the public API. They're very much for the benefit of human programmers and don't do anything on their own to guarantee correct program behavior at runtime. On that note, if you have some insecure code, it doesn't help to make it non-public; it's still getting run at some point, presumably, so the vulnerability may still be exploitable through some outside interface that leads to that code path. Instead, you should fix it and alert anyone who might be affected.

In general, if you want to know what sorts of things in your C++ code do exist in some direct way at runtime and how, a great thing to do is to have your compiler stop after compilation proper and output assembly. g++ takes the -S flag for this purpose, for instance, and you can pick between att and intel syntax with -masm=[DIALECT]. You can also walk through your program instruction-by-instruction in a debugger like gdb (see the stepi and nexti commands there, and maybe also the documentation for TUI mode which is very helpful when doing this).

If you're not familiar with the instructions and features supported by the processor you're targeting, you can learn about it by reading the programmer's manuals for the processor, which are often freely available; for x86 those are Intel's. You can also pick up a more tutorial-style book about working close-to-the-metal (I like Low-Level Programming by Igor Zhirkov).

C++-centric resources do often make it clear when something has runtime implications if it's required by the standard. However, the standard gives a lot of latitude to compiler authors at the hardware level as C++ is designed to be portable. As such, in order to develop an intuition for how your code might look post-compilation, I'd say it's easier to look at things from the hardware side for starters. You know that whatever's happening it must be terms the processor can understand, and there's only so many things you can say to a given processor. A bit past that, it can help to study how C compilers work in order to get a foothold—C is much simpler and closer-to-the-metal than C++ on the whole, so it's easier to understand from that angle, and much of the most "dangerous" parts of C++ are the parts that are shared with C or closeby to them. Studying how the operating system you're targeting works in detail is also helpful, especially its process model and memory management features (provided that you're not writing code to run on bare metal or the like).

Things are different if you're running code on an interpreter. C++ generally isn't run this way of course, but with many "higher-level" languages it's possible to exploit the behavior of the interpreter in a way that's far above the level of the processor, such as the classic case of trying to get a web server to send malicious SQL commands to a database. Of course, if you write an interpreter in C++, you can open up that whole can of worms, but that's not a C++-specific topic exactly.

As a postscript, there actually is a sense in which member access specifiers can have a direct effect on the behavior of your program at runtime. If you put two separate sections in a class with the same access specifier, the compiler is free to reorder them. For instance, with

class C {
public:
    int n;
public:
    int m;
};

the compiler is free to put m before n in memory. If your program's behavior depends on the data members of instances of C being laid out a certain way, this may cause bugs, depending on the compiler. I imagine that's not quite what you had in mind, though.

Zoë Sparks
  • 179
  • 2
3

No

Reason: Once compiled, private, protected and public are gone.


In C++, you can think about these keywords as nothing more or less than directives to tell the compiler that certain member accesses are invalid code. I.e, they instruct the compiler where it should fail with an error. That's in the same vein as including a static_assert() in your code: It makes the code invalid if a certain assumption does not hold.

Since error messages are only generated while compiling, the output of a successful compiler run contains no remains of these three keywords. The compiler has checked the rules, your code complies and is translated to machine code. Both the compiler and the access modifiers have done their work at this point and exit the stage. What happens when the compiled code is executed is none of their business anymore.

This is especially true since C++ allows programmers to shoot themselves by circumventing all the safeguards that the compiler enforces. For instance, consider this simple example:


class Foo {
    public:
        Foo() = default;
        const int& getFoo() const {
            return foo;
        }
    private:
        int foo = 0;
};

Foo myFoo;
const_cast<int&>(myFoo->getFoo()) = 666;    //insert evil value into myFoo
cout << "My number is " << myFoo->getFoo() << "!\n";    // a truly evil message...

"All" that the evil programmer has done, is to cast away constness from a reference (as so much existing code does all over the place), and it has completely circumvented the private "protection" of foo. The compiler doesn't care that the reference points to a private member, it only considers that the reference is const qualified, and that the programmer has explicitly stated to ignore that const qualifier. And since the C++ compiler treats the programmer as an omniscient god who surely knows better, it simply follows its orders.

2

For public/protected/private to be making a difference, you would have to be allowing someone to compile and link their C++ code against your headers & binary.

If someone already has that much access to your system, then you have already lost from a security standpoint. As other answers have mentioned, C++ code that you allow to run in the same address space as your code can potentially access everything in that address space. private is just an instruction to the compiler not to compile a direct access to the field, but you can use one of many techniques to indirectly access the memory where the field is stored anyway. So none of the following will be safe:

  • your code
  • anything loaded into memory by your code
  • anything external that your code has permission to access

If there are any data or methods that you cannot allow an attacker to access, then you simply cannot allow them to compile their code against yours and have it run. i.e. you need to run your code on a machine you control and "security implications" are all about the interface through which you allow others to interact with the machine. This makes public/protected/private totally irrelevant to your security; an attacker doesn't need to care about them until they've already managed to get your machine to link their code to yours and execute it, which is far too late to stop them doing anything.

Ben
  • 1,017
  • 6
  • 10
  • Based Ben. I was kind of waiting for this to be addressed given that when you write plugins for certain programs, you have to interface with one of its factory classes. – Anon Nov 16 '21 at 03:21
1

Security implies that you wish to protect something. Anyone with the right tools can disassemble the binary, look at what is being done, and, with enough time and experience, can reasonably infer the original code. Or at least that is the way you should treat your code in a security related environment.

If there is something actually secret, you should not distribute it, or use some tested and proofed encryption scheme to guard it. It should still be hard to break, even if an attacker knows every last detail of how the secret is created, except the decryption key.

But, to come back to your question, there is one instance I can think of, where handling of private and public differs after compilation: dynamic libraries. The compiler has to keep all public members and functions visible, but I could envision that there is no such guarantee for private members, especially if they are const or constexpr.

0

I would explain it by comparing public, protected, and private to the locks on bathroom stalls. You should honor those locks because honoring those locks will save you from unpleasantness.

But at the same time, you should not store your valuables in the stall and count on the stall lock to protect them.

emory
  • 179
  • 1
  • 7
-1

I didn't see that the OP specified the question was specific to statically compiled languages. In dynamically loaded languages (ie; java) leaving unintended public entry points is definitely an invitation to hacking.

ddyer
  • 4,060
  • 15
  • 18