├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── proposal.md │ └── bug_report.md └── workflows │ └── ci.yml ├── core ├── init.go ├── auxiliary.go ├── auxiliary_test.go ├── exit_test.go ├── exit.go ├── enclave_test.go ├── enclave.go ├── coffer.go ├── crypto.go ├── coffer_test.go ├── crypto_test.go ├── buffer_test.go └── buffer.go ├── examples ├── streams │ └── streams.go ├── stream │ ├── stream_test.go │ ├── stream.go │ └── memprof1.svg ├── socketkey │ ├── socketkey_test.go │ └── socketkey.go ├── deadlock │ ├── x02 │ │ ├── poc_test.go │ │ └── poc.go │ └── x01 │ │ ├── poc.go │ │ └── poc_test.go ├── stdin │ └── stdin.go ├── README.md └── casting │ ├── casting_test.go │ └── casting.go ├── go.mod ├── AUTHORS ├── logo.svg ├── go.sum ├── memguard_test.go ├── memguard.go ├── enclave.go ├── signals.go ├── enclave_test.go ├── signals_test.go ├── README.md ├── docs.go ├── stream.go ├── stream_test.go ├── LICENSE ├── buffer.go └── buffer_test.go /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Get help with something 4 | labels: question 5 | --- 6 | 7 | *Ask away...* 8 | -------------------------------------------------------------------------------- /core/init.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/awnumar/memcall" 5 | ) 6 | 7 | func init() { 8 | memcall.DisableCoreDumps() 9 | } 10 | -------------------------------------------------------------------------------- /examples/streams/streams.go: -------------------------------------------------------------------------------- 1 | package streams 2 | 3 | // MakeStreams ... 4 | func MakeStreams() { 5 | //concurrent stream access over multiple streams test 6 | } 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/awnumar/memguard 2 | 3 | go 1.23.1 4 | 5 | require ( 6 | github.com/awnumar/memcall v0.4.0 7 | golang.org/x/crypto v0.41.0 8 | golang.org/x/sys v0.35.0 9 | lukechampine.com/frand v1.5.1 10 | ) 11 | -------------------------------------------------------------------------------- /examples/stream/stream_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSlowRandByte(t *testing.T) { 9 | randByte := SlowRandByte() 10 | fmt.Println("Random byte:", randByte) 11 | } 12 | -------------------------------------------------------------------------------- /examples/socketkey/socketkey_test.go: -------------------------------------------------------------------------------- 1 | package socketkey 2 | 3 | import "testing" 4 | 5 | func TestSocketKey(t *testing.T) { 6 | SocketKey(4096) 7 | } 8 | 9 | func BenchmarkSocketKey32(b *testing.B) { 10 | b.ReportAllocs() 11 | for i := 0; i < b.N; i++ { 12 | SocketKey(32) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/deadlock/x02/poc_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package x02 4 | 5 | import ( 6 | "log" 7 | "testing" 8 | ) 9 | 10 | func TestPOC(t *testing.T) { 11 | defer func() { 12 | if err := recover(); err != nil { 13 | log.Println("panic occurred:", err) 14 | } 15 | }() 16 | 17 | POC() 18 | } 19 | -------------------------------------------------------------------------------- /core/auxiliary.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | var ( 8 | // Ascertain and store the system memory page size. 9 | pageSize = os.Getpagesize() 10 | ) 11 | 12 | // Round a length to a multiple of the system page size. 13 | func roundToPageSize(length int) int { 14 | return (length + (pageSize - 1)) & (^(pageSize - 1)) 15 | } 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Entries should be added alphabetically in the form: 2 | # Name or Organization 3 | 4 | Andrew LeFevre 5 | Awn Umar 6 | Carlo Alberto Ferraris 7 | dotcppfile 8 | Fedor Korotkov 9 | Jam Adams 10 | Joseph Richey 11 | Neven Sajko 12 | Paul Zeinlinger 13 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/auxiliary_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestRoundToPageSize(t *testing.T) { 9 | fmt.Println("System page size:", pageSize, "bytes") 10 | 11 | if roundToPageSize(0) != 0 { 12 | t.Error("failed with test input 0") 13 | } 14 | if roundToPageSize(1) != pageSize { 15 | t.Error("failed with test input 1") 16 | } 17 | if roundToPageSize(pageSize) != pageSize { 18 | t.Error("failed with test input page_size") 19 | } 20 | if roundToPageSize(pageSize+1) != 2*pageSize { 21 | t.Error("failed with test input page_size + 1") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/deadlock/x02/poc.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package x02 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/awnumar/memguard" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func POC() { 13 | key := memguard.NewEnclaveRandom(32) 14 | 15 | var oldLimit unix.Rlimit 16 | zeroLimit := unix.Rlimit{Cur: 0, Max: oldLimit.Max} 17 | if err := unix.Prlimit(0, unix.RLIMIT_MEMLOCK, &zeroLimit, &oldLimit); err != nil { 18 | panic(fmt.Errorf("error lowering memlock rlimit: %s", err)) 19 | } 20 | 21 | keyBytes, err := key.Open() 22 | if err != nil { 23 | panic(err) 24 | } 25 | defer keyBytes.Destroy() 26 | } 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/awnumar/memcall v0.4.0 h1:B7hgZYdfH6Ot1Goaz8jGne/7i8xD4taZie/PNSFZ29g= 2 | github.com/awnumar/memcall v0.4.0/go.mod h1:8xOx1YbfyuCg3Fy6TO8DK0kZUua3V42/goA5Ru47E8w= 3 | golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 4 | golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 5 | golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 6 | golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 7 | lukechampine.com/frand v1.5.1 h1:fg0eRtdmGFIxhP5zQJzM1lFDbD6CUfu/f+7WgAZd5/w= 8 | lukechampine.com/frand v1.5.1/go.mod h1:4VstaWc2plN4Mjr10chUD46RAVGWhpkZ5Nja8+Azp0Q= 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Proposal 3 | about: Suggest an idea for this project 4 | labels: proposal 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: bug 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behaviour: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behaviour** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **System (please complete the following information):** 24 | - OS and Kernel Versions: 25 | - Memguard Version: 26 | - Go Version: 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /memguard_test.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/awnumar/memguard/core" 8 | ) 9 | 10 | func TestScrambleBytes(t *testing.T) { 11 | buf := make([]byte, 32) 12 | ScrambleBytes(buf) 13 | if bytes.Equal(buf, make([]byte, 32)) { 14 | t.Error("buffer not scrambled") 15 | } 16 | } 17 | 18 | func TestWipeBytes(t *testing.T) { 19 | buf := make([]byte, 32) 20 | ScrambleBytes(buf) 21 | WipeBytes(buf) 22 | if !bytes.Equal(buf, make([]byte, 32)) { 23 | t.Error("buffer not wiped") 24 | } 25 | } 26 | 27 | func TestPurge(t *testing.T) { 28 | key := NewEnclaveRandom(32) 29 | buf, err := key.Open() 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | Purge() 34 | if buf.IsAlive() { 35 | t.Error("buffer not destroyed") 36 | } 37 | buf, err = key.Open() 38 | if err != core.ErrDecryptionFailed { 39 | t.Error(buf.Bytes(), err) 40 | } 41 | if buf != nil { 42 | t.Error("buffer not nil:", buf) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /memguard.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "github.com/awnumar/memguard/core" 5 | ) 6 | 7 | /* Enhancement: check for low memory locking limit and print warning?*/ 8 | 9 | /* 10 | ScrambleBytes overwrites an arbitrary buffer with cryptographically-secure random bytes. 11 | */ 12 | func ScrambleBytes(buf []byte) { 13 | if err := core.Scramble(buf); err != nil { 14 | core.Panic(err) 15 | } 16 | } 17 | 18 | /* 19 | WipeBytes overwrites an arbitrary buffer with zeroes. 20 | */ 21 | func WipeBytes(buf []byte) { 22 | core.Wipe(buf) 23 | } 24 | 25 | /* 26 | Purge resets the session key to a fresh value and destroys all existing LockedBuffers. Existing Enclave objects will no longer be decryptable. 27 | */ 28 | func Purge() { 29 | core.Purge() 30 | } 31 | 32 | /* 33 | SafePanic wipes all it can before calling panic(v). 34 | */ 35 | func SafePanic(v interface{}) { 36 | core.Panic(v) 37 | } 38 | 39 | /* 40 | SafeExit destroys everything sensitive before exiting with a specified status code. 41 | */ 42 | func SafeExit(c int) { 43 | core.Exit(c) 44 | } 45 | -------------------------------------------------------------------------------- /examples/stdin/stdin.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Awn Umar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package stdin 18 | 19 | import ( 20 | "errors" 21 | "os" 22 | 23 | "github.com/awnumar/memguard" 24 | ) 25 | 26 | // ReadKeyFromStdin reads a key from standard inputs and returns it sealed inside an Enclave object. 27 | func ReadKeyFromStdin() (*memguard.Enclave, error) { 28 | key, err := memguard.NewBufferFromReaderUntil(os.Stdin, '\n') 29 | if err != nil { 30 | // error encountered before '\n' was reached 31 | return nil, err 32 | } 33 | if key.Size() == 0 { 34 | return nil, errors.New("no input received") 35 | } 36 | return key.Seal(), nil 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | linux: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version: 'stable' 18 | - name: Build 19 | run: | 20 | go version 21 | go get ./... 22 | go build -race -v ./... 23 | - name: Test 24 | run: go test -race -v ./... 25 | - name: Bench 26 | run: go test -run=XXX -bench=. ./... 27 | 28 | macos: 29 | runs-on: macos-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Set up Go 33 | uses: actions/setup-go@v5 34 | with: 35 | go-version: 'stable' 36 | - name: Build 37 | run: | 38 | go version 39 | go get ./... 40 | go build -race -v ./... 41 | - name: Test 42 | run: go test -race -v ./... 43 | - name: Bench 44 | run: go test -run=XXX -bench=. ./... 45 | 46 | windows: 47 | runs-on: windows-latest 48 | steps: 49 | - uses: actions/checkout@v4 50 | - name: Set up Go 51 | uses: actions/setup-go@v5 52 | with: 53 | go-version: 'stable' 54 | - name: Install MinGW (for gcc) 55 | run: choco install -y mingw 56 | - name: Build 57 | run: | 58 | go version 59 | go get ./... 60 | $env:CGO_ENABLED=1; go build -race -v ./... 61 | - name: Test 62 | run: | 63 | $env:CGO_ENABLED=1; go test -race -v ./... 64 | - name: Bench 65 | run: go test -run=XXX -bench=. ./... 66 | -------------------------------------------------------------------------------- /examples/deadlock/x01/poc.go: -------------------------------------------------------------------------------- 1 | package x01 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/rand" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "time" 11 | 12 | "github.com/awnumar/memguard" 13 | "lukechampine.com/frand" 14 | ) 15 | 16 | func OpenEnclave(ctx context.Context) { 17 | n := 10 18 | data := make([][]byte, n) 19 | enclaves := make([]*memguard.Enclave, n) 20 | for i := range data { 21 | data[i] = make([]byte, 32) 22 | buf := make([]byte, 32) 23 | if _, err := io.ReadFull(rand.Reader, buf); err != nil { 24 | panic("failed to read random data") 25 | } 26 | copy(data[i], buf) 27 | enclaves[i] = memguard.NewEnclave(buf) 28 | } 29 | 30 | threads := 20 31 | for i := 0; i < threads; i++ { 32 | go func(ctx context.Context) { 33 | for { 34 | select { 35 | case <-ctx.Done(): 36 | return 37 | default: 38 | j := frand.Intn(n) 39 | immediateOpen(ctx, enclaves[j], data[j]) 40 | } 41 | } 42 | }(ctx) 43 | } 44 | <-ctx.Done() 45 | time.Sleep(time.Second) 46 | 47 | // buf := make([]byte, 1<<20) 48 | // fmt.Println(string(buf[:runtime.Stack(buf, true)])) 49 | } 50 | 51 | func openVerify(lock *memguard.Enclave, exp []byte) error { 52 | lb, err := lock.Open() 53 | if err != nil { 54 | return err 55 | } 56 | defer lb.Destroy() 57 | if !bytes.Equal(lb.Bytes(), exp) { 58 | fmt.Println(lb.Bytes(), exp) 59 | return errors.New("open verify fail") 60 | } 61 | return nil 62 | } 63 | 64 | func immediateOpen(ctx context.Context, lock *memguard.Enclave, exp []byte) { 65 | c1 := make(chan error, 1) 66 | go func() { 67 | err := openVerify(lock, exp) 68 | c1 <- err 69 | }() 70 | 71 | select { 72 | case err := <-c1: 73 | if err != nil { 74 | panic(err) 75 | } 76 | case <-ctx.Done(): 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /enclave.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "github.com/awnumar/memguard/core" 5 | ) 6 | 7 | /* 8 | Enclave is a sealed and encrypted container for sensitive data. 9 | */ 10 | type Enclave struct { 11 | *core.Enclave 12 | } 13 | 14 | /* 15 | NewEnclave seals up some data into an encrypted enclave object. The buffer is wiped after the data is copied. If the length of the buffer is zero, the function will return nil. 16 | 17 | A LockedBuffer may alternatively be converted into an Enclave object using its Seal method. This will also have the effect of destroying the LockedBuffer. 18 | */ 19 | func NewEnclave(src []byte) *Enclave { 20 | e, err := core.NewEnclave(src) 21 | if err != nil { 22 | if err == core.ErrNullEnclave { 23 | return nil 24 | } 25 | core.Panic(err) 26 | } 27 | return &Enclave{e} 28 | } 29 | 30 | /* 31 | NewEnclaveRandom generates and seals arbitrary amounts of cryptographically-secure random bytes into an encrypted enclave object. If size is not strictly positive the function will return nil. 32 | */ 33 | func NewEnclaveRandom(size int) *Enclave { 34 | // todo: stream data into enclave 35 | b := NewBufferRandom(size) 36 | return b.Seal() 37 | } 38 | 39 | /* 40 | Open decrypts an Enclave object and places its contents into an immutable LockedBuffer. An error will be returned if decryption failed. 41 | */ 42 | func (e *Enclave) Open() (*LockedBuffer, error) { 43 | b, err := core.Open(e.Enclave) 44 | if err != nil { 45 | if err != core.ErrDecryptionFailed { 46 | core.Panic(err) 47 | } 48 | return nil, err 49 | } 50 | b.Freeze() 51 | return newBuffer(b), nil 52 | } 53 | 54 | /* 55 | Size returns the number of bytes of data stored within an Enclave. 56 | */ 57 | func (e *Enclave) Size() int { 58 | return core.EnclaveSize(e.Enclave) 59 | } 60 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | In this directory are some useful and interesting code samples both for writing effective code using the library and for learning or exploratory purposes. Exploits and proof-of-concepts are also welcome. 4 | 5 | Packages are able to import one another. This allows us to build up modules of functionality that work together to create more complex systems. 6 | 7 | ## Adding a package 8 | 9 | 1. Create a directory for your program and populate it with code. Check an existing module for guidance if needed. 10 | 11 | 2. Add test code and benchmarks. 12 | 13 | The programs can then be run individually with 14 | 15 | ```bash 16 | go test -v -race ./examples/module_name 17 | ``` 18 | 19 | or one after the other 20 | 21 | ```bash 22 | go test -v -race ./examples/... 23 | ``` 24 | 25 | 3. Add your program to the end of the [packages](#packages) section of this document. 26 | 27 | ## Licencing 28 | 29 | You own your intellectual property and so you are free to choose any licence for your program. To do this, add a licence header to the top of your source files. 30 | 31 | ## Packages 32 | 33 | 0. [`Apache-2.0`] [socketkey](socketkey) :: Streaming multi-threaded client->server transfer of secure data over a socket. 34 | 1. [`Apache-2.0`] [casting](casting) :: Some examples of representing the data in allocated buffers as different types. 35 | 2. [`Apache-2.0`] [stdin](stdin) :: Reading from standard input directly into a guarded memory region and then sealing it. 36 | 3. [`Apache-2.0`] [stream](stream) :: Some examples of working with Stream objects which encrypt data in memory. 37 | 4. [[`#132`](https://github.com/awnumar/memguard/issues/132)] [deadlock](deadlock) :: Some conditions causing crashes. 38 | 5. [`Apache-2.0`] [streams](streams) :: Multi-threaded test of streams objects. 39 | -------------------------------------------------------------------------------- /signals.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "sync" 7 | 8 | "github.com/awnumar/memguard/core" 9 | ) 10 | 11 | var ( 12 | // Ensure we only start a single signal handling instance 13 | create sync.Once 14 | 15 | // Channel for updating the signal handler 16 | sigfunc = make(chan func(os.Signal), 1) 17 | 18 | // Channel that caught signals are sent to by the runtime 19 | listener = make(chan os.Signal, 4) 20 | ) 21 | 22 | /* 23 | CatchSignal assigns a given function to be run in the event of a signal being received by the process. If no signals are provided all signals will be caught. 24 | 25 | 1. Signal is caught by the process 26 | 2. Interrupt handler is executed 27 | 3. Secure session state is wiped 28 | 4. Process terminates with exit code 1 29 | 30 | This function can be called multiple times with the effect that only the last call will have any effect. 31 | */ 32 | func CatchSignal(f func(os.Signal), signals ...os.Signal) { 33 | create.Do(func() { 34 | // Start a goroutine to listen on the channels. 35 | go func() { 36 | var handler func(os.Signal) 37 | for { 38 | select { 39 | case signal := <-listener: 40 | handler(signal) 41 | core.Exit(1) 42 | case handler = <-sigfunc: 43 | } 44 | } 45 | }() 46 | }) 47 | 48 | // Update the handler function. 49 | sigfunc <- f 50 | 51 | // Notify the channel if we receive a signal. 52 | signal.Reset() 53 | signal.Notify(listener, signals...) 54 | } 55 | 56 | /* 57 | CatchInterrupt is a wrapper around CatchSignal that makes it easy to safely handle receiving interrupt signals. If an interrupt is received, the process will wipe sensitive data in memory before terminating. 58 | 59 | A subsequent call to CatchSignal will override this call. 60 | */ 61 | func CatchInterrupt() { 62 | CatchSignal(func(_ os.Signal) {}, os.Interrupt) 63 | } 64 | -------------------------------------------------------------------------------- /examples/casting/casting_test.go: -------------------------------------------------------------------------------- 1 | package casting 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "unsafe" 7 | 8 | "github.com/awnumar/memguard" 9 | ) 10 | 11 | func TestByteArray10(t *testing.T) { 12 | b, a := ByteArray10() 13 | memguard.ScrambleBytes(a[:]) 14 | if !bytes.Equal(b.Bytes(), a[:]) { 15 | t.Error("array describes incorrect memory region") 16 | } 17 | b.Destroy() 18 | } 19 | 20 | func TestUint64Array4(t *testing.T) { 21 | b, a := Uint64Array4() 22 | if uintptr(unsafe.Pointer(&b.Bytes()[0])) != uintptr(unsafe.Pointer(&a[0])) { 23 | t.Error("start pointer does not match") 24 | } 25 | b.Bytes()[24] = 1 26 | if a[3] != 1 { 27 | t.Error("incorrect alignment", b.Bytes(), a) 28 | } 29 | b.Destroy() 30 | } 31 | 32 | func testSecureStruct(b *memguard.LockedBuffer, s *Secure, offset int, t *testing.T) { 33 | if uintptr(unsafe.Pointer(&b.Bytes()[offset])) != uintptr(unsafe.Pointer(s)) { 34 | t.Error("pointers don't match") 35 | } 36 | memguard.ScrambleBytes(b.Bytes()[offset : offset+32]) 37 | if !bytes.Equal(b.Bytes()[offset:offset+32], s.Key[:]) { 38 | t.Error("key doesn't match") 39 | } 40 | b.Bytes()[offset+32] = 1 41 | b.Bytes()[offset+40] = 1 42 | if s.Salt[0] != 1 || s.Salt[1] != 1 { 43 | t.Error("salt doesn't match") 44 | } 45 | b.Bytes()[offset+48] = 1 46 | if s.Counter != 1 { 47 | t.Error("counter doesn't match") 48 | } 49 | b.Bytes()[offset+56] = 1 50 | if !s.Something { 51 | t.Error("bool flag Something doesn't match") 52 | } 53 | } 54 | 55 | func TestSecureStruct(t *testing.T) { 56 | b, s := SecureStruct() 57 | testSecureStruct(b, s, 0, t) 58 | b.Destroy() 59 | } 60 | 61 | func TestSecureStructArray(t *testing.T) { 62 | b, a := SecureStructArray() 63 | testSecureStruct(b, &a[0], 0, t) 64 | testSecureStruct(b, &a[1], 64, t) 65 | b.Destroy() 66 | } 67 | 68 | func TestSecureStructSlice(t *testing.T) { 69 | b, s := SecureStructSlice(3) 70 | testSecureStruct(b, &s[0], 0, t) 71 | testSecureStruct(b, &s[1], 64, t) 72 | testSecureStruct(b, &s[2], 128, t) 73 | b.Destroy() 74 | } 75 | -------------------------------------------------------------------------------- /enclave_test.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/awnumar/memguard/core" 8 | ) 9 | 10 | func TestNewEnclave(t *testing.T) { 11 | e := NewEnclave([]byte("yellow submarine")) 12 | if e == nil { 13 | t.Error("got nil enclave") 14 | } 15 | data, err := e.Open() 16 | if err != nil { 17 | t.Error("unexpected error:", err) 18 | } 19 | if !bytes.Equal(data.Bytes(), []byte("yellow submarine")) { 20 | t.Error("data doesn't match input") 21 | } 22 | data.Destroy() 23 | e = NewEnclave([]byte{}) 24 | if e != nil { 25 | t.Error("enclave should be nil") 26 | } 27 | } 28 | 29 | func TestNewEnclaveRandom(t *testing.T) { 30 | e := NewEnclaveRandom(32) 31 | if e == nil { 32 | t.Error("got nil enclave") 33 | } 34 | data, err := e.Open() 35 | if err != nil { 36 | t.Error("unexpected error:", err) 37 | } 38 | if len(data.Bytes()) != 32 || cap(data.Bytes()) != 32 { 39 | t.Error("buffer sizes incorrect") 40 | } 41 | if bytes.Equal(data.Bytes(), make([]byte, 32)) { 42 | t.Error("buffer not randomised") 43 | } 44 | data.Destroy() 45 | e = NewEnclaveRandom(0) 46 | if e != nil { 47 | t.Error("should be nil") 48 | } 49 | } 50 | 51 | func TestOpen(t *testing.T) { 52 | e := NewEnclave([]byte("yellow submarine")) 53 | if e == nil { 54 | t.Error("got nil enclave") 55 | } 56 | b, err := e.Open() 57 | if err != nil { 58 | t.Error("unexpected error;", err) 59 | } 60 | if b == nil { 61 | t.Error("buffer should not be nil") 62 | } 63 | if e.Size() != b.Size() { 64 | t.Error("sizes don't match") 65 | } 66 | if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) { 67 | t.Error("data does not match") 68 | } 69 | Purge() // reset the session 70 | b, err = e.Open() 71 | if err != core.ErrDecryptionFailed { 72 | t.Error("expected decryption error; got", err) 73 | } 74 | if b != nil { 75 | t.Error("buffer should be nil") 76 | } 77 | e = NewEnclaveRandom(0) 78 | if !panics(func() { 79 | e.Open() 80 | }) { 81 | t.Error("func should panic on nil enclave") 82 | } 83 | } 84 | 85 | func panics(fn func()) (panicked bool) { 86 | defer func() { 87 | panicked = (recover() != nil) 88 | }() 89 | fn() 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /examples/stream/stream.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Awn Umar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package stream 18 | 19 | import ( 20 | "io" 21 | "os" 22 | 23 | "github.com/awnumar/memguard" 24 | ) 25 | 26 | // SlowRandByte writes 16KiB of random data to a stream and then operates on it in chunks, returning a random number between 0 and 255. 27 | func SlowRandByte() byte { 28 | // Get 16KiB bytes of random data. 29 | // In the real world we might be reading from a socket instead. 30 | // Also we are free to write data in arbitrarily sized chunks. 31 | data := memguard.NewBufferRandom(1024 * 16) 32 | data.Melt() // Allow mutation so stream writer can wipe source buffer. 33 | 34 | // Create a stream object. 35 | s := memguard.NewStream() // Implements io.Reader and io.Writer interfaces. 36 | 37 | // Write the data to it. 38 | _, _ = s.Write(data.Bytes()) // Should never error or write less data. 39 | data.Destroy() // No longer need the source buffer. (Has been wiped.) 40 | 41 | // Create a buffer to work on this data in chunks. 42 | buf := memguard.NewBuffer(os.Getpagesize()) 43 | defer buf.Destroy() 44 | 45 | // Read the data back in chunks. 46 | var parity byte 47 | for { 48 | n, err := s.Read(buf.Bytes()) // Reads directly into guarded allocation. 49 | if err != nil { 50 | if err == io.EOF { 51 | break // end of data 52 | } 53 | memguard.SafePanic(err) // other error 54 | } 55 | 56 | // Do some example computation on this data. 57 | for i := 0; i < n; i++ { 58 | parity = parity ^ buf.Bytes()[i] 59 | } 60 | } 61 | 62 | // Return the result. 63 | return parity 64 | } 65 | -------------------------------------------------------------------------------- /core/exit_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestPurge(t *testing.T) { 9 | // Create a bunch of things to simulate a working environment. 10 | enclave, err := NewEnclave([]byte("yellow submarine")) 11 | if err != nil { 12 | t.Error(err) 13 | } 14 | buffer, err := NewBuffer(32) 15 | if err != nil { 16 | t.Error(err) 17 | } 18 | 19 | oldKey := getOrCreateKey() 20 | Purge() 21 | key := getOrCreateKey() 22 | 23 | // Verify that the buffers list contains only the important buffers. 24 | buffers.RLock() 25 | if len(buffers.list) != 3 { 26 | t.Error("buffers list was not flushed", buffers.list) 27 | } 28 | for i := range buffers.list { 29 | if !buffers.list[i].Alive() { 30 | t.Error("should not have destroyed excluded buffers") 31 | } 32 | } 33 | if !key.right.Alive() || !key.left.Alive() || !key.rand.Alive() { 34 | t.Error("buffers left in list aren't the right ones") 35 | } 36 | buffers.RUnlock() 37 | 38 | // Verify that the buffer was destroyed. 39 | if buffer.alive { 40 | t.Error("buffer was not destroyed") 41 | } 42 | 43 | // Verify that the old key was destroyed. 44 | if oldKey.left.alive || oldKey.right.alive { 45 | t.Error("old key was not destroyed") 46 | } 47 | 48 | // Verify that the new key is not destroyed. 49 | if !key.left.alive || !key.right.alive { 50 | t.Error("current key is destroyed") 51 | } 52 | 53 | // Verify that the key changed by decrypting the Enclave. 54 | if _, err := Open(enclave); err != ErrDecryptionFailed { 55 | t.Error("expected decryption failed; got", err) 56 | } 57 | 58 | // Create a buffer with invalid canary. 59 | b, err := NewBuffer(32) 60 | if err != nil { 61 | t.Error(err) 62 | } 63 | Scramble(b.inner) 64 | b.Freeze() 65 | if !panics(func() { 66 | Purge() 67 | }) { 68 | t.Error("did not panic") 69 | } 70 | if !bytes.Equal(b.data, make([]byte, 32)) { 71 | t.Error("data not wiped") 72 | } 73 | buffers.remove(b) 74 | } 75 | 76 | func TestPanic(t *testing.T) { 77 | // Call Panic and check if it panics. 78 | if !panics(func() { 79 | Panic("test") 80 | }) { 81 | t.Error("did not panic") 82 | } 83 | } 84 | 85 | func panics(fn func()) (panicked bool) { 86 | defer func() { 87 | panicked = (recover() != nil) 88 | }() 89 | fn() 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /signals_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package memguard 5 | 6 | import ( 7 | "net" 8 | "os" 9 | "os/exec" 10 | "testing" 11 | ) 12 | 13 | func TestCatchSignal(t *testing.T) { 14 | // If we're within the testing subprocess, run test. 15 | if os.Getenv("WITHIN_SUBPROCESS") == "1" { 16 | // Start a listener object 17 | listener, err := net.Listen("tcp", "127.0.0.1:") 18 | if err != nil { 19 | SafePanic(err) 20 | } 21 | defer listener.Close() 22 | 23 | // Spawn a handler to catch interrupts 24 | CatchSignal(func(s os.Signal) { 25 | listener.Close() 26 | }) 27 | 28 | // Grab a handle on the running process 29 | process, err := os.FindProcess(os.Getpid()) 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | 34 | // Send it an interrupt signal 35 | if err := process.Signal(os.Interrupt); err != nil { 36 | t.Error(err) 37 | } 38 | } 39 | 40 | // Construct the subprocess with its initial state 41 | cmd := exec.Command(os.Args[0], "-test.run=TestCatchSignal") 42 | cmd.Env = append(os.Environ(), "WITHIN_SUBPROCESS=1") 43 | 44 | // Execute the subprocess and inspect its exit code 45 | err := cmd.Run().(*exec.ExitError) 46 | if err.ExitCode() != 1 { 47 | // if exit code is -1 it was likely killed by the signal 48 | t.Error("Wanted exit code 1, got", err.ExitCode(), "err:", err) 49 | } 50 | 51 | // Todo: catch this violation (segfault) 52 | // 53 | // b := NewBuffer(32) 54 | // bA := (*[64]byte)(unsafe.Pointer(&b.Bytes()[0])) 55 | // bA[42] = 0x69 // write to guard page region 56 | } 57 | 58 | func TestCatchInterrupt(t *testing.T) { 59 | if os.Getenv("WITHIN_SUBPROCESS") == "1" { 60 | // Start the interrupt handler 61 | CatchInterrupt() 62 | 63 | // Grab a handle on the running process 64 | process, err := os.FindProcess(os.Getpid()) 65 | if err != nil { 66 | t.Error(err) 67 | } 68 | 69 | // Send it an interrupt signal 70 | if err := process.Signal(os.Interrupt); err != nil { 71 | t.Error(err) 72 | } 73 | } 74 | 75 | // Construct the subprocess with its initial state 76 | cmd := exec.Command(os.Args[0], "-test.run=TestCatchInterrupt") 77 | cmd.Env = append(os.Environ(), "WITHIN_SUBPROCESS=1") 78 | 79 | // Execute the subprocess and inspect its exit code 80 | err := cmd.Run().(*exec.ExitError) 81 | if err.ExitCode() != 1 { 82 | // if exit code is -1 it was likely killed by the signal 83 | t.Error("Wanted exit code 1, got", err.ExitCode(), "err:", err) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /core/exit.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/awnumar/memcall" 8 | ) 9 | 10 | /* 11 | Purge wipes all sensitive data and keys before reinitialising the session with a fresh encryption key and secure values. Subsequent library operations will use these fresh values and the old data is assumed to be practically unrecoverable. 12 | 13 | The creation of new Enclave objects should wait for this function to return since subsequent Enclave objects will use the newly created key. 14 | 15 | This function should be called before the program terminates, or else the provided Exit or Panic functions should be used to terminate. 16 | */ 17 | func Purge() { 18 | var opErr error 19 | 20 | func() { 21 | // Halt the re-key cycle and prevent new enclaves or keys being created. 22 | keyMtx.Lock() 23 | defer keyMtx.Unlock() 24 | if !key.Destroyed() { 25 | key.Lock() 26 | defer key.Unlock() 27 | } 28 | 29 | // Get a snapshot of existing Buffers. 30 | snapshot := buffers.flush() 31 | 32 | // Destroy them, performing the usual sanity checks. 33 | for _, b := range snapshot { 34 | if err := b.destroy(); err != nil { 35 | if opErr == nil { 36 | opErr = err 37 | } else { 38 | opErr = fmt.Errorf("%s; %s", opErr.Error(), err.Error()) 39 | } 40 | // buffer destroy failed; wipe instead 41 | b.Lock() 42 | defer b.Unlock() 43 | if !b.mutable { 44 | if err := memcall.Protect(b.inner, memcall.ReadWrite()); err != nil { 45 | // couldn't change it to mutable; we can't wipe it! (could this happen?) 46 | // not sure what we can do at this point, just warn and move on 47 | fmt.Fprintf(os.Stderr, "!WARNING: failed to wipe immutable data at address %p", &b.data) 48 | continue // wipe in subprocess? 49 | } 50 | } 51 | Wipe(b.data) 52 | } 53 | } 54 | }() 55 | 56 | // If we encountered an error, panic. 57 | if opErr != nil { 58 | panic(opErr) 59 | } 60 | } 61 | 62 | /* 63 | Exit terminates the process with a specified exit code but securely wipes and cleans up sensitive data before doing so. 64 | */ 65 | func Exit(c int) { 66 | // Wipe the encryption key used to encrypt data inside Enclaves. 67 | getKey().Destroy() 68 | 69 | // Get a snapshot of existing Buffers. 70 | snapshot := buffers.copy() // copy ensures the buffers stay in the list until they are destroyed. 71 | 72 | // Destroy them, performing the usual sanity checks. 73 | for _, b := range snapshot { 74 | b.Destroy() 75 | } 76 | 77 | // Exit with the specified exit code. 78 | os.Exit(c) 79 | } 80 | 81 | /* 82 | Panic is identical to the builtin panic except it purges the session before calling panic. 83 | */ 84 | func Panic(v interface{}) { 85 | Purge() // creates a new key so it is safe to recover from this panic 86 | panic(v) 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

MemGuard

4 |

Software enclave for storage of sensitive information in memory.

5 |

6 | 7 | 8 |

9 |

10 | 11 | --- 12 | 13 | This package attempts to reduce the likelihood of sensitive data being exposed when in memory. It aims to support all major operating systems and is written in pure Go. 14 | 15 | ## Features 16 | 17 | * Sensitive data is encrypted and authenticated in memory with XSalsa20Poly1305. The [scheme](https://spacetime.dev/encrypting-secrets-in-memory) used also [defends against cold-boot attacks](https://spacetime.dev/memory-retention-attacks). 18 | * Memory allocation bypasses the language runtime by [using system calls](https://github.com/awnumar/memcall) to query the kernel for resources directly. This avoids interference from the garbage-collector. 19 | * Buffers that store plaintext data are fortified with guard pages and canary values to detect spurious accesses and overflows. 20 | * Effort is taken to prevent sensitive data from touching the disk. This includes locking memory to prevent swapping and handling core dumps. 21 | * Kernel-level immutability is implemented so that attempted modification of protected regions results in an access violation. 22 | * Multiple endpoints provide session purging and safe termination capabilities as well as signal handling to prevent remnant data being left behind. 23 | * Side-channel attacks are mitigated against by making sure that the copying and comparison of data is done in constant-time. 24 | 25 | Some features were inspired by [libsodium](https://github.com/jedisct1/libsodium), so credits to them. 26 | 27 | Full documentation and a complete overview of the API can be found [here](https://godoc.org/github.com/awnumar/memguard). Interesting and useful code samples can be found within the [examples](examples) subpackage. 28 | 29 | ## Installation 30 | 31 | ``` 32 | $ go get github.com/awnumar/memguard 33 | ``` 34 | 35 | API is experimental and may have unstable changes. You should pin a version. [[modules](https://github.com/golang/go/wiki/Modules)] 36 | 37 | ## Contributing 38 | 39 | * Submitting program samples to [`./examples`](examples). 40 | * Reporting bugs, vulnerabilities, and any difficulties in using the API. 41 | * Writing useful security and crypto libraries that utilise memguard. 42 | * Implementing kernel-specific/cpu-specific protections. 43 | * Submitting performance improvements. 44 | 45 | Issues are for reporting bugs and for discussion on proposals. Pull requests should be made against master. 46 | -------------------------------------------------------------------------------- /core/enclave_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestNewEnclave(t *testing.T) { 9 | // Initialise some sample plaintext. 10 | data := []byte("yellow submarine") 11 | 12 | // Create the Enclave object from this data. 13 | e, err := NewEnclave(data) 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | 18 | // Check that the buffer has been wiped. 19 | if !bytes.Equal(data, make([]byte, 16)) { 20 | t.Error("data buffer was not wiped") 21 | } 22 | 23 | // Verify the length of the ciphertext is correct. 24 | if len(e.ciphertext) != len(data)+Overhead { 25 | t.Error("ciphertext has unexpected length;", len(e.ciphertext)) 26 | } 27 | 28 | // Attempt with an empty data slice. 29 | data = make([]byte, 0) 30 | _, err = NewEnclave(data) 31 | if err != ErrNullEnclave { 32 | t.Error("expected ErrNullEnclave; got", err) 33 | } 34 | } 35 | 36 | func TestSeal(t *testing.T) { 37 | // Create a new buffer for testing with. 38 | b, err := NewBuffer(32) 39 | if err != nil { 40 | t.Error(err) 41 | } 42 | 43 | // Encrypt it into an Enclave. 44 | e, err := Seal(b) 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | 49 | // Do a sanity check on the length of the ciphertext. 50 | if len(e.ciphertext) != 32+Overhead { 51 | t.Error("ciphertext has unexpected length:", len(e.ciphertext)) 52 | } 53 | 54 | // Check that the buffer was destroyed. 55 | if b.alive { 56 | t.Error("buffer was not consumed") 57 | } 58 | 59 | // Decrypt the enclave into a new buffer. 60 | buf, err := Open(e) 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | 65 | // Check that the decrypted data is correct. 66 | if !bytes.Equal(buf.Data(), make([]byte, 32)) { 67 | t.Error("decrypted data does not match original") 68 | } 69 | 70 | // Attempt sealing the destroyed buffer. 71 | e, err = Seal(b) 72 | if err != ErrBufferExpired { 73 | t.Error("expected ErrBufferExpired; got", err) 74 | } 75 | if e != nil { 76 | t.Error("expected nil enclave in error case") 77 | } 78 | 79 | // Destroy the hanging buffer. 80 | buf.Destroy() 81 | } 82 | 83 | func TestOpen(t *testing.T) { 84 | // Initialise an enclave to test on. 85 | data := []byte("yellow submarine") 86 | e, err := NewEnclave(data) 87 | if err != nil { 88 | t.Error(err) 89 | } 90 | 91 | // Open it. 92 | buf, err := Open(e) 93 | if err != nil { 94 | t.Error(err) 95 | } 96 | 97 | // Sanity check the output. 98 | if !bytes.Equal(buf.Data(), []byte("yellow submarine")) { 99 | t.Error("decrypted data does not match original") 100 | } 101 | buf.Destroy() 102 | 103 | // Modify the ciphertext to trigger an error case. 104 | for i := range e.ciphertext { 105 | e.ciphertext[i] = 0xdb 106 | } 107 | 108 | // Check for the error. 109 | buf, err = Open(e) 110 | if err != ErrDecryptionFailed { 111 | t.Error("expected decryption error; got", err) 112 | } 113 | if buf != nil { 114 | t.Error("expected nil buffer in error case") 115 | } 116 | } 117 | 118 | func TestEnclaveSize(t *testing.T) { 119 | if EnclaveSize(&Enclave{make([]byte, 1234)}) != 1234-Overhead { 120 | t.Error("invalid enclave size") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /docs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package memguard implements a secure software enclave for the storage of sensitive information in memory. 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/awnumar/memguard" 11 | ) 12 | 13 | func main() { 14 | // Safely terminate in case of an interrupt signal 15 | memguard.CatchInterrupt() 16 | 17 | // Purge the session when we return 18 | defer memguard.Purge() 19 | 20 | // Generate a key sealed inside an encrypted container 21 | key := memguard.NewEnclaveRandom(32) 22 | 23 | // Passing the key off to another function 24 | key = invert(key) 25 | 26 | // Decrypt the result returned from invert 27 | keyBuf, err := key.Open() 28 | if err != nil { 29 | fmt.Fprintln(os.Stderr, err) 30 | return 31 | } 32 | defer keyBuf.Destroy() 33 | 34 | // Um output it 35 | fmt.Println(keyBuf.Bytes()) 36 | } 37 | 38 | func invert(key *memguard.Enclave) *memguard.Enclave { 39 | // Decrypt the key into a local copy 40 | b, err := key.Open() 41 | if err != nil { 42 | memguard.SafePanic(err) 43 | } 44 | defer b.Destroy() // Destroy the copy when we return 45 | 46 | // Open returns the data in an immutable buffer, so make it mutable 47 | b.Melt() 48 | 49 | // Set every element to its complement 50 | for i := range b.Bytes() { 51 | b.Bytes()[i] = ^b.Bytes()[i] 52 | } 53 | 54 | // Return the new data in encrypted form 55 | return b.Seal() // <- sealing also destroys b 56 | } 57 | 58 | There are two main container objects exposed in this API. Enclave objects encrypt data and store the ciphertext whereas LockedBuffers are more like guarded memory allocations. There is a limit on the maximum number of LockedBuffer objects that can exist at any one time, imposed by the system's mlock limits. There is no limit on Enclaves. 59 | 60 | The general workflow is to store sensitive information in Enclaves when it is not immediately needed and decrypt it when and where it is. After use, the LockedBuffer should be destroyed. 61 | 62 | If you need access to the data inside a LockedBuffer in a type not covered by any methods provided by this API, you can type-cast the allocation's memory to whatever type you want. 63 | 64 | key := memguard.NewBuffer(32) 65 | keyArrayPtr := (*[32]byte)(unsafe.Pointer(&key.Bytes()[0])) // do not dereference 66 | 67 | This is of course an unsafe operation and so care must be taken to ensure that the cast is valid and does not result in memory unsafety. Further examples of code and interesting use-cases can be found in the examples subpackage. 68 | 69 | Several functions exist to make the mass purging of data very easy. It is recommended to make use of them when appropriate. 70 | 71 | // Start an interrupt handler that will clean up memory before exiting 72 | memguard.CatchInterrupt() 73 | 74 | // Purge the session when returning from the main function of your program 75 | defer memguard.Purge() 76 | 77 | // Use the safe variants of exit functions provided in the stdlib 78 | memguard.SafeExit(1) 79 | memguard.SafePanic(err) 80 | 81 | // Destroy LockedBuffers as soon as possible after using them 82 | b, err := enclave.Open() 83 | if err != nil { 84 | memguard.SafePanic(err) 85 | } 86 | defer b.Destroy() 87 | 88 | Core dumps are disabled by default. If you absolutely require them, you can enable them by using unix.Setrlimit to set RLIMIT_CORE to an appropriate value. 89 | */ 90 | package memguard 91 | -------------------------------------------------------------------------------- /core/enclave.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | key = &Coffer{} 10 | keyMtx = sync.Mutex{} 11 | ) 12 | 13 | func getOrCreateKey() *Coffer { 14 | keyMtx.Lock() 15 | defer keyMtx.Unlock() 16 | 17 | if key.Destroyed() { 18 | key = NewCoffer() 19 | } 20 | 21 | return key 22 | } 23 | 24 | func getKey() *Coffer { 25 | keyMtx.Lock() 26 | defer keyMtx.Unlock() 27 | 28 | return key 29 | } 30 | 31 | // ErrNullEnclave is returned when attempting to construct an enclave of size less than one. 32 | var ErrNullEnclave = errors.New(" enclave size must be greater than zero") 33 | 34 | /* 35 | Enclave is a sealed and encrypted container for sensitive data. 36 | */ 37 | type Enclave struct { 38 | ciphertext []byte 39 | } 40 | 41 | /* 42 | NewEnclave is a raw constructor for the Enclave object. The given buffer is wiped after the enclave is created. 43 | */ 44 | func NewEnclave(buf []byte) (*Enclave, error) { 45 | // Return an error if length < 1. 46 | if len(buf) < 1 { 47 | return nil, ErrNullEnclave 48 | } 49 | 50 | // Create a new Enclave. 51 | e := new(Enclave) 52 | 53 | // Get a view of the key. 54 | k, err := getOrCreateKey().View() 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | // Encrypt the plaintext. 60 | e.ciphertext, err = Encrypt(buf, k.Data()) 61 | if err != nil { 62 | Panic(err) // key is not 32 bytes long 63 | } 64 | 65 | // Destroy our copy of the key. 66 | k.Destroy() 67 | 68 | // Wipe the given buffer. 69 | Wipe(buf) 70 | 71 | return e, nil 72 | } 73 | 74 | /* 75 | Seal consumes a given Buffer object and returns its data secured and encrypted inside an Enclave. The given Buffer is destroyed after the Enclave is created. 76 | */ 77 | func Seal(b *Buffer) (*Enclave, error) { 78 | // Check if the Buffer has been destroyed. 79 | if !b.Alive() { 80 | return nil, ErrBufferExpired 81 | } 82 | 83 | b.Melt() // Make the buffer mutable so that we can wipe it. 84 | 85 | // Construct the Enclave from the Buffer's data. 86 | e, err := func() (*Enclave, error) { 87 | b.RLock() // Attain a read lock. 88 | defer b.RUnlock() 89 | return NewEnclave(b.Data()) 90 | }() 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | // Destroy the Buffer object. 96 | b.Destroy() 97 | 98 | // Return the newly created Enclave. 99 | return e, nil 100 | } 101 | 102 | /* 103 | Open decrypts an Enclave and puts the contents into a Buffer object. The given Enclave is left untouched and may be reused. 104 | 105 | The Buffer object should be destroyed after the contents are no longer needed. 106 | */ 107 | func Open(e *Enclave) (*Buffer, error) { 108 | // Allocate a secure Buffer to hold the decrypted data. 109 | b, err := NewBuffer(len(e.ciphertext) - Overhead) 110 | if err != nil { 111 | Panic(" ciphertext has invalid length") // ciphertext has invalid length 112 | } 113 | 114 | // Grab a view of the key. 115 | k, err := getOrCreateKey().View() 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | // Decrypt the enclave into the buffer we created. 121 | _, err = Decrypt(e.ciphertext, k.Data(), b.Data()) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | // Destroy our copy of the key. 127 | k.Destroy() 128 | 129 | // Return the contents of the Enclave inside a Buffer. 130 | return b, nil 131 | } 132 | 133 | /* 134 | EnclaveSize returns the number of bytes of plaintext data stored inside an Enclave. 135 | */ 136 | func EnclaveSize(e *Enclave) int { 137 | return len(e.ciphertext) - Overhead 138 | } 139 | -------------------------------------------------------------------------------- /examples/socketkey/socketkey.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Awn Umar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package socketkey 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "net" 23 | "os" 24 | 25 | "github.com/awnumar/memguard" 26 | ) 27 | 28 | // Save the data here so we can compare it later. Obviously this leaks the secret. 29 | var data []byte 30 | 31 | // NOTE: Some lines are commented out for the sake of tests. 32 | 33 | /* 34 | SocketKey is a streaming multi-threaded client->server transfer of secure data over a socket. 35 | */ 36 | func SocketKey(size int) { 37 | // Create a server to listen on. 38 | listener, err := net.Listen("tcp", "127.0.0.1:4128") 39 | if err != nil { 40 | memguard.SafePanic(err) 41 | } 42 | defer listener.Close() 43 | 44 | // Catch signals and close the listener before terminating safely. 45 | memguard.CatchSignal(func(s os.Signal) { 46 | fmt.Println("Received signal:", s.String()) 47 | listener.Close() 48 | }, os.Interrupt, os.Kill) 49 | 50 | // Purge the session before returning. 51 | defer memguard.Purge() 52 | 53 | // Create a client to connect to our server. 54 | go func() { 55 | // Connect to our server 56 | addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:4128") 57 | if err != nil { 58 | memguard.SafePanic(err) 59 | } 60 | conn, err := net.DialTCP("tcp", nil, addr) 61 | if err != nil { 62 | memguard.SafePanic(err) 63 | } 64 | defer conn.Close() 65 | 66 | // Create a buffer filled with random bytes 67 | buf := memguard.NewBufferRandom(size) 68 | defer buf.Destroy() 69 | 70 | // Save a copy of the key for comparison later. 71 | data = make([]byte, buf.Size()) 72 | copy(data, buf.Bytes()) 73 | 74 | // fmt.Printf("Sending key: %#v\n", buf.Bytes()) 75 | 76 | // Send the data to the server 77 | var total, written int 78 | for total = 0; total < size; total += written { 79 | written, err = conn.Write(buf.Bytes()[total:]) 80 | if err != nil { 81 | memguard.SafePanic(err) 82 | } 83 | } 84 | }() 85 | 86 | // Accept connections from clients 87 | conn, err := listener.Accept() 88 | if err != nil { 89 | memguard.SafePanic(err) 90 | } 91 | 92 | // Read the data directly into a guarded memory region 93 | buf, err := memguard.NewBufferFromReader(conn, size) 94 | if err != nil { 95 | memguard.SafePanic(err) 96 | } 97 | defer buf.Destroy() 98 | conn.Close() 99 | 100 | // fmt.Printf("Received key: %#v\n", buf.Bytes()) 101 | 102 | // Compare the key to make sure it wasn't corrupted. 103 | if !bytes.Equal(data, buf.Bytes()) { 104 | memguard.SafePanic(fmt.Sprint("sent != received ::", data, buf.Bytes())) 105 | } 106 | 107 | // Seal the key into an encrypted Enclave object. 108 | key := buf.Seal() 109 | // <-- buf is destroyed by this point 110 | 111 | // fmt.Printf("Encrypted key: %#v\n", key) 112 | 113 | // Decrypt the key into a new buffer. 114 | buf, err = key.Open() 115 | if err != nil { 116 | memguard.SafePanic(err) 117 | } 118 | 119 | // fmt.Printf("Decrypted key: %#v\n", buf.Bytes()) 120 | 121 | // Destroy the buffer. 122 | buf.Destroy() 123 | } 124 | -------------------------------------------------------------------------------- /examples/casting/casting.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Awn Umar 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package casting 18 | 19 | import ( 20 | "unsafe" 21 | 22 | "github.com/awnumar/memguard" 23 | ) 24 | 25 | // Secure is some generic example struct containing sensitive information. 26 | type Secure struct { 27 | Key [32]byte 28 | Salt [2]uint64 29 | Counter uint64 30 | Something bool 31 | } 32 | 33 | // ByteArray10 allocates and returns a region of memory represented as a fixed-size 10 byte array. 34 | func ByteArray10() (*memguard.LockedBuffer, *[10]byte) { 35 | // Allocate 10 bytes of memory 36 | b := memguard.NewBuffer(10) 37 | 38 | // Return the LockedBuffer along with the cast pointer 39 | return b, (*[10]byte)(unsafe.Pointer(&b.Bytes()[0])) 40 | } 41 | 42 | // Uint64Array4 allocates a 32 byte memory region and returns it represented as a sequence of four unsigned 64 bit integer values. 43 | func Uint64Array4() (*memguard.LockedBuffer, *[4]uint64) { 44 | // Allocate the correct amount of memory 45 | b := memguard.NewBuffer(32) 46 | 47 | // Return the LockedBuffer along with the cast pointer 48 | return b, (*[4]uint64)(unsafe.Pointer(&b.Bytes()[0])) 49 | } 50 | 51 | // SecureStruct allocates a region of memory the size of a struct type and returns a pointer to that memory represented as that struct type. 52 | func SecureStruct() (*memguard.LockedBuffer, *Secure) { 53 | // Initialise an instance of the struct type 54 | s := new(Secure) 55 | 56 | // Allocate a LockedBuffer of the correct size 57 | b := memguard.NewBuffer(int(unsafe.Sizeof(*s))) 58 | 59 | // Return the LockedBuffer along with the initialised struct 60 | return b, (*Secure)(unsafe.Pointer(&b.Bytes()[0])) 61 | } 62 | 63 | // SecureStructArray allocates enough memory to hold an array of Secure structs and returns them. 64 | func SecureStructArray() (*memguard.LockedBuffer, *[2]Secure) { 65 | // Initialise an instance of the struct type 66 | s := new(Secure) 67 | 68 | // Allocate a LockedBuffer of four times the size of the struct type 69 | b := memguard.NewBuffer(int(unsafe.Sizeof(*s)) * 2) 70 | 71 | // Cast a pointer to the start of the memory into a pointer of a fixed size array of Secure structs of length four 72 | secureArray := (*[2]Secure)(unsafe.Pointer(&b.Bytes()[0])) 73 | 74 | // Return the LockedBuffer along with the array 75 | return b, secureArray 76 | } 77 | 78 | // SecureStructSlice takes a length and returns a slice of Secure struct values of that length. 79 | func SecureStructSlice(size int) (*memguard.LockedBuffer, []Secure) { 80 | if size < 1 { 81 | return nil, nil 82 | } 83 | 84 | // Initialise an instance of the struct type 85 | s := new(Secure) 86 | 87 | // Allocate the enough memory to store the struct values 88 | b := memguard.NewBuffer(int(unsafe.Sizeof(*s)) * size) 89 | 90 | // Construct the slice from its parameters 91 | var sl = struct { 92 | addr uintptr 93 | len int 94 | cap int 95 | }{uintptr(unsafe.Pointer(&b.Bytes()[0])), size, size} 96 | 97 | // Return the LockedBuffer along with the constructed slice 98 | return b, *(*[]Secure)(unsafe.Pointer(&sl)) 99 | } 100 | -------------------------------------------------------------------------------- /core/coffer.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Interval of time between each verify & re-key cycle. 10 | const interval = 500 * time.Millisecond 11 | 12 | // ErrCofferExpired is returned when a function attempts to perform an operation using a secure key container that has been wiped and destroyed. 13 | var ErrCofferExpired = errors.New(" attempted usage of destroyed key object") 14 | 15 | /* 16 | Coffer is a specialized container for securing highly-sensitive, 32 byte values. 17 | */ 18 | type Coffer struct { 19 | sync.Mutex 20 | 21 | left *Buffer 22 | right *Buffer 23 | 24 | rand *Buffer 25 | } 26 | 27 | // NewCoffer is a raw constructor for the *Coffer object. 28 | func NewCoffer() *Coffer { 29 | s := new(Coffer) 30 | s.left, _ = NewBuffer(32) 31 | s.right, _ = NewBuffer(32) 32 | s.rand, _ = NewBuffer(32) 33 | 34 | s.Init() 35 | 36 | go func(s *Coffer) { 37 | ticker := time.NewTicker(interval) 38 | 39 | for range ticker.C { 40 | if err := s.Rekey(); err != nil { 41 | break 42 | } 43 | } 44 | }(s) 45 | 46 | return s 47 | } 48 | 49 | // Init is used to reset the value stored inside a Coffer to a new random 32 byte value, overwriting the old. 50 | func (s *Coffer) Init() error { 51 | s.Lock() 52 | defer s.Unlock() 53 | if s.destroyed() { 54 | return ErrCofferExpired 55 | } 56 | 57 | if err := Scramble(s.left.Data()); err != nil { 58 | return err 59 | } 60 | if err := Scramble(s.right.Data()); err != nil { 61 | return err 62 | } 63 | 64 | // left = left XOR hash(right) 65 | hr := Hash(s.right.Data()) 66 | for i := range hr { 67 | s.left.Data()[i] ^= hr[i] 68 | } 69 | Wipe(hr) 70 | 71 | return nil 72 | } 73 | 74 | /* 75 | View returns a snapshot of the contents of a Coffer inside a Buffer. As usual the Buffer should be destroyed as soon as possible after use by calling the Destroy method. 76 | */ 77 | func (s *Coffer) View() (*Buffer, error) { 78 | s.Lock() 79 | defer s.Unlock() 80 | if s.destroyed() { 81 | return nil, ErrCofferExpired 82 | } 83 | b, _ := NewBuffer(32) 84 | 85 | // data = hash(right) XOR left 86 | h := Hash(s.right.Data()) 87 | 88 | for i := range b.Data() { 89 | b.Data()[i] = h[i] ^ s.left.Data()[i] 90 | } 91 | Wipe(h) 92 | 93 | return b, nil 94 | } 95 | 96 | /* 97 | Rekey is used to re-key a Coffer. Ideally this should be done at short, regular intervals. 98 | */ 99 | func (s *Coffer) Rekey() error { 100 | s.Lock() 101 | defer s.Unlock() 102 | if s.destroyed() { 103 | return ErrCofferExpired 104 | } 105 | 106 | if err := Scramble(s.rand.Data()); err != nil { 107 | return err 108 | } 109 | 110 | // Hash the current right partition for later. 111 | hashRightCurrent := Hash(s.right.Data()) 112 | 113 | // new_right = current_right XOR buf32 114 | for i := range s.right.Data() { 115 | s.right.Data()[i] ^= s.rand.Data()[i] 116 | } 117 | 118 | // new_left = current_left XOR hash(current_right) XOR hash(new_right) 119 | hashRightNew := Hash(s.right.Data()) 120 | for i := range s.left.Data() { 121 | s.left.Data()[i] ^= hashRightCurrent[i] ^ hashRightNew[i] 122 | } 123 | Wipe(hashRightNew) 124 | 125 | return nil 126 | } 127 | 128 | /* 129 | Destroy wipes and cleans up all memory related to a Coffer object. Once this method has been called, the Coffer can no longer be used and a new one should be created instead. 130 | */ 131 | func (s *Coffer) Destroy() error { 132 | s.Lock() 133 | defer s.Unlock() 134 | 135 | err1 := s.left.destroy() 136 | if err1 == nil { 137 | buffers.remove(s.left) 138 | } 139 | err2 := s.right.destroy() 140 | if err2 == nil { 141 | buffers.remove(s.right) 142 | } 143 | err3 := s.rand.destroy() 144 | if err3 == nil { 145 | buffers.remove(s.rand) 146 | } 147 | 148 | errS := "" 149 | if err1 != nil { 150 | errS = errS + err1.Error() + "\n" 151 | } 152 | if err2 != nil { 153 | errS = errS + err2.Error() + "\n" 154 | } 155 | if err3 != nil { 156 | errS = errS + err3.Error() + "\n" 157 | } 158 | if errS == "" { 159 | return nil 160 | } 161 | return errors.New(errS) 162 | } 163 | 164 | // Destroyed returns a boolean value indicating if a Coffer has been destroyed. 165 | func (s *Coffer) Destroyed() bool { 166 | if s == nil { 167 | return true 168 | } 169 | 170 | s.Lock() 171 | defer s.Unlock() 172 | 173 | return s.destroyed() 174 | } 175 | 176 | func (s *Coffer) destroyed() bool { 177 | if s.left == nil || s.right == nil { 178 | return true 179 | } 180 | 181 | return s.left.isDestroyed() || s.right.isDestroyed() 182 | } 183 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "container/list" 5 | "io" 6 | "os" 7 | "sync" 8 | 9 | "github.com/awnumar/memguard/core" 10 | ) 11 | 12 | var ( 13 | // StreamChunkSize is the maximum amount of data that is locked into memory at a time. 14 | // If you get error allocating memory, increase your system's mlock limits. 15 | // Use 'ulimit -l' to see mlock limit on unix systems. 16 | StreamChunkSize = c 17 | c = os.Getpagesize() * 4 18 | ) 19 | 20 | type queue struct { 21 | *list.List 22 | } 23 | 24 | // add data to back of queue 25 | func (q *queue) join(e *Enclave) { 26 | q.PushBack(e) 27 | } 28 | 29 | // add data to front of queue 30 | func (q *queue) push(e *Enclave) { 31 | q.PushFront(e) 32 | } 33 | 34 | // pop data off front of queue 35 | // returns nil if queue is empty 36 | func (q *queue) pop() *Enclave { 37 | e := q.Front() // get element at front of queue 38 | if e == nil { 39 | return nil // no data 40 | } 41 | q.Remove(e) // success => remove value 42 | return e.Value.(*Enclave) // unwrap and return (potential panic) 43 | } 44 | 45 | /* 46 | Stream is an in-memory encrypted container implementing the reader and writer interfaces. 47 | 48 | It is most useful when you need to store lots of data in memory and are able to work on it in chunks. 49 | */ 50 | type Stream struct { 51 | sync.Mutex 52 | *queue 53 | } 54 | 55 | // NewStream initialises a new empty Stream object. 56 | func NewStream() *Stream { 57 | return &Stream{queue: &queue{List: list.New()}} 58 | } 59 | 60 | /* 61 | Write encrypts and writes some given data to a Stream object. 62 | 63 | The data is broken down into chunks and added to the stream in order. The last thing to be written to the stream is the last thing that will be read back. 64 | */ 65 | func (s *Stream) Write(data []byte) (int, error) { 66 | s.Lock() 67 | defer s.Unlock() 68 | 69 | for i := 0; i < len(data); i += c { 70 | if i+c > len(data) { 71 | s.join(NewEnclave(data[len(data)-(len(data)%c):])) 72 | } else { 73 | s.join(NewEnclave(data[i : i+c])) 74 | } 75 | } 76 | return len(data), nil 77 | } 78 | 79 | /* 80 | Read decrypts and places some data from a Stream object into a provided buffer. 81 | 82 | If there is no data, the call will return an io.EOF error. If the caller provides a buffer 83 | that is too small to hold the next chunk of data, the remaining bytes are re-encrypted and 84 | added to the front of the queue to be returned in the next call. 85 | 86 | To be performant, have 87 | */ 88 | func (s *Stream) Read(buf []byte) (int, error) { 89 | s.Lock() 90 | defer s.Unlock() 91 | 92 | // Grab the next chunk of data from the stream. 93 | b, err := s.next() 94 | if err != nil { 95 | return 0, err 96 | } 97 | defer b.Destroy() 98 | 99 | // Copy the contents into the given buffer. 100 | core.Copy(buf, b.Bytes()) 101 | 102 | // Check if there is data left over. 103 | if len(buf) < b.Size() { 104 | // Re-encrypt it and push onto the front of the list. 105 | c := NewBuffer(b.Size() - len(buf)) 106 | c.Copy(b.Bytes()[len(buf):]) 107 | s.push(c.Seal()) 108 | return len(buf), nil 109 | } 110 | 111 | // Not enough data or perfect amount of data. 112 | // Either way we copied the entire buffer. 113 | return b.Size(), nil 114 | } 115 | 116 | // Size returns the number of bytes of data currently stored within a Stream object. 117 | func (s *Stream) Size() int { 118 | s.Lock() 119 | defer s.Unlock() 120 | 121 | var n int 122 | for e := s.Front(); e != nil; e = e.Next() { 123 | n += e.Value.(*Enclave).Size() 124 | } 125 | return n 126 | } 127 | 128 | // Next grabs the next chunk of data from the Stream and returns it decrypted inside a LockedBuffer. Any error from the stream is forwarded. 129 | func (s *Stream) Next() (*LockedBuffer, error) { 130 | s.Lock() 131 | defer s.Unlock() 132 | 133 | return s.next() 134 | } 135 | 136 | // does not acquire mutex lock 137 | func (s *Stream) next() (*LockedBuffer, error) { 138 | // Pop data from the front of the list. 139 | e := s.pop() 140 | if e == nil { 141 | return newNullBuffer(), io.EOF 142 | } 143 | 144 | // Decrypt the data into a guarded allocation. 145 | b, err := e.Open() 146 | if err != nil { 147 | return newNullBuffer(), err 148 | } 149 | return b, nil 150 | } 151 | 152 | // Flush reads all of the data from a Stream and returns it inside a LockedBuffer. If an error is encountered before all the data could be read, it is returned along with any data read up until that point. 153 | func (s *Stream) Flush() (*LockedBuffer, error) { 154 | return NewBufferFromEntireReader(s) 155 | } 156 | -------------------------------------------------------------------------------- /core/crypto.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/subtle" 6 | "errors" 7 | "runtime" 8 | "unsafe" 9 | 10 | "golang.org/x/crypto/blake2b" 11 | "golang.org/x/crypto/nacl/secretbox" 12 | ) 13 | 14 | // Overhead is the size by which the ciphertext exceeds the plaintext. 15 | const Overhead int = secretbox.Overhead + 24 // auth + nonce 16 | 17 | // ErrInvalidKeyLength is returned when attempting to encrypt or decrypt with a key that is not exactly 32 bytes in size. 18 | var ErrInvalidKeyLength = errors.New(" key must be exactly 32 bytes") 19 | 20 | // ErrBufferTooSmall is returned when the decryption function, Open, is given an output buffer that is too small to hold the plaintext. In practice the plaintext will be Overhead bytes smaller than the ciphertext returned by the encryption function, Seal. 21 | var ErrBufferTooSmall = errors.New(" the given buffer is too small to hold the plaintext") 22 | 23 | // ErrDecryptionFailed is returned when the attempted decryption fails. This can occur if the given key is incorrect or if the ciphertext is invalid. 24 | var ErrDecryptionFailed = errors.New(" decryption failed: key is wrong or ciphertext is corrupt") 25 | 26 | // Encrypt takes a plaintext message and a 32 byte key and returns an authenticated ciphertext. 27 | func Encrypt(plaintext, key []byte) ([]byte, error) { 28 | // Check the length of the key is correct. 29 | if len(key) != 32 { 30 | return nil, ErrInvalidKeyLength 31 | } 32 | 33 | // Get a reference to the key's underlying array without making a copy. 34 | k := (*[32]byte)(unsafe.Pointer(&key[0])) 35 | 36 | // Allocate space for and generate a nonce value. 37 | var nonce [24]byte 38 | if err := Scramble(nonce[:]); err != nil { 39 | Panic(err) 40 | } 41 | 42 | // Encrypt m and return the result. 43 | return secretbox.Seal(nonce[:], plaintext, &nonce, k), nil 44 | } 45 | 46 | /* 47 | Decrypt decrypts a given ciphertext with a given 32 byte key and writes the result to the start of a given buffer. 48 | 49 | The buffer must be large enough to contain the decrypted data. This is in practice Overhead bytes less than the length of the ciphertext returned by the Seal function above. This value is the size of the nonce plus the size of the Poly1305 authenticator. 50 | 51 | The size of the decrypted data is returned. 52 | */ 53 | func Decrypt(ciphertext, key []byte, output []byte) (int, error) { 54 | // Check the length of the key is correct. 55 | if len(key) != 32 { 56 | return 0, ErrInvalidKeyLength 57 | } 58 | 59 | // Check the capacity of the given output buffer. 60 | if cap(output) < (len(ciphertext) - Overhead) { 61 | return 0, ErrBufferTooSmall 62 | } 63 | 64 | // Get a reference to the key's underlying array without making a copy. 65 | k := (*[32]byte)(unsafe.Pointer(&key[0])) 66 | 67 | // Retrieve and store the nonce value. 68 | var nonce [24]byte 69 | Copy(nonce[:], ciphertext[:24]) 70 | 71 | // Decrypt and return the result. 72 | m, ok := secretbox.Open(nil, ciphertext[24:], &nonce, k) 73 | if ok { // Decryption successful. 74 | Move(output[:cap(output)], m) // Move plaintext to given output buffer. 75 | return len(m), nil // Return length of decrypted plaintext. 76 | } 77 | 78 | // Decryption unsuccessful. Either the key was wrong or the authentication failed. 79 | return 0, ErrDecryptionFailed 80 | } 81 | 82 | // Hash implements a cryptographic hash function using Blake2b. 83 | func Hash(b []byte) []byte { 84 | h := blake2b.Sum256(b) 85 | return h[:] 86 | } 87 | 88 | // Scramble fills a given buffer with cryptographically-secure random bytes. 89 | func Scramble(buf []byte) error { 90 | if _, err := rand.Read(buf); err != nil { 91 | return err 92 | } 93 | 94 | // See Wipe 95 | runtime.KeepAlive(buf) 96 | return nil 97 | } 98 | 99 | // Wipe takes a buffer and wipes it with zeroes. 100 | func Wipe(buf []byte) { 101 | for i := range buf { 102 | buf[i] = 0 103 | } 104 | 105 | // This should keep buf's backing array live and thus prevent dead store 106 | // elimination, according to discussion at 107 | // https://github.com/golang/go/issues/33325 . 108 | runtime.KeepAlive(buf) 109 | } 110 | 111 | // Copy is identical to Go's builtin copy function except the copying is done in constant time. This is to mitigate against side-channel attacks. 112 | func Copy(dst, src []byte) { 113 | if len(dst) > len(src) { 114 | subtle.ConstantTimeCopy(1, dst[:len(src)], src) 115 | } else if len(dst) < len(src) { 116 | subtle.ConstantTimeCopy(1, dst, src[:len(dst)]) 117 | } else { 118 | subtle.ConstantTimeCopy(1, dst, src) 119 | } 120 | } 121 | 122 | // Move is identical to Copy except it wipes the source buffer after the copy operation is executed. 123 | func Move(dst, src []byte) { 124 | Copy(dst, src) 125 | Wipe(src) 126 | } 127 | 128 | // Equal does a constant-time comparison of two byte slices. This is to mitigate against side-channel attacks. 129 | func Equal(x, y []byte) bool { 130 | return subtle.ConstantTimeCompare(x, y) == 1 131 | } 132 | -------------------------------------------------------------------------------- /core/coffer_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "math/rand/v2" 6 | "sync" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestNewCoffer(t *testing.T) { 12 | s := NewCoffer() 13 | 14 | // Attain a lock to halt the verify & rekey cycle. 15 | s.Lock() 16 | 17 | // Verify that fields are not nil. 18 | if s.left == nil || s.right == nil { 19 | t.Error("one or more fields are not initialised") 20 | } 21 | 22 | // Verify that fields are the expected sizes. 23 | if len(s.left.Data()) != 32 { 24 | t.Error("left side has unexpected lengths") 25 | } 26 | if len(s.right.Data()) != 32 { 27 | t.Error("right size has unexpected lengths") 28 | } 29 | 30 | // Verify that the data fields are not zeroed. 31 | if bytes.Equal(s.left.Data(), make([]byte, 32)) { 32 | t.Error("left side is zeroed") 33 | } 34 | if bytes.Equal(s.right.Data(), make([]byte, 32)) { 35 | t.Error("right side is zeroed") 36 | } 37 | 38 | s.Unlock() // Release mutex to allow destruction. 39 | s.Destroy() 40 | } 41 | 42 | func TestCofferInit(t *testing.T) { 43 | s := NewCoffer() 44 | 45 | // Get the value stored inside. 46 | view, err := s.View() 47 | if err != nil { 48 | t.Error("unexpected error") 49 | } 50 | value := make([]byte, 32) 51 | copy(value, view.Data()) 52 | view.Destroy() 53 | 54 | // Re-init the buffer with a new value. 55 | if err := s.Init(); err != nil { 56 | t.Error("unexpected error;", err) 57 | } 58 | 59 | // Get the new value stored inside. 60 | view, err = s.View() 61 | if err != nil { 62 | t.Error("unexpected error") 63 | } 64 | newValue := make([]byte, 32) 65 | copy(newValue, view.Data()) 66 | view.Destroy() 67 | 68 | // Compare them. 69 | if bytes.Equal(value, newValue) { 70 | t.Error("value was not refreshed") 71 | } 72 | 73 | s.Destroy() 74 | 75 | // Check error condition. 76 | if err := s.Init(); err != ErrCofferExpired { 77 | t.Error("expected ErrCofferExpired; got", err) 78 | } 79 | } 80 | 81 | func TestCofferView(t *testing.T) { 82 | s := NewCoffer() 83 | 84 | // Get the value stored inside. 85 | view, err := s.View() 86 | if err != nil { 87 | t.Error("unexpected error") 88 | } 89 | if view == nil { 90 | t.Error("returned object is nil") 91 | } 92 | 93 | // Some sanity checks on the inner value. 94 | if view.Data() == nil || len(view.Data()) != 32 { 95 | t.Error("unexpected data; got", view.Data()) 96 | } 97 | if bytes.Equal(view.Data(), make([]byte, 32)) { 98 | t.Error("value inside coffer is zero") 99 | } 100 | 101 | // Destroy our temporary view of the coffer's contents. 102 | view.Destroy() 103 | 104 | s.Destroy() 105 | 106 | // Check error condition. 107 | view, err = s.View() 108 | if err != ErrCofferExpired { 109 | t.Error("expected ErrCofferExpired; got", err) 110 | } 111 | if view != nil { 112 | t.Error("expected nil buffer object") 113 | } 114 | } 115 | 116 | func TestCofferRekey(t *testing.T) { 117 | s := NewCoffer() 118 | 119 | // remember the value stored inside 120 | view, err := s.View() 121 | if err != nil { 122 | t.Error("unexpected error;", err) 123 | } 124 | orgValue := make([]byte, 32) 125 | copy(orgValue, view.Data()) 126 | view.Destroy() 127 | 128 | // remember the value of the partitions 129 | left := make([]byte, 32) 130 | right := make([]byte, 32) 131 | s.Lock() // halt re-key cycle 132 | copy(left, s.left.Data()) 133 | copy(right, s.right.Data()) 134 | s.Unlock() // un-halt re-key cycle 135 | 136 | s.Rekey() // force a re-key 137 | 138 | view, err = s.View() 139 | if err != nil { 140 | t.Error("unexpected error;", err) 141 | } 142 | newValue := make([]byte, 32) 143 | copy(newValue, view.Data()) 144 | view.Destroy() 145 | 146 | if !bytes.Equal(orgValue, newValue) { 147 | t.Error("value inside coffer changed!!") 148 | } 149 | 150 | if bytes.Equal(left, s.left.Data()) || bytes.Equal(right, s.right.Data()) { 151 | t.Error("partition values did not change") 152 | } 153 | 154 | s.Destroy() 155 | 156 | if err := s.Rekey(); err != ErrCofferExpired { 157 | t.Error("expected ErrCofferExpired; got", err) 158 | } 159 | } 160 | 161 | func TestCofferDestroy(t *testing.T) { 162 | s := NewCoffer() 163 | s.Destroy() 164 | 165 | // Check metadata flags. 166 | if !s.Destroyed() { 167 | t.Error("expected destroyed") 168 | } 169 | 170 | // Check both partitions are destroyed. 171 | if s.left.alive || s.right.alive { 172 | t.Error("some partition not destroyed") 173 | } 174 | } 175 | 176 | func TestCofferConcurrent(t *testing.T) { 177 | testConcurrency := 10 178 | testDuration := 2 * time.Second 179 | 180 | funcs := []func(s *Coffer) error{ 181 | func(s *Coffer) error { 182 | return s.Rekey() 183 | }, 184 | func(s *Coffer) error { 185 | _, err := s.View() 186 | return err 187 | }, 188 | } 189 | wg := &sync.WaitGroup{} 190 | 191 | s := NewCoffer() 192 | defer s.Destroy() 193 | 194 | start := time.Now() 195 | 196 | for range testConcurrency { 197 | wg.Add(1) 198 | go func(t *testing.T) { 199 | defer wg.Done() 200 | defer func() { 201 | if r := recover(); r != nil { 202 | // Log panic -- it's likely just ran out of mlock space. 203 | t.Logf("Recovered from panic: %s", r) 204 | } 205 | }() 206 | fIndex := rand.IntN(len(funcs)) 207 | for time.Since(start) < testDuration { 208 | err := funcs[fIndex](s) 209 | if err != nil && err != ErrCofferExpired { 210 | t.Errorf("unexpected error: %v", err) 211 | } 212 | } 213 | }(t) 214 | } 215 | 216 | wg.Wait() 217 | } 218 | -------------------------------------------------------------------------------- /core/crypto_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "testing" 7 | ) 8 | 9 | func TestCopy(t *testing.T) { 10 | a := make([]byte, 8) 11 | Scramble(a) 12 | b := make([]byte, 16) 13 | Scramble(b) 14 | c := make([]byte, 32) 15 | Scramble(c) 16 | 17 | // dst > src 18 | Copy(b, a) 19 | if !bytes.Equal(b[:8], a) { 20 | t.Error("incorrect copying") 21 | } 22 | 23 | // dst < src 24 | Copy(b, c) 25 | if !bytes.Equal(b, c[:16]) { 26 | t.Error("incorrect copying") 27 | } 28 | 29 | // dst = src 30 | b2 := make([]byte, 16) 31 | Scramble(b2) 32 | Copy(b, b2) 33 | if !bytes.Equal(b, b2) { 34 | t.Error("incorrect copying") 35 | } 36 | } 37 | 38 | func TestMove(t *testing.T) { 39 | a := make([]byte, 32) 40 | Scramble(a) 41 | b := make([]byte, 32) 42 | Scramble(b) 43 | 44 | Move(a, b) 45 | if !bytes.Equal(b, make([]byte, 32)) { 46 | t.Error("src buffer was not wiped") 47 | } 48 | } 49 | 50 | func TestCompare(t *testing.T) { 51 | a := make([]byte, 8) 52 | Scramble(a) 53 | b := make([]byte, 16) 54 | Scramble(b) 55 | c := make([]byte, 16) 56 | copy(c, b) 57 | 58 | // not equal 59 | if Equal(a, b) { 60 | t.Error("expected not equal") 61 | } 62 | 63 | // equal 64 | if !Equal(b, c) { 65 | t.Error("expected equal") 66 | } 67 | 68 | c[8] = ^c[8] 69 | 70 | // not equal 71 | if Equal(b, c) { 72 | t.Error("expected not equal") 73 | } 74 | } 75 | 76 | func TestScramble(t *testing.T) { 77 | b := make([]byte, 32) 78 | Scramble(b) 79 | if bytes.Equal(b, make([]byte, 32)) { 80 | t.Error("buffer not scrambled") 81 | } 82 | c := make([]byte, 32) 83 | Scramble(c) 84 | if bytes.Equal(b, make([]byte, 32)) { 85 | t.Error("buffer not scrambled") 86 | } 87 | if bytes.Equal(b, c) { 88 | t.Error("random repeated") 89 | } 90 | } 91 | 92 | func TestHash(t *testing.T) { 93 | known := make(map[string]string) 94 | known[""] = "DldRwCblQ7Loqy6wYJnaodHl30d3j3eH+qtFzfEv46g=" 95 | known["hash"] = "l+2qaVlkOBNtzRKFU+kEvAP1JkJvcn0nC2mEH7bPUNM=" 96 | known["test"] = "kosgNmlD4q/RHrwOri5TqTvxd6T881vMZNUDcE5l4gI=" 97 | 98 | for k, v := range known { 99 | if base64.StdEncoding.EncodeToString(Hash([]byte(k))) != v { 100 | t.Error("digest doesn't match known values") 101 | } 102 | } 103 | } 104 | 105 | func TestWipe(t *testing.T) { 106 | b := make([]byte, 32) 107 | Scramble(b) 108 | Wipe(b) 109 | for i := range b { 110 | if b[i] != 0 { 111 | t.Error("wipe unsuccessful") 112 | } 113 | } 114 | } 115 | 116 | func TestEncryptDecrypt(t *testing.T) { 117 | // Declare the plaintext and the key. 118 | m := make([]byte, 64) 119 | Scramble(m) 120 | k := make([]byte, 32) 121 | Scramble(k) 122 | 123 | // Encrypt the message. 124 | x, err := Encrypt(m, k) 125 | if err != nil { 126 | t.Error("expected no errors; got", err) 127 | } 128 | 129 | // Decrypt the message. 130 | dm := make([]byte, len(x)-Overhead) 131 | length, err := Decrypt(x, k, dm) 132 | if err != nil { 133 | t.Error("expected no errors; got", err) 134 | } 135 | if length != len(x)-Overhead { 136 | t.Error("unexpected plaintext length; got", length) 137 | } 138 | 139 | // Verify that the plaintexts match. 140 | if !bytes.Equal(m, dm) { 141 | t.Error("decrypted plaintext does not match original") 142 | } 143 | 144 | // Attempt decryption /w buffer that is too small to hold the output. 145 | out := make([]byte, len(x)-Overhead-1) 146 | length, err = Decrypt(x, k, out) 147 | if err != ErrBufferTooSmall { 148 | t.Error("expected error; got", err) 149 | } 150 | if length != 0 { 151 | t.Error("expected zero length; got", length) 152 | } 153 | 154 | // Construct a buffer that has the correct capacity but a smaller length. 155 | out = make([]byte, len(x)-Overhead) 156 | smallOut := out[:2] 157 | if len(smallOut) != 2 || cap(smallOut) != len(x)-Overhead { 158 | t.Error("invalid construction for test") 159 | } 160 | length, err = Decrypt(x, k, smallOut) 161 | if err != nil { 162 | t.Error("unexpected error:", err) 163 | } 164 | if length != len(x)-Overhead { 165 | t.Error("unexpected length; got", length) 166 | } 167 | if !bytes.Equal(m, smallOut[:len(x)-Overhead]) { 168 | t.Error("decrypted plaintext does not match original") 169 | } 170 | 171 | // Generate an incorrect key. 172 | ik := make([]byte, 32) 173 | Scramble(ik) 174 | 175 | // Attempt decryption with the incorrect key. 176 | length, err = Decrypt(x, ik, dm) 177 | if length != 0 { 178 | t.Error("expected length = 0; got", length) 179 | } 180 | if err != ErrDecryptionFailed { 181 | t.Error("expected error with incorrect key; got", err) 182 | } 183 | 184 | // Modify the ciphertext somewhat. 185 | for i := range x { 186 | if i%32 == 0 { 187 | x[i] = 0xdb 188 | } 189 | } 190 | 191 | // Attempt decryption of the invalid ciphertext with the correct key. 192 | length, err = Decrypt(x, k, dm) 193 | if length != 0 { 194 | t.Error("expected length = 0; got", length) 195 | } 196 | if err != ErrDecryptionFailed { 197 | t.Error("expected error with modified ciphertext; got", err) 198 | } 199 | 200 | // Generate a key of an invalid length. 201 | ik = make([]byte, 16) 202 | Scramble(ik) 203 | 204 | // Attempt encryption with the invalid key. 205 | ix, err := Encrypt(m, ik) 206 | if err != ErrInvalidKeyLength { 207 | t.Error("expected error with invalid key; got", err) 208 | } 209 | if ix != nil { 210 | t.Error("expected nil ciphertext; got", dm) 211 | } 212 | 213 | // Attempt decryption with the invalid key. 214 | length, err = Decrypt(x, ik, dm) 215 | if length != 0 { 216 | t.Error("expected length = 0; got", length) 217 | } 218 | if err != ErrInvalidKeyLength { 219 | t.Error("expected error with invalid key; got", err) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /stream_test.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "runtime" 8 | "testing" 9 | 10 | "github.com/awnumar/memguard/core" 11 | ) 12 | 13 | func write(t *testing.T, s *Stream, b []byte) { 14 | n, err := s.Write(b) 15 | if err != nil { 16 | t.Error("write should always succeed", err) 17 | } 18 | if n != len(b) { 19 | t.Error("not all data was written") 20 | } 21 | if !bytes.Equal(b, make([]byte, len(b))) { 22 | t.Error("buffer not wiped") 23 | } 24 | } 25 | 26 | func read(t *testing.T, s *Stream, ref []byte, expectedErr error) { 27 | b := make([]byte, len(ref)) 28 | n, err := s.Read(b) 29 | if err != expectedErr { 30 | t.Error("Expected", expectedErr, "got;", err) 31 | } 32 | if n != len(b) { 33 | t.Error("not enough data read") 34 | } 35 | if !bytes.Equal(ref, b) { 36 | t.Error("data mismatch") 37 | } 38 | } 39 | 40 | func TestStreamNextFlush(t *testing.T) { 41 | s := NewStream() 42 | 43 | size := 2*StreamChunkSize + 1024 44 | b := make([]byte, size) 45 | ScrambleBytes(b) 46 | ref := make([]byte, len(b)) 47 | copy(ref, b) 48 | write(t, s, b) 49 | 50 | c, err := s.Next() 51 | if err != nil { 52 | t.Error(err) 53 | } 54 | if c.Size() != StreamChunkSize { 55 | t.Error(c.Size()) 56 | } 57 | if !c.EqualTo(ref[:StreamChunkSize]) { 58 | t.Error("incorrect data") 59 | } 60 | c.Destroy() 61 | 62 | c, err = s.Flush() 63 | if err != nil { 64 | t.Error(err) 65 | } 66 | if c.Size() != size-StreamChunkSize { 67 | t.Error("unexpected length:", c.Size()) 68 | } 69 | if !c.EqualTo(ref[StreamChunkSize:]) { 70 | t.Error("incorrect data") 71 | } 72 | c.Destroy() 73 | } 74 | 75 | func TestStreamReadWrite(t *testing.T) { 76 | // Create new stream object. 77 | s := NewStream() 78 | 79 | // Initialise some data to store. 80 | b := make([]byte, 1024) 81 | ScrambleBytes(b) 82 | ref := make([]byte, len(b)) 83 | copy(ref, b) 84 | 85 | // Write the data to the stream. 86 | write(t, s, b) 87 | 88 | // Read the data back. 89 | read(t, s, ref, nil) 90 | 91 | // Check for end of data error 92 | read(t, s, nil, io.EOF) 93 | 94 | // Write more than the pagesize to the stream 95 | b = make([]byte, os.Getpagesize()*4+16) 96 | ScrambleBytes(b) 97 | copy(b[os.Getpagesize()*4:], []byte("yellow submarine")) 98 | ref = make([]byte, len(b)) 99 | copy(ref, b) 100 | write(t, s, b) 101 | 102 | // Read back four pages 103 | for i := 0; i < 4; i++ { 104 | read(t, s, ref[i*os.Getpagesize():(i+1)*os.Getpagesize()], nil) 105 | } 106 | 107 | // Read back the remaining data 108 | read(t, s, []byte("yellow submarine"), nil) 109 | 110 | // Should be no data left 111 | read(t, s, nil, io.EOF) 112 | 113 | // Test reading less data than is in the chunk 114 | data := make([]byte, 16) 115 | ScrambleBytes(data) 116 | ref = make([]byte, len(data)) 117 | copy(ref, data) 118 | write(t, s, data) 119 | write(t, s, data) // have two enclaves in the stream, 32 bytes total 120 | read(t, s, ref[:4], nil) 121 | read(t, s, ref[4:8], nil) 122 | read(t, s, ref[8:], nil) 123 | read(t, s, make([]byte, 16), nil) 124 | read(t, s, nil, io.EOF) 125 | 126 | // Test reading after purging the session 127 | ScrambleBytes(data) 128 | write(t, s, data) 129 | Purge() 130 | read(t, s, nil, core.ErrDecryptionFailed) 131 | } 132 | 133 | func TestStreamingSanity(t *testing.T) { 134 | s := NewStream() 135 | 136 | // write 2 pages + 1024 bytes to the stream 137 | size := 2*os.Getpagesize() + 1024 138 | b := make([]byte, size) 139 | ScrambleBytes(b) 140 | ref := make([]byte, len(b)) 141 | copy(ref, b) 142 | write(t, s, b) 143 | 144 | // read it back exactly 145 | c, err := NewBufferFromReader(s, size) 146 | if err != nil { 147 | t.Error(err) 148 | } 149 | if c.Size() != size { 150 | t.Error("not enough data read back") 151 | } 152 | if !c.EqualTo(ref) { 153 | t.Error("data mismatch") 154 | } 155 | c.Destroy() 156 | 157 | // should be no data left 158 | read(t, s, nil, io.EOF) 159 | 160 | // write the data back to the stream 161 | copy(b, ref) 162 | write(t, s, b) 163 | 164 | // read it all back 165 | c, err = NewBufferFromEntireReader(s) 166 | if err != nil { 167 | t.Error(err) 168 | } 169 | if c.Size() != size { 170 | t.Error("not enough data read back") 171 | } 172 | if !c.EqualTo(ref) { 173 | t.Error("data mismatch") 174 | } 175 | c.Destroy() 176 | 177 | // should be no data left 178 | read(t, s, nil, io.EOF) 179 | 180 | // write a page + 1024 bytes 181 | size = os.Getpagesize() + 1024 182 | b = make([]byte, size) 183 | b[size-1] = 'x' 184 | write(t, s, b) 185 | 186 | // read it back until the delimiter 187 | c, err = NewBufferFromReaderUntil(s, 'x') 188 | if err != nil { 189 | t.Error(err) 190 | } 191 | if c.Size() != size-1 { 192 | t.Error("not enough data read back:", c.Size(), "want", size-1) 193 | } 194 | if !c.EqualTo(make([]byte, size-1)) { 195 | t.Error("data mismatch") 196 | } 197 | c.Destroy() 198 | 199 | // should be no data left 200 | read(t, s, nil, io.EOF) 201 | } 202 | 203 | func TestStreamSize(t *testing.T) { 204 | s := NewStream() 205 | 206 | if s.Size() != 0 { 207 | t.Error("size is", s.Size()) 208 | } 209 | 210 | size := 1024 * 32 211 | b := make([]byte, size) 212 | write(t, s, b) 213 | 214 | if s.Size() != size { 215 | t.Error("size should be", size, "instead is", s.Size()) 216 | } 217 | } 218 | 219 | func BenchmarkStreamWrite(b *testing.B) { 220 | b.ReportAllocs() 221 | b.SetBytes(int64(StreamChunkSize)) 222 | 223 | s := NewStream() 224 | buf := make([]byte, StreamChunkSize) 225 | for i := 0; i < b.N; i++ { 226 | s.Write(buf) 227 | } 228 | runtime.KeepAlive(s) 229 | } 230 | 231 | func BenchmarkStreamRead(b *testing.B) { 232 | s := NewStream() 233 | buf := make([]byte, StreamChunkSize) 234 | for i := 0; i < b.N; i++ { 235 | s.Write(buf) 236 | } 237 | 238 | b.ReportAllocs() 239 | b.SetBytes(int64(StreamChunkSize)) 240 | b.ResetTimer() 241 | 242 | for i := 0; i < b.N; i++ { 243 | s.Read(buf) 244 | } 245 | 246 | runtime.KeepAlive(s) 247 | } 248 | -------------------------------------------------------------------------------- /examples/deadlock/x01/poc_test.go: -------------------------------------------------------------------------------- 1 | package x01 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | const duration = 10 * time.Second 13 | 14 | func TestPanicsPoC(t *testing.T) { 15 | sigs := make(chan os.Signal, 1) 16 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 17 | 18 | ctx, cancel := context.WithTimeout(context.Background(), duration) 19 | go func() { 20 | select { 21 | case <-sigs: 22 | cancel() 23 | } 24 | }() 25 | OpenEnclave(ctx) 26 | } 27 | 28 | // ############# 29 | 30 | // panic: runtime error: index out of range [0] with length 0 31 | 32 | // goroutine 2060 [running]: 33 | // github.com/awnumar/memguard/core.(*Coffer).View(0xc0000962a0, 0x0, 0x0, 0x0) 34 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/coffer.go:112 +0x3a9 35 | // github.com/awnumar/memguard/core.Open(0xc00000e0e0, 0xc00001c290, 0xc00007a591, 0xc00007a590) 36 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/enclave.go:101 +0xa5 37 | // github.com/awnumar/memguard.(*Enclave).Open(0xc000010040, 0xc000070770, 0x1, 0x1) 38 | // /home/awn/src/go/src/github.com/awnumar/memguard/enclave.go:43 +0x50 39 | // github.com/awnumar/memguard/examples/panics.openVerify(0xc000010040, 0xc000016600, 0x20, 0x20, 0x0, 0x0) 40 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/panics/poc.go:55 +0x5c 41 | // github.com/awnumar/memguard/examples/panics.immediateOpen.func1(0xc000010040, 0xc000016600, 0x20, 0x20, 0xc00024af60) 42 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/panics/poc.go:70 +0x5b 43 | // created by github.com/awnumar/memguard/examples/panics.immediateOpen 44 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/panics/poc.go:69 +0xdf 45 | // FAIL github.com/awnumar/memguard/examples/panics 0.835s 46 | // FAIL 47 | 48 | // ############# 49 | 50 | // WARNING: DATA RACE 51 | // Write at 0x0000007aa588 by goroutine 114: 52 | // github.com/awnumar/memguard/core.Purge() 53 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/exit.go:54 +0x82 54 | // github.com/awnumar/memguard/core.Panic() 55 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/exit.go:85 +0x2f 56 | // github.com/awnumar/memguard/core.NewBuffer() 57 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/buffer.go:75 +0x8ce 58 | // github.com/awnumar/memguard/core.Open() 59 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/enclave.go:95 +0x6b 60 | // github.com/awnumar/memguard.(*Enclave).Open() 61 | // /home/awn/src/go/src/github.com/awnumar/memguard/enclave.go:43 +0x4f 62 | // github.com/awnumar/memguard/examples/unsound.openVerify() 63 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:55 +0x5b 64 | // github.com/awnumar/memguard/examples/unsound.immediateOpen.func1() 65 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:70 +0x5a 66 | 67 | // Previous read at 0x0000007aa588 by goroutine 50: 68 | // github.com/awnumar/memguard/core.Purge.func1() 69 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/exit.go:22 +0x52 70 | // github.com/awnumar/memguard/core.Purge() 71 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/exit.go:50 +0x44 72 | // github.com/awnumar/memguard/core.Panic() 73 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/exit.go:85 +0x2f 74 | // github.com/awnumar/memguard.(*Enclave).Open() 75 | // /home/awn/src/go/src/github.com/awnumar/memguard/enclave.go:46 +0xa7 76 | // github.com/awnumar/memguard/examples/unsound.openVerify() 77 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:55 +0x5b 78 | // github.com/awnumar/memguard/examples/unsound.immediateOpen.func1() 79 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:70 +0x5a 80 | 81 | // Goroutine 114 (running) created at: 82 | // github.com/awnumar/memguard/examples/unsound.immediateOpen() 83 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:69 +0xde 84 | // github.com/awnumar/memguard/examples/unsound.OpenEnclave.func1() 85 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:40 +0x238 86 | 87 | // Goroutine 50 (running) created at: 88 | // github.com/awnumar/memguard/examples/unsound.immediateOpen() 89 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:69 +0xde 90 | // github.com/awnumar/memguard/examples/unsound.OpenEnclave.func1() 91 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:40 +0x238 92 | 93 | // ############# 94 | 95 | // panic: could not acquire lock on 0x7f7ef201d000, limit reached? [Err: cannot allocate memory] 96 | 97 | // goroutine 1992 [running]: 98 | // github.com/awnumar/memguard/core.Panic(0x607140, 0xc000223a10) 99 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/exit.go:86 +0x48 100 | // github.com/awnumar/memguard/core.NewBuffer(0x20, 0x7d61c0, 0x64, 0xd) 101 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/buffer.go:75 +0x8cf 102 | // github.com/awnumar/memguard/core.Open(0xc0000b80c0, 0xc000016520, 0xc0000783f1, 0xc0000783f0) 103 | // /home/awn/src/go/src/github.com/awnumar/memguard/core/enclave.go:95 +0x6c 104 | // github.com/awnumar/memguard.(*Enclave).Open(0xc0000a6048, 0xc0000eef70, 0x1, 0x1) 105 | // /home/awn/src/go/src/github.com/awnumar/memguard/enclave.go:43 +0x50 106 | // github.com/awnumar/memguard/examples/unsound.openVerify(0xc0000a6048, 0xc0000da080, 0x20, 0x20, 0x0, 0x0) 107 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:55 +0x5c 108 | // github.com/awnumar/memguard/examples/unsound.immediateOpen.func1(0xc0000a6048, 0xc0000da080, 0x20, 0x20, 0xc000236a80) 109 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:70 +0x5b 110 | // created by github.com/awnumar/memguard/examples/unsound.immediateOpen 111 | // /home/awn/src/go/src/github.com/awnumar/memguard/examples/unsound/poc.go:69 +0xdf 112 | // FAIL github.com/awnumar/memguard/examples/unsound 0.853s 113 | // FAIL 114 | -------------------------------------------------------------------------------- /core/buffer_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "unsafe" 7 | ) 8 | 9 | func TestNewBuffer(t *testing.T) { 10 | // Check the error case with zero length. 11 | b, err := NewBuffer(0) 12 | if err != ErrNullBuffer { 13 | t.Error("expected ErrNullBuffer; got", err) 14 | } 15 | if b != nil { 16 | t.Error("expected nil buffer; got", b) 17 | } 18 | 19 | // Check the error case with negative length. 20 | b, err = NewBuffer(-1) 21 | if err != ErrNullBuffer { 22 | t.Error("expected ErrNullBuffer; got", err) 23 | } 24 | if b != nil { 25 | t.Error("expected nil buffer; got", b) 26 | } 27 | 28 | // Test normal execution. 29 | b, err = NewBuffer(32) 30 | if err != nil { 31 | t.Error("expected nil err; got", err) 32 | } 33 | if !b.alive { 34 | t.Error("did not expect destroyed buffer") 35 | } 36 | if len(b.Data()) != 32 || cap(b.Data()) != 32 { 37 | t.Errorf("buffer has invalid length (%d) or capacity (%d)", len(b.Data()), cap(b.Data())) 38 | } 39 | if !b.mutable { 40 | t.Error("buffer is not marked mutable") 41 | } 42 | if len(b.memory) != roundToPageSize(32)+(2*pageSize) { 43 | t.Error("allocated incorrect length of memory") 44 | } 45 | if !bytes.Equal(b.Data(), make([]byte, 32)) { 46 | t.Error("container is not zero-filled") 47 | } 48 | 49 | // Check if the buffer was added to the buffers list. 50 | if !buffers.exists(b) { 51 | t.Error("buffer not in buffers list") 52 | } 53 | 54 | // Destroy the buffer. 55 | b.Destroy() 56 | } 57 | 58 | func TestLotsOfAllocs(t *testing.T) { 59 | for i := 1; i <= 16385; i++ { 60 | b, err := NewBuffer(i) 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | if !b.alive || !b.mutable { 65 | t.Error("invalid metadata") 66 | } 67 | if len(b.data) != i { 68 | t.Error("invalid data length") 69 | } 70 | if len(b.memory) != roundToPageSize(i)+2*pageSize { 71 | t.Error("memory length invalid") 72 | } 73 | if len(b.preguard) != pageSize || len(b.postguard) != pageSize { 74 | t.Error("guard pages length invalid") 75 | } 76 | if len(b.canary) != len(b.inner)-i { 77 | t.Error("canary length invalid") 78 | } 79 | if len(b.inner)%pageSize != 0 { 80 | t.Error("inner length is not multiple of page size") 81 | } 82 | for j := range b.data { 83 | b.data[j] = 1 84 | } 85 | for j := range b.data { 86 | if b.data[j] != 1 { 87 | t.Error("region rw test failed") 88 | } 89 | } 90 | b.Destroy() 91 | } 92 | } 93 | 94 | func TestData(t *testing.T) { 95 | b, err := NewBuffer(32) 96 | if err != nil { 97 | t.Error(err) 98 | } 99 | datasegm := b.data 100 | datameth := b.Data() 101 | 102 | // Check for naive equality. 103 | if !bytes.Equal(datasegm, datameth) { 104 | t.Error("naive equality check failed") 105 | } 106 | 107 | // Modify and check if the change was reflected in both. 108 | datameth[0] = 1 109 | datameth[31] = 1 110 | if !bytes.Equal(datasegm, datameth) { 111 | t.Error("modified equality check failed") 112 | } 113 | 114 | // Do a deep comparison. 115 | if uintptr(unsafe.Pointer(&datameth[0])) != uintptr(unsafe.Pointer(&datasegm[0])) { 116 | t.Error("pointer values differ") 117 | } 118 | if len(datameth) != len(datasegm) || cap(datameth) != cap(datasegm) { 119 | t.Error("length or capacity values differ") 120 | } 121 | 122 | b.Destroy() 123 | if b.Data() != nil { 124 | t.Error("expected nil data slice for destroyed buffer") 125 | } 126 | } 127 | 128 | func TestBufferState(t *testing.T) { 129 | b, err := NewBuffer(32) 130 | if err != nil { 131 | t.Error("expected nil err; got", err) 132 | } 133 | 134 | if b.Mutable() != true { 135 | t.Error("state mismatch: mutability") 136 | } 137 | 138 | if b.Alive() != true { 139 | t.Error("state mismatch: alive") 140 | } 141 | 142 | b.Freeze() 143 | 144 | if b.Mutable() != false { 145 | t.Error("state mismatch: mutability") 146 | } 147 | 148 | if b.Alive() != true { 149 | t.Error("state mismatch: alive") 150 | } 151 | 152 | b.Melt() 153 | 154 | if b.Mutable() != true { 155 | t.Error("state mismatch: mutability") 156 | } 157 | 158 | if b.Alive() != true { 159 | t.Error("state mismatch: alive") 160 | } 161 | 162 | b.Destroy() 163 | 164 | if b.Mutable() != false { 165 | t.Error("state mismatch: mutability") 166 | } 167 | 168 | if b.Alive() != false { 169 | t.Error("state mismatch: alive") 170 | } 171 | } 172 | 173 | func TestDestroy(t *testing.T) { 174 | // Allocate a new buffer. 175 | b, err := NewBuffer(32) 176 | if err != nil { 177 | t.Error("expected nil err; got", err) 178 | } 179 | 180 | // Destroy it. 181 | b.Destroy() 182 | 183 | // Pick apart the destruction. 184 | if b.Data() != nil { 185 | t.Error("expected bytes buffer to be nil; got", b.Data()) 186 | } 187 | if b.memory != nil { 188 | t.Error("expected memory to be nil; got", b.memory) 189 | } 190 | if b.mutable || b.alive { 191 | t.Error("buffer should be dead and immutable") 192 | } 193 | if b.preguard != nil || b.postguard != nil { 194 | t.Error("guard page slice references are not nil") 195 | } 196 | if b.inner != nil { 197 | t.Error("inner pages slice reference not nil") 198 | } 199 | if b.canary != nil { 200 | t.Error("canary slice reference not nil") 201 | } 202 | 203 | // Check if the buffer was removed from the buffers list. 204 | if buffers.exists(b) { 205 | t.Error("buffer is still in buffers list") 206 | } 207 | 208 | // Call destroy again to check idempotency. 209 | b.Destroy() 210 | 211 | // Verify that it didn't come back to life. 212 | if b.Data() != nil { 213 | t.Error("expected bytes buffer to be nil; got", b.Data()) 214 | } 215 | if b.memory != nil { 216 | t.Error("expected memory to be nil; got", b.memory) 217 | } 218 | if b.mutable || b.alive { 219 | t.Error("buffer should be dead and immutable") 220 | } 221 | if b.preguard != nil || b.postguard != nil { 222 | t.Error("guard page slice references are not nil") 223 | } 224 | if b.inner != nil { 225 | t.Error("inner pages slice reference not nil") 226 | } 227 | if b.canary != nil { 228 | t.Error("canary slice reference not nil") 229 | } 230 | } 231 | 232 | func TestBufferList(t *testing.T) { 233 | // Create a new BufferList for testing with. 234 | l := new(bufferList) 235 | 236 | // Create some example buffers to test with. 237 | a := new(Buffer) 238 | b := new(Buffer) 239 | 240 | // Check what Exists is saying. 241 | if l.exists(a) || l.exists(b) { 242 | t.Error("list is empty yet contains buffers?!") 243 | } 244 | 245 | // Add our two buffers to the list. 246 | l.add(a) 247 | if len(l.list) != 1 || l.list[0] != a { 248 | t.Error("buffer was not added correctly") 249 | } 250 | l.add(b) 251 | if len(l.list) != 2 || l.list[1] != b { 252 | t.Error("buffer was not added correctly") 253 | } 254 | 255 | // Now check that they exist. 256 | if !l.exists(a) || !l.exists(b) { 257 | t.Error("expected buffers to be in list") 258 | } 259 | 260 | // Remove the buffers from the list. 261 | l.remove(a) 262 | if len(l.list) != 1 || l.list[0] != b { 263 | t.Error("buffer was not removed correctly") 264 | } 265 | l.remove(b) 266 | if len(l.list) != 0 { 267 | t.Error("item was not removed correctly") 268 | } 269 | 270 | // Check what exists is saying now. 271 | if l.exists(a) || l.exists(b) { 272 | t.Error("list is empty yet contains buffers?!") 273 | } 274 | 275 | // Add the buffers again to test Empty. 276 | l.add(a) 277 | l.add(b) 278 | bufs := l.flush() 279 | if l.list != nil { 280 | t.Error("list was not nullified") 281 | } 282 | if len(bufs) != 2 || bufs[0] != a || bufs[1] != b { 283 | t.Error("buffers dump incorrect") 284 | } 285 | 286 | // Try appending again. 287 | l.add(a) 288 | if !l.exists(a) || l.exists(b) { 289 | t.Error("list is in invalid state") 290 | } 291 | l.remove(a) 292 | } 293 | -------------------------------------------------------------------------------- /core/buffer.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "unsafe" 7 | 8 | "github.com/awnumar/memcall" 9 | ) 10 | 11 | var ( 12 | buffers = new(bufferList) 13 | ) 14 | 15 | // ErrNullBuffer is returned when attempting to construct a buffer of size less than one. 16 | var ErrNullBuffer = errors.New(" buffer size must be greater than zero") 17 | 18 | // ErrBufferExpired is returned when attempting to perform an operation on or with a buffer that has been destroyed. 19 | var ErrBufferExpired = errors.New(" buffer has been purged from memory and can no longer be used") 20 | 21 | /* 22 | Buffer is a structure that holds raw sensitive data. 23 | 24 | The number of Buffers that can exist at one time is limited by how much memory your system's kernel allows each process to mlock/VirtualLock. Therefore you should call DestroyBuffer on Buffers that you no longer need, ideally defering a Destroy call after creating a new one. 25 | */ 26 | type Buffer struct { 27 | sync.RWMutex // Local mutex lock // TODO: this does not protect 'data' field 28 | 29 | alive bool // Signals that destruction has not come 30 | mutable bool // Mutability state of underlying memory 31 | 32 | data []byte // Portion of memory holding the data 33 | memory []byte // Entire allocated memory region 34 | 35 | preguard []byte // Guard page addressed before the data 36 | inner []byte // Inner region between the guard pages 37 | postguard []byte // Guard page addressed after the data 38 | 39 | canary []byte // Value written behind data to detect spillage 40 | } 41 | 42 | /* 43 | NewBuffer is a raw constructor for the Buffer object. 44 | */ 45 | func NewBuffer(size int) (*Buffer, error) { 46 | var err error 47 | 48 | if size < 1 { 49 | return nil, ErrNullBuffer 50 | } 51 | 52 | b := new(Buffer) 53 | 54 | // Allocate the total needed memory 55 | innerLen := roundToPageSize(size) 56 | b.memory, err = memcall.Alloc((2 * pageSize) + innerLen) 57 | if err != nil { 58 | Panic(err) 59 | } 60 | 61 | // Construct slice reference for data buffer. 62 | b.data = unsafe.Slice(&b.memory[pageSize+innerLen-size], size) 63 | 64 | // Construct slice references for page sectors. 65 | b.preguard = unsafe.Slice(&b.memory[0], pageSize) 66 | b.inner = unsafe.Slice(&b.memory[pageSize], innerLen) 67 | b.postguard = unsafe.Slice(&b.memory[pageSize+innerLen], pageSize) 68 | 69 | // Construct slice reference for canary portion of inner page. 70 | b.canary = unsafe.Slice(&b.memory[pageSize], len(b.inner)-len(b.data)) 71 | 72 | // Lock the pages that will hold sensitive data. 73 | if err := memcall.Lock(b.inner); err != nil { 74 | Panic(err) 75 | } 76 | 77 | // Initialise the canary value and reference regions. 78 | if err := Scramble(b.canary); err != nil { 79 | Panic(err) 80 | } 81 | Copy(b.preguard, b.canary) 82 | Copy(b.postguard, b.canary) 83 | 84 | // Make the guard pages inaccessible. 85 | if err := memcall.Protect(b.preguard, memcall.NoAccess()); err != nil { 86 | Panic(err) 87 | } 88 | if err := memcall.Protect(b.postguard, memcall.NoAccess()); err != nil { 89 | Panic(err) 90 | } 91 | 92 | // Set remaining properties 93 | b.alive = true 94 | b.mutable = true 95 | 96 | // Append the container to list of active buffers. 97 | buffers.add(b) 98 | 99 | // Return the created Buffer to the caller. 100 | return b, nil 101 | } 102 | 103 | // Data returns a byte slice representing the memory region containing the data. 104 | func (b *Buffer) Data() []byte { 105 | return b.data 106 | } 107 | 108 | // Inner returns a byte slice representing the entire inner memory pages. This should NOT be used unless you have a specific need. 109 | func (b *Buffer) Inner() []byte { 110 | return b.inner 111 | } 112 | 113 | // Freeze makes the underlying memory of a given buffer immutable. This will do nothing if the Buffer has been destroyed. 114 | func (b *Buffer) Freeze() { 115 | if err := b.freeze(); err != nil { 116 | Panic(err) 117 | } 118 | } 119 | 120 | func (b *Buffer) freeze() error { 121 | b.Lock() 122 | defer b.Unlock() 123 | 124 | if !b.alive { 125 | return nil 126 | } 127 | 128 | if b.mutable { 129 | if err := memcall.Protect(b.inner, memcall.ReadOnly()); err != nil { 130 | return err 131 | } 132 | b.mutable = false 133 | } 134 | 135 | return nil 136 | } 137 | 138 | // Melt makes the underlying memory of a given buffer mutable. This will do nothing if the Buffer has been destroyed. 139 | func (b *Buffer) Melt() { 140 | if err := b.melt(); err != nil { 141 | Panic(err) 142 | } 143 | } 144 | 145 | func (b *Buffer) melt() error { 146 | b.Lock() 147 | defer b.Unlock() 148 | 149 | if !b.alive { 150 | return nil 151 | } 152 | 153 | if !b.mutable { 154 | if err := memcall.Protect(b.inner, memcall.ReadWrite()); err != nil { 155 | return err 156 | } 157 | b.mutable = true 158 | } 159 | return nil 160 | } 161 | 162 | // Scramble attempts to overwrite the data with cryptographically-secure random bytes. 163 | func (b *Buffer) Scramble() { 164 | if err := b.scramble(); err != nil { 165 | Panic(err) 166 | } 167 | } 168 | 169 | func (b *Buffer) scramble() error { 170 | b.Lock() 171 | defer b.Unlock() 172 | return Scramble(b.Data()) 173 | } 174 | 175 | /* 176 | Destroy performs some security checks, securely wipes the contents of, and then releases a Buffer's memory back to the OS. If a security check fails, the process will attempt to wipe all it can before safely panicking. 177 | 178 | If the Buffer has already been destroyed, the function does nothing and returns nil. 179 | */ 180 | func (b *Buffer) Destroy() { 181 | if err := b.destroy(); err != nil { 182 | Panic(err) 183 | } 184 | // Remove this one from global slice. 185 | buffers.remove(b) 186 | } 187 | 188 | func (b *Buffer) destroy() error { 189 | if b == nil { 190 | return nil 191 | } 192 | 193 | // Attain a mutex lock on this Buffer. 194 | b.Lock() 195 | defer b.Unlock() 196 | 197 | // Return if it's already destroyed. 198 | if !b.alive { 199 | return nil 200 | } 201 | 202 | // Make all of the memory readable and writable. 203 | if err := memcall.Protect(b.memory, memcall.ReadWrite()); err != nil { 204 | return err 205 | } 206 | b.mutable = true 207 | 208 | // Wipe data field. 209 | Wipe(b.data) 210 | 211 | // Verify the canary 212 | if !Equal(b.preguard, b.postguard) || !Equal(b.preguard[:len(b.canary)], b.canary) { 213 | return errors.New(" canary verification failed; buffer overflow detected") 214 | } 215 | 216 | // Wipe the memory. 217 | Wipe(b.memory) 218 | 219 | // Unlock pages locked into memory. 220 | if err := memcall.Unlock(b.inner); err != nil { 221 | return err 222 | } 223 | 224 | // Free all related memory. 225 | if err := memcall.Free(b.memory); err != nil { 226 | return err 227 | } 228 | 229 | // Reset the fields. 230 | b.alive = false 231 | b.mutable = false 232 | b.data = nil 233 | b.memory = nil 234 | b.preguard = nil 235 | b.inner = nil 236 | b.postguard = nil 237 | b.canary = nil 238 | return nil 239 | } 240 | 241 | // Alive returns true if the buffer has not been destroyed. 242 | func (b *Buffer) Alive() bool { 243 | b.RLock() 244 | defer b.RUnlock() 245 | return b.alive 246 | } 247 | 248 | // Mutable returns true if the buffer is mutable. 249 | func (b *Buffer) Mutable() bool { 250 | b.RLock() 251 | defer b.RUnlock() 252 | return b.mutable 253 | } 254 | 255 | // isDestroyed returns true if the buffer is destroyed 256 | func (b *Buffer) isDestroyed() bool { 257 | b.RLock() 258 | defer b.RUnlock() 259 | return b.data == nil 260 | } 261 | 262 | // BufferList stores a list of buffers in a thread-safe manner. 263 | type bufferList struct { 264 | sync.RWMutex 265 | list []*Buffer 266 | } 267 | 268 | // Add appends a given Buffer to the list. 269 | func (l *bufferList) add(b ...*Buffer) { 270 | l.Lock() 271 | defer l.Unlock() 272 | 273 | l.list = append(l.list, b...) 274 | } 275 | 276 | // Copy returns an instantaneous snapshot of the list. 277 | func (l *bufferList) copy() []*Buffer { 278 | l.Lock() 279 | defer l.Unlock() 280 | 281 | list := make([]*Buffer, len(l.list)) 282 | copy(list, l.list) 283 | 284 | return list 285 | } 286 | 287 | // Remove removes a given Buffer from the list. 288 | func (l *bufferList) remove(b *Buffer) { 289 | l.Lock() 290 | defer l.Unlock() 291 | 292 | for i, v := range l.list { 293 | if v == b { 294 | l.list = append(l.list[:i], l.list[i+1:]...) 295 | break 296 | } 297 | } 298 | } 299 | 300 | // Exists checks if a given buffer is in the list. 301 | func (l *bufferList) exists(b *Buffer) bool { 302 | l.RLock() 303 | defer l.RUnlock() 304 | 305 | for _, v := range l.list { 306 | if b == v { 307 | return true 308 | } 309 | } 310 | 311 | return false 312 | } 313 | 314 | // Flush clears the list and returns its previous contents. 315 | func (l *bufferList) flush() []*Buffer { 316 | l.Lock() 317 | defer l.Unlock() 318 | 319 | list := make([]*Buffer, len(l.list)) 320 | copy(list, l.list) 321 | 322 | l.list = nil 323 | 324 | return list 325 | } 326 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /buffer.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "unsafe" 8 | 9 | "github.com/awnumar/memguard/core" 10 | ) 11 | 12 | /* 13 | LockedBuffer is a structure that holds raw sensitive data. 14 | 15 | The number of LockedBuffers that you are able to create is limited by how much memory your system's kernel allows each process to mlock/VirtualLock. Therefore you should call Destroy on LockedBuffers that you no longer need or defer a Destroy call after creating a new LockedBuffer. 16 | */ 17 | type LockedBuffer struct { 18 | *core.Buffer 19 | } 20 | 21 | // Constructs a LockedBuffer object from a core.Buffer while also setting up the finalizer for it. 22 | func newBuffer(buf *core.Buffer) *LockedBuffer { 23 | return &LockedBuffer{buf} 24 | } 25 | 26 | // Constructs a quasi-destroyed LockedBuffer with size zero. 27 | func newNullBuffer() *LockedBuffer { 28 | return &LockedBuffer{new(core.Buffer)} 29 | } 30 | 31 | /* 32 | NewBuffer creates a mutable data container of the specified size. 33 | */ 34 | func NewBuffer(size int) *LockedBuffer { 35 | // Construct a Buffer of the specified size. 36 | buf, err := core.NewBuffer(size) 37 | if err != nil { 38 | return newNullBuffer() 39 | } 40 | 41 | // Construct and return the wrapped container object. 42 | return newBuffer(buf) 43 | } 44 | 45 | /* 46 | NewBufferFromBytes constructs an immutable buffer from a byte slice. The source buffer is wiped after the value has been copied over to the created container. 47 | */ 48 | func NewBufferFromBytes(src []byte) *LockedBuffer { 49 | // Construct a buffer of the correct size. 50 | b := NewBuffer(len(src)) 51 | if b.Size() == 0 { 52 | return b 53 | } 54 | 55 | // Move the data over. 56 | b.Move(src) 57 | 58 | // Make the buffer immutable. 59 | b.Freeze() 60 | 61 | // Return the created Buffer object. 62 | return b 63 | } 64 | 65 | /* 66 | NewBufferFromReader reads some number of bytes from an io.Reader into an immutable LockedBuffer. 67 | 68 | An error is returned precisely when the number of bytes read is less than the requested amount. Any data read is returned in either case. 69 | */ 70 | func NewBufferFromReader(r io.Reader, size int) (*LockedBuffer, error) { 71 | // Construct a buffer of the provided size. 72 | b := NewBuffer(size) 73 | if b.Size() == 0 { 74 | return b, nil 75 | } 76 | 77 | // Attempt to fill it with data from the Reader. 78 | if n, err := io.ReadFull(r, b.Bytes()); err != nil { 79 | if n == 0 { 80 | // nothing was read 81 | b.Destroy() 82 | return newNullBuffer(), err 83 | } 84 | 85 | // partial read 86 | d := NewBuffer(n) 87 | d.Copy(b.Bytes()[:n]) 88 | d.Freeze() 89 | b.Destroy() 90 | return d, err 91 | } 92 | 93 | // success 94 | b.Freeze() 95 | return b, nil 96 | } 97 | 98 | /* 99 | NewBufferFromReaderUntil constructs an immutable buffer containing data sourced from an io.Reader object. 100 | 101 | If an error is encountered before the delimiter value, the error will be returned along with the data read up until that point. 102 | */ 103 | func NewBufferFromReaderUntil(r io.Reader, delim byte) (*LockedBuffer, error) { 104 | // Construct a buffer with a data page that fills an entire memory page. 105 | b := NewBuffer(os.Getpagesize()) 106 | 107 | // Loop over the buffer a byte at a time. 108 | for i := 0; ; i++ { 109 | // If we have filled this buffer... 110 | if i == b.Size() { 111 | // Construct a new buffer that is a page size larger. 112 | c := NewBuffer(b.Size() + os.Getpagesize()) 113 | 114 | // Copy the data over. 115 | c.Copy(b.Bytes()) 116 | 117 | // Destroy the old one and reassign its variable. 118 | b.Destroy() 119 | b = c 120 | } 121 | 122 | // Attempt to read a single byte. 123 | n, err := r.Read(b.Bytes()[i : i+1]) 124 | if n != 1 { // if we did not read a byte 125 | if err == nil { // and there was no error 126 | i-- // try again 127 | continue 128 | } 129 | // if instead there was an error, we're done early 130 | if i == 0 { // no data read 131 | b.Destroy() 132 | return newNullBuffer(), err 133 | } 134 | d := NewBuffer(i) 135 | d.Copy(b.Bytes()[:i]) 136 | d.Freeze() 137 | b.Destroy() 138 | return d, err 139 | } 140 | // we managed to read a byte, check if it was the delimiter 141 | // note that errors are ignored in this case where we got data 142 | if b.Bytes()[i] == delim { 143 | if i == 0 { 144 | // if first byte was delimiter, there's no data to return 145 | b.Destroy() 146 | return newNullBuffer(), nil 147 | } 148 | d := NewBuffer(i) 149 | d.Copy(b.Bytes()[:i]) 150 | d.Freeze() 151 | b.Destroy() 152 | return d, nil 153 | } 154 | } 155 | } 156 | 157 | /* 158 | NewBufferFromEntireReader reads from an io.Reader into an immutable buffer. It will continue reading until EOF. 159 | 160 | A nil error is returned precisely when we managed to read all the way until EOF. Any data read is returned in either case. 161 | */ 162 | func NewBufferFromEntireReader(r io.Reader) (*LockedBuffer, error) { 163 | // Create a buffer with a data region of one page size. 164 | b := NewBuffer(os.Getpagesize()) 165 | 166 | for read := 0; ; { 167 | // Attempt to read some data from the reader. 168 | n, err := r.Read(b.Bytes()[read:]) 169 | 170 | // Nothing read but no error, try again. 171 | if n == 0 && err == nil { 172 | continue 173 | } 174 | 175 | // 1) so either have data and no error 176 | // 2) or have error and no data 177 | // 3) or both have data and have error 178 | 179 | // Increment the read count by the number of bytes that we just read. 180 | read += n 181 | 182 | if err != nil { 183 | // Suppress EOF error 184 | if err == io.EOF { 185 | err = nil 186 | } 187 | // We're done, return the data. 188 | if read == 0 { 189 | // No data read. 190 | b.Destroy() 191 | return newNullBuffer(), err 192 | } 193 | d := NewBuffer(read) 194 | d.Copy(b.Bytes()[:read]) 195 | d.Freeze() 196 | b.Destroy() 197 | return d, err 198 | } 199 | 200 | // If we've filled this buffer, grow it by another page size. 201 | if len(b.Bytes()[read:]) == 0 { 202 | d := NewBuffer(b.Size() + os.Getpagesize()) 203 | d.Copy(b.Bytes()) 204 | b.Destroy() 205 | b = d 206 | } 207 | } 208 | } 209 | 210 | /* 211 | NewBufferRandom constructs an immutable buffer filled with cryptographically-secure random bytes. 212 | */ 213 | func NewBufferRandom(size int) *LockedBuffer { 214 | // Construct a buffer of the specified size. 215 | b := NewBuffer(size) 216 | if b.Size() == 0 { 217 | return b 218 | } 219 | 220 | // Fill the buffer with random bytes. 221 | b.Scramble() 222 | 223 | // Make the buffer immutable. 224 | b.Freeze() 225 | 226 | // Return the created Buffer object. 227 | return b 228 | } 229 | 230 | // Freeze makes a LockedBuffer's memory immutable. The call can be reversed with Melt. 231 | func (b *LockedBuffer) Freeze() { 232 | b.Buffer.Freeze() 233 | } 234 | 235 | // Melt makes a LockedBuffer's memory mutable. The call can be reversed with Freeze. 236 | func (b *LockedBuffer) Melt() { 237 | b.Buffer.Melt() 238 | } 239 | 240 | /* 241 | Seal takes a LockedBuffer object and returns its contents encrypted inside a sealed Enclave object. The LockedBuffer is subsequently destroyed and its contents wiped. 242 | 243 | If Seal is called on a destroyed buffer, a nil enclave is returned. 244 | */ 245 | func (b *LockedBuffer) Seal() *Enclave { 246 | e, err := core.Seal(b.Buffer) 247 | if err != nil { 248 | if err == core.ErrBufferExpired { 249 | return nil 250 | } 251 | core.Panic(err) 252 | } 253 | return &Enclave{e} 254 | } 255 | 256 | /* 257 | Copy performs a time-constant copy into a LockedBuffer. Move is preferred if the source is not also a LockedBuffer or if the source is no longer needed. 258 | */ 259 | func (b *LockedBuffer) Copy(src []byte) { 260 | b.CopyAt(0, src) 261 | } 262 | 263 | /* 264 | CopyAt performs a time-constant copy into a LockedBuffer at an offset. Move is preferred if the source is not also a LockedBuffer or if the source is no longer needed. 265 | */ 266 | func (b *LockedBuffer) CopyAt(offset int, src []byte) { 267 | if !b.IsAlive() { 268 | return 269 | } 270 | 271 | b.Lock() 272 | defer b.Unlock() 273 | 274 | core.Copy(b.Bytes()[offset:], src) 275 | } 276 | 277 | /* 278 | Move performs a time-constant move into a LockedBuffer. The source is wiped after the bytes are copied. 279 | */ 280 | func (b *LockedBuffer) Move(src []byte) { 281 | b.MoveAt(0, src) 282 | } 283 | 284 | /* 285 | MoveAt performs a time-constant move into a LockedBuffer at an offset. The source is wiped after the bytes are copied. 286 | */ 287 | func (b *LockedBuffer) MoveAt(offset int, src []byte) { 288 | if !b.IsAlive() { 289 | return 290 | } 291 | 292 | b.Lock() 293 | defer b.Unlock() 294 | 295 | core.Move(b.Bytes()[offset:], src) 296 | } 297 | 298 | /* 299 | Scramble attempts to overwrite the data with cryptographically-secure random bytes. 300 | */ 301 | func (b *LockedBuffer) Scramble() { 302 | if !b.IsAlive() { 303 | return 304 | } 305 | 306 | b.Buffer.Scramble() 307 | } 308 | 309 | /* 310 | Wipe attempts to overwrite the data with zeros. 311 | */ 312 | func (b *LockedBuffer) Wipe() { 313 | if !b.IsAlive() { 314 | return 315 | } 316 | 317 | b.Lock() 318 | defer b.Unlock() 319 | 320 | core.Wipe(b.Bytes()) 321 | } 322 | 323 | /* 324 | Size gives you the length of a given LockedBuffer's data segment. A destroyed LockedBuffer will have a size of zero. 325 | */ 326 | func (b *LockedBuffer) Size() int { 327 | return len(b.Bytes()) 328 | } 329 | 330 | /* 331 | Destroy wipes and frees the underlying memory of a LockedBuffer. The LockedBuffer will not be accessible or usable after this calls is made. 332 | */ 333 | func (b *LockedBuffer) Destroy() { 334 | b.Buffer.Destroy() 335 | } 336 | 337 | /* 338 | IsAlive returns a boolean value indicating if a LockedBuffer is alive, i.e. that it has not been destroyed. 339 | */ 340 | func (b *LockedBuffer) IsAlive() bool { 341 | return b.Buffer.Alive() 342 | } 343 | 344 | /* 345 | IsMutable returns a boolean value indicating if a LockedBuffer is mutable. 346 | */ 347 | func (b *LockedBuffer) IsMutable() bool { 348 | return b.Buffer.Mutable() 349 | } 350 | 351 | /* 352 | EqualTo performs a time-constant comparison on the contents of a LockedBuffer with a given buffer. A destroyed LockedBuffer will always return false. 353 | */ 354 | func (b *LockedBuffer) EqualTo(buf []byte) bool { 355 | b.RLock() 356 | defer b.RUnlock() 357 | 358 | return core.Equal(b.Bytes(), buf) 359 | } 360 | 361 | /* 362 | Functions for representing the memory region as various data types. 363 | */ 364 | 365 | /* 366 | Bytes returns a byte slice referencing the protected region of memory. 367 | */ 368 | func (b *LockedBuffer) Bytes() []byte { 369 | return b.Buffer.Data() 370 | } 371 | 372 | /* 373 | Reader returns a Reader object referencing the protected region of memory. 374 | */ 375 | func (b *LockedBuffer) Reader() *bytes.Reader { 376 | return bytes.NewReader(b.Bytes()) 377 | } 378 | 379 | /* 380 | String returns a string representation of the protected region of memory. 381 | */ 382 | func (b *LockedBuffer) String() string { 383 | slice := b.Bytes() 384 | return *(*string)(unsafe.Pointer(&slice)) 385 | } 386 | 387 | /* 388 | Uint16 returns a slice pointing to the protected region of memory with the data represented as a sequence of unsigned 16 bit integers. Its length will be half that of the byte slice, excluding any remaining part that doesn't form a complete uint16 value. 389 | 390 | If called on a destroyed LockedBuffer, a nil slice will be returned. 391 | */ 392 | func (b *LockedBuffer) Uint16() []uint16 { 393 | 394 | // Check if still alive. 395 | if !b.Buffer.Alive() { 396 | return nil 397 | } 398 | 399 | b.RLock() 400 | defer b.RUnlock() 401 | 402 | // Compute size of new slice representation. 403 | size := b.Size() / 2 404 | if size < 1 { 405 | return nil 406 | } 407 | 408 | // Construct the new slice representation. 409 | var sl = struct { 410 | addr uintptr 411 | len int 412 | cap int 413 | }{uintptr(unsafe.Pointer(&b.Bytes()[0])), size, size} 414 | 415 | // Cast the representation to the correct type and return it. 416 | return *(*[]uint16)(unsafe.Pointer(&sl)) 417 | } 418 | 419 | /* 420 | Uint32 returns a slice pointing to the protected region of memory with the data represented as a sequence of unsigned 32 bit integers. Its length will be one quarter that of the byte slice, excluding any remaining part that doesn't form a complete uint32 value. 421 | 422 | If called on a destroyed LockedBuffer, a nil slice will be returned. 423 | */ 424 | func (b *LockedBuffer) Uint32() []uint32 { 425 | 426 | // Check if still alive. 427 | if !b.Buffer.Alive() { 428 | return nil 429 | } 430 | 431 | b.RLock() 432 | defer b.RUnlock() 433 | 434 | // Compute size of new slice representation. 435 | size := b.Size() / 4 436 | if size < 1 { 437 | return nil 438 | } 439 | 440 | // Construct the new slice representation. 441 | var sl = struct { 442 | addr uintptr 443 | len int 444 | cap int 445 | }{uintptr(unsafe.Pointer(&b.Bytes()[0])), size, size} 446 | 447 | // Cast the representation to the correct type and return it. 448 | return *(*[]uint32)(unsafe.Pointer(&sl)) 449 | } 450 | 451 | /* 452 | Uint64 returns a slice pointing to the protected region of memory with the data represented as a sequence of unsigned 64 bit integers. Its length will be one eighth that of the byte slice, excluding any remaining part that doesn't form a complete uint64 value. 453 | 454 | If called on a destroyed LockedBuffer, a nil slice will be returned. 455 | */ 456 | func (b *LockedBuffer) Uint64() []uint64 { 457 | 458 | // Check if still alive. 459 | if !b.Buffer.Alive() { 460 | return nil 461 | } 462 | 463 | b.RLock() 464 | defer b.RUnlock() 465 | 466 | // Compute size of new slice representation. 467 | size := b.Size() / 8 468 | if size < 1 { 469 | return nil 470 | } 471 | 472 | // Construct the new slice representation. 473 | var sl = struct { 474 | addr uintptr 475 | len int 476 | cap int 477 | }{uintptr(unsafe.Pointer(&b.Bytes()[0])), size, size} 478 | 479 | // Cast the representation to the correct type and return it. 480 | return *(*[]uint64)(unsafe.Pointer(&sl)) 481 | } 482 | 483 | /* 484 | Int8 returns a slice pointing to the protected region of memory with the data represented as a sequence of signed 8 bit integers. If called on a destroyed LockedBuffer, a nil slice will be returned. 485 | */ 486 | func (b *LockedBuffer) Int8() []int8 { 487 | 488 | // Check if still alive. 489 | if !b.Buffer.Alive() { 490 | return nil 491 | } 492 | 493 | b.RLock() 494 | defer b.RUnlock() 495 | 496 | // Construct the new slice representation. 497 | var sl = struct { 498 | addr uintptr 499 | len int 500 | cap int 501 | }{uintptr(unsafe.Pointer(&b.Bytes()[0])), b.Size(), b.Size()} 502 | 503 | // Cast the representation to the correct type and return it. 504 | return *(*[]int8)(unsafe.Pointer(&sl)) 505 | } 506 | 507 | /* 508 | Int16 returns a slice pointing to the protected region of memory with the data represented as a sequence of signed 16 bit integers. Its length will be half that of the byte slice, excluding any remaining part that doesn't form a complete int16 value. 509 | 510 | If called on a destroyed LockedBuffer, a nil slice will be returned. 511 | */ 512 | func (b *LockedBuffer) Int16() []int16 { 513 | 514 | // Check if still alive. 515 | if !b.Buffer.Alive() { 516 | return nil 517 | } 518 | 519 | b.RLock() 520 | defer b.RUnlock() 521 | 522 | // Compute size of new slice representation. 523 | size := b.Size() / 2 524 | if size < 1 { 525 | return nil 526 | } 527 | 528 | // Construct the new slice representation. 529 | var sl = struct { 530 | addr uintptr 531 | len int 532 | cap int 533 | }{uintptr(unsafe.Pointer(&b.Bytes()[0])), size, size} 534 | 535 | // Cast the representation to the correct type and return it. 536 | return *(*[]int16)(unsafe.Pointer(&sl)) 537 | } 538 | 539 | /* 540 | Int32 returns a slice pointing to the protected region of memory with the data represented as a sequence of signed 32 bit integers. Its length will be one quarter that of the byte slice, excluding any remaining part that doesn't form a complete int32 value. 541 | 542 | If called on a destroyed LockedBuffer, a nil slice will be returned. 543 | */ 544 | func (b *LockedBuffer) Int32() []int32 { 545 | 546 | // Check if still alive. 547 | if !b.Buffer.Alive() { 548 | return nil 549 | } 550 | 551 | b.RLock() 552 | defer b.RUnlock() 553 | 554 | // Compute size of new slice representation. 555 | size := b.Size() / 4 556 | if size < 1 { 557 | return nil 558 | } 559 | 560 | // Construct the new slice representation. 561 | var sl = struct { 562 | addr uintptr 563 | len int 564 | cap int 565 | }{uintptr(unsafe.Pointer(&b.Bytes()[0])), size, size} 566 | 567 | // Cast the representation to the correct type and return it. 568 | return *(*[]int32)(unsafe.Pointer(&sl)) 569 | } 570 | 571 | /* 572 | Int64 returns a slice pointing to the protected region of memory with the data represented as a sequence of signed 64 bit integers. Its length will be one eighth that of the byte slice, excluding any remaining part that doesn't form a complete int64 value. 573 | 574 | If called on a destroyed LockedBuffer, a nil slice will be returned. 575 | */ 576 | func (b *LockedBuffer) Int64() []int64 { 577 | 578 | // Check if still alive. 579 | if !b.Buffer.Alive() { 580 | return nil 581 | } 582 | 583 | b.RLock() 584 | defer b.RUnlock() 585 | 586 | // Compute size of new slice representation. 587 | size := b.Size() / 8 588 | if size < 1 { 589 | return nil 590 | } 591 | 592 | // Construct the new slice representation. 593 | var sl = struct { 594 | addr uintptr 595 | len int 596 | cap int 597 | }{uintptr(unsafe.Pointer(&b.Bytes()[0])), size, size} 598 | 599 | // Cast the representation to the correct type and return it. 600 | return *(*[]int64)(unsafe.Pointer(&sl)) 601 | } 602 | 603 | /* 604 | ByteArray8 returns a pointer to some 8 byte array. Care must be taken not to dereference the pointer and instead pass it around as-is. 605 | 606 | The length of the buffer must be at least 8 bytes in size and the LockedBuffer should not be destroyed. In either of these cases a nil value is returned. 607 | */ 608 | func (b *LockedBuffer) ByteArray8() *[8]byte { 609 | 610 | // Check if still alive. 611 | if !b.Buffer.Alive() { 612 | return nil 613 | } 614 | 615 | b.RLock() 616 | defer b.RUnlock() 617 | 618 | // Check if the length is large enough. 619 | if len(b.Bytes()) < 8 { 620 | return nil 621 | } 622 | 623 | // Cast the representation to the correct type. 624 | return (*[8]byte)(unsafe.Pointer(&b.Bytes()[0])) 625 | } 626 | 627 | /* 628 | ByteArray16 returns a pointer to some 16 byte array. Care must be taken not to dereference the pointer and instead pass it around as-is. 629 | 630 | The length of the buffer must be at least 16 bytes in size and the LockedBuffer should not be destroyed. In either of these cases a nil value is returned. 631 | */ 632 | func (b *LockedBuffer) ByteArray16() *[16]byte { 633 | 634 | // Check if still alive. 635 | if !b.Buffer.Alive() { 636 | return nil 637 | } 638 | 639 | b.RLock() 640 | defer b.RUnlock() 641 | 642 | // Check if the length is large enough. 643 | if len(b.Bytes()) < 16 { 644 | return nil 645 | } 646 | 647 | // Cast the representation to the correct type. 648 | return (*[16]byte)(unsafe.Pointer(&b.Bytes()[0])) 649 | } 650 | 651 | /* 652 | ByteArray32 returns a pointer to some 32 byte array. Care must be taken not to dereference the pointer and instead pass it around as-is. 653 | 654 | The length of the buffer must be at least 32 bytes in size and the LockedBuffer should not be destroyed. In either of these cases a nil value is returned. 655 | */ 656 | func (b *LockedBuffer) ByteArray32() *[32]byte { 657 | 658 | // Check if still alive. 659 | if !b.Buffer.Alive() { 660 | return nil 661 | } 662 | 663 | b.RLock() 664 | defer b.RUnlock() 665 | 666 | // Check if the length is large enough. 667 | if len(b.Bytes()) < 32 { 668 | return nil 669 | } 670 | 671 | // Cast the representation to the correct type. 672 | return (*[32]byte)(unsafe.Pointer(&b.Bytes()[0])) 673 | } 674 | 675 | /* 676 | ByteArray64 returns a pointer to some 64 byte array. Care must be taken not to dereference the pointer and instead pass it around as-is. 677 | 678 | The length of the buffer must be at least 64 bytes in size and the LockedBuffer should not be destroyed. In either of these cases a nil value is returned. 679 | */ 680 | func (b *LockedBuffer) ByteArray64() *[64]byte { 681 | 682 | // Check if still alive. 683 | if !b.Buffer.Alive() { 684 | return nil 685 | } 686 | 687 | b.RLock() 688 | defer b.RUnlock() 689 | 690 | // Check if the length is large enough. 691 | if len(b.Bytes()) < 64 { 692 | return nil 693 | } 694 | 695 | // Cast the representation to the correct type. 696 | return (*[64]byte)(unsafe.Pointer(&b.Bytes()[0])) 697 | } 698 | -------------------------------------------------------------------------------- /buffer_test.go: -------------------------------------------------------------------------------- 1 | package memguard 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "errors" 7 | "io" 8 | mrand "math/rand" 9 | "os" 10 | "runtime" 11 | "testing" 12 | "unsafe" 13 | ) 14 | 15 | func TestPtrSafetyWithGC(t *testing.T) { 16 | dataToLock := []byte(`abcdefgh`) 17 | b := NewBufferFromBytes(dataToLock) 18 | dataPtr := b.Bytes() 19 | 20 | finalizerCalledChan := make(chan bool) 21 | runtime.SetFinalizer(b, func(_ *LockedBuffer) { 22 | finalizerCalledChan <- true 23 | }) 24 | // b is now unreachable 25 | 26 | runtime.GC() 27 | finalizerCalled := <-finalizerCalledChan 28 | if finalizerCalled == false { 29 | t.Error("this should never occur") 30 | } 31 | 32 | // Check that data hasn't been garbage collected 33 | if !bytes.Equal(dataPtr, []byte(`abcdefgh`)) { 34 | t.Error("data does not have the value we set") 35 | } 36 | } 37 | 38 | func TestNewBuffer(t *testing.T) { 39 | b := NewBuffer(32) 40 | if b == nil { 41 | t.Error("buffer should not be nil") 42 | } 43 | if len(b.Bytes()) != 32 || cap(b.Bytes()) != 32 { 44 | t.Error("buffer sizes incorrect") 45 | } 46 | if !bytes.Equal(b.Bytes(), make([]byte, 32)) { 47 | t.Error("buffer is not zeroed") 48 | } 49 | if !b.IsMutable() { 50 | t.Error("buffer should be mutable") 51 | } 52 | if !b.IsAlive() { 53 | t.Error("buffer should not be destroyed") 54 | } 55 | b.Destroy() 56 | b = NewBuffer(0) 57 | if b.Bytes() != nil { 58 | t.Error("data slice should be nil") 59 | } 60 | if b.Size() != 0 { 61 | t.Error("size should be zero", b.Size()) 62 | } 63 | if b.IsAlive() { 64 | t.Error("buffer should be destroyed") 65 | } 66 | if b.IsMutable() { 67 | t.Error("buffer should be immutable") 68 | } 69 | } 70 | 71 | func TestNewBufferFromBytes(t *testing.T) { 72 | data := []byte("yellow submarine") 73 | b := NewBufferFromBytes(data) 74 | if b == nil { 75 | t.Error("buffer should not be nil") 76 | } 77 | if len(b.Bytes()) != 16 || cap(b.Bytes()) != 16 { 78 | t.Error("buffer sizes invalid") 79 | } 80 | if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) { 81 | t.Error("data does not match\n", b.Bytes(), "\n", data) 82 | } 83 | if !bytes.Equal(data, make([]byte, 16)) { 84 | t.Error("source buffer not wiped") 85 | } 86 | if b.IsMutable() { 87 | t.Error("buffer should be immutable") 88 | } 89 | if !b.IsAlive() { 90 | t.Error("buffer should not be destroyed") 91 | } 92 | b.Destroy() 93 | b = NewBufferFromBytes([]byte{}) 94 | if b.Bytes() != nil { 95 | t.Error("data slice should be nil") 96 | } 97 | if b.Size() != 0 { 98 | t.Error("size should be zero", b.Size()) 99 | } 100 | if b.IsAlive() { 101 | t.Error("buffer should be destroyed") 102 | } 103 | if b.IsMutable() { 104 | t.Error("buffer should be immutable") 105 | } 106 | } 107 | 108 | func TestNewBufferFromReader(t *testing.T) { 109 | b, err := NewBufferFromReader(rand.Reader, 4096) 110 | if err != nil { 111 | t.Error(err) 112 | } 113 | if b.Size() != 4096 { 114 | t.Error("buffer of incorrect size") 115 | } 116 | if bytes.Equal(b.Bytes(), make([]byte, 4096)) { 117 | t.Error("didn't read from reader") 118 | } 119 | if b.IsMutable() { 120 | t.Error("expected buffer to be immutable") 121 | } 122 | b.Destroy() 123 | 124 | r := bytes.NewReader([]byte("yellow submarine")) 125 | b, err = NewBufferFromReader(r, 16) 126 | if err != nil { 127 | t.Error(err) 128 | } 129 | if b.Size() != 16 { 130 | t.Error("buffer of incorrect size") 131 | } 132 | if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) { 133 | t.Error("incorrect data") 134 | } 135 | if b.IsMutable() { 136 | t.Error("expected buffer to be immutable") 137 | } 138 | b.Destroy() 139 | 140 | r = bytes.NewReader([]byte("yellow submarine")) 141 | b, err = NewBufferFromReader(r, 17) 142 | if err == nil { 143 | t.Error("expected error got nil;", err) 144 | } 145 | if b.Size() != 16 { 146 | t.Error("incorrect size") 147 | } 148 | if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) { 149 | t.Error("incorrect data") 150 | } 151 | if b.IsMutable() { 152 | t.Error("expected buffer to be immutable") 153 | } 154 | b.Destroy() 155 | 156 | r = bytes.NewReader([]byte("")) 157 | b, err = NewBufferFromReader(r, 32) 158 | if err == nil { 159 | t.Error("expected error got nil") 160 | } 161 | if b.IsAlive() { 162 | t.Error("expected destroyed buffer") 163 | } 164 | if b.IsMutable() { 165 | t.Error("expected immutable buffer") 166 | } 167 | if b.Size() != 0 { 168 | t.Error("expected nul sized buffer") 169 | } 170 | r = bytes.NewReader([]byte("yellow submarine")) 171 | b, err = NewBufferFromReader(r, 0) 172 | if err != nil { 173 | t.Error(err) 174 | } 175 | if b.Bytes() != nil { 176 | t.Error("data slice should be nil") 177 | } 178 | if b.Size() != 0 { 179 | t.Error("size should be zero", b.Size()) 180 | } 181 | if b.IsAlive() { 182 | t.Error("buffer should be destroyed") 183 | } 184 | if b.IsMutable() { 185 | t.Error("buffer should be immutable") 186 | } 187 | } 188 | 189 | type s struct { 190 | count int 191 | } 192 | 193 | func (reader *s) Read(p []byte) (n int, err error) { 194 | if mrand.Intn(2) == 0 { 195 | return 0, nil 196 | } 197 | reader.count++ 198 | if reader.count == 5000 { 199 | copy(p, []byte{1}) 200 | return 1, nil 201 | } 202 | copy(p, []byte{0}) 203 | return 1, nil 204 | } 205 | 206 | func TestNewBufferFromReaderUntil(t *testing.T) { 207 | data := make([]byte, 5000) 208 | data[4999] = 1 209 | r := bytes.NewReader(data) 210 | b, err := NewBufferFromReaderUntil(r, 1) 211 | if err != nil { 212 | t.Error(err) 213 | } 214 | if b.Size() != 4999 { 215 | t.Error("buffer has incorrect size") 216 | } 217 | for i := range b.Bytes() { 218 | if b.Bytes()[i] != 0 { 219 | t.Error("incorrect data") 220 | } 221 | } 222 | if b.IsMutable() { 223 | t.Error("expected buffer to be immutable") 224 | } 225 | b.Destroy() 226 | 227 | r = bytes.NewReader(data[:32]) 228 | b, err = NewBufferFromReaderUntil(r, 1) 229 | if err == nil { 230 | t.Error("expected error got nil") 231 | } 232 | if b.Size() != 32 { 233 | t.Error("invalid size") 234 | } 235 | for i := range b.Bytes() { 236 | if b.Bytes()[i] != 0 { 237 | t.Error("incorrect data") 238 | } 239 | } 240 | if b.IsMutable() { 241 | t.Error("expected buffer to be immutable") 242 | } 243 | b.Destroy() 244 | 245 | r = bytes.NewReader([]byte{'x'}) 246 | b, err = NewBufferFromReaderUntil(r, 'x') 247 | if err != nil { 248 | t.Error(err) 249 | } 250 | if b.Size() != 0 { 251 | t.Error("expected no data") 252 | } 253 | if b.IsAlive() { 254 | t.Error("expected dead buffer") 255 | } 256 | 257 | r = bytes.NewReader([]byte("")) 258 | b, err = NewBufferFromReaderUntil(r, 1) 259 | if err == nil { 260 | t.Error("expected error got nil") 261 | } 262 | if b.IsAlive() { 263 | t.Error("expected destroyed buffer") 264 | } 265 | if b.IsMutable() { 266 | t.Error("expected immutable buffer") 267 | } 268 | if b.Size() != 0 { 269 | t.Error("expected nul sized buffer") 270 | } 271 | 272 | rr := new(s) 273 | b, err = NewBufferFromReaderUntil(rr, 1) 274 | if err != nil { 275 | t.Error(err) 276 | } 277 | if b.Size() != 4999 { 278 | t.Error("invalid size") 279 | } 280 | for i := range b.Bytes() { 281 | if b.Bytes()[i] != 0 { 282 | t.Error("invalid data") 283 | } 284 | } 285 | if b.IsMutable() { 286 | t.Error("expected buffer to be immutable") 287 | } 288 | b.Destroy() 289 | } 290 | 291 | type ss struct { 292 | count int 293 | } 294 | 295 | func (reader *ss) Read(p []byte) (n int, err error) { 296 | if mrand.Intn(2) == 0 { 297 | return 0, nil 298 | } 299 | reader.count++ 300 | if reader.count == 5000 { 301 | return 0, io.EOF 302 | } 303 | copy(p, []byte{0}) 304 | return 1, nil 305 | } 306 | 307 | type se struct { 308 | count int 309 | } 310 | 311 | func (reader *se) Read(p []byte) (n int, err error) { 312 | copy(p, []byte{0}) 313 | reader.count++ 314 | if reader.count == 5000 { 315 | return 1, errors.New("shut up bro") 316 | } 317 | return 1, nil 318 | } 319 | 320 | func TestNewBufferFromEntireReader(t *testing.T) { 321 | r := bytes.NewReader([]byte("yellow submarine")) 322 | b, err := NewBufferFromEntireReader(r) 323 | if err != nil { 324 | t.Error(err) 325 | } 326 | if b.Size() != 16 { 327 | t.Error("incorrect size", b.Size()) 328 | } 329 | if !b.EqualTo([]byte("yellow submarine")) { 330 | t.Error("incorrect data", b.String()) 331 | } 332 | if b.IsMutable() { 333 | t.Error("buffer should be immutable") 334 | } 335 | b.Destroy() 336 | 337 | data := make([]byte, 16000) 338 | ScrambleBytes(data) 339 | r = bytes.NewReader(data) 340 | b, err = NewBufferFromEntireReader(r) 341 | if err != nil { 342 | t.Error(err) 343 | } 344 | if b.Size() != len(data) { 345 | t.Error("incorrect size", b.Size()) 346 | } 347 | if !b.EqualTo(data) { 348 | t.Error("incorrect data") 349 | } 350 | if b.IsMutable() { 351 | t.Error("buffer should be immutable") 352 | } 353 | b.Destroy() 354 | 355 | r = bytes.NewReader([]byte{}) 356 | b, err = NewBufferFromEntireReader(r) 357 | if err != nil { 358 | t.Error(err) 359 | } 360 | if b.Size() != 0 { 361 | t.Error("buffer should be nil size") 362 | } 363 | if b.IsAlive() { 364 | t.Error("buffer should appear destroyed") 365 | } 366 | 367 | rr := new(ss) 368 | b, err = NewBufferFromEntireReader(rr) 369 | if err != nil { 370 | t.Error(err) 371 | } 372 | if b.Size() != 4999 { 373 | t.Error("incorrect size", b.Size()) 374 | } 375 | if !b.EqualTo(make([]byte, 4999)) { 376 | t.Error("incorrect data") 377 | } 378 | if b.IsMutable() { 379 | t.Error("buffer should be immutable") 380 | } 381 | b.Destroy() 382 | 383 | re := new(se) 384 | b, err = NewBufferFromEntireReader(re) 385 | if err == nil { 386 | t.Error("expected error got nil") 387 | } 388 | if b.Size() != 5000 { 389 | t.Error(b.Size()) 390 | } 391 | if !b.EqualTo(make([]byte, 5000)) { 392 | t.Error("incorrect data") 393 | } 394 | if b.IsMutable() { 395 | t.Error("buffer should be immutable") 396 | } 397 | b.Destroy() 398 | 399 | // real world test 400 | f, err := os.Open("LICENSE") 401 | if err != nil { 402 | t.Error(err) 403 | } 404 | data, err = io.ReadAll(f) 405 | if err != nil { 406 | t.Error(err) 407 | } 408 | _, err = f.Seek(0, 0) 409 | if err != nil { 410 | t.Error(err) 411 | } 412 | b, err = NewBufferFromEntireReader(f) 413 | if err != nil { 414 | t.Error(err) 415 | } 416 | if !b.EqualTo(data) { 417 | t.Error("incorrect data") 418 | } 419 | if b.IsMutable() { 420 | t.Error("buffer should be immutable") 421 | } 422 | b.Destroy() 423 | f.Close() 424 | } 425 | 426 | func TestNewBufferRandom(t *testing.T) { 427 | b := NewBufferRandom(32) 428 | if b == nil { 429 | t.Error("buffer is nil") 430 | } 431 | if len(b.Bytes()) != 32 || cap(b.Bytes()) != 32 { 432 | t.Error("buffer sizes incorrect") 433 | } 434 | if bytes.Equal(b.Bytes(), make([]byte, 32)) { 435 | t.Error("buffer is zeroed") 436 | } 437 | if b.IsMutable() { 438 | t.Error("buffer should be immutable") 439 | } 440 | if !b.IsAlive() { 441 | t.Error("buffer should not be destroyed") 442 | } 443 | b.Destroy() 444 | b = NewBufferRandom(0) 445 | if b.Bytes() != nil { 446 | t.Error("data slice should be nil") 447 | } 448 | if b.Size() != 0 { 449 | t.Error("size should be zero", b.Size()) 450 | } 451 | if b.IsAlive() { 452 | t.Error("buffer should be destroyed") 453 | } 454 | if b.IsMutable() { 455 | t.Error("buffer should be immutable") 456 | } 457 | } 458 | 459 | func TestFreeze(t *testing.T) { 460 | b := NewBuffer(8) 461 | if b == nil { 462 | t.Error("buffer is nil") 463 | } 464 | if !b.IsMutable() { 465 | t.Error("buffer isn't mutable") 466 | } 467 | b.Freeze() 468 | if b.IsMutable() { 469 | t.Error("buffer did not change to immutable") 470 | } 471 | if !bytes.Equal(b.Bytes(), make([]byte, 8)) { 472 | t.Error("buffer changed value") // also tests readability 473 | } 474 | b.Freeze() // Test idempotency 475 | if b.IsMutable() { 476 | t.Error("buffer should be immutable") 477 | } 478 | if !bytes.Equal(b.Bytes(), make([]byte, 8)) { 479 | t.Error("buffer changed value") // also tests readability 480 | } 481 | b.Destroy() 482 | b.Freeze() 483 | if b.IsMutable() { 484 | t.Error("buffer is mutable") 485 | } 486 | if b.IsAlive() { 487 | t.Error("buffer should be destroyed") 488 | } 489 | b = newNullBuffer() 490 | b.Freeze() 491 | if b.IsMutable() { 492 | t.Error("buffer should be immutable") 493 | } 494 | } 495 | 496 | func TestMelt(t *testing.T) { 497 | b := NewBuffer(8) 498 | if b == nil { 499 | t.Error("buffer is nil") 500 | } 501 | b.Freeze() 502 | if b.IsMutable() { 503 | t.Error("buffer is mutable") 504 | } 505 | b.Melt() 506 | if !b.IsMutable() { 507 | t.Error("buffer did not become mutable") 508 | } 509 | if !bytes.Equal(b.Bytes(), make([]byte, 8)) { 510 | t.Error("buffer changed value") // also tests readability 511 | } 512 | b.Bytes()[0] = 0x1 // test writability 513 | if b.Bytes()[0] != 0x1 { 514 | t.Error("buffer value not changed") 515 | } 516 | b.Melt() // Test idempotency 517 | if !b.IsMutable() { 518 | t.Error("buffer should be mutable") 519 | } 520 | b.Bytes()[0] = 0x2 521 | if b.Bytes()[0] != 0x2 { 522 | t.Error("buffer value not changed") 523 | } 524 | b.Destroy() 525 | b.Melt() 526 | if b.IsMutable() { 527 | t.Error("buffer shouldn't be mutable") 528 | } 529 | if b.IsAlive() { 530 | t.Error("buffer should be destroyed") 531 | } 532 | b = newNullBuffer() 533 | b.Melt() 534 | if b.IsMutable() { 535 | t.Error("buffer should be immutable") 536 | } 537 | } 538 | 539 | func TestSeal(t *testing.T) { 540 | b := NewBufferRandom(32) 541 | if b == nil { 542 | t.Error("buffer is nil") 543 | } 544 | data := make([]byte, 32) 545 | copy(data, b.Bytes()) 546 | e := b.Seal() 547 | if e == nil { 548 | t.Error("got nil enclave") 549 | } 550 | if b.IsAlive() { 551 | t.Error("buffer should be destroyed") 552 | } 553 | b, err := e.Open() 554 | if err != nil { 555 | t.Error("unexpected error;", err) 556 | } 557 | if !bytes.Equal(b.Bytes(), data) { 558 | t.Error("data does not match") 559 | } 560 | b.Destroy() 561 | e = b.Seal() // call on destroyed buffer 562 | if e != nil { 563 | t.Error("expected nil enclave") 564 | } 565 | } 566 | 567 | func TestCopy(t *testing.T) { 568 | b := NewBuffer(16) 569 | if b == nil { 570 | t.Error("buffer is nil") 571 | } 572 | b.Copy([]byte("yellow submarine")) 573 | if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) { 574 | t.Error("copy unsuccessful") 575 | } 576 | b.Destroy() 577 | b.Copy([]byte("yellow submarine")) 578 | if b.Bytes() != nil { 579 | t.Error("buffer should be destroyed") 580 | } 581 | b = newNullBuffer() 582 | b.Copy([]byte("yellow submarine")) 583 | } 584 | 585 | func TestCopyAt(t *testing.T) { 586 | b := NewBuffer(8) 587 | if b == nil { 588 | t.Error("got nil buffer") 589 | } 590 | b.CopyAt(0, []byte("1234")) 591 | if !bytes.Equal(b.Bytes()[:4], []byte("1234")) { 592 | t.Error("copy unsuccessful") 593 | } 594 | if !bytes.Equal(b.Bytes()[4:], []byte{0, 0, 0, 0}) { 595 | t.Error("copy overflow") 596 | } 597 | b.CopyAt(4, []byte("5678")) 598 | if !bytes.Equal(b.Bytes(), []byte("12345678")) { 599 | t.Error("copy unsuccessful") 600 | } 601 | b.Destroy() 602 | b.CopyAt(4, []byte("hmmm")) 603 | if b.Bytes() != nil { 604 | t.Error("buffer should be destroyed") 605 | } 606 | b = newNullBuffer() 607 | b.CopyAt(4, []byte("yellow submarine")) 608 | } 609 | 610 | func TestMove(t *testing.T) { 611 | b := NewBuffer(16) 612 | if b == nil { 613 | t.Error("buffer is nil") 614 | } 615 | b.Move([]byte("yellow submarine")) 616 | if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) { 617 | t.Error("copy unsuccessful") 618 | } 619 | data := []byte("yellow submarine") 620 | b.Move(data) 621 | for b := range data { 622 | if data[b] != 0x0 { 623 | t.Error("buffer was not wiped", b) 624 | } 625 | } 626 | b.Destroy() 627 | b.Move(data) 628 | if b.Bytes() != nil { 629 | t.Error("buffer should be destroyed") 630 | } 631 | b = newNullBuffer() 632 | b.Move([]byte("yellow submarine")) 633 | } 634 | 635 | func TestMoveAt(t *testing.T) { 636 | b := NewBuffer(8) 637 | if b == nil { 638 | t.Error("got nil buffer") 639 | } 640 | data := []byte("12345678") 641 | b.MoveAt(0, data[:4]) 642 | if !bytes.Equal(b.Bytes()[:4], []byte("1234")) { 643 | t.Error("copy unsuccessful") 644 | } 645 | if !bytes.Equal(b.Bytes()[4:], []byte{0, 0, 0, 0}) { 646 | t.Error("copy overflow") 647 | } 648 | b.MoveAt(4, data[4:]) 649 | if !bytes.Equal(b.Bytes(), []byte("12345678")) { 650 | t.Error("copy unsuccessful") 651 | } 652 | if !bytes.Equal(data, make([]byte, 8)) { 653 | t.Error("buffer not wiped") 654 | } 655 | b.Destroy() 656 | b.MoveAt(4, []byte("hmmm")) 657 | if b.Bytes() != nil { 658 | t.Error("buffer should be destroyed") 659 | } 660 | b = newNullBuffer() 661 | b.MoveAt(4, []byte("yellow submarine")) 662 | } 663 | 664 | func TestScramble(t *testing.T) { 665 | b := NewBuffer(32) 666 | if b == nil { 667 | t.Error("buffer is nil") 668 | } 669 | b.Scramble() 670 | if bytes.Equal(b.Bytes(), make([]byte, 32)) { 671 | t.Error("buffer was not randomised") 672 | } 673 | one := make([]byte, 32) 674 | copy(one, b.Bytes()) 675 | b.Scramble() 676 | if bytes.Equal(b.Bytes(), make([]byte, 32)) { 677 | t.Error("buffer was not randomised") 678 | } 679 | if bytes.Equal(b.Bytes(), one) { 680 | t.Error("buffer did not change") 681 | } 682 | b.Destroy() 683 | b.Scramble() 684 | if b.Bytes() != nil { 685 | t.Error("buffer should be destroyed") 686 | } 687 | b = newNullBuffer() 688 | b.Scramble() 689 | } 690 | 691 | func TestWipe(t *testing.T) { 692 | b := NewBufferRandom(32) 693 | if b == nil { 694 | t.Error("got nil buffer") 695 | } 696 | b.Melt() 697 | if bytes.Equal(b.Bytes(), make([]byte, 32)) { 698 | t.Error("buffer was not randomised") 699 | } 700 | b.Wipe() 701 | for i := range b.Bytes() { 702 | if b.Bytes()[i] != 0 { 703 | t.Error("buffer was not wiped; index", i) 704 | } 705 | } 706 | b.Destroy() 707 | b.Wipe() 708 | if b.Bytes() != nil { 709 | t.Error("buffer should be destroyed") 710 | } 711 | b = newNullBuffer() 712 | b.Wipe() 713 | } 714 | 715 | func TestSize(t *testing.T) { 716 | b := NewBuffer(1234) 717 | if b == nil { 718 | t.Error("got nil buffer") 719 | } 720 | if b.Size() != 1234 { 721 | t.Error("size does not match expected") 722 | } 723 | b.Destroy() 724 | if b.Size() != 0 { 725 | t.Error("destroyed buffer size should be zero") 726 | } 727 | b = newNullBuffer() 728 | if b.Size() != 0 { 729 | t.Error("size should be zero") 730 | } 731 | } 732 | 733 | func TestDestroy(t *testing.T) { 734 | b := NewBuffer(32) 735 | if b == nil { 736 | t.Error("got nil buffer") 737 | } 738 | if b.Bytes() == nil { 739 | t.Error("expected buffer to not be nil") 740 | } 741 | if len(b.Bytes()) != 32 || cap(b.Bytes()) != 32 { 742 | t.Error("buffer sizes incorrect") 743 | } 744 | if !b.IsAlive() { 745 | t.Error("buffer should be alive") 746 | } 747 | if !b.IsMutable() { 748 | t.Error("buffer should be mutable") 749 | } 750 | b.Destroy() 751 | if b.Bytes() != nil { 752 | t.Error("expected buffer to be nil") 753 | } 754 | if len(b.Bytes()) != 0 || cap(b.Bytes()) != 0 { 755 | t.Error("buffer sizes incorrect") 756 | } 757 | if b.IsAlive() { 758 | t.Error("buffer should be destroyed") 759 | } 760 | if b.IsMutable() { 761 | t.Error("buffer should be immutable") 762 | } 763 | b.Destroy() 764 | if b.Bytes() != nil { 765 | t.Error("expected buffer to be nil") 766 | } 767 | if len(b.Bytes()) != 0 || cap(b.Bytes()) != 0 { 768 | t.Error("buffer sizes incorrect") 769 | } 770 | if b.IsAlive() { 771 | t.Error("buffer should be destroyed") 772 | } 773 | if b.IsMutable() { 774 | t.Error("buffer should be immutable") 775 | } 776 | b = newNullBuffer() 777 | b.Destroy() 778 | if b.IsAlive() { 779 | t.Error("buffer should be dead") 780 | } 781 | } 782 | 783 | func TestIsAlive(t *testing.T) { 784 | b := NewBuffer(8) 785 | if b == nil { 786 | t.Error("got nil buffer") 787 | } 788 | if !b.IsAlive() { 789 | t.Error("invalid state") 790 | } 791 | if b.IsAlive() != b.IsAlive() { 792 | t.Error("states don't match") 793 | } 794 | b.Destroy() 795 | if b.IsAlive() { 796 | t.Error("invalid state") 797 | } 798 | if b.IsAlive() != b.IsAlive() { 799 | t.Error("states don't match") 800 | } 801 | b = newNullBuffer() 802 | if b.IsAlive() { 803 | t.Error("buffer should be dead") 804 | } 805 | } 806 | 807 | func TestIsMutable(t *testing.T) { 808 | b := NewBuffer(8) 809 | if b == nil { 810 | t.Error("got nil buffer") 811 | } 812 | if !b.IsMutable() { 813 | t.Error("invalid state") 814 | } 815 | if b.IsMutable() != b.IsMutable() { 816 | t.Error("states don't match") 817 | } 818 | b.Freeze() 819 | if b.IsMutable() { 820 | t.Error("invalid state") 821 | } 822 | if b.IsMutable() != b.IsMutable() { 823 | t.Error("states don't match") 824 | } 825 | b.Destroy() 826 | if b.IsMutable() { 827 | t.Error("invalid state") 828 | } 829 | if b.IsMutable() != b.IsMutable() { 830 | t.Error("states don't match") 831 | } 832 | b = newNullBuffer() 833 | if b.IsMutable() { 834 | t.Error("buffer should be immutable") 835 | } 836 | } 837 | 838 | func TestEqualTo(t *testing.T) { 839 | b := NewBufferFromBytes([]byte("yellow submarine")) 840 | if !b.EqualTo([]byte("yellow submarine")) { 841 | t.Error("comparison incorrect") 842 | } 843 | if b.EqualTo([]byte("yellow")) { 844 | t.Error("comparison incorrect") 845 | } 846 | b.Destroy() 847 | if b.EqualTo([]byte("yellow submarine")) { 848 | t.Error("comparison with destroyed should be false") 849 | } 850 | b = newNullBuffer() 851 | if !b.EqualTo([]byte{}) { 852 | t.Error("buffer should be size zero") 853 | } 854 | } 855 | 856 | func TestBytes(t *testing.T) { 857 | b := NewBufferFromBytes([]byte("yellow submarine")) 858 | if b == nil { 859 | t.Error("got nil buffer") 860 | } 861 | if !bytes.Equal(b.Bytes(), []byte("yellow submarine")) { 862 | t.Error("not equal contents") 863 | } 864 | b.Melt() 865 | b.Bytes()[8] = ^b.Bytes()[8] 866 | if !bytes.Equal(b.Buffer.Data(), b.Bytes()) { 867 | t.Error("methods disagree") 868 | } 869 | b.Destroy() 870 | if b.Bytes() != nil { 871 | t.Error("expected nil buffer") 872 | } 873 | b = newNullBuffer() 874 | if b.Bytes() != nil { 875 | t.Error("buffer should be nil") 876 | } 877 | } 878 | 879 | func TestReader(t *testing.T) { 880 | b := NewBufferRandom(32) 881 | c, err := NewBufferFromReader(b.Reader(), 32) 882 | if err != nil { 883 | t.Error(err) 884 | } 885 | if !bytes.Equal(b.Bytes(), c.Bytes()) { 886 | t.Error("data not equal") 887 | } 888 | b.Destroy() 889 | c.Destroy() 890 | if c.Reader().Size() != 0 { 891 | t.Error("expected nul reader") 892 | } 893 | b = newNullBuffer() 894 | if c.Reader().Size() != 0 { 895 | t.Error("expected nul reader") 896 | } 897 | } 898 | 899 | func TestString(t *testing.T) { 900 | b := NewBufferRandom(32) 901 | b.Melt() 902 | s := b.String() 903 | for i := range b.Bytes() { 904 | b.Bytes()[i] = 'x' 905 | if string(b.Bytes()) != s { 906 | t.Error("string does not map same memory") 907 | } 908 | } 909 | b.Destroy() 910 | s = b.String() 911 | if s != "" { 912 | t.Error("string should be empty") 913 | } 914 | b = newNullBuffer() 915 | if s != "" { 916 | t.Error("string should be empty") 917 | } 918 | } 919 | 920 | func TestUint16(t *testing.T) { 921 | b := NewBuffer(32) 922 | if b == nil { 923 | t.Error("got nil buffer") 924 | } 925 | u16 := b.Uint16() 926 | if len(u16) != 16 || cap(u16) != 16 { 927 | t.Error("sizes incorrect") 928 | } 929 | if uintptr(unsafe.Pointer(&u16[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 930 | t.Error("pointer locations differ") 931 | } 932 | b.Destroy() 933 | b = NewBuffer(3) 934 | if b == nil { 935 | t.Error("got nil buffer") 936 | } 937 | u16 = b.Uint16() 938 | if len(u16) != 1 || cap(u16) != 1 { 939 | t.Error("sizes should be 1") 940 | } 941 | if uintptr(unsafe.Pointer(&u16[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 942 | t.Error("pointer locations differ") 943 | } 944 | b.Destroy() 945 | b = NewBuffer(1) 946 | if b == nil { 947 | t.Error("got nil buffer") 948 | } 949 | u16 = b.Uint16() 950 | if u16 != nil { 951 | t.Error("expected nil slice") 952 | } 953 | b.Destroy() 954 | if b.Uint16() != nil { 955 | t.Error("expected nil slice as buffer destroyed") 956 | } 957 | b = newNullBuffer() 958 | if b.Uint16() != nil { 959 | t.Error("should be nil") 960 | } 961 | } 962 | 963 | func TestUint32(t *testing.T) { 964 | b := NewBuffer(32) 965 | if b == nil { 966 | t.Error("got nil buffer") 967 | } 968 | u32 := b.Uint32() 969 | if len(u32) != 8 || cap(u32) != 8 { 970 | t.Error("sizes incorrect") 971 | } 972 | if uintptr(unsafe.Pointer(&u32[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 973 | t.Error("pointer locations differ") 974 | } 975 | b.Destroy() 976 | b = NewBuffer(5) 977 | if b == nil { 978 | t.Error("got nil buffer") 979 | } 980 | u32 = b.Uint32() 981 | if len(u32) != 1 || cap(u32) != 1 { 982 | t.Error("sizes should be 1") 983 | } 984 | if uintptr(unsafe.Pointer(&u32[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 985 | t.Error("pointer locations differ") 986 | } 987 | b.Destroy() 988 | b = NewBuffer(3) 989 | if b == nil { 990 | t.Error("got nil buffer") 991 | } 992 | u32 = b.Uint32() 993 | if u32 != nil { 994 | t.Error("expected nil slice") 995 | } 996 | b.Destroy() 997 | if b.Uint32() != nil { 998 | t.Error("expected nil slice as buffer destroyed") 999 | } 1000 | b = newNullBuffer() 1001 | if b.Uint32() != nil { 1002 | t.Error("should be nil") 1003 | } 1004 | } 1005 | 1006 | func TestUint64(t *testing.T) { 1007 | b := NewBuffer(32) 1008 | if b == nil { 1009 | t.Error("got nil buffer") 1010 | } 1011 | u64 := b.Uint64() 1012 | if len(u64) != 4 || cap(u64) != 4 { 1013 | t.Error("sizes incorrect") 1014 | } 1015 | if uintptr(unsafe.Pointer(&u64[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1016 | t.Error("pointer locations differ") 1017 | } 1018 | b.Destroy() 1019 | b = NewBuffer(9) 1020 | if b == nil { 1021 | t.Error("got nil buffer") 1022 | } 1023 | u64 = b.Uint64() 1024 | if len(u64) != 1 || cap(u64) != 1 { 1025 | t.Error("sizes should be 1") 1026 | } 1027 | if uintptr(unsafe.Pointer(&u64[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1028 | t.Error("pointer locations differ") 1029 | } 1030 | b.Destroy() 1031 | b = NewBuffer(7) 1032 | if b == nil { 1033 | t.Error("got nil buffer") 1034 | } 1035 | u64 = b.Uint64() 1036 | if u64 != nil { 1037 | t.Error("expected nil slice") 1038 | } 1039 | b.Destroy() 1040 | if b.Uint64() != nil { 1041 | t.Error("expected nil slice as buffer destroyed") 1042 | } 1043 | b = newNullBuffer() 1044 | if b.Uint64() != nil { 1045 | t.Error("should be nil") 1046 | } 1047 | } 1048 | 1049 | func TestInt8(t *testing.T) { 1050 | b := NewBuffer(32) 1051 | if b == nil { 1052 | t.Error("got nil buffer") 1053 | } 1054 | i8 := b.Int8() 1055 | if len(i8) != 32 || cap(i8) != 32 { 1056 | t.Error("sizes incorrect") 1057 | } 1058 | if uintptr(unsafe.Pointer(&i8[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1059 | t.Error("pointer locations differ") 1060 | } 1061 | b.Destroy() 1062 | if b.Int8() != nil { 1063 | t.Error("expected nil slice as buffer destroyed") 1064 | } 1065 | b = newNullBuffer() 1066 | if b.Int8() != nil { 1067 | t.Error("should be nil") 1068 | } 1069 | } 1070 | 1071 | func TestInt16(t *testing.T) { 1072 | b := NewBuffer(32) 1073 | if b == nil { 1074 | t.Error("got nil buffer") 1075 | } 1076 | i16 := b.Int16() 1077 | if len(i16) != 16 || cap(i16) != 16 { 1078 | t.Error("sizes incorrect") 1079 | } 1080 | if uintptr(unsafe.Pointer(&i16[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1081 | t.Error("pointer locations differ") 1082 | } 1083 | b.Destroy() 1084 | b = NewBuffer(3) 1085 | if b == nil { 1086 | t.Error("got nil buffer") 1087 | } 1088 | i16 = b.Int16() 1089 | if len(i16) != 1 || cap(i16) != 1 { 1090 | t.Error("sizes should be 1") 1091 | } 1092 | if uintptr(unsafe.Pointer(&i16[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1093 | t.Error("pointer locations differ") 1094 | } 1095 | b.Destroy() 1096 | b = NewBuffer(1) 1097 | if b == nil { 1098 | t.Error("got nil buffer") 1099 | } 1100 | i16 = b.Int16() 1101 | if i16 != nil { 1102 | t.Error("expected nil slice") 1103 | } 1104 | b.Destroy() 1105 | if b.Int16() != nil { 1106 | t.Error("expected nil slice as buffer destroyed") 1107 | } 1108 | b = newNullBuffer() 1109 | if b.Int16() != nil { 1110 | t.Error("should be nil") 1111 | } 1112 | } 1113 | 1114 | func TestInt32(t *testing.T) { 1115 | b := NewBuffer(32) 1116 | if b == nil { 1117 | t.Error("got nil buffer") 1118 | } 1119 | i32 := b.Int32() 1120 | if len(i32) != 8 || cap(i32) != 8 { 1121 | t.Error("sizes incorrect") 1122 | } 1123 | if uintptr(unsafe.Pointer(&i32[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1124 | t.Error("pointer locations differ") 1125 | } 1126 | b.Destroy() 1127 | b = NewBuffer(5) 1128 | if b == nil { 1129 | t.Error("got nil buffer") 1130 | } 1131 | i32 = b.Int32() 1132 | if len(i32) != 1 || cap(i32) != 1 { 1133 | t.Error("sizes should be 1") 1134 | } 1135 | if uintptr(unsafe.Pointer(&i32[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1136 | t.Error("pointer locations differ") 1137 | } 1138 | b.Destroy() 1139 | b = NewBuffer(3) 1140 | if b == nil { 1141 | t.Error("got nil buffer") 1142 | } 1143 | i32 = b.Int32() 1144 | if i32 != nil { 1145 | t.Error("expected nil slice") 1146 | } 1147 | b.Destroy() 1148 | if b.Int32() != nil { 1149 | t.Error("expected nil slice as buffer destroyed") 1150 | } 1151 | b = newNullBuffer() 1152 | if b.Int32() != nil { 1153 | t.Error("should be nil") 1154 | } 1155 | } 1156 | 1157 | func TestInt64(t *testing.T) { 1158 | b := NewBuffer(32) 1159 | if b == nil { 1160 | t.Error("got nil buffer") 1161 | } 1162 | i64 := b.Int64() 1163 | if len(i64) != 4 || cap(i64) != 4 { 1164 | t.Error("sizes incorrect") 1165 | } 1166 | if uintptr(unsafe.Pointer(&i64[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1167 | t.Error("pointer locations differ") 1168 | } 1169 | b.Destroy() 1170 | b = NewBuffer(9) 1171 | if b == nil { 1172 | t.Error("got nil buffer") 1173 | } 1174 | i64 = b.Int64() 1175 | if len(i64) != 1 || cap(i64) != 1 { 1176 | t.Error("sizes should be 1") 1177 | } 1178 | if uintptr(unsafe.Pointer(&i64[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1179 | t.Error("pointer locations differ") 1180 | } 1181 | b.Destroy() 1182 | b = NewBuffer(7) 1183 | if b == nil { 1184 | t.Error("got nil buffer") 1185 | } 1186 | i64 = b.Int64() 1187 | if i64 != nil { 1188 | t.Error("expected nil slice") 1189 | } 1190 | b.Destroy() 1191 | if b.Int64() != nil { 1192 | t.Error("expected nil slice as buffer destroyed") 1193 | } 1194 | b = newNullBuffer() 1195 | if b.Int32() != nil { 1196 | t.Error("should be nil") 1197 | } 1198 | } 1199 | 1200 | func TestByteArray8(t *testing.T) { 1201 | b := NewBuffer(8) 1202 | if b == nil { 1203 | t.Error("got nil buffer") 1204 | } 1205 | if uintptr(unsafe.Pointer(&b.ByteArray8()[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1206 | t.Error("pointer locations differ") 1207 | } 1208 | b.Destroy() 1209 | b = NewBuffer(7) 1210 | if b == nil { 1211 | t.Error("got nil buffer") 1212 | } 1213 | if b.ByteArray8() != nil { 1214 | t.Error("expected nil byte array") 1215 | } 1216 | b.Destroy() 1217 | if b.ByteArray8() != nil { 1218 | t.Error("expected nil byte array from destroyed buffer") 1219 | } 1220 | b = newNullBuffer() 1221 | if b.ByteArray8() != nil { 1222 | t.Error("should be nil") 1223 | } 1224 | } 1225 | 1226 | func TestByteArray16(t *testing.T) { 1227 | b := NewBuffer(16) 1228 | if b == nil { 1229 | t.Error("got nil buffer") 1230 | } 1231 | if uintptr(unsafe.Pointer(&b.ByteArray16()[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1232 | t.Error("pointer locations differ") 1233 | } 1234 | b.Destroy() 1235 | b = NewBuffer(15) 1236 | if b == nil { 1237 | t.Error("got nil buffer") 1238 | } 1239 | if b.ByteArray16() != nil { 1240 | t.Error("expected nil byte array") 1241 | } 1242 | b.Destroy() 1243 | if b.ByteArray16() != nil { 1244 | t.Error("expected nil byte array from destroyed buffer") 1245 | } 1246 | b = newNullBuffer() 1247 | if b.ByteArray16() != nil { 1248 | t.Error("should be nil") 1249 | } 1250 | } 1251 | 1252 | func TestByteArray32(t *testing.T) { 1253 | b := NewBuffer(32) 1254 | if b == nil { 1255 | t.Error("got nil buffer") 1256 | } 1257 | if uintptr(unsafe.Pointer(&b.ByteArray32()[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1258 | t.Error("pointer locations differ") 1259 | } 1260 | b.Destroy() 1261 | b = NewBuffer(31) 1262 | if b == nil { 1263 | t.Error("got nil buffer") 1264 | } 1265 | if b.ByteArray32() != nil { 1266 | t.Error("expected nil byte array") 1267 | } 1268 | b.Destroy() 1269 | if b.ByteArray32() != nil { 1270 | t.Error("expected nil byte array from destroyed buffer") 1271 | } 1272 | b = newNullBuffer() 1273 | if b.ByteArray32() != nil { 1274 | t.Error("should be nil") 1275 | } 1276 | } 1277 | 1278 | func TestByteArray64(t *testing.T) { 1279 | b := NewBuffer(64) 1280 | if b == nil { 1281 | t.Error("got nil buffer") 1282 | } 1283 | if uintptr(unsafe.Pointer(&b.ByteArray64()[0])) != uintptr(unsafe.Pointer(&b.Bytes()[0])) { 1284 | t.Error("pointer locations differ") 1285 | } 1286 | b.Destroy() 1287 | b = NewBuffer(63) 1288 | if b == nil { 1289 | t.Error("got nil buffer") 1290 | } 1291 | if b.ByteArray64() != nil { 1292 | t.Error("expected nil byte array") 1293 | } 1294 | b.Destroy() 1295 | if b.ByteArray64() != nil { 1296 | t.Error("expected nil byte array from destroyed buffer") 1297 | } 1298 | b = newNullBuffer() 1299 | if b.ByteArray64() != nil { 1300 | t.Error("should be nil") 1301 | } 1302 | } 1303 | -------------------------------------------------------------------------------- /examples/stream/memprof1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 296 | memguard.test 297 | 298 | 299 | cluster_L 300 | 301 | 302 | 303 | 304 | File: memguard.test 305 | 306 | 307 | File: memguard.test 308 | Type: alloc_space 309 | Time: May 20, 2020 at 10:44pm (BST) 310 | Showing nodes accounting for 23970.57MB, 99.42% of 24111.31MB total 311 | Dropped 27 nodes (cum <= 120.56MB) 312 | 313 | 314 | 315 | 316 | 317 | N1 318 | 319 | 320 | memguard 321 | newNullBuffer 322 | 15049.17MB (62.42%) 323 | 324 | 325 | 326 | 327 | 328 | NN1_0 329 | 330 | 331 | 332 | 333 | 334 | 176B 335 | 336 | 337 | 338 | 339 | 340 | N1->NN1_0 341 | 342 | 343 | 344 | 345 | 346 | 347 | 12720.13MB 348 | 349 | 350 | 351 | 352 | 353 | NN1_1 354 | 355 | 356 | 357 | 358 | 359 | 16B 360 | 361 | 362 | 363 | 364 | 365 | N1->NN1_1 366 | 367 | 368 | 369 | 370 | 371 | 372 | 2329.04MB 373 | 374 | 375 | 376 | 377 | 378 | N2 379 | 380 | 381 | secretbox 382 | sliceForAppend 383 | 8884.37MB (36.85%) 384 | 385 | 386 | 387 | 388 | 389 | NN2_0 390 | 391 | 392 | 393 | 394 | 395 | 18kB 396 | 397 | 398 | 399 | 400 | 401 | N2->NN2_0 402 | 403 | 404 | 405 | 406 | 407 | 408 | 6795.57MB 409 | 410 | 411 | 412 | 413 | 414 | NN2_1 415 | 416 | 417 | 418 | 419 | 420 | 16kB 421 | 422 | 423 | 424 | 425 | 426 | N2->NN2_1 427 | 428 | 429 | 430 | 431 | 432 | 433 | 2088.80MB 434 | 435 | 436 | 437 | 438 | 439 | N3 440 | 441 | 442 | testing 443 | (*B) 444 | launch 445 | 0 of 23752.61MB (98.51%) 446 | 447 | 448 | 449 | 450 | 451 | N4 452 | 453 | 454 | testing 455 | (*B) 456 | runN 457 | 0 of 24106.09MB (100%) 458 | 459 | 460 | 461 | 462 | 463 | N3->N4 464 | 465 | 466 | 467 | 468 | 469 | 470 | 23752.61MB 471 | 472 | 473 | 474 | 475 | 476 | N5 477 | 478 | 479 | memguard 480 | BenchmarkStreamRead 481 | 1.52MB (0.0063%) 482 | of 20340.85MB (84.36%) 483 | 484 | 485 | 486 | 487 | 488 | N4->N5 489 | 490 | 491 | 492 | 493 | 494 | 495 | 20340.85MB 496 | 497 | 498 | 499 | 500 | 501 | N12 502 | 503 | 504 | memguard 505 | BenchmarkStreamWrite 506 | 0.51MB (0.0021%) 507 | of 3765.23MB (15.62%) 508 | 509 | 510 | 511 | 512 | 513 | N4->N12 514 | 515 | 516 | 517 | 518 | 519 | 520 | 3765.23MB 521 | 522 | 523 | 524 | 525 | 526 | NN5_0 527 | 528 | 529 | 530 | 531 | 532 | 16kB 533 | 534 | 535 | 536 | 537 | 538 | N5->NN5_0 539 | 540 | 541 | 542 | 543 | 544 | 545 | 1.52MB 546 | 547 | 548 | 549 | 550 | 551 | N7 552 | 553 | 554 | memguard 555 | (*Stream) 556 | Write 557 | 0 of 6912.58MB (28.67%) 558 | 559 | 560 | 561 | 562 | 563 | N5->N7 564 | 565 | 566 | 567 | 568 | 569 | 570 | 3147.85MB 571 | 572 | 573 | 574 | 575 | 576 | N14 577 | 578 | 579 | memguard 580 | (*Stream) 581 | Read 582 | 0 of 17191.48MB (71.30%) 583 | 584 | 585 | 586 | 587 | 588 | N5->N14 589 | 590 | 591 | 592 | 593 | 594 | 595 | 17191.48MB 596 | 597 | 598 | 599 | 600 | 601 | N6 602 | 603 | 604 | memguard 605 | (*Stream) 606 | next 607 | 0 of 17191.48MB (71.30%) 608 | 609 | 610 | 611 | 612 | 613 | N6->N1 614 | 615 | 616 | 617 | 618 | 619 | 620 | 15049.17MB 621 | (inline) 622 | 623 | 624 | 625 | 626 | 627 | N13 628 | 629 | 630 | memguard 631 | (*Enclave) 632 | Open 633 | 0 of 2142.31MB (8.89%) 634 | 635 | 636 | 637 | 638 | 639 | N6->N13 640 | 641 | 642 | 643 | 644 | 645 | 646 | 2142.31MB 647 | 648 | 649 | 650 | 651 | 652 | N11 653 | 654 | 655 | memguard 656 | NewEnclave 657 | 4MB (0.017%) 658 | of 6893.08MB (28.59%) 659 | 660 | 661 | 662 | 663 | 664 | N7->N11 665 | 666 | 667 | 668 | 669 | 670 | 671 | 6893.08MB 672 | 673 | 674 | 675 | 676 | 677 | N8 678 | 679 | 680 | testing 681 | (*B) 682 | run1 683 | func1 684 | 0 of 353.48MB (1.47%) 685 | 686 | 687 | 688 | 689 | 690 | N8->N4 691 | 692 | 693 | 694 | 695 | 696 | 697 | 353.48MB 698 | 699 | 700 | 701 | 702 | 703 | N9 704 | 705 | 706 | core 707 | NewEnclave 708 | 15.50MB (0.064%) 709 | of 6889.08MB (28.57%) 710 | 711 | 712 | 713 | 714 | 715 | N10 716 | 717 | 718 | core 719 | Encrypt 720 | 15.50MB (0.064%) 721 | of 6811.07MB (28.25%) 722 | 723 | 724 | 725 | 726 | 727 | N9->N10 728 | 729 | 730 | 731 | 732 | 733 | 734 | 6811.07MB 735 | 736 | 737 | 738 | 739 | 740 | N18 741 | 742 | 743 | secretbox 744 | Seal 745 | 0 of 6795.57MB (28.18%) 746 | 747 | 748 | 749 | 750 | 751 | N10->N18 752 | 753 | 754 | 755 | 756 | 757 | 758 | 6795.57MB 759 | 760 | 761 | 762 | 763 | 764 | N11->N9 765 | 766 | 767 | 768 | 769 | 770 | 771 | 6889.08MB 772 | 773 | 774 | 775 | 776 | 777 | N12->N7 778 | 779 | 780 | 781 | 782 | 783 | 784 | 3764.73MB 785 | 786 | 787 | 788 | 789 | 790 | N16 791 | 792 | 793 | core 794 | Open 795 | 0 of 2137.31MB (8.86%) 796 | 797 | 798 | 799 | 800 | 801 | N13->N16 802 | 803 | 804 | 805 | 806 | 807 | 808 | 2137.31MB 809 | 810 | 811 | 812 | 813 | 814 | N14->N6 815 | 816 | 817 | 818 | 819 | 820 | 821 | 17191.48MB 822 | 823 | 824 | 825 | 826 | 827 | N15 828 | 829 | 830 | core 831 | Decrypt 832 | 0 of 2088.80MB (8.66%) 833 | 834 | 835 | 836 | 837 | 838 | N17 839 | 840 | 841 | secretbox 842 | Open 843 | 0 of 2088.80MB (8.66%) 844 | 845 | 846 | 847 | 848 | 849 | N15->N17 850 | 851 | 852 | 853 | 854 | 855 | 856 | 2088.80MB 857 | 858 | 859 | 860 | 861 | 862 | N16->N15 863 | 864 | 865 | 866 | 867 | 868 | 869 | 2088.80MB 870 | 871 | 872 | 873 | 874 | 875 | N17->N2 876 | 877 | 878 | 879 | 880 | 881 | 882 | 2088.80MB 883 | (inline) 884 | 885 | 886 | 887 | 888 | 889 | N18->N2 890 | 891 | 892 | 893 | 894 | 895 | 896 | 6795.57MB 897 | (inline) 898 | 899 | 900 | 901 | 902 | --------------------------------------------------------------------------------