Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
nkraetzschmar committed Apr 15, 2024
0 parents commit 006dc63
Show file tree
Hide file tree
Showing 34 changed files with 2,461 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .devcontainer/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM debian
COPY pkgs.list /
RUN apt-get update \
&& apt-get install -y --no-install-recommends $(cat /pkgs.list) \
&& rm /pkgs.list
RUN chmod -s /usr/bin/newuidmap \
&& setcap cap_setuid=ep /usr/bin/newuidmap \
&& chmod -s /usr/bin/newgidmap \
&& setcap cap_setgid=ep /usr/bin/newgidmap
RUN groupadd --gid 1000 dev \
&& useradd --uid 1000 --gid 1000 --shell /bin/bash --create-home \
-K SUB_UID_MIN=32768 \
-K SUB_UID_MAX=49151 \
-K SUB_UID_COUNT=2048 \
-K SUB_GID_MIN=32768 \
-K SUB_GID_MAX=49151 \
-K SUB_GID_COUNT=2048 \
dev
USER dev
WORKDIR /home/dev
14 changes: 14 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "dev",
"build": {
"dockerfile": "Containerfile"
},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.cpptools"
]
}
},
"runArgs": [ "--security-opt", "seccomp=unconfined", "--security-opt", "label=disable", "--security-opt", "apparmor=unconfined" ]
}
21 changes: 21 additions & 0 deletions .devcontainer/pkgs.list
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
attr
binutils
ca-certificates
debootstrap
gcc
gcc-aarch64-linux-gnu
gcc-x86-64-linux-gnu
git
htop
libc6-dev
libc6-dev-amd64-cross
libc6-dev-arm64-cross
libcap2-bin
make
man-db
openssl
patchelf
procps
strace
uidmap
wesperanto
94 changes: 94 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: build
on: push
jobs:
build:
strategy:
matrix:
arch:
- x86_64
- aarch64
fail-fast: false
name: ${{ matrix.arch }}
runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-latest-arm' || 'ubuntu-latest' }}
steps:
- name: setup arm runner
if: matrix.arch == 'aarch64'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends podman uidmap slirp4netns dbus-user-session
id="$(id -u)"
sudo systemctl start user@$id
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$id/bus
systemctl --user start dbus
mkdir -p "$HOME/.config/containers"
echo 'unqualified-search-registries = ["docker.io"]' > "$HOME/.config/containers/registries.conf"
- uses: actions/checkout@v4
- name: build dev container
run: podman build -t dev -f .devcontainer/Containerfile .devcontainer
- name: start dev container
run: >-
podman run --rm
--security-opt seccomp=unconfined
--security-opt label=disable
--security-opt apparmor=unconfined
--uidmap 0:1:1000
--uidmap 1000:0:1
--uidmap 1001:1001:64536
--gidmap 0:1:1000
--gidmap 1000:0:1
--gidmap 1001:1001:64536
-v "$PWD:/workdir"
-w /workdir
-d --name dev
dev tail -f /dev/null
- name: build
run: podman exec dev make all
- name: test
run: podman exec dev make test
- name: stop dev container
run: podman stop dev
- name: upload artifact
uses: actions/upload-artifact@v4
with:
name: fake_xattr_${{ matrix.arch }}
path: fake_xattr
release:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: download artifact (x86_64)
uses: actions/download-artifact@v4
with:
name: fake_xattr_x86_64
path: fake_xattr_x86_64
- name: download artifact (aarch64)
uses: actions/download-artifact@v4
with:
name: fake_xattr_aarch64
path: fake_xattr_aarch64
- name: setup env context
run: |
repo='${{ github.repository }}'
commit='${{ github.sha }}'
echo "artifact_prefix=${repo#*/}-${commit::8}" | tee -a "$GITHUB_ENV"
- name: pack artifacts
run: |
for arch in x86_64 aarch64; do
tar -c --transform "s|^fake_xattr_|${artifact_prefix}-|" "fake_xattr_$arch" | gzip > "${artifact_prefix}-${arch}.tar.gz"
done
- name: publish release
run: |
if gh release view latest > /dev/null 2>&1; then
gh release delete -y latest
fi
git tag --force latest
git push --force origin latest
gh release create latest \
--verify-tag \
--notes "created by GitHub actions run [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" \
"${artifact_prefix}-x86_64.tar.gz" \
"${artifact_prefix}-aarch64.tar.gz" \
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
*.o

