awnumar / memguard

Secure software enclave for storage of sensitive information in memory.
Apache License 2.0
2.5k stars 123 forks source link

[Question] How to validate the effective protection of this library? #39

Closed denisbrodbeck closed 6 years ago

denisbrodbeck commented 6 years ago

I'm trying to understand the effective protection this library offers.

Playing with the API seems pretty straightforward, e.g. trying to edit a read-only buffer SIGSEGVs.

msg := []byte("hello")
message, _ := memguard.NewFromBytes(msg, true)
message.Buffer()[0] = 'a' // This SIGSEGVs, good

As far I understand the intention of this library, it tries to protect any interaction with it's memory from outside interference unless going through its API. Does this mean, it protects from other processes (like gdb, ptrace or malicious trojan memory dumper) reading the content of all variables governed by this library?

My understanding is, that this usecase is indeed the intention of this library. How would I accomplish to verify this protection, exactly? So far, I can execute the libraries' tests, but those test are more or less "my code || under my control || normal golang code". What about other (linux/c++/c) utilities inspecting my running application?

What I am looking for is a more general test or guidance on how to exactly verify the guaranteed behavior of this library.

Given following code:

package main

import (
    "bufio"
    "fmt"
    "os"
    "github.com/awnumar/memguard"
)

func main() {
    msg1 := []byte("hello")
    msg2, _ := memguard.NewFromBytes([]byte("nice"), true)
    fmt.Println(string(msg1), string(msg2.Buffer()))
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() { // <--- app holds here
        fmt.Println(scanner.Text())
    }
}

How do I verify from an external app (dbg, some malicious memory-dumper, etc.), that msg1 is readable from memory but msg2 is protected? Would my app still SIGSEGV, when it notices another OS process trying to read the content of my protected buffer?

Hope my question makes sense to you:)

awnumar commented 6 years ago

Yes, the memory is still protected from other processes. The protections are guaranteed at the kernel level, not just run-time checks.

However, I'm not sure how you could test for this other than to attempt to read all of the process's memory at run-time via a call to /proc/[pid]/maps or using gdb. If you manage that, please do report back.

denisbrodbeck commented 6 years ago

Thanks for your feedback.

I've hacked together an example, which reads /proc/[pid]/maps and /proc/[pid]/mem and outputs all 32 byte long memory contents (32 byte is my example for a NaCl secret key).

The golang app consists basically of:

type message struct {
  ID        []byte                 // 32 byte
  Recipient []byte                 // 32 byte
  Sender    []byte                 // 32 byte
  Message   *memguard.LockedBuffer // Gets 32 byte input
  Meta      []byte                 // 32 byte
}

func main(){
msg := newMessage(
  []byte("(((id    | 1234567890123456  )))"),
  []byte("(((recip | bobby@spacer.com  )))"),
  []byte("(((sender| eva@secretmoon.m  )))"),
  []byte("(((msg   | VERY SECRET VERY  )))"), // this should be protected
  []byte("(((meta  | 2017-10-21 10:26  )))"),
  )
// ... Wait until user ends
}

After starting the app you switch to another terminal and read the memory with the help of a (lengthy) python script.

Output with memguard:

(((id | 1234567890123456 )))(((recip | bobby@spacer.com )))(((sender| eva@secretmoon.m )))(((meta | 2017-10-21 10:26 )))

Output without memguard:

(((id | 1234567890123456 )))(((recip | bobby@spacer.com )))(((sender| eva@secretmoon.m )))(((msg | VERY SECRET VERY )))(((meta | 2017-10-21 10:26 )))

So… this is pretty good from my point of view :smile:

Have a look at a working example in my repo memgrdpeek.

I suppose someone with more memory-hacking skills might eventually pry more data from memory, but that's as far I could get.

@awnumar What do you think?

awnumar commented 6 years ago

@denisbrodbeck Hey, that's pretty good.

Although I think memguard is cheating slightly there because we allocate whole pages of memory using mmap, so there are no 32-byte constants on the heap. Rather, our containers are stored in the virtual address space of the process. (A basic overview can be found here.)

So, just looking for 32-byte constants in the heap won't help you find it. And you can't just dump everything because then you could accidentally access one of the guard pages, and you can't scan for it for the same reason.

Thinking about it, if you're root, you could possibly look for the .buffer field of a container. Since slices are in reality just pointers with sugar, you could possibly attempt to directly access the memory that the slice is referencing? I'm not 100% sure.

wallrj commented 1 week ago

@awnumar Thanks for memguard. I think I saw you present it the Bristol Go meetup some years ago but it went way over my head at the time :-).

As a recent user of memguard, I asked myself the same question as @denisbrodbeck and then found this issue:

How to validate the effective protection of this library?

Some of the questions asked in the description may not have been answered and some comments may be out of date.

@denisbrodbeck wrote: Does this mean, it protects from other processes (like gdb, ptrace or malicious trojan memory dumper) reading the content of all variables governed by this library?

I think the short answer is yes it does protect, but with the following details. You can use gdb (gcore) to dump the memory of a Go process that has memguard enclaves and locked buffers. (as demonstrated in https://github.com/awnumar/memguard_test).

The dump will contain:

In https://spacetime.dev/encrypting-secrets-in-memory @awnumar wrote:

The most pressing issue at the moment is that the package relies on cryptographic primitives implemented by the Go standard library which does not secure its own memory and may leak values that are passed to it.

Does that mean that the raw bytes of the enclave decryption key may be found elsewhere in the memory dump?