Toward a capabilities-based security model for Golang

178 views
Skip to first unread message

j...@tendermint.com

unread,
Dec 15, 2017, 6:05:24 PM12/15/17
to golang-nuts

This proposal is a combination of two proposals to evolve Golang towards a capabilties-based language.

  1. Modify the const keyword to allow holding mutable (and new immutable) types.
  2. Implement faster immutable struct assignment.

In Rust, immutability is deep, so you can't modify the internals of any object if your reference is not Mut. From https://doc.rust-lang.org/book/first-edition/mutability.html:

Mutability is a property of either a borrow (&mut) or a binding (let mut). This means that, for example, you cannot have a struct with some fields mutable and some immutable: The mutability of a struct is in its binding.

In Go, the design philosophy includes embeddable encapsulation. We can leverage the language rules to create a capabilities-based language. The addressable (mutable) vs non-addressable (immutable) struct semantics takes us 90% of the way there. What's missing I think are 2 things:

  1. Secure modules w/ the const-mutable type
  2. Compiler optimizations for immutable struct copying.

1. Secure modules w/ the const-mutable type

The prososal is to allow what would otherwise be assignable to a var, to const (but disallow function calls).

// These are all OK
const MyArray = make([]byte, 10)          // const mutable (slice) type (all slices are mutable)
const MyStruct = MyStruct{}               // const immutable (struct) type
const MyStructP = &MyStruct{}             // const mutable (struct) type
const MyStruct MyInterface = MyStruct{}   // const immutable (interface) type w/ struct value
const MyStructP MyInterface = &MyStruct{} // const mutable (interface) type w/ pointer value
const MyFunc = func(){...}                // const immutable func type (all funcs are immutable)

Here, const doesn't mean immutable. It just means you can't change the shallow value.

There is already a distinction between typed and untyped consts. This only works for typed consts, and further introduces a distinction between const-mutable const-immutable types. While the procedural behavior of const func types are always "immutable", they may have side effects.

The right hand side expression of a const declaration may not include a function call. This limits the scope of these new const-mutable and const-immutable to those that won't cause a const initialization fiasco (see https://groups.google.com/forum/m/#!topic/golang-nuts/BnjG3N77Ico).

The type of values you can assign to a const are more expressive, so we now have no excuse to declare module-level var variables which can be (maliciously or not) modified by anyone who imports the module.

(Of course we can declare a global function func GetMyStructP() *MyStruct { ... }) that more or less does the same thing, but nobody does that because it's easier to use var, and what's the point of doing the right thing unless there's a commitment to evolve the language toward a capabilities-based system?).

Go linters can start tagging exported global vars that aren't annotated to be safe to mutate by anyone.

Compiler optimizations for immutable struct copying.

You might have seen the rule of thumb for values-vs-pointers: if your struct has few fields, and you don’t need to mutate it, then you don’t need to use pointers. On the other hand, if the struct has too many fields, then you might want to use pointers.

I find it difficult to always tell ahead of time whether structs ought to be pointers or not, but recently I discovered a nice way to work around the performance problem:

type BigStructWrapper interface {}
type BigStruct struct { ... }

// cpy is immutable but this is slow.
// I've tested this w/ benchmarks and very large
// structs that are nested many levels.
{
  var str BigStruct = BigStruct{...}
  var cpy BigStruct = str
}

// cpy is still immutable and this is fast.
{
  var str BigStructWrapper = BigStruct{...}
  var cpy BigStructWrapper = str
}

Interface values are like pointers, but the value of an interface is not addressable so you can't mutate an interface value's shallow fields unless it's a pointer.

But a non-pointer struct is immutable, so I suspect it's possible for Golang to optimize the copying/assignment of these structs by allocating them somewhere outside the stack with ref counting. Then we have a complete capabilities-based security model for Golang.

(1) Forget the old rule about using pointers when the struct has too many fields. Always use non-pointer values if you don't want others to mutate your copy.

(2) Do not expose anything that can be mutated by a malicious party, even the users of your module.

My hope is that Golang can evolve in this way, and even evolve the plugin system, such that we can allow the plugging-in of relatively untrusted (e.g. after filtering the AST for bad imports etc) code (see https://golang.org/pkg/plugin/). This is already useful but if we also allow for plugins to become released, then I think that's everything we need to create safe runtime extensibility of Golang services.  We'd certainly use it for [Tendermint](https://github.com/tendermint/tendermint).

Jae Kwon

unread,
Dec 15, 2017, 6:18:38 PM12/15/17
to golang-nuts
I'll follow the procedure here tomorrow: https://github.com/golang/proposal
Meanwhile, any feedback appreciated!

matthe...@gmail.com

unread,
Dec 16, 2017, 10:56:30 AM12/16/17
to golang-nuts
My understanding of this model is you have a group of processes interacting with each other, each with a set of capabilities. Some of these processes load untrusted plugins into their memory space and make calls into them.

There's the problem of the plugin reading the key and using it for arbitrary purposes. The key is not guessable, but if stolen from a process then it could be rewritten to add that stolen capability to another compromised process.

What's stopping a malicious plugin from writing what it wants in its process space? If plugin security is a concern then I'd guess having them as a separate process would be better, which would mean const isn't necessary.

Can you provide some specific examples?

Matt

Jae Kwon

unread,
Dec 16, 2017, 11:54:02 AM12/16/17
to golang-nuts
I've posted to here: https://github.com/golang/go/issues/23157

I will post your question and a response there.
Reply all
Reply to author
Forward
0 new messages