Repository: opencontainers/runc
Pull request #2682 (2020-11-13 19:59:24)

Implement seccomp notify support
Add support for notify seccomp actions. Seccomp profiles can now specify the SCMP_ACT_NOTIFY action that notifies a user space application (seccomp agent) when such syscall is performed. The seccomp agent can, in turn, decide to perform the syscall on behalf of the container and set the syscall return values or even inject file descriptors to the container. This, for example, allows for unprivileged containers to run some privileged syscalls that the seccomp agent performs on its behalf.

The format and more details can be found in the OCI runtime specification, in the [seccomp section][oci-seccomp]. If you are a developer and want more information, check out the [linux kernel documentation][linux-doc], [libseccomp-golang][libseccomp] and [Christian Brauner's blog post][brauner-blog]. To use this feature you need Linux >= 5.7 and libseccomp >= 2.5.0.
 
[oci-seccomp]: https://github.com/opencontainers/runtime-spec/blob/20a2d9782986ec2a7e0812ebe1515f73736c6a0c/config-linux.md#seccomp 
[linux-doc]: https://www.kernel.org/doc/html/v5.7/userspace-api/seccomp_filter.html#userspace-notification 
[libseccomp]: https://github.com/seccomp/libseccomp-golang 
[brauner-blog]: https://brauner.github.io/2020/07/23/seccomp-notify.html 

kolyshkin: CI failure is a known (but rare) flake, see https://github.com/opencontainers/runc/issues/2425
```
=== RUN   TestExecInTTY
    execin_test.go:349: unexpected carriage-return in output
--- FAIL: TestExecInTTY (0.15s)
```

(restarted the job)
vbatts: https://github.com/opencontainers/runtime-spec/pull/1074 merged now
rata: Hi! I'm a coworker of @mauriciovasquezbernal, I'll coordinate with him to fix the conflicts in this PR and remove the draft when ready! :)
rata: Hi!

I’ve rebased the PR and fixed all the conflicts, all tests should be green too.

The OCI runtime-spec change this patch implements [was recently merged][oci] and I see that [the spec change is already merged in runc][runc-spec] too. Should we mark this PR for 1.0 milestone? Currently it is in the post 1.0 milestone.

I’m keeping the PR as a draft for now, as the [libseccomp-golang PR][libseccomp] is not yet merged and we need it for this PR (not only for tests, we use some consts in core runc too).  However, changes on the libseccomp PR will have minimal (most likely none) consequences to this code. So, we keep it as draft until that PR is merged, but please review whenever you can :)


[oci]: https://github.com/opencontainers/runtime-spec/pull/1074
[runc-spec]: https://github.com/opencontainers/runc/commit/dd2caace166b1f1614c446f8bb32268cdd0b1c3c 
[libseccomp]: https://github.com/seccomp/libseccomp-golang/pull/59 
rata: Updated, fixed a conflict in go.sum too
rata: Rebased on top of latest master
alban: Thanks for the reviews!

* https://github.com/seccomp/libseccomp-golang/pull/59 has been merged so I updated the vendoring: we don't need to rely on the fork anymore.
* I rebased the branch
* I removed the 'draft' status of this PR.
* I added the fixes that @rata prepared.
* Still two concerns from @kolyshkin that I need to check:
  1. [ ] Explain possible restrictions on read/write in the commitmsg (syncParentReady after seccomp)
  2. [ ] Comment "seccomp as close to execve as possible"
alban: I rebased the branch and addressed the 2 last comments. The tests pass fine now.

rata: Rebased on top of latest master to fix some conflicts.
rata: @AkihiroSuda @kolyshkin I know you are probably busy with runc 1.0 release, but when you have time PTAL. I've rebased on master to fix conflicts and removed astyle and golint (unneeded dependencies)

Also, what do you think of moving the milestone to 1.1.0 maybe? (now it is in post-1.0) It will be great for us to merge soon :)
rata: Rebased on top of latest master
rata: I fixed the conflicts caused by the recent merge of https://github.com/opencontainers/runc/pull/3011. It took me some hours, though, and is getting painful to maintain such a big change out of tree.

@kolyshkin @AkihiroSuda any chance you can review this soon, so we hopefully merge this soon too? :)

Please note [as I mentioned previously][comment] that the spec change this patch implements is [already vendored in runc master][runc-spec] too. So, merging this PR is the only thing left.

[runc-spec]: https://github.com/opencontainers/runc/commit/dd2caace166b1f1614c446f8bb32268cdd0b1c3c 
[comment]: https://github.com/opencontainers/runc/pull/2682#issuecomment-802867420
rata: @kolyshkin @AkihiroSuda friendly ping?

There are conflicts again, I'll rebase and fix them, but having a review will greatly help anyways :)
rata: Rebased on top of latest master and fixed the conflicts. CI on my fork is green too :)
rata: @kolyshkin @AkihiroSuda friendly ping?

I've just rebased and did a very minor change: improve the error msg to this as suggested by @thaJeztah [in the moby PR][link]. The change is this line:

https://github.com/opencontainers/runc/pull/2682/files#diff-f0214a0f16408fc7f168c6fc9837d189590025cc1813ebf7c1d751136936dfbfR727

Before if the seccomp agent was not running the error was:
```
$ sudo ../runc/runc run mycontainer
ERRO[0000] unable to start container process: dial unix /tmp/socket.sock: connect: no such file or directory 
```

Now it is:
```
$ sudo ../runc/runc run mycontainer
ERRO[0000] unable to start container process: failed to connect with seccomp agent specified in the seccomp profile: dial unix /tmp/socket.sock: connect: no such file or directory 
```

[link]: https://github.com/moby/moby/pull/42604#issuecomment-876763471
rata: @AkihiroSuda thanks! PTAL. I addressed all the review comments.

I've also added one more integration test (to test returning errors too, that was missing an integration test).

PTAL, thanks again for the review! :)
rata: I've updated the PR with some simple changes:
* Fixed compilation when building without seccomp build tag (`make BUILDTAGS="" all`): Added `func main()` to `contrib/cmd/seccompagent/unsupported.go` so compilation works fine
* Removed `skopeo` dep from `.github/workflows/test.yml`: it was not needed.
* Rename `SCMP_ACT_IMAGINARY_ACTION` --> `SCMP_ACT_NON_EXIXTENT_ACTION` and `SCMP_ARCH_IMAGINARY_ARCH` --> `SCMP_ARCH_NON_EXISTENT_ARCH` in tests added by this PR
* Removed a redundant `TODO`: we were adding a TODO comment in `libcontainer/seccomp/seccomp_linux.go` saying seccomp flags are ignored. However, this is already mentioned [here][link], it is already the case without this PR and there is nothing in this PR that makes it relevant to add such a comment IMHO. So, just removed the TODO comment we were adding.

[link]: https://github.com/opencontainers/runc/blob/713748d4a8ff418bc8218ffb5f6c80ed396b8c1a/libcontainer/specconv/spec_linux.go#L850-L853
AkihiroSuda: Sorry needs rebase
rata: @AkihiroSuda thanks a lot! Rebased and the simple conflict is fixed :)

Also, I've added another simple test case: Check that starting when no seccomp agent is running fails with a clear error (the test was missing, no code change was needed).

CI should be green too (just run the CI on a fork) :tada: 
rata: Just rebased to solve the trivial conflicts with the merges done some hours ago.

@AkihiroSuda It lost your LGTM after the rebase. Can you please LGTM again? :)

@kolyshkin @cyphar ping? :)
rata: @kolyshkin thanks, I've addressed all the review comments (except [this one][link] that not sure I follow). PTAL :)

[link]: https://github.com/opencontainers/runc/pull/2682#discussion_r682821640
rata: Oh, it seems the cirrus checks are not run on my fork so that's why it doesn't work. Will see that, but please have a look. The runc code, except for some added things to the cirrus CI, won't need to change :)
rata: Ok, the issue should be fixed now :)

It was failing because, when splitting the e2e test for old kernels in a different file (for [this comment][link]), I added a check for the actual error msg (not just the return code as it was before). The error msg was not what I expected, therefore it failed. However, the error runc show before was not clear. So, I've kept the expected error (clear error msg output) in the test and fixed the runc code to show a clear error. See the test in `tests/integration/seccomp-notify-compat.bats` for the error msg.

@kolyshkin PTAL, thanks!

[link]: https://github.com/opencontainers/runc/pull/2682#discussion_r683560945
rata: @kolyshkin Improved that too, thanks! PTAL :)
rata: @kolyshkin cool, thanks for the review! I addressed all the comments. I've also cherry-picked your commit from #3171 and dropped the commit to download it manually.

All tests are green too. If you want to merge PR #3171 first, I can drop the commit here (or maybe git is smart enough that we don't even need to drop it?)
rata: PR #3171 was just merged, so I rebased this. I hope this is ready to go in now :crossed_fingers: 
kolyshkin: @rata can you modify the PR description to include a proposed changelog entry for runc 1.1.0? A few sentences telling about the feature, it's requirements and possible uses. A draft is fine -- maintainers may edit it later.
rata: @kolyshkin sure!
rata: @kolyshkin added the changelog description with all those items!

@AkihiroSuda maybe you want to review again now? :)

The centos-stream-8 failure seems unrelated to this PR (the same code did past the tests on my fork). It was stuck trying to installing dependencies and it seems there are several retries already in place to work-around some issues. It might be related to that
kolyshkin: centos-stream-8 CI hit a snag; restarted
AkihiroSuda: @cyphar PTAL

rata: Friendly ping?

Hi! The 1.1.0 milestone is due in ~2 weeks and it will be nice to see this feature there (this is planned for that milestone so far). Is there anything else I can do?
vbatts: dang. This has had 2 LGTMs, but hoping for a @cyphar review.
Now it needs to be rebased again. 😵 
@opencontainers/runc-maintainers can we get eyes on this?
rata: I rebased to fix the conflicts, tests are green. But the LGTM are lost after the rebase. @kolyshkin @AkihiroSuda can you please LGTM again? :)
kolyshkin: @AkihiroSuda this needs your still-LGTM after a rebase.

@cyphar PTAL

Patch:
diff --git a/.gitignore b/.gitignore
index 98775d60..76aefa1e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ vendor/pkg
 /runc-*
 contrib/cmd/recvtty/recvtty
 contrib/cmd/sd-helper/sd-helper
+contrib/cmd/seccompagent/seccompagent
 man/man8
 release
 Vagrantfile
diff --git a/Makefile b/Makefile
index f83c3ec8..7ddf3fae 100644
--- a/Makefile
+++ b/Makefile
@@ -30,11 +30,14 @@ GO_BUILD_STATIC := CGO_ENABLED=1 $(GO) build -trimpath $(EXTRA_FLAGS) -tags "$(B
 runc:
 	$(GO_BUILD) -o runc .
 
-all: runc recvtty sd-helper
+all: runc recvtty sd-helper seccompagent
 
 recvtty sd-helper:
 	$(GO_BUILD) -o contrib/cmd/$@/$@ ./contrib/cmd/$@
 
+seccompagent:
+	$(GO_BUILD) -o contrib/cmd/seccompagent/seccompagent ./contrib/cmd/seccompagent
+
 static:
 	$(GO_BUILD_STATIC) -o runc .
 	$(GO_BUILD_STATIC) -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
