SecDevOps is hard. So how do we go about exploring and implementing this amorphous topic while not losing sight of the goal of creating secure software? To be honest, there is no silver bullet — but there is hope. We are arguably at the dawn of a new era where it has never been easier to get into software development. Never before have so many resources been available between blogs, YouTube and conferences. With this information, we can look forward and see what currently exists that can help us create secure software.
In this post, I will discuss a technique I recently used to harden GO Binaries on Linux systems by limiting Syscalls while shifting as far left as possible.
The challenge: reducing the attack surface of GO Binaries on Linux
I recently found myself in the situation where I was working with two different binaries which were both being executed on the same Linux system and I wanted to implement SecComp filters to limit the Syscalls available to each process. Why would I want to do this? First, if either process were to be compromised in some way then at least the compromise would be limited to the Syscalls available to the compromised process. Not a silver bullet solution but at least this reduces the attack surface to a degree. Second, applying SecComp filters to individual processes allows me to ensure that the Syscalls available to process X are not necessarily available to process Y, which are both running on the same system. And last, this typically happens in Docker containers were a global SecComp filter is applied to the container which therefore applies the SecComp filters to all the processes in the container. This is not ideal unless both processes needed to make use of the exact same Syscalls, which they did not.
The solution: using libseccomp to specify SecComp filters
So where is the shift left in any of this? To achieve the goal of applying SecComp filters to specific processes, I made use of the libseccomp project which consists of a very useful library and bindings for Go. The libseccomp library allows you to specify SecComp filters programmatically which are then enforced at runtime. For example, if I wanted to prevent my Go binary from using the “chown” Syscall, I would use the following code:
Markup for GoLang code
filter, _ := seccomp.NewFilter(seccomp.ActAllow) filter.AddRule(seccomp.GetSyscallFromName("chown"), seccomp.ActErrno.SetReturnCode(int16(syscall.EPERM))) filter.Load()
I would then compile my Go code i.e go build -o binaryWithSeccomp, and voila! I now have a binary with embedded SecComp filters, and if this executed binary were to attempt to make use of the chown Syscall at any point, it would be blocked by the Kernel via SecComp.
This became very useful for my two processes as process X needed permissions to execute “chown” but process Y did not. A custom Docker SecComp profile would also not suffice. This new technique allowed me to create predefined Go SecComp filters programmatically which can be automatically injected into the Go binaries at build time at any point in the SDLC. Just be sure you are 100% sure which Syscalls a binary requires or you’ll end up deploying broken binaries. 🙂
Conclusion and next steps
In conclusion, this is just one of many capabilities out there to help us deliver secure code, and SecDevOps allows us to chain these together in an ever changing environment. Therefore, it is important for us to identify what capabilities exist and how they can be applied in the SDLC to achieve more secure software.
1. libseccomp – https://github.com/seccomp/libseccomp
2. libseccomp-golang – https://github.com/seccomp/libseccomp-golang
3. seccomp2 – http://man7.org/linux/man-pages/man2/seccomp.2.html