TamaGo

By Andrea Barisani on 11 February, 2020

Andrea Barisani

11 February, 2020

Introduction

What you see in this screenshot is a secure shell (SSH) connection to a host, you might think that this is business as usual and wonder why is it worth showing off...

Well, you might be surprised to know that there is not a single line of C involved in serving this connection as well as the underlying terminal and functionality.

In fact we are connecting to WithSecures own USB armory Mk II single board computer, while its USB drivers, TCP/IP stack, SSH server and hosted applications are entirely written in the Go language, under our TamaGo framework.

Creating a Go unikernel

WithSecure Consulting is heavily involved in testing all kind of embedded systems, but we also build them as part of our security engineering offering, theWithSecure Foundry

For this reason we often develop secure embedded firmware from scratch, gathering on our know-how in breaking them in the first place.

In the realm of embedded system firmware development we are often confronted with two choices:

  • Creating firmware on micro-controllers with unsafe low level languages such as C
  • Creating firmware on more powerful System-on-Chips with higher level languages, but having to run complex OSes and dependencies underneath

Both choices are not ideal as in either scenario there is a considerable amount of potentially unsafe low level code, resulting in a considerable attack surface.

The concept of unikernels is not new, to reduce the attack surface of an individual application/functionality. However existing unikernel implementations, more often than not, are either hypervisor based or just hide low level languages and complexity away from its users, rather than eliminating it.

The TamaGo framework spawns from the desire of building secure firmware for small embedded systems without any C dependency and, in fact, without even having to run an operating systems, while at the same time leveraging on the full unencumbered capabilities of higher level languages such as Go.

At WithSecure Foundry we often developed firmware applications in Go as its cryptographic library is capable and highly trusted and its creation of statically linked compiled binaries well suites the need for embedded system firmware.

However we often see a burden in having to carry over a full blown Linux environment to launch such applications and therefore we wondered if we could simply have a free standing Go runtime, developing device driver in the same language, hence TamaGo was born!

Killing C

TamaGo reduces the attack surface of embedded systems firmware by removing any run time dependency on C code and inherently complex Operating Systems.

It does so with minimal changes to the Go run time and a set of packages to provide drivers for supported boards.

TamaGo currently provides drivers for the NXP i.MX6UL System-on-Chip family, such as the one used in the USB armory Mk II.

In about 3000 lines of code you get drivers for SoC initialization, USB, hardware accelerated encryption and random number generation, all written in a memory safe language!

Less is more

Using TamaGo requires a single import in your Go application:

package main
import (
_ "github.com/f-secure-foundry/tamago/usbarmory/mark-two"
)
func main() {
// your code
}

It's as easy as that!

Our work focuses in supporting free standing Go applications that can be coded as usual, while running on bare metal hardware and without an underlying OS.

You might think that such effort involves a lot of dirty hacks but in the end it truly does not, TamaGo is the direct result of realizing that the Go run time is surprisingly, albeit probably unintentionally, adept for being executed free standing and without dependencies.

We strongly believe that the adoption of new operating frameworks should hinge on trust, therefore we wanted to prove that a Go unikernel can be achieved with clean and minimal modifications to the existing Go runtime.

Much of the effort has been placed to understand whether Go bare metal support can be achieved without complex re-implementation of memory allocation, threading, ASM/C OS primitives that would “pollute” the Go run time to unacceptable levels.

Complexity is the enemy of verifiability and our work strongly focuses on existing Go run time code re-use.

Building Go firmware

If Go is your language of choice and you want to develop embedded system firmware you can grab a USB armory Mk II and run Go applications with very few limitations.

Our example application runs SSH, HTTP and HTTPS servers in pure Go.

We also plan to support more hardware, such as the Raspberry PI Zero, in the near future as adding new ARM targets doesn't require much effort.