diff --git a/contrib/cmd/seccompagent/README.md b/contrib/cmd/seccompagent/README.md
new file mode 100644
index 00000000..84fe7c14
--- /dev/null
+++ b/contrib/cmd/seccompagent/README.md
@@ -0,0 +1,62 @@
+# Seccomp Agent
+
+## Warning
+
+Please note this is an example agent, as such it is possible that specially
+crafted messages can produce bad behaviour. Please use it as an example only.
+
+Also, this agent is used for integration tests. Be aware that changing the
+behaviour can break the integration tests.
+
+## Get started
+
+Compile runc and seccompagent:
+```bash
+make all
+```
+
+Run the seccomp agent in the background:
+```bash
+sudo ./contrib/cmd/seccompagent/seccompagent &
+```
+
+Prepare a container:
+```bash
+mkdir container-seccomp-notify
+cd container-seccomp-notify
+mkdir rootfs
+docker export $(docker create busybox) | tar -C rootfs -xvf -
+```
+
+Copy the example `config.json` file from the directory where this README.md is
+to the container directory you prepared earlier (`container-seccomp-notify`).
+This is a config.json as generated by `runc spec` at time of writing, with only
+the `args` and `seccomp` sections modified.
+
+Then start the container:
+```bash
+runc run mycontainerid
+```
+
+The container will output something like this:
+```bash
++ cd /dev/shm
++ mkdir test-dir
++ touch test-file
++ chmod 777 test-file
+chmod: changing permissions of 'test-file': No medium found
++ ls -l /dev/shm
+total 0
+drwxr-xr-x 2 root root 40 Jul 21 14:09 test-dir-foo
+-rw-r--r-- 1 root root  0 Jul 21 14:09 test-file
++ echo Note the agent added a suffix for the directory name and chmod fails
+Note the agent added a suffix for the directory name and chmod fails
+```
+
+This shows a simple example that runs in /dev/shm just because it is a tmpfs in
+the example config.json.
+
+The agent makes all chmod calls fail with ENOMEDIUM, as the example output shows.
+
+For mkdir, the agent adds a "-foo" suffix: the container runs "mkdir test-dir"
+but the directory created is "test-dir-foo".
diff --git a/contrib/cmd/seccompagent/config.json b/contrib/cmd/seccompagent/config.json
new file mode 100644
index 00000000..e4f91810
--- /dev/null
+++ b/contrib/cmd/seccompagent/config.json
@@ -0,0 +1,197 @@
+{
+	"ociVersion": "1.0.2-dev",
+	"process": {
+		"terminal": true,
+		"user": {
+			"uid": 0,
+			"gid": 0
+		},
+		"args": [
+			"sh",
+			"-c",
+			"set -x; cd /dev/shm; mkdir test-dir; touch test-file; chmod 777 test-file; ls -l /dev/shm; echo \"Note the agent added a suffix for the directory name and chmod fails\" "
+		],
+		"env": [
+			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+			"TERM=xterm"
+		],
+		"cwd": "/",
+		"capabilities": {
+			"bounding": [
+				"CAP_AUDIT_WRITE",
+				"CAP_KILL",
+				"CAP_NET_BIND_SERVICE"
+			],
+			"effective": [
+				"CAP_AUDIT_WRITE",
+				"CAP_KILL",
+				"CAP_NET_BIND_SERVICE"
+			],
+			"inheritable": [
+				"CAP_AUDIT_WRITE",
+				"CAP_KILL",
+				"CAP_NET_BIND_SERVICE"
+			],
+			"permitted": [
+				"CAP_AUDIT_WRITE",
+				"CAP_KILL",
+				"CAP_NET_BIND_SERVICE"
+			],
+			"ambient": [
+				"CAP_AUDIT_WRITE",
+				"CAP_KILL",
+				"CAP_NET_BIND_SERVICE"
+			]
+		},
+		"rlimits": [
+			{
+				"type": "RLIMIT_NOFILE",
+				"hard": 1024,
+				"soft": 1024
+			}
+		],
+		"noNewPrivileges": true
+	},
+	"root": {
+		"path": "rootfs",
+		"readonly": true
+	},
+	"hostname": "runc",
+	"mounts": [
+		{
+			"destination": "/proc",
+			"type": "proc",
+			"source": "proc"
+		},
+		{
+			"destination": "/dev",
+			"type": "tmpfs",
+			"source": "tmpfs",
+			"options": [
+				"nosuid",
+				"strictatime",
+				"mode=755",
+				"size=65536k"
+			]
+		},
+		{
+			"destination": "/dev/pts",
+			"type": "devpts",
+			"source": "devpts",
+			"options": [
+				"nosuid",
+				"noexec",
+				"newinstance",
+				"ptmxmode=0666",
+				"mode=0620",
+				"gid=5"
+			]
+		},
+		{
+			"destination": "/dev/shm",
+			"type": "tmpfs",
+			"source": "shm",
+			"options": [
+				"nosuid",
+				"noexec",
+				"nodev",
+				"mode=1777",
+				"size=65536k"
+			]
+		},
+		{
+			"destination": "/dev/mqueue",
+			"type": "mqueue",
+			"source": "mqueue",
+			"options": [
+				"nosuid",
+				"noexec",
+				"nodev"
+			]
+		},
+		{
+			"destination": "/sys",
+			"type": "sysfs",
+			"source": "sysfs",
+			"options": [
+				"nosuid",
+				"noexec",
+				"nodev",
+				"ro"
+			]
+		},
+		{
+			"destination": "/sys/fs/cgroup",
+			"type": "cgroup",
+			"source": "cgroup",
+			"options": [
+				"nosuid",
+				"noexec",
+				"nodev",
+				"relatime",
+				"ro"
+			]
+		}
+	],
+	"linux": {
+		"seccomp": {
+			"defaultAction": "SCMP_ACT_ALLOW",
+			"listenerPath": "/run/seccomp-agent.socket",
+			"listenerMetadata": "foo",
+			"architectures": [ "SCMP_ARCH_X86", "SCMP_ARCH_X32" ],
+			"syscalls": [
+				{
+					"names": [ "chmod", "fchmod", "fchmodat", "mkdir" ],
+					"action": "SCMP_ACT_NOTIFY"
+				}
+			]
+		},
+		"resources": {
+			"devices": [
+				{
+					"allow": false,
+					"access": "rwm"
+				}
+			]
+		},
+		"namespaces": [
+			{
+				"type": "pid"
+			},
+			{
+				"type": "network"
+			},
+			{
+				"type": "ipc"
+			},
+			{
+				"type": "uts"
+			},
+			{
+				"type": "mount"
+			},
+			{
+				"type": "cgroup"
+			}
+		],
+		"maskedPaths": [
+			"/proc/acpi",
+			"/proc/asound",
+			"/proc/kcore",
+			"/proc/keys",
+			"/proc/latency_stats",
+			"/proc/timer_list",
+			"/proc/timer_stats",
+			"/proc/sched_debug",
+			"/sys/firmware",
+			"/proc/scsi"
+		],
+		"readonlyPaths": [
+			"/proc/bus",
+			"/proc/fs",
+			"/proc/irq",
+			"/proc/sys",
+			"/proc/sysrq-trigger"
+		]
+	}
+}
diff --git a/contrib/cmd/seccompagent/seccompagent.go b/contrib/cmd/seccompagent/seccompagent.go
new file mode 100644
index 00000000..02714d64
--- /dev/null
+++ b/contrib/cmd/seccompagent/seccompagent.go
@@ -0,0 +1,291 @@
+// +build linux,cgo,seccomp
+
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"os"
+	"path/filepath"
+	"strings"
+
+	securejoin "github.com/cyphar/filepath-securejoin"
+	"github.com/opencontainers/runtime-spec/specs-go"
+	libseccomp "github.com/seccomp/libseccomp-golang"
+	"github.com/sirupsen/logrus"
+	"golang.org/x/sys/unix"
+)
+
+var (
+	socketFile string
+	pidFile    string
+)
+
+func closeStateFds(recvFds []int) {
+	for i := range recvFds {
+		unix.Close(i)
+	}
+}
+
+// parseStateFds returns the seccomp-fd and closes the rest of the fds in recvFds.
+// In case of error, no fd is closed.
+// StateFds is assumed to be formatted as specs.ContainerProcessState.Fds and
+// recvFds the corresponding list of received fds in the same SCM_RIGHT message.
+func parseStateFds(stateFds []string, recvFds []int) (uintptr, error) {
+	// Let's find the index in stateFds of the seccomp-fd.
+	idx := -1
+	err := false
+
+	for i, name := range stateFds {
+		if name == specs.SeccompFdName && idx == -1 {
+			idx = i
+			continue
+		}
+
+		// We found the seccompFdName twice. Error out!
+		if name == specs.SeccompFdName && idx != -1 {
+			err = true
+		}
+	}
+
+	if idx == -1 || err {
+		return 0, errors.New("seccomp fd not found or malformed containerProcessState.Fds")
+	}
+
+	if idx >= len(recvFds) || idx < 0 {
+		return 0, errors.New("seccomp fd index out of range")
+	}
+
+	fd := uintptr(recvFds[idx])
+
+	for i := range recvFds {
+		if i == idx {
+			continue
+		}
+
+		unix.Close(recvFds[i])
+	}
+
+	return fd, nil
+}
+
+func handleNewMessage(sockfd int) (uintptr, string, error) {
+	const maxNameLen = 4096
+	stateBuf := make([]byte, maxNameLen)
+	oobSpace := unix.CmsgSpace(4)
+	oob := make([]byte, oobSpace)
+
+	n, oobn, _, _, err := unix.Recvmsg(sockfd, stateBuf, oob, 0)
+	if err != nil {
+		return 0, "", err
+	}
+	if n >= maxNameLen || oobn != oobSpace {
+		return 0, "", fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
+	}
+
+	// Truncate.
+	stateBuf = stateBuf[:n]
+	oob = oob[:oobn]
+
+	scms, err := unix.ParseSocketControlMessage(oob)
+	if err != nil {
+		return 0, "", err
+	}
+	if len(scms) != 1 {
+		return 0, "", fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
+	}
+	scm := scms[0]
+
+	fds, err := unix.ParseUnixRights(&scm)
+	if err != nil {
+		return 0, "", err
+	}
+
+	containerProcessState := &specs.ContainerProcessState{}
+	err = json.Unmarshal(stateBuf, containerProcessState)
+	if err != nil {
+		closeStateFds(fds)
+		return 0, "", fmt.Errorf("cannot parse OCI state: %w", err)
+	}
+
+	fd, err := parseStateFds(containerProcessState.Fds, fds)
+	if err != nil {
+		closeStateFds(fds)
+		return 0, "", err
+	}
+
+	return fd, containerProcessState.Metadata, nil
+}
+
+func readArgString(pid uint32, offset int64) (string, error) {
+	buffer := make([]byte, 4096) // PATH_MAX
+
+	memfd, err := unix.Open(fmt.Sprintf("/proc/%d/mem", pid), unix.O_RDONLY, 0o777)
+	if err != nil {
+		return "", err
+	}
+	defer unix.Close(memfd)
+
+	_, err = unix.Pread(memfd, buffer, offset)
+	if err != nil {
+		return "", err
+	}
+
+	buffer[len(buffer)-1] = 0
+	s := buffer[:bytes.IndexByte(buffer, 0)]
+	return string(s), nil
+}
+
+func runMkdirForContainer(pid uint32, fileName string, mode uint32, metadata string) error {
+	// We validated before that metadata is not a string that can make
+	// newFile a file in a different location other than root.
+	newFile := fmt.Sprintf("%s-%s", fileName, metadata)
+	root := fmt.Sprintf("/proc/%d/cwd/", pid)
+
+	if strings.HasPrefix(fileName, "/") {
+		// If it starts with /, use the rootfs as base
+		root = fmt.Sprintf("/proc/%d/root/", pid)
+	}
+
+	path, err := securejoin.SecureJoin(root, newFile)
+	if err != nil {
+		return err
+	}
+
+	return unix.Mkdir(path, mode)
+}
+
+// notifHandler handles seccomp notifications and responses
+func notifHandler(fd libseccomp.ScmpFd, metadata string) {
+	defer unix.Close(int(fd))
+	for {
+		req, err := libseccomp.NotifReceive(fd)
+		if err != nil {
+			logrus.Errorf("Error in NotifReceive(): %s", err)
+			continue
+		}
+		syscallName, err := req.Data.Syscall.GetName()
+		if err != nil {
+			logrus.Errorf("Error decoding syscall %v(): %s", req.Data.Syscall, err)
+			continue
+		}
+		logrus.Debugf("Received syscall %q, pid %v, arch %q, args %+v", syscallName, req.Pid, req.Data.Arch, req.Data.Args)
+
+		resp := &libseccomp.ScmpNotifResp{
+			ID:    req.ID,
+			Error: 0,
+			Val:   0,
+			Flags: libseccomp.NotifRespFlagContinue,
+		}
+
+		// TOCTOU check
+		if err := libseccomp.NotifIDValid(fd, req.ID); err != nil {
+			logrus.Errorf("TOCTOU check failed: req.ID is no longer valid: %s", err)
+			continue
+		}
+
+		switch syscallName {
+		case "mkdir":
+			fileName, err := readArgString(req.Pid, int64(req.Data.Args[0]))
+			if err != nil {
+				logrus.Errorf("Cannot read argument: %s", err)
+				resp.Error = int32(unix.ENOSYS)
+				resp.Val = ^uint64(0) // -1
+				goto sendResponse
+			}
+
+			logrus.Debugf("mkdir: %q", fileName)
+
+			// TOCTOU check
+			if err := libseccomp.NotifIDValid(fd, req.ID); err != nil {
+				logrus.Errorf("TOCTOU check failed: req.ID is no longer valid: %s", err)
+				continue
+			}
+
+			err = runMkdirForContainer(req.Pid, fileName, uint32(req.Data.Args[1]), metadata)
+			if err != nil {
+				resp.Error = int32(unix.ENOSYS)
+				resp.Val = ^uint64(0) // -1
+			}
+			resp.Flags = 0
+		case "chmod", "fchmod", "fchmodat":
+			resp.Error = int32(unix.ENOMEDIUM)
+			resp.Val = ^uint64(0) // -1
+			resp.Flags = 0
+		}
+
+	sendResponse:
+		if err = libseccomp.NotifRespond(fd, resp); err != nil {
+			logrus.Errorf("Error in notification response: %s", err)
+			continue
+		}
+	}
+}
+
+func main() {
+	flag.StringVar(&socketFile, "socketfile", "/run/seccomp-agent.socket", "Socket file")
+	flag.StringVar(&pidFile, "pid-file", "", "Pid file")
+	logrus.SetLevel(logrus.DebugLevel)
+
+	// Parse arguments
+	flag.Parse()
+	if flag.NArg() > 0 {
+		flag.PrintDefaults()
+		logrus.Fatal("Invalid command")
+	}
+
+	if err := os.Remove(socketFile); err != nil && !errors.Is(err, os.ErrNotExist) {
+		logrus.Fatalf("Cannot cleanup socket file: %v", err)
+	}
+
+	if pidFile != "" {
+		pid := fmt.Sprintf("%d", os.Getpid())
+		if err := ioutil.WriteFile(pidFile, []byte(pid), 0o644); err != nil {
+			logrus.Fatalf("Cannot write pid file: %v", err)
+		}
+	}
+
+	logrus.Info("Waiting for seccomp file descriptors")
+	l, err := net.Listen("unix", socketFile)
+	if err != nil {
+		logrus.Fatalf("Cannot listen: %s", err)
+	}
+	defer l.Close()
+
+	for {
+		conn, err := l.Accept()
+		if err != nil {
+			logrus.Errorf("Cannot accept connection: %s", err)
+			continue
+		}
+		socket, err := conn.(*net.UnixConn).File()
+		conn.Close()
+		if err != nil {
+			logrus.Errorf("Cannot get socket: %v", err)
+			continue
+		}
+		newFd, metadata, err := handleNewMessage(int(socket.Fd()))
+		socket.Close()
+		if err != nil {
+			logrus.Errorf("Error receiving seccomp file descriptor: %v", err)
+			continue
+		}
+
+		// Make sure we don't allow strings like "/../p", as that means
+		// a file in a different location than expected. We just want
+		// safe things to use as a suffix for a file name.
+		metadata = filepath.Base(metadata)
+		if strings.Contains(metadata, "/") {
+			// Fallback to a safe string.
+			metadata = "agent-generated-suffix"
+		}
+
+		logrus.Infof("Received new seccomp fd: %v", newFd)
+		go notifHandler(libseccomp.ScmpFd(newFd), metadata)
+	}
+}
diff --git a/contrib/cmd/seccompagent/unsupported.go b/contrib/cmd/seccompagent/unsupported.go
new file mode 100644
index 00000000..b6756eeb
--- /dev/null
+++ b/contrib/cmd/seccompagent/unsupported.go
@@ -0,0 +1,9 @@
+// +build !linux !cgo !seccomp
+
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Not supported, to use this compile with build tag: seccomp.")
+}
diff --git a/go.mod b/go.mod
index a45e2da8..64d19452 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
 	github.com/mrunalp/fileutils v0.5.0
 	github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
 	github.com/opencontainers/selinux v1.8.4
-	github.com/seccomp/libseccomp-golang v0.9.1
+	github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921
 	github.com/sirupsen/logrus v1.8.1
 	github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
 	// NOTE: urfave/cli must be <= v1.22.1 due to a regression: https://github.com/urfave/cli/issues/1092