# generated
syscall_lookup.c

# executables
fake_xattr
do_syscall

# tests
test_xattr_db
test_seccomp_unotify

.tmp
74 changes: 74 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
MAKEFLAGS += --no-builtin-rules

.DELETE_ON_ERROR:
.SILENT:
.SECONDEXPANSION:

CFLAGS := -std=c11 -Wall -Wextra -Wshadow -Wdeclaration-after-statement -Wno-multichar -Werror -O2

ifdef TEST_DEBUG
CFLAGS += -DTEST_DEBUG=$(TEST_DEBUG)
endif

generated := syscall_lookup.c
executables := fake_xattr do_syscall
tests := test_xattr_db test_seccomp_unotify

xattr_db_objects := xattr_db.o zalloc.o
seccomp_unotify_objects := seccomp_unotify.o clone_vfork.o syscall_lookup.o
test_objects := mem_account.o fd_account.o child_account.o

fake_xattr_objects := main.o mem_account.o $(seccomp_unotify_objects) $(xattr_db_objects)
do_syscall_objects := do_syscall.o syscall_lookup.o zalloc.o

test_xattr_db_objects := test_xattr_db.o $(xattr_db_objects) $(test_objects)
test_seccomp_unotify_objects := test_seccomp_unotify.o $(seccomp_unotify_objects) $(test_objects)

objects := $(foreach executable,$(executables) $(tests),$($(executable)_objects))
uniq_objects :=
$(foreach object,$(objects),$(if $(filter $(object),$(uniq_objects)),,$(eval uniq_objects += $(object))))

outputs := $(generated) $(objects) $(executables) $(tests)

.PHONY: all clean test $(addprefix @,$(tests))

all: $(executables)

clean:
echo rm $(outputs)
rm -rf "$$(readlink .tmp)"
rm -f $(outputs) .tmp

@test_seccomp_unotify: do_syscall .tmp .tmp/chroot

define test_target
@$(1): $(1)
unshare --map-root-user --map-auto ./$$<
endef

$(foreach test,$(tests),$(eval $(call test_target,$(test))))

test: integration_test $(addprefix @,$(tests)) all .tmp
./$<

$(foreach object,$(filter-out $(generated),$(objects:.o=.c)),$(eval $(shell $(CC) -MM $(object) | tr -d '\\')))

.tmp:
echo MKTEMP $@
ln -sf "$$(mktemp -d)" $@

.tmp/chroot: test_chroot_import_bin do_syscall | .tmp
[ -e $@ ] || mkdir $@
./$< $@ $(wordlist 2,$(words $^),$^) $$(command -v sh) $$(command -v cat) $$(command -v head) $$(command -v tail)

$(executables) $(tests): %: $$($$@_objects)
echo LINK $^ '->' $@
$(CC) $(CFLAGS) -o $@ $^

$(generated): %.c: gen_%
echo GEN $@
CC=$(CC) ./$< > $@

$(uniq_objects): %.o: %.c
echo CC $< '->' $@
$(CC) $(CFLAGS) -o $@ -c $<
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# seccomp_fake_xattr

