Exploring SO_PEERSEC

Hank Jacobs
The Startup
Published in
3 min readOct 11, 2020

SO_PEERSEC was mentioned on an internal mailing list at work recently. Being unfamiliar with SO_PEERSEC, I did some cursory googling and came across some information but nothing that explained SO_PEERSEC in a way that I fully understood¹. This prompted me to do a deeper dive.

First stop was the trusty man-pages. I checked man(7) under both socket and unix and turned up nothing. It seemed likeSO_PEERSEC hadn’t been documented yet².

Next up, kernel source code spelunking! Doing a search for SO_PEERSEC definitely brought up a fair number of hits but nothing with any helpful comments or documentation. I was far too lazy to read the source at that point so I went back to the drawing board.

Revisiting Google, I dug up a patch specifically for adding SO_PEERSEC to man(7). Bingo. Here’s how the patch described it:

SO_PEERSECThis read-only socket option returns the
security context of the peer socket connected to this socket.
By default, this will be the same as the security context of
the process that created the peer socket unless overridden
by the policy or by a process with the required permissions.

Great! Now we’re getting somewhere. Connecting the above description with what’s described in the SO_PEERSEC patch painted a clearer picture for me. The original patch references SO_PEERSEC being exposed to Linux Security Modules (LSM). It would seem that SO_PEERSEC allows processes to fetch the security context of a peer connected to a given Unix socket where “security context” is LSM specific. Armed with a better understanding of SO_PEERSEC, I decided to experiment and see what the above definition actually meant in practice.

To start, I took a look at the Go standard library to see if SO_PEERSEC was exposed there. Unfortunately, it was not. That led me to create sopeersec, a Go package for invoking SO_PEERSEC on a socket. Once I got the relevant code working, I proceeded to come up with some contrived examples.

First, I wrote a small Go program:

package mainimport (
"fmt"
"log"
"net"
"os"
"syscall"
"github.com/hankjacobs/sopeersec"
)
func main() {

l, err := net.Listen("unix", "/home/ubuntu/test/test.socket")
if err != nil {
log.Fatal("listen error:", err)
}
defer l.Close()
defer os.Remove("/home/ubuntu/test/test.socket")
for {
conn, _ := l.Accept()
unixConn := conn.(*net.UnixConn)
osFile, _ := unixConn.File()
label, _ := sopeersec.GetsockoptPeerSec(int(osFile.Fd()), syscall.SOL_SOCKET) fmt.Println(string(label))
fmt.Println("---")
}
}

In short, this program opens a unix socket, waits for a process to connect to it, invokes SO_PEERSEC and prints its output.

With that program running, I used socat to connect to the socket:

socat - UNIX-CONNECT:/home/ubuntu/test/test.socket

That produced the following output from our test program:

unconfined
---

So far so good. This makes sense since netcat isn’t running under any LSM in my setup.

Now, let’s try this with a process that is confined.

From working with Docker, I know that AppArmor is an LSM that helps enforce access controls and is used by Docker to confine a container. With this knowledge, I spun up a Docker container that was able to access our test socket:

docker run --rm -it --mount type=bind,src=/home/ubuntu/test,destination=/test alpine/socat - UNIX-CONNECT:/test/test.socket

and with that, we get the following from our go program:

docker-default (enforce)
---

Awesome, something different! Let’s see if we can make sense of the output. Docker, by default, uses the docker-default AppArmor profile to confine a container. Also, AppArmor supports two modes, enforce or complain, with Docker opting to enforce the profile rather than just complain. Both of which explains why the output is docker-default (enforce). It’s worth pointing out that this output is specific to AppArmor. If, say, the program was running under SELinx, we’d get a different output. I’ll leave that as an exercise to the reader.

So where does this leave us? Well, in summary, SO_PEERSEC is a read-only getsocketopt operation that allows a caller to get the security context of a peer connected to a Unix socket. For example, a server process can get the AppArmor profile and mode (“enforce” or “complain”) of a client process if it is running under AppArmor. The security context can then be used by the server to do things such as authenticate or authorize the client.

¹ In hindsight, the patch introducing SO_PEERSEC does a great job at succinctly explaining it but I was lacking the requiste knowledge for it to click

² I later stumbled across a patch that adds this documentation. More on that later.

--

--