diff --git a/go.sum b/go.sum
index 7937c033..186bf535 100644
--- a/go.sum
+++ b/go.sum
@@ -52,8 +52,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
-github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
+github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921 h1:58EBmR2dMNL2n/FnbQewK3D14nXr0V9CObDSvMJLq+Y=
+github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go
index 2dad5c8b..e0db8e01 100644
--- a/libcontainer/configs/config.go
+++ b/libcontainer/configs/config.go
@@ -31,10 +31,12 @@ type IDMap struct {
 // for syscalls. Additional architectures can be added by specifying them in
 // Architectures.
 type Seccomp struct {
-	DefaultAction   Action     `json:"default_action"`
-	Architectures   []string   `json:"architectures"`
-	Syscalls        []*Syscall `json:"syscalls"`
-	DefaultErrnoRet *uint      `json:"default_errno_ret"`
+	DefaultAction    Action     `json:"default_action"`
+	Architectures    []string   `json:"architectures"`
+	Syscalls         []*Syscall `json:"syscalls"`
+	DefaultErrnoRet  *uint      `json:"default_errno_ret"`
+	ListenerPath     string     `json:"listener_path,omitempty"`
+	ListenerMetadata string     `json:"listener_metadata,omitempty"`
 }
 
 // Action is taken upon rule match in Seccomp
@@ -47,6 +49,7 @@ const (
 	Allow
 	Trace
 	Log
+	Notify
 )
 
 // Operator is a comparison operator to be used when matching syscall arguments in Seccomp
diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go
index 18d12916..81bc2c29 100644
--- a/libcontainer/factory_linux.go
+++ b/libcontainer/factory_linux.go
@@ -357,7 +357,7 @@ func (l *LinuxFactory) StartInitialization() (err error) {
 	defer func() {
 		// We have an error during the initialization of the container's init,
 		// send it back to the parent process in the form of an initError.
-		if werr := utils.WriteJSON(pipe, syncT{procError}); werr != nil {
+		if werr := writeSync(pipe, procError); werr != nil {
 			fmt.Fprintln(os.Stderr, err)
 			return
 		}
diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go
index 5bbe2920..9f7dc75f 100644
--- a/libcontainer/init_linux.go
+++ b/libcontainer/init_linux.go
@@ -271,6 +271,36 @@ func syncParentHooks(pipe io.ReadWriter) error {
 	return readSync(pipe, procResume)
 }
 
+// syncParentSeccomp sends to the given pipe a JSON payload which
+// indicates that the parent should pick up the seccomp fd with pidfd_getfd()
+// and send it to the seccomp agent over a unix socket. It then waits for
+// the parent to indicate that it is cleared to resume and closes the seccompFd.
+// If the seccompFd is -1, there isn't anything to sync with the parent, so it
+// returns no error.
+func syncParentSeccomp(pipe io.ReadWriter, seccompFd int) error {
+	if seccompFd == -1 {
+		return nil
+	}
+
+	// Tell parent.
+	if err := writeSyncWithFd(pipe, procSeccomp, seccompFd); err != nil {
+		unix.Close(seccompFd)
+		return err
+	}
+
+	// Wait for parent to give the all-clear.
+	if err := readSync(pipe, procSeccompDone); err != nil {
+		unix.Close(seccompFd)
+		return fmt.Errorf("sync parent seccomp: %w", err)
+	}
+
+	if err := unix.Close(seccompFd); err != nil {
+		return fmt.Errorf("close seccomp fd: %w", err)
+	}
+
+	return nil
+}
+
 // setupUser changes the groups, gid, and uid for the user inside the container
 func setupUser(config *initConfig) error {
 	// Set up defaults.
diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go
index ba025812..13812e73 100644
--- a/libcontainer/process_linux.go
+++ b/libcontainer/process_linux.go
@@ -5,6 +5,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"net"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -172,6 +173,42 @@ func (p *setnsProcess) start() (retErr error) {
 		case procHooks:
 			// This shouldn't happen.
 			panic("unexpected procHooks in setns")
+		case procSeccomp:
+			if p.config.Config.Seccomp.ListenerPath == "" {
+				return errors.New("listenerPath is not set")
+			}
+
+			seccompFd, err := recvSeccompFd(uintptr(p.pid()), uintptr(sync.Fd))
+			if err != nil {
+				return err
+			}
+			defer unix.Close(seccompFd)
+
+			bundle, annotations := utils.Annotations(p.config.Config.Labels)
+			containerProcessState := &specs.ContainerProcessState{
+				Version:  specs.Version,
+				Fds:      []string{specs.SeccompFdName},
+				Pid:      p.cmd.Process.Pid,
+				Metadata: p.config.Config.Seccomp.ListenerMetadata,
+				State: specs.State{
+					Version:     specs.Version,
+					ID:          p.config.ContainerId,
+					Status:      specs.StateRunning,
+					Pid:         p.initProcessPid,
+					Bundle:      bundle,
+					Annotations: annotations,
+				},
+			}
+			if err := sendContainerProcessState(p.config.Config.Seccomp.ListenerPath,
+				containerProcessState, seccompFd); err != nil {
+				return err
+			}
+
+			// Sync with child.
+			if err := writeSync(p.messageSockPair.parent, procSeccompDone); err != nil {
+				return err
+			}
+			return nil
 		default:
 			return errors.New("invalid JSON payload from child")
 		}
@@ -426,6 +463,41 @@ func (p *initProcess) start() (retErr error) {
 
 	ierr := parseSync(p.messageSockPair.parent, func(sync *syncT) error {
 		switch sync.Type {
+		case procSeccomp:
+			if p.config.Config.Seccomp.ListenerPath == "" {
+				return errors.New("listenerPath is not set")
+			}
+
+			seccompFd, err := recvSeccompFd(uintptr(childPid), uintptr(sync.Fd))
+			if err != nil {
+				return err
+			}
+			defer unix.Close(seccompFd)
+
+			s, err := p.container.currentOCIState()
+			if err != nil {
+				return err
+			}
+
+			// initProcessStartTime hasn't been set yet.
+			s.Pid = p.cmd.Process.Pid
+			s.Status = specs.StateCreating
+			containerProcessState := &specs.ContainerProcessState{
+				Version:  specs.Version,
+				Fds:      []string{specs.SeccompFdName},
+				Pid:      s.Pid,
+				Metadata: p.config.Config.Seccomp.ListenerMetadata,
+				State:    *s,
+			}
+			if err := sendContainerProcessState(p.config.Config.Seccomp.ListenerPath,
+				containerProcessState, seccompFd); err != nil {
+				return err
+			}
+
+			// Sync with child.
+			if err := writeSync(p.messageSockPair.parent, procSeccompDone); err != nil {
+				return err
+			}
 		case procReady:
 			// set rlimits, this has to be done here because we lose permissions
 			// to raise the limits once we enter a user-namespace
@@ -486,7 +558,7 @@ func (p *initProcess) start() (retErr error) {
 
 			// Sync with child.
 			if err := writeSync(p.messageSockPair.parent, procRun); err != nil {
-				return fmt.Errorf("error writing syncT 'run': %w", err)
+				return err
 			}
 			sentRun = true
 		case procHooks:
@@ -518,7 +590,7 @@ func (p *initProcess) start() (retErr error) {
 			}
 			// Sync with child.
 			if err := writeSync(p.messageSockPair.parent, procResume); err != nil {
-				return fmt.Errorf("error writing syncT 'resume': %w", err)
+				return err
 			}
 			sentResume = true
 		default:
@@ -621,6 +693,46 @@ func (p *initProcess) forwardChildLogs() chan error {
 	return logs.ForwardLogs(p.logFilePair.parent)
 }
 
+func recvSeccompFd(childPid, childFd uintptr) (int, error) {
+	pidfd, _, errno := unix.Syscall(unix.SYS_PIDFD_OPEN, childPid, 0, 0)
+	if errno != 0 {
+		return -1, fmt.Errorf("performing SYS_PIDFD_OPEN syscall: %w", errno)
+	}
+	defer unix.Close(int(pidfd))
+
+	seccompFd, _, errno := unix.Syscall(unix.SYS_PIDFD_GETFD, pidfd, childFd, 0)
+	if errno != 0 {
+		return -1, fmt.Errorf("performing SYS_PIDFD_GETFD syscall: %w", errno)
+	}
+
+	return int(seccompFd), nil
+}
+
+func sendContainerProcessState(listenerPath string, state *specs.ContainerProcessState, fd int) error {
+	conn, err := net.Dial("unix", listenerPath)
+	if err != nil {
+		return fmt.Errorf("failed to connect with seccomp agent specified in the seccomp profile: %w", err)
+	}
+
+	socket, err := conn.(*net.UnixConn).File()
+	if err != nil {
+		return fmt.Errorf("cannot get seccomp socket: %w", err)
+	}
+	defer socket.Close()
+
+	b, err := json.Marshal(state)
+	if err != nil {
+		return fmt.Errorf("cannot marshall seccomp state: %w", err)
+	}
+
+	err = utils.SendFds(socket, b, fd)
+	if err != nil {
+		return fmt.Errorf("cannot send seccomp fd to %s: %w", listenerPath, err)
+	}
+
+	return nil
+}
+
 func getPipeFds(pid int) ([]string, error) {
 	fds := make([]string, 3)
 
diff --git a/libcontainer/seccomp/config.go b/libcontainer/seccomp/config.go
index 2e5adfd3..2c69a51c 100644
--- a/libcontainer/seccomp/config.go
+++ b/libcontainer/seccomp/config.go
@@ -17,12 +17,13 @@ var operators = map[string]configs.Operator{
 }
 
 var actions = map[string]configs.Action{
-	"SCMP_ACT_KILL":  configs.Kill,
-	"SCMP_ACT_ERRNO": configs.Errno,
-	"SCMP_ACT_TRAP":  configs.Trap,
-	"SCMP_ACT_ALLOW": configs.Allow,
-	"SCMP_ACT_TRACE": configs.Trace,
-	"SCMP_ACT_LOG":   configs.Log,
+	"SCMP_ACT_KILL":   configs.Kill,
+	"SCMP_ACT_ERRNO":  configs.Errno,
+	"SCMP_ACT_TRAP":   configs.Trap,
+	"SCMP_ACT_ALLOW":  configs.Allow,
+	"SCMP_ACT_TRACE":  configs.Trace,
+	"SCMP_ACT_LOG":    configs.Log,
+	"SCMP_ACT_NOTIFY": configs.Notify,
 }
 
 var archs = map[string]string{
diff --git a/libcontainer/seccomp/patchbpf/enosys_linux.go b/libcontainer/seccomp/patchbpf/enosys_linux.go
index 37a05b5a..dfb8a0a8 100644
--- a/libcontainer/seccomp/patchbpf/enosys_linux.go
+++ b/libcontainer/seccomp/patchbpf/enosys_linux.go
@@ -43,6 +43,11 @@ const uintptr_t C_SET_MODE_FILTER = SECCOMP_SET_MODE_FILTER;
 #endif
 const uintptr_t C_FILTER_FLAG_LOG = SECCOMP_FILTER_FLAG_LOG;
 
+#ifndef SECCOMP_FILTER_FLAG_NEW_LISTENER
+#	define SECCOMP_FILTER_FLAG_NEW_LISTENER (1UL << 3)
+#endif
+const uintptr_t C_FILTER_FLAG_NEW_LISTENER = SECCOMP_FILTER_FLAG_NEW_LISTENER;
+
 // We use the AUDIT_ARCH_* values because those are the ones used by the kernel
 // and SCMP_ARCH_* sometimes has fake values (such as SCMP_ARCH_X32). But we
 // use <seccomp.h> so we get libseccomp's fallback definitions of AUDIT_ARCH_*.
@@ -582,9 +587,9 @@ func enosysPatchFilter(config *configs.Seccomp, filter *libseccomp.ScmpFilter) (
 	return fprog, nil
 }
 
-func filterFlags(filter *libseccomp.ScmpFilter) (flags uint, noNewPrivs bool, err error) {
+func filterFlags(config *configs.Seccomp, filter *libseccomp.ScmpFilter) (flags uint, noNewPrivs bool, err error) {
 	// Ignore the error since pre-2.4 libseccomp is treated as API level 0.
-	apiLevel, _ := libseccomp.GetApi()
+	apiLevel, _ := libseccomp.GetAPI()
 
 	noNewPrivs, err = filter.GetNoNewPrivsBit()
 	if err != nil {
@@ -600,26 +605,38 @@ func filterFlags(filter *libseccomp.ScmpFilter) (flags uint, noNewPrivs bool, er
 	}
 
 	// TODO: Support seccomp flags not yet added to libseccomp-golang...
+
+	for _, call := range config.Syscalls {
+		if call.Action == configs.Notify {
+			flags |= uint(C.C_FILTER_FLAG_NEW_LISTENER)
+			break
+		}
+	}
+
 	return
 }
 
-func sysSeccompSetFilter(flags uint, filter []unix.SockFilter) (err error) {
+func sysSeccompSetFilter(flags uint, filter []unix.SockFilter) (fd int, err error) {
 	fprog := unix.SockFprog{
 		Len:    uint16(len(filter)),
 		Filter: &filter[0],
 	}
+	fd = -1 // only return a valid fd when C_FILTER_FLAG_NEW_LISTENER is set
 	// If no seccomp flags were requested we can use the old-school prctl(2).
 	if flags == 0 {
 		err = unix.Prctl(unix.PR_SET_SECCOMP,
 			unix.SECCOMP_MODE_FILTER,
 			uintptr(unsafe.Pointer(&fprog)), 0, 0)
 	} else {
-		_, _, errno := unix.RawSyscall(unix.SYS_SECCOMP,
+		fdptr, _, errno := unix.RawSyscall(unix.SYS_SECCOMP,
 			uintptr(C.C_SET_MODE_FILTER),
 			uintptr(flags), uintptr(unsafe.Pointer(&fprog)))
 		if errno != 0 {
 			err = errno
 		}
+		if flags&uint(C.C_FILTER_FLAG_NEW_LISTENER) != 0 {
+			fd = int(fdptr)
+		}
 	}
 	runtime.KeepAlive(filter)
 	runtime.KeepAlive(fprog)
@@ -631,17 +648,17 @@ func sysSeccompSetFilter(flags uint, filter []unix.SockFilter) (err error) {
 // patches said filter to handle -ENOSYS in a much nicer manner than the
 // default libseccomp default action behaviour, and loads the patched filter
 // into the kernel for the current process.
-func PatchAndLoad(config *configs.Seccomp, filter *libseccomp.ScmpFilter) error {
+func PatchAndLoad(config *configs.Seccomp, filter *libseccomp.ScmpFilter) (int, error) {
 	// Generate a patched filter.
 	fprog, err := enosysPatchFilter(config, filter)
 	if err != nil {
-		return fmt.Errorf("error patching filter: %w", err)
+		return -1, fmt.Errorf("error patching filter: %w", err)
 	}
 
 	// Get the set of libseccomp flags set.
-	seccompFlags, noNewPrivs, err := filterFlags(filter)
+	seccompFlags, noNewPrivs, err := filterFlags(config, filter)
 	if err != nil {
-		return fmt.Errorf("unable to fetch seccomp filter flags: %w", err)
+		return -1, fmt.Errorf("unable to fetch seccomp filter flags: %w", err)
 	}
 
 	// Set no_new_privs if it was requested, though in runc we handle
@@ -649,13 +666,15 @@ func PatchAndLoad(config *configs.Seccomp, filter *libseccomp.ScmpFilter) error
 	if noNewPrivs {
 		logrus.Warnf("potentially misconfigured filter -- setting no_new_privs in seccomp path")
 		if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
-			return fmt.Errorf("error enabling no_new_privs bit: %w", err)
+			return -1, fmt.Errorf("error enabling no_new_privs bit: %w", err)
 		}
 	}
 
 	// Finally, load the filter.
-	if err := sysSeccompSetFilter(seccompFlags, fprog); err != nil {
-		return fmt.Errorf("error loading seccomp filter: %w", err)
+	fd, err := sysSeccompSetFilter(seccompFlags, fprog)
+	if err != nil {
+		return -1, fmt.Errorf("error loading seccomp filter: %w", err)
 	}
-	return nil
+
+	return fd, nil
 }
diff --git a/libcontainer/seccomp/seccomp_linux.go b/libcontainer/seccomp/seccomp_linux.go
index f4625082..ad599e99 100644
--- a/libcontainer/seccomp/seccomp_linux.go
+++ b/libcontainer/seccomp/seccomp_linux.go
@@ -16,12 +16,13 @@ import (
 )
 
 var (
-	actAllow = libseccomp.ActAllow
-	actTrap  = libseccomp.ActTrap
-	actKill  = libseccomp.ActKill
-	actTrace = libseccomp.ActTrace.SetReturnCode(int16(unix.EPERM))
-	actLog   = libseccomp.ActLog
-	actErrno = libseccomp.ActErrno.SetReturnCode(int16(unix.EPERM))
+	actAllow  = libseccomp.ActAllow
+	actTrap   = libseccomp.ActTrap
+	actKill   = libseccomp.ActKill
+	actTrace  = libseccomp.ActTrace.SetReturnCode(int16(unix.EPERM))
+	actLog    = libseccomp.ActLog
+	actErrno  = libseccomp.ActErrno.SetReturnCode(int16(unix.EPERM))
+	actNotify = libseccomp.ActNotify
 )
 
 const (
@@ -29,54 +30,89 @@ const (
 	syscallMaxArguments int = 6
 )
 
-// Filters given syscalls in a container, preventing them from being used
-// Started in the container init process, and carried over to all child processes
-// Setns calls, however, require a separate invocation, as they are not children
-// of the init until they join the namespace
-func InitSeccomp(config *configs.Seccomp) error {
+// InitSeccomp installs the seccomp filters to be used in the container as
+// specified in config.
+// Returns the seccomp file descriptor if any of the filters include a
+// SCMP_ACT_NOTIFY action, otherwise returns -1.
+func InitSeccomp(config *configs.Seccomp) (int, error) {
 	if config == nil {
-		return errors.New("cannot initialize Seccomp - nil config passed")
+		return -1, errors.New("cannot initialize Seccomp - nil config passed")
 	}
 
 	defaultAction, err := getAction(config.DefaultAction, config.DefaultErrnoRet)
 	if err != nil {
-		return errors.New("error initializing seccomp - invalid default action")
+		return -1, errors.New("error initializing seccomp - invalid default action")
+	}
+
+	// Ignore the error since pre-2.4 libseccomp is treated as API level 0.
+	apiLevel, _ := libseccomp.GetAPI()
+	for _, call := range config.Syscalls {
+		if call.Action == configs.Notify {
+			if apiLevel < 6 {
+				return -1, fmt.Errorf("seccomp notify unsupported: API level: got %d, want at least 6. Please try with libseccomp >= 2.5.0 and Linux >= 5.7", apiLevel)
+			}
+
+			// We can't allow the write syscall to notify to the seccomp agent.
+			// After InitSeccomp() is called, we need to syncParentSeccomp() to write the seccomp fd plain
+			// number, so the parent sends it to the seccomp agent. If we use SCMP_ACT_NOTIFY on write, we
+			// never can write the seccomp fd to the parent and therefore the seccomp agent never receives
+			// the seccomp fd and runc is hang during initialization.
+			//
+			// Note that read()/close(), that are also used in syncParentSeccomp(), _can_ use SCMP_ACT_NOTIFY.
+			// Because we write the seccomp fd on the pipe to the parent, the parent is able to proceed and
+			// send the seccomp fd to the agent (it is another process and not subject to the seccomp
+			// filter). We will be blocked on read()/close() inside syncParentSeccomp() but if the seccomp
+			// agent allows those syscalls to proceed, initialization works just fine and the agent can
+			// handle future read()/close() syscalls as it wanted.
+			if call.Name == "write" {
+				return -1, errors.New("SCMP_ACT_NOTIFY cannot be used for the write syscall")
+			}
+		}
+	}
+
+	// See comment on why write is not allowed. The same reason applies, as this can mean handling write too.
+	if defaultAction == actNotify {
+		return -1, errors.New("SCMP_ACT_NOTIFY cannot be used as default action")
 	}
 
 	filter, err := libseccomp.NewFilter(defaultAction)
 	if err != nil {
-		return fmt.Errorf("error creating filter: %w", err)
+		return -1, fmt.Errorf("error creating filter: %w", err)
 	}
 
 	// Add extra architectures
 	for _, arch := range config.Architectures {
 		scmpArch, err := libseccomp.GetArchFromString(arch)
 		if err != nil {
-			return fmt.Errorf("error validating Seccomp architecture: %w", err)
+			return -1, fmt.Errorf("error validating Seccomp architecture: %w", err)
 		}
 		if err := filter.AddArch(scmpArch); err != nil {
-			return fmt.Errorf("error adding architecture to seccomp filter: %w", err)
+			return -1, fmt.Errorf("error adding architecture to seccomp filter: %w", err)
 		}
 	}
 
 	// Unset no new privs bit
 	if err := filter.SetNoNewPrivsBit(false); err != nil {
-		return fmt.Errorf("error setting no new privileges: %w", err)
+		return -1, fmt.Errorf("error setting no new privileges: %w", err)
 	}
 
 	// Add a rule for each syscall
 	for _, call := range config.Syscalls {
 		if call == nil {
-			return errors.New("encountered nil syscall while initializing Seccomp")
+			return -1, errors.New("encountered nil syscall while initializing Seccomp")
 		}
+
 		if err := matchCall(filter, call, defaultAction); err != nil {
-			return err
+			return -1, err
 		}
 	}
-	if err := patchbpf.PatchAndLoad(config, filter); err != nil {
-		return fmt.Errorf("error loading seccomp filter into kernel: %w", err)
+
+	seccompFd, err := patchbpf.PatchAndLoad(config, filter)
+	if err != nil {
+		return -1, fmt.Errorf("error loading seccomp filter into kernel: %w", err)
 	}
-	return nil
+
+	return seccompFd, nil
 }
 
 // Convert Libcontainer Action to Libseccomp ScmpAction
@@ -100,6 +136,8 @@ func getAction(act configs.Action, errnoRet *uint) (libseccomp.ScmpAction, error
 		return actTrace, nil
 	case configs.Log:
 		return actLog, nil
+	case configs.Notify:
+		return actNotify, nil
 	default:
 		return libseccomp.ActInvalid, errors.New("invalid action, cannot use in rule")
 	}
diff --git a/libcontainer/seccomp/seccomp_unsupported.go b/libcontainer/seccomp/seccomp_unsupported.go
index 6e593dbe..293e9c5c 100644
--- a/libcontainer/seccomp/seccomp_unsupported.go
+++ b/libcontainer/seccomp/seccomp_unsupported.go
@@ -12,11 +12,11 @@ import (
 var ErrSeccompNotEnabled = errors.New("seccomp: config provided but seccomp not supported")
 
 // InitSeccomp does nothing because seccomp is not supported.
-func InitSeccomp(config *configs.Seccomp) error {
+func InitSeccomp(config *configs.Seccomp) (int, error) {
 	if config != nil {
-		return ErrSeccompNotEnabled
+		return -1, ErrSeccompNotEnabled
 	}
-	return nil
+	return -1, nil
 }
 
 // Version returns major, minor, and micro.
diff --git a/libcontainer/setns_init_linux.go b/libcontainer/setns_init_linux.go
index 09a7d895..a2c9efc4 100644
--- a/libcontainer/setns_init_linux.go
+++ b/libcontainer/setns_init_linux.go
@@ -71,7 +71,12 @@ func (l *linuxSetnsInit) Init() error {
 	// do this before dropping capabilities; otherwise do it as late as possible
 	// just before execve so as few syscalls take place after it as possible.
 	if l.config.Config.Seccomp != nil && !l.config.NoNewPrivileges {
-		if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil {
+		seccompFd, err := seccomp.InitSeccomp(l.config.Config.Seccomp)
+		if err != nil {
+			return err
+		}
+
+		if err := syncParentSeccomp(l.pipe, seccompFd); err != nil {
 			return err
 		}
 	}
@@ -85,9 +90,14 @@ func (l *linuxSetnsInit) Init() error {
 	// place afterward (reducing the amount of syscalls that users need to
 	// enable in their seccomp profiles).
 	if l.config.Config.Seccomp != nil && l.config.NoNewPrivileges {
-		if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil {
+		seccompFd, err := seccomp.InitSeccomp(l.config.Config.Seccomp)
+		if err != nil {
 			return fmt.Errorf("unable to init seccomp: %w", err)
 		}
+
+		if err := syncParentSeccomp(l.pipe, seccompFd); err != nil {
+			return err
+		}
 	}
 	logrus.Debugf("setns_init: about to exec")
 	// Close the log pipe fd so the parent's ForwardLogs can exit.
diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
index f0d3506e..991c08a1 100644
--- a/libcontainer/specconv/spec_linux.go
+++ b/libcontainer/specconv/spec_linux.go
@@ -882,6 +882,9 @@ func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) {
 	newConfig.DefaultAction = newDefaultAction
 	newConfig.DefaultErrnoRet = config.DefaultErrnoRet
 
+	newConfig.ListenerPath = config.ListenerPath
+	newConfig.ListenerMetadata = config.ListenerMetadata
+
 	// Loop through all syscall blocks and convert them to libcontainer format
 	for _, call := range config.Syscalls {
 		newAction, err := seccomp.ConvertStringToAction(string(call.Action))
diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go
index c02f0c45..6dfea999 100644
--- a/libcontainer/standard_init_linux.go
+++ b/libcontainer/standard_init_linux.go
@@ -156,7 +156,12 @@ func (l *linuxStandardInit) Init() error {
 	// do this before dropping capabilities; otherwise do it as late as possible
 	// just before execve so as few syscalls take place after it as possible.
 	if l.config.Config.Seccomp != nil && !l.config.NoNewPrivileges {
-		if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil {
+		seccompFd, err := seccomp.InitSeccomp(l.config.Config.Seccomp)
+		if err != nil {
+			return err
+		}
+
+		if err := syncParentSeccomp(l.pipe, seccompFd); err != nil {
 			return err
 		}
 	}
@@ -181,6 +186,21 @@ func (l *linuxStandardInit) Init() error {
 	if err != nil {
 		return err
 	}
+	// Set seccomp as close to execve as possible, so as few syscalls take
+	// place afterward (reducing the amount of syscalls that users need to
+	// enable in their seccomp profiles). However, this needs to be done
+	// before closing the pipe since we need it to pass the seccompFd to
+	// the parent.
+	if l.config.Config.Seccomp != nil && l.config.NoNewPrivileges {
+		seccompFd, err := seccomp.InitSeccomp(l.config.Config.Seccomp)
+		if err != nil {
+			return fmt.Errorf("unable to init seccomp: %w", err)
+		}
+
+		if err := syncParentSeccomp(l.pipe, seccompFd); err != nil {
+			return err
+		}
+	}
 	// Close the pipe to signal that we have completed our init.
 	logrus.Debugf("init: closing the pipe to signal completion")
 	_ = l.pipe.Close()
@@ -202,6 +222,7 @@ func (l *linuxStandardInit) Init() error {
 	if _, err := unix.Write(fd, []byte("0")); err != nil {
 		return &os.PathError{Op: "write exec fifo", Path: fifoPath, Err: err}
 	}
+
 	// Close the O_PATH fifofd fd before exec because the kernel resets
 	// dumpable in the wrong order. This has been fixed in newer kernels, but
 	// we keep this to ensure CVE-2016-9962 doesn't re-emerge on older kernels.
@@ -209,14 +230,6 @@ func (l *linuxStandardInit) Init() error {
 	// since been resolved.
 	// https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318
 	_ = unix.Close(l.fifoFd)
-	// Set seccomp as close to execve as possible, so as few syscalls take
-	// place afterward (reducing the amount of syscalls that users need to
-	// enable in their seccomp profiles).
-	if l.config.Config.Seccomp != nil && l.config.NoNewPrivileges {
-		if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil {
-			return fmt.Errorf("unable to init seccomp: %w", err)
-		}
-	}
 
 	s := l.config.SpecState
 	s.Pid = unix.Getpid()
diff --git a/libcontainer/sync.go b/libcontainer/sync.go
index 906a8c2c..c9a23ef3 100644
--- a/libcontainer/sync.go
+++ b/libcontainer/sync.go
@@ -22,16 +22,22 @@ type syncType string
 //
 // procReady   --> [final setup]
 //             <-- procRun
+//
+// procSeccomp --> [pick up seccomp fd with pidfd_getfd()]
+//             <-- procSeccompDone
 const (
-	procError  syncType = "procError"
-	procReady  syncType = "procReady"
-	procRun    syncType = "procRun"
-	procHooks  syncType = "procHooks"
-	procResume syncType = "procResume"
+	procError       syncType = "procError"
+	procReady       syncType = "procReady"
+	procRun         syncType = "procRun"
+	procHooks       syncType = "procHooks"
+	procResume      syncType = "procResume"
+	procSeccomp     syncType = "procSeccomp"
+	procSeccompDone syncType = "procSeccompDone"
 )
 
 type syncT struct {
 	Type syncType `json:"type"`
+	Fd   int      `json:"fd"`
 }
 
 // initError is used to wrap errors for passing them via JSON,
@@ -47,7 +53,16 @@ func (i initError) Error() string {
 // writeSync is used to write to a synchronisation pipe. An error is returned
 // if there was a problem writing the payload.
 func writeSync(pipe io.Writer, sync syncType) error {
-	return utils.WriteJSON(pipe, syncT{sync})
+	return writeSyncWithFd(pipe, sync, -1)
+}
+
+// writeSyncWithFd is used to write to a synchronisation pipe. An error is
+// returned if there was a problem writing the payload.
+func writeSyncWithFd(pipe io.Writer, sync syncType, fd int) error {
+	if err := utils.WriteJSON(pipe, syncT{sync, fd}); err != nil {
+		return fmt.Errorf("writing syncT %q: %w", string(sync), err)
+	}
+	return nil
 }
 
 // readSync is used to read from a synchronisation pipe. An error is returned
diff --git a/libcontainer/utils/cmsg.go b/libcontainer/utils/cmsg.go
index 52a61655..7ef9da21 100644
--- a/libcontainer/utils/cmsg.go
+++ b/libcontainer/utils/cmsg.go
@@ -86,6 +86,11 @@ func SendFd(socket *os.File, name string, fd uintptr) error {
 	if len(name) >= MaxNameLen {
 		return fmt.Errorf("sendfd: filename too long: %s", name)
 	}
-	oob := unix.UnixRights(int(fd))
-	return unix.Sendmsg(int(socket.Fd()), []byte(name), oob, nil, 0)
+	return SendFds(socket, []byte(name), int(fd))
+}
+
+// SendFds sends a list of files descriptor and msg over the given AF_UNIX socket.
+func SendFds(socket *os.File, msg []byte, fds ...int) error {
+	oob := unix.UnixRights(fds...)
+	return unix.Sendmsg(int(socket.Fd()), msg, oob, nil, 0)
 }
diff --git a/vendor/github.com/seccomp/libseccomp-golang/.travis.yml b/vendor/github.com/seccomp/libseccomp-golang/.travis.yml
new file mode 100644
index 00000000..5240d462
--- /dev/null
+++ b/vendor/github.com/seccomp/libseccomp-golang/.travis.yml
@@ -0,0 +1,57 @@
+# Travis CI configuration for libseccomp-golang
+
+# https://docs.travis-ci.com/user/reference/bionic
+# https://wiki.ubuntu.com/Releases
+
+dist: bionic
+sudo: false
+
+notifications:
+  email:
+    on_success: always
+    on_failure: always
+
+arch:
+  - amd64
+
+os:
+  - linux
+
+language: go
+
+jobs:
+  include:
+    - name: "last libseccomp 2.5.0"
+      env:
+      - SECCOMP_VER=2.5.0
+      - SECCOMP_SHA256SUM=1ffa7038d2720ad191919816db3479295a4bcca1ec14e02f672539f4983014f3
+    - name: "compat libseccomp 2.4.4"
+      env:
+      - SECCOMP_VER=2.4.4
+      - SECCOMP_SHA256SUM=4e79738d1ef3c9b7ca9769f1f8b8d84fc17143c2c1c432e53b9c64787e0ff3eb
+    - name: "compat libseccomp 2.2.1"
+      env:
+      - SECCOMP_VER=2.2.1
+      - SECCOMP_SHA256SUM=0ba1789f54786c644af54cdffc9fd0dd0a8bb2b2ee153933f658855d2851a740
+
+addons:
+  apt:
+    packages:
+      - build-essential
+      - astyle
+      - golint
+      - gperf
+
+install:
+  - go get -u golang.org/x/lint/golint
+
+# run all of the tests independently, fail if any of the tests error
+script:
+  - wget https://github.com/seccomp/libseccomp/releases/download/v$SECCOMP_VER/libseccomp-$SECCOMP_VER.tar.gz
+  - echo $SECCOMP_SHA256SUM libseccomp-$SECCOMP_VER.tar.gz | sha256sum -c
+  - tar xf libseccomp-$SECCOMP_VER.tar.gz
+  - pushd libseccomp-$SECCOMP_VER && ./configure --prefix=/opt/libseccomp-$SECCOMP_VER && make && sudo make install && popd
+  - make check-syntax
+  - make lint
+  - PKG_CONFIG_PATH=/opt/libseccomp-$SECCOMP_VER/lib/pkgconfig LD_LIBRARY_PATH=/opt/libseccomp-$SECCOMP_VER/lib make vet
+  - PKG_CONFIG_PATH=/opt/libseccomp-$SECCOMP_VER/lib/pkgconfig LD_LIBRARY_PATH=/opt/libseccomp-$SECCOMP_VER/lib make test
diff --git a/vendor/github.com/seccomp/libseccomp-golang/SUBMITTING_PATCHES b/vendor/github.com/seccomp/libseccomp-golang/CONTRIBUTING.md
similarity index 69%
rename from vendor/github.com/seccomp/libseccomp-golang/SUBMITTING_PATCHES
rename to vendor/github.com/seccomp/libseccomp-golang/CONTRIBUTING.md
index 744e5cd6..d6862cbd 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/SUBMITTING_PATCHES
+++ b/vendor/github.com/seccomp/libseccomp-golang/CONTRIBUTING.md
@@ -8,11 +8,11 @@ to the rules described here, but by following the instructions below you
 should have a much easier time getting your work merged with the upstream
 project.
 
-* Test Your Code
+## Test Your Code Using Existing Tests
 
-There are two possible tests you can run to verify your code.  The first test
-is used to check the formatting and coding style of your changes, you can run
-the test with the following command:
+There are two possible tests you can run to verify your code.  The first
+test is used to check the formatting and coding style of your changes, you
+can run the test with the following command:
 
 	# make check-syntax
 
@@ -27,30 +27,13 @@ with the following command:
 
 ... if there are any faults or errors they will be displayed.
 
-* Generate the Patch(es)
+## Add New Tests for New Functionality
 
-Depending on how you decided to work with the libseccomp code base and what
-tools you are using there are different ways to generate your patch(es).
-However, regardless of what tools you use, you should always generate your
-patches using the "unified" diff/patch format and the patches should always
-apply to the libseccomp source tree using the following command from the top
-directory of the libseccomp sources:
+Any submissions which add functionality, or significantly change the existing
+code, should include additional tests to verify the proper operation of the
+proposed changes.
 
-	# patch -p1 < changes.patch
-
-If you are not using git, stacked git (stgit), or some other tool which can
-generate patch files for you automatically, you may find the following command
-helpful in generating patches, where "libseccomp.orig/" is the unmodified
-source code directory and "libseccomp/" is the source code directory with your
-changes:
-
-	# diff -purN libseccomp-golang.orig/ libseccomp-golang/
-
-When in doubt please generate your patch and try applying it to an unmodified
-copy of the libseccomp sources; if it fails for you, it will fail for the rest
-of us.
-
-* Explain Your Work
+## Explain Your Work
 
 At the top of every patch you should include a description of the problem you
 are trying to solve, how you solved it, and why you chose the solution you
@@ -59,7 +42,7 @@ if you can describe/include a reproducer for the problem in the description as
 well as instructions on how to test for the bug and verify that it has been
 fixed.
 
-* Sign Your Work
+## Sign Your Work
 
 The sign-off is a simple line at the end of the patch description, which
 certifies that you wrote it or otherwise have the right to pass it on as an
@@ -97,16 +80,49 @@ your real name, saying:
 
 	Signed-off-by: Random J Developer <random@developer.example.org>
 
-* Email Your Patch(es)
+You can add this to your commit description in `git` with `git commit -s`
+
+## Post Your Patches Upstream
+
+The libseccomp project accepts both GitHub pull requests and patches sent via
+the mailing list.  GitHub pull requests are preferred.  This sections below
+explain how to contribute via either method. Please read each step and perform
+all steps that apply to your chosen contribution method.
+
+### Submitting via Email
+
+Depending on how you decided to work with the libseccomp code base and what
+tools you are using there are different ways to generate your patch(es).
+However, regardless of what tools you use, you should always generate your
+patches using the "unified" diff/patch format and the patches should always
+apply to the libseccomp source tree using the following command from the top
+directory of the libseccomp sources:
+
+	# patch -p1 < changes.patch
+
+If you are not using git, stacked git (stgit), or some other tool which can
+generate patch files for you automatically, you may find the following command
+helpful in generating patches, where "libseccomp.orig/" is the unmodified
+source code directory and "libseccomp/" is the source code directory with your
+changes:
+
+	# diff -purN libseccomp.orig/ libseccomp/
+
+When in doubt please generate your patch and try applying it to an unmodified
+copy of the libseccomp sources; if it fails for you, it will fail for the rest
+of us.
 
 Finally, you will need to email your patches to the mailing list so they can
-be reviewed and potentially merged into the main libseccomp-golang repository.
-When sending patches to the mailing list it is important to send your email in
-text form, no HTML mail please, and ensure that your email client does not
-mangle your patches.  It should be possible to save your raw email to disk and
-apply it directly to the libseccomp source code; if that fails then you likely
-have a problem with your email client.  When in doubt try a test first by
-sending yourself an email with your patch and attempting to apply the emailed
-patch to the libseccomp-golang repository; if it fails for you, it will fail
-for the rest of us trying to test your patch and include it in the main
-libseccomp-golang repository.
+be reviewed and potentially merged into the main libseccomp repository.  When
+sending patches to the mailing list it is important to send your email in text
+form, no HTML mail please, and ensure that your email client does not mangle
+your patches.  It should be possible to save your raw email to disk and apply
+it directly to the libseccomp source code; if that fails then you likely have
+a problem with your email client.  When in doubt try a test first by sending
+yourself an email with your patch and attempting to apply the emailed patch to
+the libseccomp repository; if it fails for you, it will fail for the rest of
+us trying to test your patch and include it in the main libseccomp repository.
+
+### Submitting via GitHub
+
+See [this guide](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) if you've never done this before.
diff --git a/vendor/github.com/seccomp/libseccomp-golang/Makefile b/vendor/github.com/seccomp/libseccomp-golang/Makefile
index 1ff4cc89..38cfa852 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/Makefile
+++ b/vendor/github.com/seccomp/libseccomp-golang/Makefile
@@ -18,8 +18,14 @@ fix-syntax:
 vet:
 	go vet -v
 
+# Previous bugs have made the tests freeze until the timeout. Golang default
+# timeout for tests is 10 minutes, which is too long, considering current tests
+# can be executed in less than 1 second. Reduce the timeout, so problems can
+# be noticed earlier in the CI.
+TEST_TIMEOUT=10s
+
 test:
-	go test -v
+	go test -v -timeout $(TEST_TIMEOUT)
 
 lint:
 	@$(if $(shell which golint),true,$(error "install golint and include it in your PATH"))
diff --git a/vendor/github.com/seccomp/libseccomp-golang/README b/vendor/github.com/seccomp/libseccomp-golang/README.md
similarity index 68%
rename from vendor/github.com/seccomp/libseccomp-golang/README
rename to vendor/github.com/seccomp/libseccomp-golang/README.md
index 66839a46..806a5ddf 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/README
+++ b/vendor/github.com/seccomp/libseccomp-golang/README.md
@@ -1,7 +1,8 @@
-libseccomp-golang: Go Language Bindings for the libseccomp Project
+![libseccomp Golang Bindings](https://github.com/seccomp/libseccomp-artwork/blob/main/logo/libseccomp-color_text.png)
 ===============================================================================
 https://github.com/seccomp/libseccomp-golang
-https://github.com/seccomp/libseccomp
+
+[![Build Status](https://img.shields.io/travis/seccomp/libseccomp-golang/main.svg)](https://travis-ci.org/seccomp/libseccomp-golang)
 
 The libseccomp library provides an easy to use, platform independent, interface
 to the Linux Kernel's syscall filtering mechanism.  The libseccomp API is
@@ -12,40 +13,39 @@ be familiar to, and easily adopted by, application developers.
 The libseccomp-golang library provides a Go based interface to the libseccomp
 library.
 
-* Online Resources
+## Online Resources
 
 The library source repository currently lives on GitHub at the following URLs:
 
-	-> https://github.com/seccomp/libseccomp-golang
-	-> https://github.com/seccomp/libseccomp
+* https://github.com/seccomp/libseccomp-golang
+* https://github.com/seccomp/libseccomp
 
 The project mailing list is currently hosted on Google Groups at the URL below,
 please note that a Google account is not required to subscribe to the mailing
 list.
 
-	-> https://groups.google.com/d/forum/libseccomp
+* https://groups.google.com/d/forum/libseccomp
 
 Documentation is also available at:
 
-	-> https://godoc.org/github.com/seccomp/libseccomp-golang
+* https://godoc.org/github.com/seccomp/libseccomp-golang
 
-* Installing the package
+## Installing the package
 
 The libseccomp-golang bindings require at least Go v1.2.1 and GCC v4.8.4;
 earlier versions may yield unpredictable results.  If you meet these
 requirements you can install this package using the command below:
 
-	$ go get github.com/seccomp/libseccomp-golang
+	# go get github.com/seccomp/libseccomp-golang
 
-* Testing the Library
+## Testing the Library
 
 A number of tests and lint related recipes are provided in the Makefile, if
 you want to run the standard regression tests, you can excute the following:
 
-	$ make check
+	# make check
 
 In order to execute the 'make lint' recipe the 'golint' tool is needed, it
 can be found at:
 
-	-> https://github.com/golang/lint
-
+* https://github.com/golang/lint
diff --git a/vendor/github.com/seccomp/libseccomp-golang/go.mod b/vendor/github.com/seccomp/libseccomp-golang/go.mod
new file mode 100644
index 00000000..6384b376
--- /dev/null
+++ b/vendor/github.com/seccomp/libseccomp-golang/go.mod
@@ -0,0 +1,3 @@
+module github.com/seccomp/libseccomp-golang
+
+go 1.14
diff --git a/vendor/github.com/seccomp/libseccomp-golang/go.sum b/vendor/github.com/seccomp/libseccomp-golang/go.sum
new file mode 100644
index 00000000..72ae1611
--- /dev/null
+++ b/vendor/github.com/seccomp/libseccomp-golang/go.sum
@@ -0,0 +1,23 @@
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 h1:gkI/wGGwpcG5W4hLCzZNGxA4wzWBGGDStRI1MrjDl2Q=
+golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/vendor/github.com/seccomp/libseccomp-golang/seccomp.go b/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
index a3cc5382..e9b92e22 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
+++ b/vendor/github.com/seccomp/libseccomp-golang/seccomp.go
@@ -20,6 +20,13 @@ import (
 
 // C wrapping code
 
+// To compile libseccomp-golang against a specific version of libseccomp:
+// cd ../libseccomp && mkdir -p prefix
+// ./configure --prefix=$PWD/prefix && make && make install
+// cd ../libseccomp-golang
+// PKG_CONFIG_PATH=$PWD/../libseccomp/prefix/lib/pkgconfig/ make
+// LD_PRELOAD=$PWD/../libseccomp/prefix/lib/libseccomp.so.2.5.0 PKG_CONFIG_PATH=$PWD/../libseccomp/prefix/lib/pkgconfig/ make test
+
 // #cgo pkg-config: libseccomp
 // #include <stdlib.h>
 // #include <seccomp.h>
@@ -34,19 +41,25 @@ type VersionError struct {
 	minimum string
 }
 
+func init() {
+	// This forces the cgo libseccomp to initialize its internal API support state,
+	// which is necessary on older versions of libseccomp in order to work
+	// correctly.
+	GetAPI()
+}
+
 func (e VersionError) Error() string {
-	format := "Libseccomp version too low: "
+	messageStr := ""
 	if e.message != "" {
-		format += e.message + ": "
+		messageStr = e.message + ": "
 	}
-	format += "minimum supported is "
+	minimumStr := ""
 	if e.minimum != "" {
-		format += e.minimum + ": "
+		minimumStr = e.minimum
 	} else {
-		format += "2.2.0: "
+		minimumStr = "2.2.0"
 	}
-	format += "detected %d.%d.%d"
-	return fmt.Sprintf(format, verMajor, verMinor, verMicro)
+	return fmt.Sprintf("Libseccomp version too low: %sminimum supported is %s: detected %d.%d.%d", messageStr, minimumStr, verMajor, verMinor, verMicro)
 }
 
 // ScmpArch represents a CPU architecture. Seccomp can restrict syscalls on a
@@ -69,9 +82,61 @@ type ScmpCondition struct {
 	Operand2 uint64        `json:"operand_two,omitempty"`
 }
 
-// ScmpSyscall represents a Linux System Call
+// Seccomp userspace notification structures associated with filters that use the ActNotify action.
+
+// ScmpSyscall identifies a Linux System Call by its number.
 type ScmpSyscall int32
 
+// ScmpFd represents a file-descriptor used for seccomp userspace notifications.
+type ScmpFd int32
+
+// ScmpNotifData describes the system call context that triggered a notification.
+//
+// Syscall:      the syscall number
+// Arch:         the filter architecture
+// InstrPointer: address of the instruction that triggered a notification
+// Args:         arguments (up to 6) for the syscall
+//
+type ScmpNotifData struct {
+	Syscall      ScmpSyscall `json:"syscall,omitempty"`
+	Arch         ScmpArch    `json:"arch,omitempty"`
+	InstrPointer uint64      `json:"instr_pointer,omitempty"`
+	Args         []uint64    `json:"args,omitempty"`
+}
+
+// ScmpNotifReq represents a seccomp userspace notification. See NotifReceive() for
+// info on how to pull such a notification.
+//
+// ID:    notification ID
+// Pid:   process that triggered the notification event
+// Flags: filter flags (see seccomp(2))
+// Data:  system call context that triggered the notification
+//
+type ScmpNotifReq struct {
+	ID    uint64        `json:"id,omitempty"`
+	Pid   uint32        `json:"pid,omitempty"`
+	Flags uint32        `json:"flags,omitempty"`
+	Data  ScmpNotifData `json:"data,omitempty"`
+}
+
+// ScmpNotifResp represents a seccomp userspace notification response. See NotifRespond()
+// for info on how to push such a response.
+//
+// ID:    notification ID (must match the corresponding ScmpNotifReq ID)
+// Error: must be 0 if no error occurred, or an error constant from package
+//        syscall (e.g., syscall.EPERM, etc). In the latter case, it's used
+//        as an error return from the syscall that created the notification.
+// Val:   return value for the syscall that created the notification. Only
+//        relevant if Error is 0.
+// Flags: userspace notification response flag (e.g., NotifRespFlagContinue)
+//
+type ScmpNotifResp struct {
+	ID    uint64 `json:"id,omitempty"`
+	Error int32  `json:"error,omitempty"`
+	Val   uint64 `json:"val,omitempty"`
+	Flags uint32 `json:"flags,omitempty"`
+}
+
 // Exported Constants
 
 const (
@@ -117,6 +182,10 @@ const (
 	ArchS390 ScmpArch = iota
 	// ArchS390X represents 64-bit System z/390 syscalls
 	ArchS390X ScmpArch = iota
+	// ArchPARISC represents 32-bit PA-RISC
+	ArchPARISC ScmpArch = iota
+	// ArchPARISC64 represents 64-bit PA-RISC
+	ArchPARISC64 ScmpArch = iota
 )
 
 const (
@@ -125,10 +194,14 @@ const (
 	// ActInvalid is a placeholder to ensure uninitialized ScmpAction
 	// variables are invalid
 	ActInvalid ScmpAction = iota
-	// ActKill kills the process
+	// ActKill kills the thread that violated the rule. It is the same as ActKillThread.
+	// All other threads from the same thread group will continue to execute.
 	ActKill ScmpAction = iota
 	// ActTrap throws SIGSYS
 	ActTrap ScmpAction = iota
+	// ActNotify triggers a userspace notification. This action is only usable when
+	// libseccomp API level 6 or higher is supported.
+	ActNotify ScmpAction = iota
 	// ActErrno causes the syscall to return a negative error code. This
 	// code can be set with the SetReturnCode method
 	ActErrno ScmpAction = iota
@@ -141,6 +214,14 @@ const (
 	// This action is only usable when libseccomp API level 3 or higher is
 	// supported.
 	ActLog ScmpAction = iota
+	// ActKillThread kills the thread that violated the rule. It is the same as ActKill.
+	// All other threads from the same thread group will continue to execute.
+	ActKillThread ScmpAction = iota
+	// ActKillProcess kills the process that violated the rule.
+	// All threads in the thread group are also terminated.
+	// This action is only usable when libseccomp API level 3 or higher is
+	// supported.
+	ActKillProcess ScmpAction = iota
 )
 
 const (
@@ -172,6 +253,21 @@ const (
 	CompareMaskedEqual ScmpCompareOp = iota
 )
 
+var (
+	// ErrSyscallDoesNotExist represents an error condition where
+	// libseccomp is unable to resolve the syscall
+	ErrSyscallDoesNotExist = fmt.Errorf("could not resolve syscall name")
+)
+
+const (
+	// Userspace notification response flags
+
+	// NotifRespFlagContinue tells the kernel to continue executing the system
+	// call that triggered the notification. Must only be used when the notication
+	// response's error is 0.
+	NotifRespFlagContinue uint32 = 1
+)
+
 // Helpers for types
 
 // GetArchFromString returns an ScmpArch constant from a string representing an
@@ -214,6 +310,10 @@ func GetArchFromString(arch string) (ScmpArch, error) {
 		return ArchS390, nil
 	case "s390x":
 		return ArchS390X, nil
+	case "parisc":
+		return ArchPARISC, nil
+	case "parisc64":
+		return ArchPARISC64, nil
 	default:
 		return ArchInvalid, fmt.Errorf("cannot convert unrecognized string %q", arch)
 	}
@@ -254,6 +354,10 @@ func (a ScmpArch) String() string {
 		return "s390"
 	case ArchS390X:
 		return "s390x"
+	case ArchPARISC:
+		return "parisc"
+	case ArchPARISC64:
+		return "parisc64"
 	case ArchNative:
 		return "native"
 	case ArchInvalid:
@@ -290,8 +394,10 @@ func (a ScmpCompareOp) String() string {
 // String returns a string representation of a seccomp match action
 func (a ScmpAction) String() string {
 	switch a & 0xFFFF {
-	case ActKill:
-		return "Action: Kill Process"
+	case ActKill, ActKillThread:
+		return "Action: Kill thread"
+	case ActKillProcess:
+		return "Action: Kill process"
 	case ActTrap:
 		return "Action: Send SIGSYS"
 	case ActErrno:
@@ -299,6 +405,8 @@ func (a ScmpAction) String() string {
 	case ActTrace:
 		return fmt.Sprintf("Action: Notify tracing processes with code %d",
 			(a >> 16))
+	case ActNotify:
+		return "Action: Notify userspace"
 	case ActLog:
 		return "Action: Log system call"
 	case ActAllow:
@@ -334,23 +442,23 @@ func GetLibraryVersion() (major, minor, micro uint) {
 	return verMajor, verMinor, verMicro
 }
 
-// GetApi returns the API level supported by the system.
+// GetAPI returns the API level supported by the system.
 // Returns a positive int containing the API level, or 0 with an error if the
 // API level could not be detected due to the library being older than v2.4.0.
 // See the seccomp_api_get(3) man page for details on available API levels:
-// https://github.com/seccomp/libseccomp/blob/master/doc/man/man3/seccomp_api_get.3
-func GetApi() (uint, error) {
-	return getApi()
+// https://github.com/seccomp/libseccomp/blob/main/doc/man/man3/seccomp_api_get.3
+func GetAPI() (uint, error) {
+	return getAPI()
 }
 
-// SetApi forcibly sets the API level. General use of this function is strongly
+// SetAPI forcibly sets the API level. General use of this function is strongly
 // discouraged.
 // Returns an error if the API level could not be set. An error is always
 // returned if the library is older than v2.4.0
 // See the seccomp_api_get(3) man page for details on available API levels:
-// https://github.com/seccomp/libseccomp/blob/master/doc/man/man3/seccomp_api_get.3
-func SetApi(api uint) error {
-	return setApi(api)
+// https://github.com/seccomp/libseccomp/blob/main/doc/man/man3/seccomp_api_get.3
+func SetAPI(api uint) error {
+	return setAPI(api)
 }
 
 // Syscall functions
@@ -375,7 +483,7 @@ func (s ScmpSyscall) GetNameByArch(arch ScmpArch) (string, error) {
 
 	cString := C.seccomp_syscall_resolve_num_arch(arch.toNative(), C.int(s))
 	if cString == nil {
-		return "", fmt.Errorf("could not resolve syscall name for %#x", int32(s))
+		return "", ErrSyscallDoesNotExist
 	}
 	defer C.free(unsafe.Pointer(cString))
 
@@ -398,7 +506,7 @@ func GetSyscallFromName(name string) (ScmpSyscall, error) {
 
 	result := C.seccomp_syscall_resolve_name(cString)
 	if result == scmpError {
-		return 0, fmt.Errorf("could not resolve name to syscall: %q", name)
+		return 0, ErrSyscallDoesNotExist
 	}
 
 	return ScmpSyscall(result), nil
@@ -422,7 +530,7 @@ func GetSyscallFromNameByArch(name string, arch ScmpArch) (ScmpSyscall, error) {
 
 	result := C.seccomp_syscall_resolve_name_arch(arch.toNative(), cString)
 	if result == scmpError {
-		return 0, fmt.Errorf("could not resolve name to syscall: %q on %v", name, arch)
+		return 0, ErrSyscallDoesNotExist
 	}
 
 	return ScmpSyscall(result), nil
@@ -495,11 +603,10 @@ type ScmpFilter struct {
 	lock      sync.Mutex
 }
 
-// NewFilter creates and returns a new filter context.
-// Accepts a default action to be taken for syscalls which match no rules in
-// the filter.
-// Returns a reference to a valid filter context, or nil and an error if the
-// filter context could not be created or an invalid default action was given.
+// NewFilter creates and returns a new filter context.  Accepts a default action to be
+// taken for syscalls which match no rules in the filter.
+// Returns a reference to a valid filter context, or nil and an error
+// if the filter context could not be created or an invalid default action was given.
 func NewFilter(defaultAction ScmpAction) (*ScmpFilter, error) {
 	if err := ensureSupportedVersion(); err != nil {
 		return nil, err
@@ -519,8 +626,8 @@ func NewFilter(defaultAction ScmpAction) (*ScmpFilter, error) {
 	filter.valid = true
 	runtime.SetFinalizer(filter, filterFinalizer)
 
-	// Enable TSync so all goroutines will receive the same rules
-	// If the kernel does not support TSYNC, allow us to continue without error
+	// Enable TSync so all goroutines will receive the same rules.
+	// If the kernel does not support TSYNC, allow us to continue without error.
 	if err := filter.setFilterAttr(filterAttrTsync, 0x1); err != nil && err != syscall.ENOTSUP {
 		filter.Release()
 		return nil, fmt.Errorf("could not create filter - error setting tsync bit: %v", err)
@@ -552,9 +659,8 @@ func (f *ScmpFilter) Reset(defaultAction ScmpAction) error {
 		return errBadFilter
 	}
 
-	retCode := C.seccomp_reset(f.filterCtx, defaultAction.toNative())
-	if retCode != 0 {
-		return syscall.Errno(-1 * retCode)
+	if retCode := C.seccomp_reset(f.filterCtx, defaultAction.toNative()); retCode != 0 {
+		return errRc(retCode)
 	}
 
 	return nil
@@ -600,11 +706,12 @@ func (f *ScmpFilter) Merge(src *ScmpFilter) error {
 	}
 
 	// Merge the filters
-	retCode := C.seccomp_merge(f.filterCtx, src.filterCtx)
-	if syscall.Errno(-1*retCode) == syscall.EINVAL {
-		return fmt.Errorf("filters could not be merged due to a mismatch in attributes or invalid filter")
-	} else if retCode != 0 {
-		return syscall.Errno(-1 * retCode)
+	if retCode := C.seccomp_merge(f.filterCtx, src.filterCtx); retCode != 0 {
+		e := errRc(retCode)
+		if e == syscall.EINVAL {
+			return fmt.Errorf("filters could not be merged due to a mismatch in attributes or invalid filter")
+		}
+		return e
 	}
 
 	src.valid = false
@@ -633,12 +740,13 @@ func (f *ScmpFilter) IsArchPresent(arch ScmpArch) (bool, error) {
 		return false, errBadFilter
 	}
 
-	retCode := C.seccomp_arch_exist(f.filterCtx, arch.toNative())
-	if syscall.Errno(-1*retCode) == syscall.EEXIST {
-		// -EEXIST is "arch not present"
-		return false, nil
-	} else if retCode != 0 {
-		return false, syscall.Errno(-1 * retCode)
+	if retCode := C.seccomp_arch_exist(f.filterCtx, arch.toNative()); retCode != 0 {
+		e := errRc(retCode)
+		if e == syscall.EEXIST {
+			// -EEXIST is "arch not present"
+			return false, nil
+		}
+		return false, e
 	}
 
 	return true, nil
@@ -661,9 +769,10 @@ func (f *ScmpFilter) AddArch(arch ScmpArch) error {
 	// Libseccomp returns -EEXIST if the specified architecture is already
 	// present. Succeed silently in this case, as it's not fatal, and the
 	// architecture is present already.
-	retCode := C.seccomp_arch_add(f.filterCtx, arch.toNative())
-	if retCode != 0 && syscall.Errno(-1*retCode) != syscall.EEXIST {
-		return syscall.Errno(-1 * retCode)
+	if retCode := C.seccomp_arch_add(f.filterCtx, arch.toNative()); retCode != 0 {
+		if e := errRc(retCode); e != syscall.EEXIST {
+			return e
+		}
 	}
 
 	return nil
@@ -686,9 +795,10 @@ func (f *ScmpFilter) RemoveArch(arch ScmpArch) error {
 	// Similar to AddArch, -EEXIST is returned if the arch is not present
 	// Succeed silently in that case, this is not fatal and the architecture
 	// is not present in the filter after RemoveArch
-	retCode := C.seccomp_arch_remove(f.filterCtx, arch.toNative())
-	if retCode != 0 && syscall.Errno(-1*retCode) != syscall.EEXIST {
-		return syscall.Errno(-1 * retCode)
+	if retCode := C.seccomp_arch_remove(f.filterCtx, arch.toNative()); retCode != 0 {
+		if e := errRc(retCode); e != syscall.EEXIST {
+			return e
+		}
 	}
 
 	return nil
@@ -705,7 +815,7 @@ func (f *ScmpFilter) Load() error {
 	}
 
 	if retCode := C.seccomp_load(f.filterCtx); retCode != 0 {
-		return syscall.Errno(-1 * retCode)
+		return errRc(retCode)
 	}
 
 	return nil
@@ -764,8 +874,9 @@ func (f *ScmpFilter) GetNoNewPrivsBit() (bool, error) {
 func (f *ScmpFilter) GetLogBit() (bool, error) {
 	log, err := f.getFilterAttr(filterAttrLog)
 	if err != nil {
-		api, apiErr := getApi()
-		if (apiErr != nil && api == 0) || (apiErr == nil && api < 3) {
+		// Ignore error, if not supported returns apiLevel == 0
+		apiLevel, _ := GetAPI()
+		if apiLevel < 3 {
 			return false, fmt.Errorf("getting the log bit is only supported in libseccomp 2.4.0 and newer with API level 3 or higher")
 		}
 
@@ -779,6 +890,30 @@ func (f *ScmpFilter) GetLogBit() (bool, error) {
 	return true, nil
 }
 
+// GetSSB returns the current state the SSB bit will be set to on the filter
+// being loaded, or an error if an issue was encountered retrieving the value.
+// The SSB bit tells the kernel that a seccomp user is not interested in enabling
+// Speculative Store Bypass mitigation.
+// The SSB bit is only usable when libseccomp API level 4 or higher is
+// supported.
+func (f *ScmpFilter) GetSSB() (bool, error) {
+	ssb, err := f.getFilterAttr(filterAttrSSB)
+	if err != nil {
+		api, apiErr := getAPI()
+		if (apiErr != nil && api == 0) || (apiErr == nil && api < 4) {
+			return false, fmt.Errorf("getting the SSB flag is only supported in libseccomp 2.5.0 and newer with API level 4 or higher")
+		}
+
+		return false, err
+	}
+
+	if ssb == 0 {
+		return false, nil
+	}
+
+	return true, nil
+}
+
 // SetBadArchAction sets the default action taken on a syscall for an
 // architecture not in the filter, or an error if an issue was encountered
 // setting the value.
@@ -818,8 +953,9 @@ func (f *ScmpFilter) SetLogBit(state bool) error {
 
 	err := f.setFilterAttr(filterAttrLog, toSet)
 	if err != nil {
-		api, apiErr := getApi()
-		if (apiErr != nil && api == 0) || (apiErr == nil && api < 3) {
+		// Ignore error, if not supported returns apiLevel == 0
+		apiLevel, _ := GetAPI()
+		if apiLevel < 3 {
 			return fmt.Errorf("setting the log bit is only supported in libseccomp 2.4.0 and newer with API level 3 or higher")
 		}
 	}
@@ -827,6 +963,28 @@ func (f *ScmpFilter) SetLogBit(state bool) error {
 	return err
 }
 
+// SetSSB sets the state of the SSB bit, which will be applied on filter
+// load, or an error if an issue was encountered setting the value.
+// The SSB bit is only usable when libseccomp API level 4 or higher is
+// supported.
+func (f *ScmpFilter) SetSSB(state bool) error {
+	var toSet C.uint32_t = 0x0
+
+	if state {
+		toSet = 0x1
+	}
+
+	err := f.setFilterAttr(filterAttrSSB, toSet)
+	if err != nil {
+		api, apiErr := getAPI()
+		if (apiErr != nil && api == 0) || (apiErr == nil && api < 4) {
+			return fmt.Errorf("setting the SSB flag is only supported in libseccomp 2.5.0 and newer with API level 4 or higher")
+		}
+	}
+
+	return err
+}
+
 // SetSyscallPriority sets a syscall's priority.
 // This provides a hint to the filter generator in libseccomp about the
 // importance of this syscall. High-priority syscalls are placed
@@ -842,7 +1000,7 @@ func (f *ScmpFilter) SetSyscallPriority(call ScmpSyscall, priority uint8) error
 
 	if retCode := C.seccomp_syscall_priority(f.filterCtx, C.int(call),
 		C.uint8_t(priority)); retCode != 0 {
-		return syscall.Errno(-1 * retCode)
+		return errRc(retCode)
 	}
 
 	return nil
@@ -907,7 +1065,7 @@ func (f *ScmpFilter) ExportPFC(file *os.File) error {
 	}
 
 	if retCode := C.seccomp_export_pfc(f.filterCtx, C.int(fd)); retCode != 0 {
-		return syscall.Errno(-1 * retCode)
+		return errRc(retCode)
 	}
 
 	return nil
@@ -928,8 +1086,41 @@ func (f *ScmpFilter) ExportBPF(file *os.File) error {
 	}
 
 	if retCode := C.seccomp_export_bpf(f.filterCtx, C.int(fd)); retCode != 0 {
-		return syscall.Errno(-1 * retCode)
+		return errRc(retCode)
 	}
 
 	return nil
 }
+
+// Userspace Notification API
+
+// GetNotifFd returns the userspace notification file descriptor associated with the given
+// filter context. Such a file descriptor is only valid after the filter has been loaded
+// and only when the filter uses the ActNotify action. The file descriptor can be used to
+// retrieve and respond to notifications associated with the filter (see NotifReceive(),
+// NotifRespond(), and NotifIDValid()).
+func (f *ScmpFilter) GetNotifFd() (ScmpFd, error) {
+	return f.getNotifFd()
+}
+
+// NotifReceive retrieves a seccomp userspace notification from a filter whose ActNotify
+// action has triggered. The caller is expected to process the notification and return a
+// response via NotifRespond(). Each invocation of this function returns one
+// notification. As multiple notifications may be pending at any time, this function is
+// normally called within a polling loop.
+func NotifReceive(fd ScmpFd) (*ScmpNotifReq, error) {
+	return notifReceive(fd)
+}
+
+// NotifRespond responds to a notification retrieved via NotifReceive(). The response Id
+// must match that of the corresponding notification retrieved via NotifReceive().
+func NotifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error {
+	return notifRespond(fd, scmpResp)
+}
+
+// NotifIDValid checks if a notification is still valid. An return value of nil means the
+// notification is still valid. Otherwise the notification is not valid. This can be used
+// to mitigate time-of-check-time-of-use (TOCTOU) attacks as described in seccomp_notify_id_valid(2).
+func NotifIDValid(fd ScmpFd, id uint64) error {
+	return notifIDValid(fd, id)
+}
diff --git a/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go b/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
index 4e36b27a..8dc7b296 100644
--- a/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
+++ b/vendor/github.com/seccomp/libseccomp-golang/seccomp_internal.go
@@ -14,6 +14,13 @@ import (
 // Get the seccomp header in scope
 // Need stdlib.h for free() on cstrings
 
+// To compile libseccomp-golang against a specific version of libseccomp:
+// cd ../libseccomp && mkdir -p prefix
+// ./configure --prefix=$PWD/prefix && make && make install
+// cd ../libseccomp-golang
+// PKG_CONFIG_PATH=$PWD/../libseccomp/prefix/lib/pkgconfig/ make
+// LD_PRELOAD=$PWD/../libseccomp/prefix/lib/libseccomp.so.2.5.0 PKG_CONFIG_PATH=$PWD/../libseccomp/prefix/lib/pkgconfig/ make test
+
 // #cgo pkg-config: libseccomp
 /*
 #include <errno.h>
@@ -50,6 +57,14 @@ const uint32_t C_ARCH_BAD = ARCH_BAD;
 #define SCMP_ARCH_S390X ARCH_BAD
 #endif
 
+#ifndef SCMP_ARCH_PARISC
+#define SCMP_ARCH_PARISC ARCH_BAD
+#endif
+
+#ifndef SCMP_ARCH_PARISC64
+#define SCMP_ARCH_PARISC64 ARCH_BAD
+#endif
+
 const uint32_t C_ARCH_NATIVE       = SCMP_ARCH_NATIVE;
 const uint32_t C_ARCH_X86          = SCMP_ARCH_X86;
 const uint32_t C_ARCH_X86_64       = SCMP_ARCH_X86_64;
@@ -67,17 +82,34 @@ const uint32_t C_ARCH_PPC64        = SCMP_ARCH_PPC64;
 const uint32_t C_ARCH_PPC64LE      = SCMP_ARCH_PPC64LE;
 const uint32_t C_ARCH_S390         = SCMP_ARCH_S390;
 const uint32_t C_ARCH_S390X        = SCMP_ARCH_S390X;
+const uint32_t C_ARCH_PARISC       = SCMP_ARCH_PARISC;
+const uint32_t C_ARCH_PARISC64     = SCMP_ARCH_PARISC64;
 
 #ifndef SCMP_ACT_LOG
 #define SCMP_ACT_LOG 0x7ffc0000U
 #endif
 
+#ifndef SCMP_ACT_KILL_PROCESS
+#define SCMP_ACT_KILL_PROCESS 0x80000000U
+#endif
+
+#ifndef SCMP_ACT_KILL_THREAD
+#define SCMP_ACT_KILL_THREAD	0x00000000U
+#endif
+
+#ifndef SCMP_ACT_NOTIFY
+#define SCMP_ACT_NOTIFY 0x7fc00000U
+#endif
+
 const uint32_t C_ACT_KILL          = SCMP_ACT_KILL;
+const uint32_t C_ACT_KILL_PROCESS  = SCMP_ACT_KILL_PROCESS;
+const uint32_t C_ACT_KILL_THREAD   = SCMP_ACT_KILL_THREAD;
 const uint32_t C_ACT_TRAP          = SCMP_ACT_TRAP;
 const uint32_t C_ACT_ERRNO         = SCMP_ACT_ERRNO(0);
 const uint32_t C_ACT_TRACE         = SCMP_ACT_TRACE(0);
 const uint32_t C_ACT_LOG           = SCMP_ACT_LOG;
 const uint32_t C_ACT_ALLOW         = SCMP_ACT_ALLOW;
+const uint32_t C_ACT_NOTIFY        = SCMP_ACT_NOTIFY;
 
 // The libseccomp SCMP_FLTATR_CTL_LOG member of the scmp_filter_attr enum was
 // added in v2.4.0
@@ -85,12 +117,16 @@ const uint32_t C_ACT_ALLOW         = SCMP_ACT_ALLOW;
     (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4)
 #define SCMP_FLTATR_CTL_LOG _SCMP_FLTATR_MIN
 #endif
+#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5
+#define SCMP_FLTATR_CTL_SSB _SCMP_FLTATR_MIN
+#endif
 
 const uint32_t C_ATTRIBUTE_DEFAULT = (uint32_t)SCMP_FLTATR_ACT_DEFAULT;
 const uint32_t C_ATTRIBUTE_BADARCH = (uint32_t)SCMP_FLTATR_ACT_BADARCH;
 const uint32_t C_ATTRIBUTE_NNP     = (uint32_t)SCMP_FLTATR_CTL_NNP;
 const uint32_t C_ATTRIBUTE_TSYNC   = (uint32_t)SCMP_FLTATR_CTL_TSYNC;
 const uint32_t C_ATTRIBUTE_LOG     = (uint32_t)SCMP_FLTATR_CTL_LOG;
+const uint32_t C_ATTRIBUTE_SSB     = (uint32_t)SCMP_FLTATR_CTL_SSB;
 
 const int      C_CMP_NE            = (int)SCMP_CMP_NE;
 const int      C_CMP_LT            = (int)SCMP_CMP_LT;
@@ -179,6 +215,51 @@ void add_struct_arg_cmp(
 
         return;
 }
+
+// The seccomp notify API functions were added in v2.5.0
+#if (SCMP_VER_MAJOR < 2) || \
+    (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5)
+
+struct seccomp_data {
+	int nr;
+	__u32 arch;
+	__u64 instruction_pointer;
+	__u64 args[6];
+};
+
+struct seccomp_notif {
+	__u64 id;
+	__u32 pid;
+	__u32 flags;
+	struct seccomp_data data;
+};
+
+struct seccomp_notif_resp {
+	__u64 id;
+	__s64 val;
+	__s32 error;
+	__u32 flags;
+};
+
+int seccomp_notify_alloc(struct seccomp_notif **req, struct seccomp_notif_resp **resp) {
+	return -EOPNOTSUPP;
+}
+int seccomp_notify_fd(const scmp_filter_ctx ctx) {
+	return -EOPNOTSUPP;
+}
+void seccomp_notify_free(struct seccomp_notif *req, struct seccomp_notif_resp *resp) {
+}
+int seccomp_notify_id_valid(int fd, uint64_t id) {
+	return -EOPNOTSUPP;
+}
+int seccomp_notify_receive(int fd, struct seccomp_notif *req) {
+	return -EOPNOTSUPP;
+}
+int seccomp_notify_respond(int fd, struct seccomp_notif_resp *resp) {
+	return -EOPNOTSUPP;
+}
+
+#endif
 */
 import "C"
 
@@ -193,6 +274,7 @@ const (
 	filterAttrNNP        scmpFilterAttr = iota
 	filterAttrTsync      scmpFilterAttr = iota
 	filterAttrLog        scmpFilterAttr = iota
+	filterAttrSSB        scmpFilterAttr = iota
 )
 
 const (
@@ -200,10 +282,10 @@ const (
 	scmpError C.int = -1
 	// Comparison boundaries to check for architecture validity
 	archStart ScmpArch = ArchNative
-	archEnd   ScmpArch = ArchS390X
+	archEnd   ScmpArch = ArchPARISC64
 	// Comparison boundaries to check for action validity
 	actionStart ScmpAction = ActKill
-	actionEnd   ScmpAction = ActLog
+	actionEnd   ScmpAction = ActKillProcess
 	// Comparison boundaries to check for comparison operator validity
 	compareOpStart ScmpCompareOp = CompareNotEqual
 	compareOpEnd   ScmpCompareOp = CompareMaskedEqual
@@ -236,7 +318,7 @@ func ensureSupportedVersion() error {
 }
 
 // Get the API level
-func getApi() (uint, error) {
+func getAPI() (uint, error) {
 	api := C.seccomp_api_get()
 	if api == 0 {
 		return 0, fmt.Errorf("API level operations are not supported")
@@ -246,9 +328,9 @@ func getApi() (uint, error) {
 }
 
 // Set the API level
-func setApi(api uint) error {
+func setAPI(api uint) error {
 	if retCode := C.seccomp_api_set(C.uint(api)); retCode != 0 {
-		if syscall.Errno(-1*retCode) == syscall.EOPNOTSUPP {
+		if errRc(retCode) == syscall.EOPNOTSUPP {
 			return fmt.Errorf("API level operations are not supported")
 		}
 
@@ -265,6 +347,10 @@ func filterFinalizer(f *ScmpFilter) {
 	f.Release()
 }
 
+func errRc(rc C.int) error {
+	return syscall.Errno(-1 * rc)
+}
+
 // Get a raw filter attribute
 func (f *ScmpFilter) getFilterAttr(attr scmpFilterAttr) (C.uint32_t, error) {
 	f.lock.Lock()
@@ -278,7 +364,7 @@ func (f *ScmpFilter) getFilterAttr(attr scmpFilterAttr) (C.uint32_t, error) {
 
 	retCode := C.seccomp_attr_get(f.filterCtx, attr.toNative(), &attribute)
 	if retCode != 0 {
-		return 0x0, syscall.Errno(-1 * retCode)
+		return 0x0, errRc(retCode)
 	}
 
 	return attribute, nil
@@ -295,7 +381,7 @@ func (f *ScmpFilter) setFilterAttr(attr scmpFilterAttr, value C.uint32_t) error
 
 	retCode := C.seccomp_attr_set(f.filterCtx, attr.toNative(), value)
 	if retCode != 0 {
-		return syscall.Errno(-1 * retCode)
+		return errRc(retCode)
 	}
 
 	return nil
@@ -316,14 +402,17 @@ func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact b
 		retCode = C.seccomp_rule_add_array(f.filterCtx, action.toNative(), C.int(call), length, cond)
 	}
 
-	if syscall.Errno(-1*retCode) == syscall.EFAULT {
-		return fmt.Errorf("unrecognized syscall %#x", int32(call))
-	} else if syscall.Errno(-1*retCode) == syscall.EPERM {
-		return fmt.Errorf("requested action matches default action of filter")
-	} else if syscall.Errno(-1*retCode) == syscall.EINVAL {
-		return fmt.Errorf("two checks on same syscall argument")
-	} else if retCode != 0 {
-		return syscall.Errno(-1 * retCode)
+	if retCode != 0 {
+		switch e := errRc(retCode); e {
+		case syscall.EFAULT:
+			return fmt.Errorf("unrecognized syscall %#x", int32(call))
+		case syscall.EPERM:
+			return fmt.Errorf("requested action matches default action of filter")
+		case syscall.EINVAL:
+			return fmt.Errorf("two checks on same syscall argument")
+		default:
+			return e
+		}
 	}
 
 	return nil
@@ -443,6 +532,10 @@ func archFromNative(a C.uint32_t) (ScmpArch, error) {
 		return ArchS390, nil
 	case C.C_ARCH_S390X:
 		return ArchS390X, nil
+	case C.C_ARCH_PARISC:
+		return ArchPARISC, nil
+	case C.C_ARCH_PARISC64:
+		return ArchPARISC64, nil
 	default:
 		return 0x0, fmt.Errorf("unrecognized architecture %#x", uint32(a))
 	}
@@ -483,6 +576,10 @@ func (a ScmpArch) toNative() C.uint32_t {
 		return C.C_ARCH_S390
 	case ArchS390X:
 		return C.C_ARCH_S390X
+	case ArchPARISC:
+		return C.C_ARCH_PARISC
+	case ArchPARISC64:
+		return C.C_ARCH_PARISC64
 	case ArchNative:
 		return C.C_ARCH_NATIVE
 	default:
@@ -517,6 +614,10 @@ func actionFromNative(a C.uint32_t) (ScmpAction, error) {
 	switch a & 0xFFFF0000 {
 	case C.C_ACT_KILL:
 		return ActKill, nil
+	case C.C_ACT_KILL_PROCESS:
+		return ActKillProcess, nil
+	case C.C_ACT_KILL_THREAD:
+		return ActKillThread, nil
 	case C.C_ACT_TRAP:
 		return ActTrap, nil
 	case C.C_ACT_ERRNO:
@@ -527,6 +628,8 @@ func actionFromNative(a C.uint32_t) (ScmpAction, error) {
 		return ActLog, nil
 	case C.C_ACT_ALLOW:
 		return ActAllow, nil
+	case C.C_ACT_NOTIFY:
+		return ActNotify, nil
 	default:
 		return 0x0, fmt.Errorf("unrecognized action %#x", uint32(a))
 	}
@@ -537,6 +640,10 @@ func (a ScmpAction) toNative() C.uint32_t {
 	switch a & 0xFFFF {
 	case ActKill:
 		return C.C_ACT_KILL
+	case ActKillProcess:
+		return C.C_ACT_KILL_PROCESS
+	case ActKillThread:
+		return C.C_ACT_KILL_THREAD
 	case ActTrap:
 		return C.C_ACT_TRAP
 	case ActErrno:
@@ -547,6 +654,8 @@ func (a ScmpAction) toNative() C.uint32_t {
 		return C.C_ACT_LOG
 	case ActAllow:
 		return C.C_ACT_ALLOW
+	case ActNotify:
+		return C.C_ACT_NOTIFY
 	default:
 		return 0x0
 	}
@@ -565,7 +674,181 @@ func (a scmpFilterAttr) toNative() uint32 {
 		return uint32(C.C_ATTRIBUTE_TSYNC)
 	case filterAttrLog:
 		return uint32(C.C_ATTRIBUTE_LOG)
+	case filterAttrSSB:
+		return uint32(C.C_ATTRIBUTE_SSB)
 	default:
 		return 0x0
 	}
 }
+
+func (a ScmpSyscall) toNative() C.uint32_t {
+	return C.uint32_t(a)
+}
+
+func syscallFromNative(a C.int) ScmpSyscall {
+	return ScmpSyscall(a)
+}
+
+func notifReqFromNative(req *C.struct_seccomp_notif) (*ScmpNotifReq, error) {
+	scmpArgs := make([]uint64, 6)
+	for i := 0; i < len(scmpArgs); i++ {
+		scmpArgs[i] = uint64(req.data.args[i])
+	}
+
+	arch, err := archFromNative(req.data.arch)
+	if err != nil {
+		return nil, err
+	}
+
+	scmpData := ScmpNotifData{
+		Syscall:      syscallFromNative(req.data.nr),
+		Arch:         arch,
+		InstrPointer: uint64(req.data.instruction_pointer),
+		Args:         scmpArgs,
+	}
+
+	scmpReq := &ScmpNotifReq{
+		ID:    uint64(req.id),
+		Pid:   uint32(req.pid),
+		Flags: uint32(req.flags),
+		Data:  scmpData,
+	}
+
+	return scmpReq, nil
+}
+
+func (scmpResp *ScmpNotifResp) toNative(resp *C.struct_seccomp_notif_resp) {
+	resp.id = C.__u64(scmpResp.ID)
+	resp.val = C.__s64(scmpResp.Val)
+	resp.error = (C.__s32(scmpResp.Error) * -1) // kernel requires a negated value
+	resp.flags = C.__u32(scmpResp.Flags)
+}
+
+// Userspace Notification API
+// Calls to C.seccomp_notify* hidden from seccomp.go
+
+func (f *ScmpFilter) getNotifFd() (ScmpFd, error) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+
+	if !f.valid {
+		return -1, errBadFilter
+	}
+
+	// Ignore error, if not supported returns apiLevel == 0
+	apiLevel, _ := GetAPI()
+	if apiLevel < 6 {
+		return -1, fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel)
+	}
+
+	fd := C.seccomp_notify_fd(f.filterCtx)
+
+	return ScmpFd(fd), nil
+}
+
+func notifReceive(fd ScmpFd) (*ScmpNotifReq, error) {
+	var req *C.struct_seccomp_notif
+	var resp *C.struct_seccomp_notif_resp
+
+	// Ignore error, if not supported returns apiLevel == 0
+	apiLevel, _ := GetAPI()
+	if apiLevel < 6 {
+		return nil, fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel)
+	}
+
+	// we only use the request here; the response is unused
+	if retCode := C.seccomp_notify_alloc(&req, &resp); retCode != 0 {
+		return nil, errRc(retCode)
+	}
+
+	defer func() {
+		C.seccomp_notify_free(req, resp)
+	}()
+
+	for {
+		retCode, errno := C.seccomp_notify_receive(C.int(fd), req)
+		if retCode == 0 {
+			break
+		}
+
+		if errno == syscall.EINTR {
+			continue
+		}
+
+		if errno == syscall.ENOENT {
+			return nil, errno
+		}
+
+		return nil, errRc(retCode)
+	}
+
+	return notifReqFromNative(req)
+}
+
+func notifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error {
+	var req *C.struct_seccomp_notif
+	var resp *C.struct_seccomp_notif_resp
+
+	// Ignore error, if not supported returns apiLevel == 0
+	apiLevel, _ := GetAPI()
+	if apiLevel < 6 {
+		return fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel)
+	}
+
+	// we only use the reponse here; the request is discarded
+	if retCode := C.seccomp_notify_alloc(&req, &resp); retCode != 0 {
+		return errRc(retCode)
+	}
+
+	defer func() {
+		C.seccomp_notify_free(req, resp)
+	}()
+
+	scmpResp.toNative(resp)
+
+	for {
+		retCode, errno := C.seccomp_notify_respond(C.int(fd), resp)
+		if retCode == 0 {
+			break
+		}
+
+		if errno == syscall.EINTR {
+			continue
+		}
+
+		if errno == syscall.ENOENT {
+			return errno
+		}
+
+		return errRc(retCode)
+	}
+
+	return nil
+}
+
+func notifIDValid(fd ScmpFd, id uint64) error {
+	// Ignore error, if not supported returns apiLevel == 0
+	apiLevel, _ := GetAPI()
+	if apiLevel < 6 {
+		return fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel)
+	}
+
+	for {
+		retCode, errno := C.seccomp_notify_id_valid(C.int(fd), C.uint64_t(id))
+		if retCode == 0 {
+			break
+		}
+
+		if errno == syscall.EINTR {
+			continue
+		}
+
+		if errno == syscall.ENOENT {
+			return errno
+		}
+
+		return errRc(retCode)
+	}
+
+	return nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 24b3c52f..e47d9431 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -50,7 +50,7 @@ github.com/opencontainers/selinux/pkg/pwalk
 github.com/opencontainers/selinux/pkg/pwalkdir
 # github.com/russross/blackfriday/v2 v2.0.1
 github.com/russross/blackfriday/v2
-# github.com/seccomp/libseccomp-golang v0.9.1
+# github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921
 ## explicit
 github.com/seccomp/libseccomp-golang
 # github.com/shurcooL/sanitized_anchor_name v1.0.0


Test patch:
diff --git a/libcontainer/specconv/spec_linux_test.go b/libcontainer/specconv/spec_linux_test.go
index a81090be..963d803a 100644
--- a/libcontainer/specconv/spec_linux_test.go
+++ b/libcontainer/specconv/spec_linux_test.go
@@ -139,10 +139,59 @@ func TestCreateHooks(t *testing.T) {
 	}
 }
 
+func TestSetupSeccompNil(t *testing.T) {
+	seccomp, err := SetupSeccomp(nil)
+	if err != nil {
+		t.Error("Expected error to be nil")
+	}
+
+	if seccomp != nil {
+		t.Error("Expected seccomp to be nil")
+	}
+}
+
+func TestSetupSeccompEmpty(t *testing.T) {
+	conf := &specs.LinuxSeccomp{}
+	seccomp, err := SetupSeccomp(conf)
+	if err != nil {
+		t.Error("Expected error to be nil")
+	}
+
+	if seccomp != nil {
+		t.Error("Expected seccomp to be nil")
+	}
+}
+
+// TestSetupSeccompWrongAction tests that a wrong action triggers an error
+func TestSetupSeccompWrongAction(t *testing.T) {
+	conf := &specs.LinuxSeccomp{
+		DefaultAction: "SCMP_ACT_NON_EXIXTENT_ACTION",
+	}
+	_, err := SetupSeccomp(conf)
+	if err == nil {
+		t.Error("Expected error")
+	}
+}
+
+// TestSetupSeccompWrongArchitecture tests that a wrong architecture triggers an error
+func TestSetupSeccompWrongArchitecture(t *testing.T) {
+	conf := &specs.LinuxSeccomp{
+		DefaultAction: "SCMP_ACT_ALLOW",
+		Architectures: []specs.Arch{"SCMP_ARCH_NON_EXISTENT_ARCH"},
+	}
+	_, err := SetupSeccomp(conf)
+	if err == nil {
+		t.Error("Expected error")
+	}
+}
+
 func TestSetupSeccomp(t *testing.T) {
+	errnoRet := uint(55)
 	conf := &specs.LinuxSeccomp{
-		DefaultAction: "SCMP_ACT_ERRNO",
-		Architectures: []specs.Arch{specs.ArchX86_64, specs.ArchARM},
+		DefaultAction:    "SCMP_ACT_ERRNO",
+		Architectures:    []specs.Arch{specs.ArchX86_64, specs.ArchARM},
+		ListenerPath:     "/var/run/mysocket",
+		ListenerMetadata: "mymetadatastring",
 		Syscalls: []specs.LinuxSyscall{
 			{
 				Names:  []string{"clone"},
@@ -157,16 +206,33 @@ func TestSetupSeccomp(t *testing.T) {
 				},
 			},
 			{
-				Names: []string{
-					"select",
-					"semctl",
-					"semget",
-					"semop",
-					"semtimedop",
-					"send",
-					"sendfile",
-				},
-				Action: "SCMP_ACT_ALLOW",
+				Names:  []string{"semctl"},
+				Action: "SCMP_ACT_KILL",
+			},
+			{
+				Names:  []string{"semget"},
+				Action: "SCMP_ACT_ERRNO",
+			},
+			{
+				Names:    []string{"send"},
+				Action:   "SCMP_ACT_ERRNO",
+				ErrnoRet: &errnoRet,
+			},
+			{
+				Names:  []string{"lchown"},
+				Action: "SCMP_ACT_TRAP",
+			},
+			{
+				Names:  []string{"lremovexattr"},
+				Action: "SCMP_ACT_TRACE",
+			},
+			{
+				Names:  []string{"mbind"},
+				Action: "SCMP_ACT_LOG",
+			},
+			{
+				Names:  []string{"mknod"},
+				Action: "SCMP_ACT_NOTIFY",
 			},
 		},
 	}
@@ -175,7 +241,7 @@ func TestSetupSeccomp(t *testing.T) {
 		t.Errorf("Couldn't create Seccomp config: %v", err)
 	}
 
-	if seccomp.DefaultAction != 2 { // SCMP_ACT_ERRNO
+	if seccomp.DefaultAction != configs.Errno {
 		t.Error("Wrong conversion for DefaultAction")
 	}
 
@@ -187,6 +253,14 @@ func TestSetupSeccomp(t *testing.T) {
 		t.Error("Expected architectures are not found")
 	}
 
+	if seccomp.ListenerPath != "/var/run/mysocket" {
+		t.Error("Expected ListenerPath is wrong")
+	}
+
+	if seccomp.ListenerMetadata != "mymetadatastring" {
+		t.Error("Expected ListenerMetadata is wrong")
+	}
+
 	calls := seccomp.Syscalls
 
 	callsLength := len(calls)
@@ -194,22 +268,58 @@ func TestSetupSeccomp(t *testing.T) {
 		t.Errorf("Expected 8 syscalls, got :%d", callsLength)
 	}
 
-	for i, call := range calls {
-		if i == 0 {
+	for _, call := range calls {
+		switch call.Name {
+		case "clone":
+			if call.Action != configs.Allow {
+				t.Error("Wrong conversion for the clone syscall action")
+			}
 			expectedCloneSyscallArgs := configs.Arg{
 				Index:    0,
-				Op:       7, // SCMP_CMP_MASKED_EQ
+				Op:       configs.MaskEqualTo,
 				Value:    unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET | unix.CLONE_NEWCGROUP,
 				ValueTwo: 0,
 			}
 			if expectedCloneSyscallArgs != *call.Args[0] {
 				t.Errorf("Wrong arguments conversion for the clone syscall under test")
 			}
+		case "semctl":
+			if call.Action != configs.Kill {
+				t.Errorf("Wrong conversion for the %s syscall action", call.Name)
+			}
+		case "semget":
+			if call.Action != configs.Errno {
+				t.Errorf("Wrong conversion for the %s syscall action", call.Name)
+			}
+			if call.ErrnoRet != nil {
+				t.Errorf("Wrong error ret for the %s syscall", call.Name)
+			}
+		case "send":
+			if call.Action != configs.Errno {
+				t.Errorf("Wrong conversion for the %s syscall action", call.Name)
+			}
+			if *call.ErrnoRet != errnoRet {
+				t.Errorf("Wrong error ret for the %s syscall", call.Name)
+			}
+		case "lchown":
+			if call.Action != configs.Trap {
+				t.Errorf("Wrong conversion for the %s syscall action", call.Name)
+			}
+		case "lremovexattr":
+			if call.Action != configs.Trace {
+				t.Errorf("Wrong conversion for the %s syscall action", call.Name)
+			}
+		case "mbind":
+			if call.Action != configs.Log {
+				t.Errorf("Wrong conversion for the %s syscall action", call.Name)
+			}
+		case "mknod":
+			if call.Action != configs.Notify {
+				t.Errorf("Wrong conversion for the %s syscall action", call.Name)
+			}
+		default:
+			t.Errorf("Unexpected syscall %s found", call.Name)
 		}
-		if call.Action != 4 {
-			t.Error("Wrong conversion for the clone syscall action")
-		}
-
 	}
 }
 
diff --git a/tests/integration/helpers.bash b/tests/integration/helpers.bash
index fde767b1..4b48e6ad 100644
--- a/tests/integration/helpers.bash
+++ b/tests/integration/helpers.bash
@@ -17,6 +17,7 @@ unset IMAGES
 RUNC="${INTEGRATION_ROOT}/../../runc"
 RECVTTY="${INTEGRATION_ROOT}/../../contrib/cmd/recvtty/recvtty"
 SD_HELPER="${INTEGRATION_ROOT}/../../contrib/cmd/sd-helper/sd-helper"
+SECCOMP_AGENT="${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/seccompagent"
 
 # Test data path.
 # shellcheck disable=SC2034
@@ -31,6 +32,11 @@ KERNEL_MAJOR="${KERNEL_VERSION%%.*}"
 KERNEL_MINOR="${KERNEL_VERSION#$KERNEL_MAJOR.}"
 KERNEL_MINOR="${KERNEL_MINOR%%.*}"
 
+ARCH=$(uname -m)
+
+# Seccomp agent socket.
+SECCCOMP_AGENT_SOCKET="$BATS_TMPDIR/seccomp-agent.sock"
+
 # Check if we're in rootless mode.
 ROOTLESS=$(id -u)
 
@@ -428,6 +434,11 @@ function requires() {
 				skip_me=1
 			fi
 			;;
+		arch_x86_64)
+			if [ "$ARCH" != "x86_64" ]; then
+				skip_me=1
+			fi
+			;;
 		*)
 			fail "BUG: Invalid requires $var."
 			;;
@@ -505,6 +516,18 @@ function teardown_recvtty() {
 	rm -rf "$dir"
 }
 
+function setup_seccompagent() {
+	("${SECCOMP_AGENT}" -socketfile="$SECCCOMP_AGENT_SOCKET" -pid-file "$BATS_TMPDIR/seccompagent.pid" &) &
+}
+
+function teardown_seccompagent() {
+	if [ -f "$BATS_TMPDIR/seccompagent.pid" ]; then
+		kill -9 "$(cat "$BATS_TMPDIR/seccompagent.pid")"
+	fi
+	rm -f "$BATS_TMPDIR/seccompagent.pid"
+	rm -f "$SECCCOMP_AGENT_SOCKET"
+}
+
 function setup_bundle() {
 	local image="$1"
 
@@ -545,3 +568,12 @@ function teardown_bundle() {
 	rm -rf "$ROOT"
 	remove_parent
 }
+
+function requires_kernel() {
+	local major_required minor_required
+	major_required=$(echo "$1" | cut -d. -f1)
+	minor_required=$(echo "$1" | cut -d. -f2)
+	if [[ "$KERNEL_MAJOR" -lt $major_required || ("$KERNEL_MAJOR" -eq $major_required && "$KERNEL_MINOR" -lt $minor_required) ]]; then
+		skip "requires kernel $1"
+	fi
+}
diff --git a/tests/integration/seccomp-notify-compat.bats b/tests/integration/seccomp-notify-compat.bats
new file mode 100644
index 00000000..8d663edd
--- /dev/null
+++ b/tests/integration/seccomp-notify-compat.bats
@@ -0,0 +1,35 @@
+#!/usr/bin/env bats
+
+load helpers
+
+function setup() {
+	if [[ "$KERNEL_MAJOR" -gt 5 || ("$KERNEL_MAJOR" -eq 5 && "$KERNEL_MINOR" -ge 6) ]]; then
+		skip "requires kernel less than 5.6"
+	fi
+
+	requires arch_x86_64
+
+	setup_seccompagent
+	setup_busybox
+}
+
+function teardown() {
+	teardown_seccompagent
+	teardown_bundle
+}
+
+# Support for seccomp notify requires Linux > 5.6, check that on older kernels
+# return an error.
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY old kernel)" {
+	# Use just any seccomp profile with a notify action.
+	update_config ' .linux.seccomp = {
+				"defaultAction": "SCMP_ACT_ALLOW",
+				"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
+				"architectures": [ "SCMP_ARCH_X86","SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
+				"syscalls": [{ "names": [ "mkdir" ], "action": "SCMP_ACT_NOTIFY" }]
+			}'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+	[[ "$output" == *"seccomp notify unsupported:"* ]]
+}
diff --git a/tests/integration/seccomp-notify.bats b/tests/integration/seccomp-notify.bats
new file mode 100644
index 00000000..e9c734ef
--- /dev/null
+++ b/tests/integration/seccomp-notify.bats
@@ -0,0 +1,200 @@
+#!/usr/bin/env bats
+
+load helpers
+
+# Support for seccomp notify requires Linux > 5.6 because
+# runc uses the pidfd_getfd system call to fetch the seccomp fd.
+# https://github.com/torvalds/linux/commit/8649c322f75c96e7ced2fec201e123b2b073bf09
+# We also require arch x86_64, to not make this fail when people run tests
+# locally on other archs.
+function setup() {
+	requires_kernel 5.6
+	requires arch_x86_64
+
+	setup_seccompagent
+	setup_busybox
+}
+
+function teardown() {
+	teardown_seccompagent
+	teardown_bundle
+}
+
+# Create config.json template with SCMP_ACT_NOTIFY actions
+# $1: command to run
+# $2: noNewPrivileges (false/true)
+# $3: list of syscalls
+function scmp_act_notify_template() {
+	# The agent intercepts mkdir syscalls and creates the folder appending
+	# "-bar" (listenerMetadata below) to the name.
+	update_config '   .process.args = ["/bin/sh", "-c", "'"$1"'"]
+			| .process.noNewPrivileges = '"$2"'
+			| .linux.seccomp = {
+				"defaultAction":"SCMP_ACT_ALLOW",
+				"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
+				"listenerMetadata": "bar",
+				"architectures": [ "SCMP_ARCH_X86","SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
+				"syscalls": [{ "names": ['"$3"'], "action": "SCMP_ACT_NOTIFY" }]
+			}'
+}
+
+# The call to seccomp is done at different places according to the value of
+# noNewPrivileges, for this reason many of the following cases are tested with
+# both values.
+
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)" {
+	scmp_act_notify_template "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" false '"mkdir"'
+
+	runc run test_busybox
+	[ "$status" -eq 0 ]
+}
+
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)" {
+	scmp_act_notify_template "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" true '"mkdir"'
+
+	runc run test_busybox
+	[ "$status" -eq 0 ]
+}
+
+@test "runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)" {
+	requires root
+
+	scmp_act_notify_template "sleep infinity" false '"mkdir"'
+
+	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
+	[ "$status" -eq 0 ]
+
+	runc exec test_busybox /bin/sh -c "mkdir /dev/shm/foo && stat /dev/shm/foo-bar"
+	[ "$status" -eq 0 ]
+}
+
+@test "runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)" {
+	requires root
+
+	scmp_act_notify_template "sleep infinity" true '"mkdir"'
+
+	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
+	runc exec test_busybox /bin/sh -c "mkdir /dev/shm/foo && stat /dev/shm/foo-bar"
+	[ "$status" -eq 0 ]
+}
+
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges false)" {
+	scmp_act_notify_template "/bin/true" false '"execve","openat","open","read","close"'
+
+	runc run test_busybox
+	[ "$status" -eq 0 ]
+}
+
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges true)" {
+	scmp_act_notify_template "/bin/true" true '"execve","openat","open","read","close"'
+
+	runc run test_busybox
+	[ "$status" -eq 0 ]
+}
+
+@test "runc run [seccomp] (empty listener path)" {
+	update_config '   .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo && stat /dev/shm/foo"]
+			| .linux.seccomp = {
+				"defaultAction":"SCMP_ACT_ALLOW",
+				"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
+				"listenerMetadata": "bar",
+			}'
+
+	runc run test_busybox
+	[ "$status" -eq 0 ]
+}
+
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY empty listener path)" {
+	scmp_act_notify_template "/bin/true" false '"mkdir"'
+	update_config '.linux.seccomp.listenerPath = ""'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+}
+
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY wrong listener path)" {
+	scmp_act_notify_template "/bin/true" false '"mkdir"'
+	update_config '.linux.seccomp.listenerPath = "/some-non-existing-listener-path.sock"'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+}
+
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY abstract listener path)" {
+	scmp_act_notify_template "/bin/true" false '"mkdir"'
+	update_config '.linux.seccomp.listenerPath = "@mysocketishere"'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+}
+
+# Check that killing the seccompagent doesn't block syscalls in
+# the container. They should return ENOSYS instead.
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY kill seccompagent)" {
+	scmp_act_notify_template "sleep 4 && mkdir /dev/shm/foo" false '"mkdir"'
+
+	sleep 2 && teardown_seccompagent &
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+	[[ "$output" == *"mkdir:"*"/dev/shm/foo"*"Function not implemented"* ]]
+}
+
+# Check that starting with no seccomp agent running fails with a clear error.
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY no seccompagent)" {
+	teardown_seccompagent
+
+	scmp_act_notify_template "/bin/true" false '"mkdir"'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+	[[ "$output" == *"failed to connect with seccomp agent"* ]]
+}
+
+# Check that agent-returned error for the syscall works.
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY error chmod)" {
+	scmp_act_notify_template "touch /dev/shm/foo && chmod 777 /dev/shm/foo" false '"chmod", "fchmod", "fchmodat"'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+	[[ "$output" == *"chmod:"*"/dev/shm/foo"*"No medium found"* ]]
+}
+
+# check that trying to use SCMP_ACT_NOTIFY with write() gives a meaningful error.
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY write)" {
+	scmp_act_notify_template "/bin/true" false '"write"'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+	[[ "$output" == *"SCMP_ACT_NOTIFY cannot be used for the write syscall"* ]]
+}
+
+# check that a startContainer hook doesn't get any extra file descriptor.
+@test "runc run [seccomp] (SCMP_ACT_NOTIFY startContainer hook)" {
+	# shellcheck disable=SC2016
+	# We use single quotes to properly delimit the $1 param to
+	# update_config(), but this shellshcheck is quite silly and fails if the
+	# multi-line string includes some $var (even when it is properly outside of the
+	# single quotes) or when we use this syntax to execute commands in the
+	# string: $(command).
+	# So, just disable this check for our usage of update_config().
+	update_config '   .process.args = ["/bin/true"]
+			| .linux.seccomp = {
+				"defaultAction":"SCMP_ACT_ALLOW",
+				"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
+				"architectures": [ "SCMP_ARCH_X86", "SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
+				"syscalls":[{ "names": [ "mkdir" ], "action": "SCMP_ACT_NOTIFY" }]
+			}
+			|.hooks = {
+				"startContainer": [ {
+						"path": "/bin/sh",
+						"args": [
+							"sh",
+							"-c",
+							"if [ $(ls /proc/self/fd/ | wc -l) -ne 4 ]; then echo \"File descriptors is not 4\". && ls /proc/self/fd/ | wc -l && exit 1; fi"
+						],
+				} ]
+			}'
+
+	runc run test_busybox
+	[ "$status" -eq 0 ]
+}
diff --git a/tests/integration/seccomp.bats b/tests/integration/seccomp.bats
index e2d52946..e81beca3 100644
--- a/tests/integration/seccomp.bats
+++ b/tests/integration/seccomp.bats
@@ -33,3 +33,69 @@ function teardown() {
 	runc run test_busybox
 	[ "$status" -eq 0 ]
 }
+
+# TODO:
+# - Test other actions like SCMP_ACT_TRAP, SCMP_ACT_TRACE, SCMP_ACT_LOG.
+# - Test args (index, value, valueTwo, etc).
+
+@test "runc run [seccomp] (SCMP_ACT_ERRNO default)" {
+	update_config '   .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"]
+			| .process.noNewPrivileges = false
+			| .linux.seccomp = {
+				"defaultAction":"SCMP_ACT_ALLOW",
+				"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32"],
+				"syscalls":[{"names":["mkdir"], "action":"SCMP_ACT_ERRNO"}]
+			}'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+	[[ "$output" == *"mkdir:"*"/dev/shm/foo"*"Operation not permitted"* ]]
+}
+
+@test "runc run [seccomp] (SCMP_ACT_ERRNO explicit errno)" {
+	update_config '   .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"]
+			| .process.noNewPrivileges = false
+			| .linux.seccomp = {
+				"defaultAction":"SCMP_ACT_ALLOW",
+				"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32"],
+				"syscalls":[{"names":["mkdir"], "action":"SCMP_ACT_ERRNO", "errnoRet": 100}]
+			}'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+	[[ "$output" == *"Network is down"* ]]
+}
+
+@test "runc run [seccomp] (SCMP_ACT_KILL)" {
+	update_config '  .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"]
+			| .process.noNewPrivileges = false
+			| .linux.seccomp = {
+				"defaultAction":"SCMP_ACT_ALLOW",
+				"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32"],
+				"syscalls":[{"names":["mkdir"], "action":"SCMP_ACT_KILL"}]
+			}'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+}
+
+# check that a startContainer hook is run with the seccomp filters applied
+@test "runc run [seccomp] (startContainer hook)" {
+	update_config '   .process.args = ["/bin/true"]
+			| .linux.seccomp = {
+				"defaultAction":"SCMP_ACT_ALLOW",
+				"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32"],
+				"syscalls":[{"names":["mkdir"], "action":"SCMP_ACT_KILL"}]
+			}
+			| .hooks = {
+				"startContainer": [ {
+						"path": "/bin/sh",
+						"args": ["sh", "-c", "mkdir /dev/shm/foo"]
+				} ]
+			}'
+
+	runc run test_busybox
+	[ "$status" -ne 0 ]
+	[[ "$output" == *"error running hook"* ]]
+	[[ "$output" == *"bad system call"* ]]
+}