Emulate extended attribute ([xattr](https://man7.org/linux/man-pages/man7/xattr.7.html)) operations in user space.

All xattr related syscalls are intercepted using the seccomp user-space notification mechanism ([seccomp_unotify](https://man7.org/linux/man-pages/man2/seccomp_unotify.2.html)) and their results emulated by a user-space handler.
The handler stores its own xattr database in memory, without ever accessing extended attributes on the underlying file system.

The main use of this is for system / distribution image builds.
It allows the build to set extended attributes that it would normally not have access to (i.e. if the build is running inside an unprivileged container or on a host with security modules loaded) in such a way that later tar archive / disk image / etc creation steps see and includes these attributes correctly.


## Usage

```
./fake_xattr [command] [args...]
```

`[command]` and all of its child processes will then run with their xattr syscalls intercepted and emulated.

> [!IMPORTANT]
> In order to install seccomp filters for its child processes, `fake_xattr` needs the `CAP_SYS_ADMIN` capability in its user namspace.
> Therefore, as an unprivileged user, it is necessary to run it with `unshare --map-root-user --map-auto ./fake_xattr` or similar means.

## Build

To build the main tool run

```
make fake_xattr
```

This requires a standard linux build environment to be present. See [Dev Container](#dev-container) for the recommended way of setting this up.

### Test

To build and run the unit and integration tests run

```
make test
```

## Dev Container

To simplify the build process this project includes a dev container. This can either be used directly by [VS Code](https://code.visualstudio.com/docs/devcontainers/containers) and [GitHub Codespaces](https://docs.github.com/en/codespaces) or as a regular container.

To use it as a regular container run

```
podman build -t dev .devcontainer
podman run --rm \
--security-opt seccomp=unconfined \
--security-opt label=disable \
--security-opt apparmor=unconfined \
--userns keep-id:uid=1000,gid=1000 \
-v "$PWD:/home/dev/workdir" \
-w /home/dev/workdir \
-it dev
```

> [!NOTE]
> The `--userns keep-id:uid=1000,gid=1000` is needed because the dev container is configured to drop privileges to a dev user (`uid=1000 gid=1000`), while podman by default will map your host system user to `uid=0 gid=0`.
> Thus the work directory would not otherwise be writable by the dev user.
> [!TIP]
> Older versions of podman do not support the `--userns keep-id:uid=1000,gid=1000` parameter.
> For these versions you will need to use the long form:
> `--uidmap 0:1:1000 --uidmap 1000:0:1 --uidmap 1001:1001:64536 --gidmap 0:1:1000 --gidmap 1000:0:1 --gidmap 1001:1001:64536`
> This does exactly the same, just in a more verbose format.
> [!TIP]
> Running the dev container with docker instead of podman may work, but is not supported and you will need to setup a uid_map from your host user to the dev user inside the container.
1 change: 1 addition & 0 deletions array_size.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#define array_size(X) (sizeof(X) / sizeof(*X))
15 changes: 15 additions & 0 deletions child_account.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <stdio.h>

#include "child_account.h"

size_t get_children(int *list, size_t size)
{
FILE *file;
size_t len;

file = fopen("/proc/thread-self/children", "r");
len = 0;
for (int child; fscanf(file, "%d", &child) != EOF; ++len) if (len < size) list[len] = child;
fclose(file);
return len;
}
3 changes: 3 additions & 0 deletions child_account.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <stddef.h>

size_t get_children(int *list, size_t size);
30 changes: 30 additions & 0 deletions clone_vfork.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#define _GNU_SOURCE
#include <linux/sched.h>
#include <sched.h>
#include <sys/mman.h>

#include "clone_vfork.h"

#define CLONE_STACK_SIZE 0x100000

pid_t clone_vfork(int (*func)(void *), void *arg, int flags)
{
void *clone_stack;
pid_t pid;

clone_stack = mmap(
NULL,
CLONE_STACK_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);

if (!clone_stack) return -1;

pid = clone(func, clone_stack + CLONE_STACK_SIZE, CLONE_VM | CLONE_VFORK | CLONE_CLEAR_SIGHAND | flags, arg);
munmap(clone_stack, CLONE_STACK_SIZE);

return pid;
}
3 changes: 3 additions & 0 deletions clone_vfork.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <sys/types.h>

pid_t clone_vfork(int (*func)(void *), void *arg, int flags);
8 changes: 8 additions & 0 deletions debug.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <stdio.h>
#include <unistd.h>

extern int debug;

#define __STR(x) #x
#define _STR(x) __STR(x)
#define debug_printf(fmt, ...) if (debug) fprintf(stderr, "\033[2mdebug: " __FILE__ ":" _STR(__LINE__) " (%s) [%d]: " fmt "\033[0m\n", __func__, getpid(), ##__VA_ARGS__)
Loading

0 comments on commit 006dc63

Please sign in to comment.