summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/Makefile2
-rw-r--r--contrib/bug216610/.gitignore3
-rw-r--r--contrib/bug216610/Dockerfile13
-rw-r--r--contrib/bug216610/Makefile30
-rw-r--r--contrib/bug216610/README.md139
-rwxr-xr-xcontrib/bug216610/c/build.sh10
-rw-r--r--contrib/bug216610/c/fib.c20
-rwxr-xr-xcontrib/bug216610/c/gcc.sh61
-rw-r--r--contrib/bug216610/go/.gitignore5
-rw-r--r--contrib/bug216610/go/fibber/fib.go32
-rw-r--r--contrib/bug216610/go/fibber/fibs_linux_amd64.s21
-rw-r--r--contrib/bug216610/go/fibber/fibs_linux_arm.s23
-rw-r--r--contrib/bug216610/go/go.mod5
-rw-r--r--contrib/bug216610/go/main.go29
-rwxr-xr-xcontrib/bug216610/mkdocker.sh18
-rwxr-xr-xcontrib/bug216610/package_fns.sh47
-rw-r--r--contrib/bug400591/Makefile4
-rw-r--r--contrib/capso/.gitignore2
-rw-r--r--contrib/capso/Makefile23
-rw-r--r--contrib/capso/README.md21
-rw-r--r--contrib/capso/bind.c29
-rw-r--r--contrib/capso/capso.c368
-rw-r--r--contrib/capso/capso.h16
-rw-r--r--contrib/pcaps4convenience18
-rw-r--r--contrib/pcaps4server2
-rw-r--r--contrib/pcaps4suid016
-rw-r--r--contrib/seccomp/explore.go277
-rw-r--r--contrib/seccomp/go.mod5
-rw-r--r--contrib/sucap/Makefile18
-rw-r--r--contrib/sucap/README.md40
-rw-r--r--contrib/sucap/su.c1638
-rw-r--r--contrib/sucap/sucap.pamconfig6
32 files changed, 2920 insertions, 21 deletions
diff --git a/contrib/Makefile b/contrib/Makefile
index 4749630..a4b5008 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -1,3 +1,3 @@
.PHONY: all clean
all clean:
- for x in bug* ; do make -C $$x $@ || exit 1 ; done
+ for x in bug* ; do $(MAKE) -C $$x $@ || exit 1 ; done
diff --git a/contrib/bug216610/.gitignore b/contrib/bug216610/.gitignore
new file mode 100644
index 0000000..1478d58
--- /dev/null
+++ b/contrib/bug216610/.gitignore
@@ -0,0 +1,3 @@
+*~
+arms
+Dockerfile
diff --git a/contrib/bug216610/Dockerfile b/contrib/bug216610/Dockerfile
new file mode 100644
index 0000000..5502b71
--- /dev/null
+++ b/contrib/bug216610/Dockerfile
@@ -0,0 +1,13 @@
+FROM debian:latest
+
+# A directory to share files via.
+RUN mkdir /shared
+
+RUN apt-get update
+RUN apt-get install -y gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi
+RUN apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
+
+# create a builder user
+RUN echo "builder:x:1000:1000:,,,:/home/builder:/bin/bash" >> /etc/passwd
+RUN echo "builder:*:19289:0:99999:7:::" >> /etc/shadow
+RUN mkdir -p /home/builder && chown builder.bin /home/builder
diff --git a/contrib/bug216610/Makefile b/contrib/bug216610/Makefile
new file mode 100644
index 0000000..ce96fb3
--- /dev/null
+++ b/contrib/bug216610/Makefile
@@ -0,0 +1,30 @@
+topdir=$(shell pwd)/../..
+include ../../Make.Rules
+
+GOTARGET=$(shell eval $$(go env) ; echo $${GOHOSTOS}_$${GOARCH})
+
+all: go/fib
+
+go/fib: go/main.go go/fibber/fib.go go/fibber/linkage.go go/fibber/fibs_$(GOTARGET).s go/fibber/fib_$(GOTARGET).syso
+ cd go && CGO_ENABLED=0 go build
+
+# Build the host native version.
+go/fibber/fib_$(GOTARGET).syso go/fibber/linkage.go: c/fib.c ./c/gcc.sh ./package_fns.sh
+ GCC=gcc ./c/gcc.sh -O3 c/fib.c -c -o go/fibber/fib_$(GOTARGET).syso
+ ./package_fns.sh fibber go/fibber/fib_$(GOTARGET).syso > go/fibber/linkage.go
+
+Dockerfile: Makefile ./mkdocker.sh
+ ./mkdocker.sh > $@
+
+# Use this build target (make arms) to extend support to include arm
+# and arm64 GOARCH values.
+arms: Dockerfile Makefile ./c/gcc.sh ./c/build.sh ./c/fib.c
+ docker run --rm -v $$PWD/c:/shared:z -h debian -u $$(id -u) -it expt shared/build.sh
+ mv c/*.syso go/fibber/
+ touch arms
+
+clean:
+ rm -f *~ arms
+ rm -f c/*.o c/*~
+ rm -f go/fib go/*~
+ rm -f go/fibber/*.syso go/fibber/*~ go/fibber/linkage.go
diff --git a/contrib/bug216610/README.md b/contrib/bug216610/README.md
new file mode 100644
index 0000000..4425715
--- /dev/null
+++ b/contrib/bug216610/README.md
@@ -0,0 +1,139 @@
+# Linking psx and C code without cgo
+
+## Overview
+
+In some embedded situations, there is a desire to compile Go binaries
+to include some C code, but not `libc` etc. For a long time, I had
+assumed this was not possible, since using `cgo` *requires* `libc` and
+`libpthread` linkage.
+
+This _embedded compilation_ need was referenced in a [bug
+filed](https://bugzilla.kernel.org/show_bug.cgi?id=216610) against the
+[`"psx"`](https://pkg.go.dev/kernel.org/pub/linux/libs/security/libcap/psx)
+package. The bug-filer was seeking an alternative to `CGO_ENABLED=1`
+compilation _requiring_ the `cgo` variant of `psx` build. However, the
+go `"runtime"` package will always
+[`panic()`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/runtime/os_linux.go;l=717-720)
+if you try this because it needs `libpthread` and `[g]libc` to work.
+
+In researching that bug report, however, I have learned there is a
+trick to combining a non-CGO built binary with compiled C code. I
+learned about it from a brief reference in the [Go Programming
+Language
+Wiki](https://zchee.github.io/golang-wiki/GcToolchainTricks/).
+
+This present directory evolved from my attempt to understand and
+hopefully resolve what was going on as reported in that bug into an
+example of this _trick_. I was unable to resolve the problem as
+reported because of the aformentioned `panic()` in the Go
+runtime. However, I was able to demonstrate embedding C code in a Go
+binary _without_ use of cgo. In such a binary, the Go-native version
+of `"psx"` is thus achievable. This is what the example in this
+present directory demonstrates.
+
+*Caveat Emptor*: this example is very fragile. The Go team only
+supports `cgo` linking against C. That being said, I'd certainly like
+to receive bug fixes, etc for this directory if you find you need to
+evolve it to make it work for your use case.
+
+## Content
+
+In this example we have:
+
+- Some C code for the functions `fib_init()` and `fib_next()` that
+combine to implement a _compute engine_ to determine [Fibonacci
+Numbers](https://en.wikipedia.org/wiki/Fibonacci_number). The source
+for this is in the sub directory `c/fib.c`.
+
+- Some Go code, in the directory `go/fibber` that uses this C compiled
+compute kernel.
+
+- `c/gcc.sh` which is a wrapper for `gcc` that adjusts the compilation
+to be digestible by Go's (internal) linker (the one that gets invoked
+when compiling `CGO_ENABLED=0`. Using `gcc` directly instead of this
+wrapper generates an incomplete binary - which miscomputes the
+expected answers. See the discussion below for what seems to be going
+on.
+
+- A top level `Makefile` to build it all.
+
+## Building and running the built binary
+
+Set things up with:
+```
+$ git clone git://git.kernel.org/pub/scm/libs/libcap/libcap.git
+$ cd libcap
+$ make all
+$ cd contrib/bug216610
+$ make clean all
+```
+When you run `./go/fib` it should generate the following output:
+```
+$ ./go/fib
+psx syscall result: PID=<nnnnn>
+fib: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
+$
+```
+Where `<nnnnn>` is the PID of the program at runtime and will be
+different each time the program is invoked.
+
+## Discussion
+
+The Fibonacci detail of what is going on is mostly uninteresting. The
+reason for developing this example was to explore the build issues in
+the reported [Bug
+216610](https://bugzilla.kernel.org/show_bug.cgi?id=216610). Ultimately,
+this example offers an alternative path to building a `nocgo` program
+that links to compute kernel of C code.
+
+The reason we have added the `c/gcc.sh` wrapper for `gcc` is that
+we've found the Go linker has a hard time digesting the
+cross-sectional `%rip` based data addressing that various optimization
+modes of gcc like to use. Specifically, in the x86_64/amd64
+architecture, if a `R_X86_64_PC32` relocation entry made in a `.text`
+section refers to an `.rodata.cst8` section in a generated `.syso`
+file, the Go linker seems to [replace this reference with a `0` offset
+to
+`(%rip)`](https://github.com/golang/go/issues/24321#issuecomment-1296084103). What
+our wrapper script does is rewrite the generated assembly to store
+these data references to the `.text` section. The Go linker has no
+problem with this _same section_ relative addressing and is able to
+link the resulting objects without problems.
+
+If you want to cross compile, we have support for 32-bit arm
+compilation: what is needed for the Raspberry PI. To get this support,
+try:
+```
+$ make clean all arms
+$ cd go
+$ GOARCH=arm CGO_ENABLED=0 go build
+```
+The generated `fib` binary runs on a 32-bit Raspberry Pi.
+
+## Future thoughts
+
+At present, this example only works on Linux with `x86_64` and `arm`
+build architectures. (In go-speak that is `linux_amd64` and
+`linux_arm`). This is because I have only provided some bridging
+assembly for Go to C calling conventions for those architecture
+targets: `./go/fibber/fibs_linux_amd64.s` and
+`./go/fibber/fibs_linux_arm.s`. The non-native, `make arms`, cross
+compilation requires the `docker` command to be available.
+
+I intend to implement an `arm64` build, when I have a system on which
+to test it.
+
+**Note** The Fedora system on which I've been developing this has some
+ SELINUX impediment to naively using the `docker -v ...` bind mount
+ option. I need the `:z` suffix for bind mounting. I don't know how
+ common an issue this is. On Fedora, building the arm variants of the
+ .syso file can be performed as follows:
+```
+$ docker run --rm -v $PWD/c:/shared:z -h debian -u $(id -u) -it expt shared/build.sh
+```
+
+## Reporting bugs
+
+Please report issues or offer improvements to this example via the
+[Fully Capable `libcap`](https://sites.google.com/site/fullycapable/)
+website.
diff --git a/contrib/bug216610/c/build.sh b/contrib/bug216610/c/build.sh
new file mode 100755
index 0000000..7458fb1
--- /dev/null
+++ b/contrib/bug216610/c/build.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#
+# Builds the following .syso files to the directory containing this script:
+#
+# fib_linux_arm.syso
+# fib_linux_arm64.syso
+
+cd ${0%/*}
+GCC=arm-linux-gnueabi-gcc ./gcc.sh -O3 fib.c -c -o fib_linux_arm.syso
+GCC=aarch64-linux-gnu-gcc ./gcc.sh -O3 fib.c -c -o fib_linux_arm64.syso
diff --git a/contrib/bug216610/c/fib.c b/contrib/bug216610/c/fib.c
new file mode 100644
index 0000000..bd665c7
--- /dev/null
+++ b/contrib/bug216610/c/fib.c
@@ -0,0 +1,20 @@
+#include <inttypes.h>
+
+struct state {
+ uint32_t b, a;
+};
+
+void fib_init(struct state *s);
+void fib_init(struct state *s)
+{
+ s->a = 0;
+ s->b = 1;
+}
+
+void fib_next(struct state *s);
+void fib_next(struct state *s)
+{
+ uint32_t next = s->a + s->b;
+ s->a = s->b;
+ s->b = next;
+}
diff --git a/contrib/bug216610/c/gcc.sh b/contrib/bug216610/c/gcc.sh
new file mode 100755
index 0000000..33655d6
--- /dev/null
+++ b/contrib/bug216610/c/gcc.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# The Go linker does not seem to know what to do with relative
+# addressing of rodata.* offset from %rip. GCC likes to use this
+# addressing mode on this architecture, so we quickly run into
+# mis-computation when the relative addressing used in a .syso file of
+# symbol located data is resolved to completely the wrong place by the
+# Go (internal) linker.
+#
+# As a workaround for this, we can modify the assembly source code
+# generated by GCC to not point at problematic '.rodata.*' sections,
+# and place this data in the good old '.text' section where Go's
+# linker can make sense of it.
+#
+# This script exists to generate a '.syso' file from some '*.c' files.
+# It works by recognizing the '*.c' command line arguments and
+# converting them into fixed-up '*.s' files. It then performs the
+# compilation for the collection of the '*.s' files. Upon success, it
+# purges the intermediate '*.s' files.
+#
+# The fragile aspect of this present script is which compiler
+# arguments should be used for the compilation from '.c' -> '.s'
+# files. What we do is accumulate arguments until we encounter our
+# first '*.c' file and use those to perform the '.c' -> '.s'
+# compilation. We build up a complete command line for gcc
+# substituting '.s' files for '.c' files in the original command
+# line. Then with the new command line assembled we invoke gcc with
+# those. If that works, we remove all of the intermediate '.s' files.
+
+GCC="${GCC:=gcc}"
+setup=0
+args=()
+final=()
+ses=()
+
+for arg in "$@"; do
+ if [[ "${arg##*.}" = "c" ]]; then
+ setup=1
+ s="${arg%.*}.s"
+ "${GCC}" "${args[@]}" -S -o "${s}" "${arg}"
+ sed -i -e 's/.*\.rodata\..*/\t.text/' "${s}"
+ final+=("${s}")
+ ses+=("${s}")
+ else
+ if [[ $setup -eq 0 ]]; then
+ args+=("${arg}")
+ fi
+ final+=("${arg}")
+ fi
+done
+
+#echo final: "${final[@]}"
+#echo args: "${args[@]}"
+#echo ses: "${ses[@]}"
+
+"${GCC}" "${final[@]}"
+if [[ $? -ne 0 ]]; then
+ echo "failed to compile"
+ exit 1
+fi
+rm -f "${ses[@]}"
diff --git a/contrib/bug216610/go/.gitignore b/contrib/bug216610/go/.gitignore
new file mode 100644
index 0000000..ae14305
--- /dev/null
+++ b/contrib/bug216610/go/.gitignore
@@ -0,0 +1,5 @@
+fib
+*.syso
+main
+go.sum
+linkage.go
diff --git a/contrib/bug216610/go/fibber/fib.go b/contrib/bug216610/go/fibber/fib.go
new file mode 100644
index 0000000..49757cd
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fib.go
@@ -0,0 +1,32 @@
+// Package fibber implements a Fibonacci sequence generator using a C
+// coded compute kernel (a .syso file).
+package fibber
+
+import (
+ "unsafe"
+)
+
+// State is the native Go form of the C.state structure.
+type State struct {
+ B, A uint32
+}
+
+// cPtr converts State into a C pointer suitable as an argument for
+// sysoCaller.
+func (s *State) cPtr() unsafe.Pointer {
+ return unsafe.Pointer(&s.B)
+}
+
+// NewState initializes a Fibonacci Number sequence generator. Upon
+// return s.A=0 and s.B=1 are the first two numbers in the sequence.
+func NewState() *State {
+ s := &State{}
+ syso__fib_init.call(s.cPtr())
+ return s
+}
+
+// Next advances the state to the next number in the sequence. Upon
+// return, s.B is the most recently calculated value.
+func (s *State) Next() {
+ syso__fib_next.call(s.cPtr())
+}
diff --git a/contrib/bug216610/go/fibber/fibs_linux_amd64.s b/contrib/bug216610/go/fibber/fibs_linux_amd64.s
new file mode 100644
index 0000000..5992d09
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fibs_linux_amd64.s
@@ -0,0 +1,21 @@
+// To transition from a Go call to a C function call, we are skating
+// on really thin ice... Ceveat Emptor!
+//
+// Ref:
+// https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/home
+//
+// This is not strictly needed, but it makes gdb debugging less
+// confusing because spacer ends up being an alias for the TEXT
+// section start.
+TEXT ·spacer(SB),$0
+ RET
+
+#define RINDEX(n) (8*n)
+
+// Header to this function wrapper is the last time we can voluntarily
+// yield to some other goroutine.
+TEXT ·syso(SB),$0-16
+ MOVQ cFn+RINDEX(0)(FP), SI
+ MOVQ state+RINDEX(1)(FP), DI
+ CALL *SI
+ RET
diff --git a/contrib/bug216610/go/fibber/fibs_linux_arm.s b/contrib/bug216610/go/fibber/fibs_linux_arm.s
new file mode 100644
index 0000000..39640a5
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fibs_linux_arm.s
@@ -0,0 +1,23 @@
+// To transition from a Go call to a C function call, we are skating
+// on really thin ice... Ceveat Emptor!
+//
+// Ref:
+// https://stackoverflow.com/questions/261419/what-registers-to-save-in-the-arm-c-calling-convention
+//
+// This is not strictly needed, but it makes gdb debugging less
+// confusing because spacer ends up being an alias for the TEXT
+// section start.
+TEXT ·spacer(SB),$0
+ RET
+
+#define FINDEX(n) (8*n)
+
+// Header to this function wrapper is the last time we can voluntarily
+// yield to some other goroutine.
+//
+// Conventions: PC == R15, SP == R13, LR == R14, IP (scratch) = R12
+TEXT ·syso(SB),$0-8
+ MOVW cFn+0(FP), R14
+ MOVW state+4(FP), R0
+ BL (R14)
+ RET
diff --git a/contrib/bug216610/go/go.mod b/contrib/bug216610/go/go.mod
new file mode 100644
index 0000000..8531994
--- /dev/null
+++ b/contrib/bug216610/go/go.mod
@@ -0,0 +1,5 @@
+module fib
+
+go 1.18
+
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69
diff --git a/contrib/bug216610/go/main.go b/contrib/bug216610/go/main.go
new file mode 100644
index 0000000..65121f6
--- /dev/null
+++ b/contrib/bug216610/go/main.go
@@ -0,0 +1,29 @@
+// Program fib uses the psx package once, and then prints the first
+// ten Fibonacci numbers.
+package main
+
+import (
+ "fmt"
+ "log"
+ "syscall"
+
+ "fib/fibber"
+
+ "kernel.org/pub/linux/libs/security/libcap/psx"
+)
+
+func main() {
+ pid, _, err := psx.Syscall3(syscall.SYS_GETPID, 0, 0, 0)
+ if err != 0 {
+ log.Fatalf("failed to get PID via psx: %v", err)
+ }
+ fmt.Print("psx syscall result: PID=")
+ fmt.Println(pid)
+ s := fibber.NewState()
+ fmt.Print("fib: ", s.A, ", ", s.B)
+ for i := 0; i < 8; i++ {
+ s.Next()
+ fmt.Print(", ", s.B)
+ }
+ fmt.Println(", ...")
+}
diff --git a/contrib/bug216610/mkdocker.sh b/contrib/bug216610/mkdocker.sh
new file mode 100755
index 0000000..860c198
--- /dev/null
+++ b/contrib/bug216610/mkdocker.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# This script generates a Dockerfile to be used for cross-compilation
+cat <<EOF
+FROM debian:latest
+
+# A directory to share files via.
+RUN mkdir /shared
+
+RUN apt-get update
+RUN apt-get install -y gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi
+RUN apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
+
+# create a builder user
+RUN echo "builder:x:$(id -u):$(id -g):,,,:/home/builder:/bin/bash" >> /etc/passwd
+RUN echo "builder:*:19289:0:99999:7:::" >> /etc/shadow
+RUN mkdir -p /home/builder && chown builder.bin /home/builder
+EOF
diff --git a/contrib/bug216610/package_fns.sh b/contrib/bug216610/package_fns.sh
new file mode 100755
index 0000000..0f4b91c
--- /dev/null
+++ b/contrib/bug216610/package_fns.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# Generate some Go code to make calling into the C code of the .syso
+# file easier.
+
+package="${1}"
+syso="${2}"
+
+if [[ -z "${syso}" ]]; then
+ echo "usage: $0 <package> <.....syso>" >&2
+ exit 1
+fi
+
+if [[ "${syso%.syso}" == "${syso}" ]]; then
+ echo "2nd argument should be a .syso file" >&2
+ exit 1
+fi
+
+cat<<EOF
+package ${package}
+
+import (
+ "unsafe"
+)
+
+// syso is how we call, indirectly, into the C-code.
+func syso(cFn, state unsafe.Pointer)
+
+type sysoCaller struct {
+ ptr unsafe.Pointer
+}
+
+// call calls the syso linked C-function, $sym().
+func (s *sysoCaller) call(data unsafe.Pointer) {
+ syso(s.ptr, data)
+}
+EOF
+
+for sym in $(objdump -x "${syso}" | grep -F 'g F' | awk '{print $6}'); do
+ cat<<EOF
+
+//go:linkname _${sym} ${sym}
+var _${sym} byte
+var syso__${sym} = &sysoCaller{ptr: unsafe.Pointer(&_${sym})}
+
+EOF
+done
diff --git a/contrib/bug400591/Makefile b/contrib/bug400591/Makefile
index 320610c..bb2e59d 100644
--- a/contrib/bug400591/Makefile
+++ b/contrib/bug400591/Makefile
@@ -1,8 +1,8 @@
all: bug
bug: bug.c ../../libcap Makefile
- make -C ../../libcap
- cc -g -I../../libcap/include --static -o $@ $< -L../../libcap -lcap
+ $(MAKE) -C ../../libcap
+ $(CC) $(CFLAGS) $(CPPFLAGS) -g -I../../libcap/include --static -o $@ $< -L../../libcap -lcap
./bug
clean:
diff --git a/contrib/capso/.gitignore b/contrib/capso/.gitignore
new file mode 100644
index 0000000..222d35d
--- /dev/null
+++ b/contrib/capso/.gitignore
@@ -0,0 +1,2 @@
+capso.so
+bind
diff --git a/contrib/capso/Makefile b/contrib/capso/Makefile
new file mode 100644
index 0000000..70af7f9
--- /dev/null
+++ b/contrib/capso/Makefile
@@ -0,0 +1,23 @@
+topdir=$(shell pwd)/../..
+include ../../Make.Rules
+
+# Always build sources this way:
+CFLAGS += -fPIC $(CAPSO_DEBUG)
+
+all: bind
+
+bind: bind.c capso.so
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ bind.c capso.so -L../../libcap -lcap
+
+../../libcap/loader.txt:
+ $(MAKE) -C ../../libcap loader.txt
+
+capso.o: capso.c capso.h ../../libcap/execable.h ../../libcap/loader.txt
+ $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBCAP_VERSION=\"libcap-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat ../../libcap/loader.txt)\" -c capso.c -o $@
+
+capso.so: capso.o
+ $(LD) $(LDFLAGS) -o $@ $< $(LIBCAPLIB) -ldl -Wl,-e,__so_start
+ sudo setcap cap_net_bind_service=p $@
+
+clean:
+ rm -f bind capso.o capso.so *~
diff --git a/contrib/capso/README.md b/contrib/capso/README.md
new file mode 100644
index 0000000..df2e878
--- /dev/null
+++ b/contrib/capso/README.md
@@ -0,0 +1,21 @@
+# Leveraging file capabilities on shared libraries
+
+This directory contains an example of a shared library (`capso.so`)
+that can be installed with file capabilities. When the library is
+linked against an unprivileged program, it includes internal support
+for re-invoking itself as a child subprocess to execute a privileged
+operation on bahalf of the parent.
+
+The idea for doing this was evolved from the way `pam_unix.so` is able
+to leverage a separate program, and `libcap`'s recently added support
+for supporting binary execution of all the `.so` files built by the
+package.
+
+The actual program example `./bind` leverages the
+`"cap_net_bind_service=p"` enabled `./capso.so` file to bind to the
+privileged port 80.
+
+A writeup of how to build and explore the behavior of this example is
+provided on the `libcap` distribution website:
+
+https://sites.google.com/site/fullycapable/capable-shared-objects
diff --git a/contrib/capso/bind.c b/contrib/capso/bind.c
new file mode 100644
index 0000000..609e4e4
--- /dev/null
+++ b/contrib/capso/bind.c
@@ -0,0 +1,29 @@
+/*
+ * Unprivileged program that binds to port 80. It does this by
+ * leveraging a file capable shared library.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "capso.h"
+
+int main(int argc, char **argv) {
+ int f = bind80("127.0.0.1");
+ if (f < 0) {
+ perror("unable to bind to port 80");
+ exit(1);
+ }
+ if (listen(f, 10) == -1) {
+ perror("unable to listen to port 80");
+ exit(1);
+ }
+ printf("Webserver code to use filedes = %d goes here.\n"
+ "(Sleeping for 60s... Try 'netstat -tlnp|grep :80')\n", f);
+ fflush(stdout);
+ sleep(60);
+ close(f);
+ printf("Done.\n");
+}
diff --git a/contrib/capso/capso.c b/contrib/capso/capso.c
new file mode 100644
index 0000000..7ca3427
--- /dev/null
+++ b/contrib/capso/capso.c
@@ -0,0 +1,368 @@
+/*
+ * Worked example for a shared object with a file capability on it
+ * leveraging itself for preprogrammed functionality.
+ *
+ * This example implements a shared library that can bind to
+ * the privileged port. ":80".
+ *
+ * The shared library needs to be installed with
+ * cap_net_bind_service=p. As a shared library, it provides the
+ * function bind80().
+ */
+
+#define _GNU_SOURCE
+
+#include <dlfcn.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "capso.h"
+
+extern char **environ;
+
+/*
+ * fake_exploit is some dedicated code to simulate a shell escape type
+ * exploit. This is obviously not something serious to include in code
+ * that has actually been audited for security, but we use it to
+ * demonstrate an aspect of file capabilities vs. setuid root for
+ * granting privilege.
+ */
+static void fake_exploit(void) {
+#ifdef ALLOW_EXPLOIT
+ const char *exploit = getenv("TRIGGER_EXPLOIT");
+ if (exploit == NULL) {
+ return;
+ }
+
+ switch (*exploit) {
+ case '^':
+ case '%':
+ exploit++;
+ cap_value_t caps = CAP_NET_BIND_SERVICE;
+ cap_t c = cap_get_proc();
+ cap_set_flag(c, CAP_INHERITABLE, 1, &caps, CAP_SET);
+ if (cap_set_proc(c)) {
+ perror("Failed to raise inheritable capability");
+ exit(1);
+ }
+ if (*(exploit-1) == '%') {
+ break;
+ }
+ cap_free(c);
+ if (cap_set_ambient(caps, CAP_SET) != 0) {
+ perror("Unable to raise ambient capability");
+ exit(1);
+ }
+ break;
+ }
+
+ char *ts = strdup(exploit);
+ if (ts == NULL) {
+ perror("Failed to duplicate exploit string");
+ exit(1);
+ }
+
+ int i, j, n = 1;
+ for (i = 0; ts[i]; i++) {
+ switch (ts[i]) {
+ case ' ':
+ case '\t':
+ n++;
+ ts[i] = '\0';
+ }
+ }
+ char **argv = calloc(n, sizeof(char *));
+ for (i = 0, j = 0; j < n; j++) {
+ char *s = ts+i;
+ argv[j] = s;
+ i += 1 + strlen(s);
+ printf("execv argv[%d] = \"%s\"\n", j, s);
+ }
+
+ execv(argv[0], argv);
+ perror("Execv failed");
+ exit(1);
+#endif /* def ALLOW_EXPLOIT */
+}
+
+/*
+ * where_am_i determines the full path for the shared libary that
+ * contains this function. It allocates the path in strdup()d memory
+ * that should be free()d by the caller. If it can't find itself, it
+ * returns NULL.
+ */
+static char *where_am_i(void)
+{
+ Dl_info info;
+ if (dladdr(where_am_i, &info) == 0) {
+ return NULL;
+ }
+ return strdup(info.dli_fname);
+}
+
+/*
+ * try_bind80 attempts to reuseably bind to port 80 with the given
+ * hostname. It returns a bound filedescriptor or -1 on error.
+ */
+static int try_bind80(const char *hostname)
+{
+ struct addrinfo *conf, *detail = NULL;
+ int err, ret = -1, one = 1;
+
+ conf = calloc(1, sizeof(*conf));
+ if (conf == NULL) {
+ return -1;
+ }
+
+ conf->ai_family = PF_UNSPEC;
+ conf->ai_socktype = SOCK_STREAM;
+ conf->ai_protocol = 0;
+ conf->ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+
+ err = getaddrinfo(hostname, "80", conf, &detail);
+ if (err != 0) {
+ goto done;
+ }
+
+ ret = socket(detail->ai_family, detail->ai_socktype, detail->ai_protocol);
+ if (ret == -1) {
+ goto done;
+ }
+
+ if (setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+ close(ret);
+ ret = -1;
+ goto done;
+ }
+
+ if (bind(ret, detail->ai_addr, detail->ai_addrlen)) {
+ close(ret);
+ ret = -1;
+ goto done;
+ }
+
+ done:
+ if (detail != NULL) {
+ freeaddrinfo(detail);
+ }
+ free(conf);
+
+ return ret;
+}
+
+/*
+ * set_fd3 forces file descriptor 3 to be associated with a unix
+ * socket that can be used to send a file descriptor back to the
+ * parent program.
+ */
+static int set_fd3(void *detail)
+{
+ int *sp = detail;
+
+ close(sp[0]);
+ if (dup2(sp[1], 3) != 3) {
+ return -1;
+ }
+ close(sp[1]);
+
+ return 0;
+}
+
+/*
+ * bind80 returns a socket filedescriptor that is bound to port 80 of
+ * the provided service address.
+ *
+ * Example:
+ *
+ * int fd = bind80("localhost");
+ *
+ * fd < 0 in the case of error.
+ */
+int bind80(const char *hostname)
+{
+ cap_launch_t helper;
+ pid_t child;
+ char const *args[3];
+ char *path;
+ int fd, ignored;
+ int sp[2];
+ char junk[1];
+ const int rec_buf_len = CMSG_SPACE(sizeof(int));
+ char *rec_buf[CMSG_SPACE(sizeof(int))];
+ struct iovec *iov;
+ struct msghdr *msg;
+
+ fd = try_bind80(hostname);
+ if (fd >= 0) {
+ return fd;
+ }
+
+#ifdef CAPSO_DEBUG
+ printf("application bind80(%s) attempt failed\n", hostname);
+ sleep(30);
+#endif
+
+ iov = calloc(1, sizeof(struct iovec));
+ if (iov == NULL) {
+ return -1;
+ }
+ msg = calloc(1, sizeof(struct msghdr));
+ if (msg == NULL) {
+ free(iov);
+ return -1;
+ }
+
+ /*
+ * Initial attempt didn't work, so try launching the shared
+ * library as an executable and getting it to yield a bound
+ * filedescriptor for us via a unix socket pair.
+ */
+ path = where_am_i();
+ if (path == NULL) {
+ perror("Unable to find self");
+ goto drop_alloc;
+ }
+
+ args[0] = "bind80-helper";
+ args[1] = hostname;
+ args[2] = NULL;
+
+ helper = cap_new_launcher(path, args, (void *) environ);
+ if (helper == NULL) {
+ goto drop_path;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sp)) {
+ goto drop_helper;
+ }
+
+ cap_launcher_callback(helper, set_fd3);
+ child = cap_launch(helper, sp);
+ close(sp[1]);
+
+ if (child <= 0) {
+ goto drop_sp;
+ }
+
+ iov[0].iov_base = junk;
+ iov[0].iov_len = 1;
+
+ msg->msg_name = NULL;
+ msg->msg_namelen = 0;
+ msg->msg_iov = iov;
+ msg->msg_iovlen = 1;
+ msg->msg_control = rec_buf;
+ msg->msg_controllen = rec_buf_len;
+
+ if (recvmsg(sp[0], msg, 0) != -1) {
+ fd = * (int *) CMSG_DATA(CMSG_FIRSTHDR(msg));
+ }
+ waitpid(child, &ignored, 0);
+
+ drop_sp:
+ close(sp[0]);
+
+ drop_helper:
+ cap_free(helper);
+
+ drop_path:
+ free(path);
+
+ drop_alloc:
+ free(msg);
+ free(iov);
+
+ return fd;
+}
+
+#include "../../libcap/execable.h"
+//#define SO_MAIN int main
+
+SO_MAIN(int argc, char **argv)
+{
+ const char *cmd = "<capso.so>";
+ const cap_value_t cap_net_bind_service = CAP_NET_BIND_SERVICE;
+ cap_t working;
+ int fd;
+ struct msghdr msg;
+ struct cmsghdr *ctrl;
+ struct iovec payload;
+ char data[CMSG_SPACE(sizeof(fd))];
+ char junk[1];
+
+#ifdef CAPSO_DEBUG
+ printf("invoking %s standalone\n", argv[0]);
+ sleep(30);
+#endif
+
+ if (argv != NULL) {
+ cmd = argv[0];
+ }
+
+ if (argc != 2 || argv[1] == NULL || !strcmp(argv[1], "--help")) {
+ fprintf(stderr, "usage: %s <hostname>\n", cmd);
+ exit(1);
+ }
+
+ working = cap_get_proc();
+ if (working == NULL) {
+ perror("Unable to read capabilities");
+ exit(1);
+ }
+
+ if (cap_set_flag(working, CAP_EFFECTIVE, 1,
+ &cap_net_bind_service, CAP_SET) != 0) {
+ perror("Unable to raise CAP_NET_BIND_SERVICE");
+ exit(1);
+ }
+
+ if (cap_set_proc(working) != 0) {
+ perror("Problem with cap_set_proc");
+ fprintf(stderr, "Try: sudo setcap cap_net_bind_service=p %s\n",
+ argv[0]);
+ exit(1);
+ }
+
+ fd = try_bind80(argv[1]);
+
+ memset(data, 0, sizeof(data));
+ memset(&payload, 0, sizeof(payload));
+
+ payload.iov_base = junk;
+ payload.iov_len = 1;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &payload;
+ msg.msg_iovlen = 1;
+ msg.msg_control = data;
+ msg.msg_controllen = sizeof(data);
+
+ ctrl = CMSG_FIRSTHDR(&msg);
+ ctrl->cmsg_level = SOL_SOCKET;
+ ctrl->cmsg_type = SCM_RIGHTS;
+ ctrl->cmsg_len = CMSG_LEN(sizeof(fd));
+
+ *((int *) CMSG_DATA(ctrl)) = fd;
+
+ if (sendmsg(3, &msg, 0) < 0) {
+ perror("Failed to write fd");
+ }
+
+ fake_exploit();
+
+#ifdef CAPSO_DEBUG
+ printf("exiting standalone %s\n", argv[0]);
+ sleep(30);
+#endif
+
+ exit(0);
+}
diff --git a/contrib/capso/capso.h b/contrib/capso/capso.h
new file mode 100644
index 0000000..ae18f3a
--- /dev/null
+++ b/contrib/capso/capso.h
@@ -0,0 +1,16 @@
+#ifndef CAPSO_H
+#define CAPSO_H
+
+/*
+ * bind80 returns a socket filedescriptor that is bound to port 80 of
+ * the provided service address.
+ *
+ * Example:
+ *
+ * int fd = bind80("localhost");
+ *
+ * fd < 0 in the case of error.
+ */
+extern int bind80(const char *hostname);
+
+#endif /* ndef CAPSO_H */
diff --git a/contrib/pcaps4convenience b/contrib/pcaps4convenience
index c46735d..b78a25b 100644
--- a/contrib/pcaps4convenience
+++ b/contrib/pcaps4convenience
@@ -63,22 +63,22 @@ p4c_test(){
# are we sane?
WICH=`which which 2>/dev/null`
if [ $WICH == "" ]; then
- # thats bad
+ # that's bad
echo "Sorry, I haven't found which"
exit
fi
- # we needt his apps
+ # we need this app
SETCAP=`which setcap 2>/dev/null`
if [ "$SETCAP" == "" ]; then
- echo "Sorry, I'm missing setcap !"
+ echo "Sorry, I'm missing setcap!"
exit
fi
- # checking setcap for SET_SETFCAP PCap ?
+ # checking setcap for SET_SETFCAP PCap?
# for now we stick to root
if [ "$( id -u )" != "0" ]; then
- echo "Sorry, you must be root !"
+ echo "Sorry, you must be root!"
exit 1
fi
}
@@ -113,7 +113,7 @@ p4c_app_convert(){
p4c_app_revert(){
- # revert a singel app
+ # revert a single app
# $1 is app name
APP=`which -a $1 2>/dev/null`
if [ "$APP" != "" ]; then
@@ -136,7 +136,7 @@ p4c_app_revert(){
p4c_convert(){
- # we go throug the APPSARRAY and call s2p_app_convert to do the job
+ # we go through the APPSARRAY and call s2p_app_convert to do the job
COUNTER=0
let UPPER=${#APPSARRAY[*]}-1
until [ $COUNTER == $UPPER ]; do
@@ -170,9 +170,9 @@ p4c_usage(){
echo "through the PAM module pam_cap.so."
echo "A user who has not the needed PCaps in his Inheritance Set CAN NOT execute"
echo "these binaries successful."
- echo "(well, still per sudo or su -c - but thats not the point here)"
+ echo "(well, still per sudo or su -c - but that's not the point here)"
echo
- echo "You need and I will check fot the utilities which and setcap."
+ echo "You need and I will check for the utilities which and setcap."
echo
echo "Your Filesystem has to support extended attributes and your kernel must have"
echo "support for POSIX File Capabilities (CONFIG_SECURITY_FILE_CAPABILITIES)."
diff --git a/contrib/pcaps4server b/contrib/pcaps4server
index af6f9ca..f72a4d3 100644
--- a/contrib/pcaps4server
+++ b/contrib/pcaps4server
@@ -8,7 +8,7 @@
# changelog:
# 1 - initial release pcaps4convenience
# 1 - 2007.02.15 - initial release
-# 2 - 2007.11.02 - changed to new setfcaps api; each app is now callable; supressed error of id
+# 2 - 2007.11.02 - changed to new setfcaps api; each app is now callable; suppressed error of id
# 3 - 2007.12.28 - changed to libcap2 package setcap/getcap
# 4 - renamed to pcaps4server
# removed suid0 and convenience files,
diff --git a/contrib/pcaps4suid0 b/contrib/pcaps4suid0
index 799df28..2cbdcee 100644
--- a/contrib/pcaps4suid0
+++ b/contrib/pcaps4suid0
@@ -77,23 +77,23 @@ p4s_test(){
# are we sane?
WICH=`which which 2>/dev/null`
if [ $WICH == "" ]; then
- # thats bad
+ # that's bad
echo "Sorry, I haven't found which"
exit
fi
- # we needt his apps
+ # we need these apps
CHMOD=`which chmod 2>/dev/null`
SETCAP=`which setcap 2>/dev/null`
if [ "$CHMOD" == "" -o "$SETCAP" == "" ]; then
- echo "Sorry, I'm missing chmod or setcap !"
+ echo "Sorry, I'm missing chmod or setcap!"
exit
fi
- # checking setcap for SET_SETFCAP PCap ?
+ # checking setcap for SET_SETFCAP PCap?
# for now we stick to root
if [ "$( id -u )" != "0" ]; then
- echo "Sorry, you must be root !"
+ echo "Sorry, you must be root!"
exit 1
fi
}
@@ -129,7 +129,7 @@ p4s_app_convert(){
p4s_app_revert(){
- # revert a singel app
+ # revert a single app
# $1 is app name
APP=`which -a $1 2>/dev/null`
if [ "$APP" != "" ]; then
@@ -153,7 +153,7 @@ p4s_app_revert(){
p4s_convert(){
- # we go throug the APPSARRAY and call s2p_app_convert to do the job
+ # we go through the APPSARRAY and call s2p_app_convert to do the job
COUNTER=0
let UPPER=${#APPSARRAY[*]}-1
until [ $COUNTER == $UPPER ]; do
@@ -190,7 +190,7 @@ p4s_usage(){
echo "If you are using pam_cap.so, you might want to change the set into the"
echo "Inherited and Effective set (check for the SET var)."
echo
- echo "You need and I will check fot the utilities which, chmod and setcap."
+ echo "You need and I will check for the utilities which, chmod and setcap."
echo
echo "Your Filesystem has to support extended attributes and your kernel must have"
echo "support for POSIX File Capabilities (CONFIG_SECURITY_FILE_CAPABILITIES)."
diff --git a/contrib/seccomp/explore.go b/contrib/seccomp/explore.go
new file mode 100644
index 0000000..8203d4f
--- /dev/null
+++ b/contrib/seccomp/explore.go
@@ -0,0 +1,277 @@
+// Program explore is evolved from the code discussed in more depth
+// here:
+//
+// https://github.com/golang/go/issues/3405
+//
+// The code here demonstrates that while PR_SET_NO_NEW_PRIVS only
+// applies to the calling thread, since
+// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=103502a35cfce0710909da874f092cb44823ca03
+// the seccomp filter application forces the setting to be mirrored on
+// all the threads of a process.
+//
+// Based on the command line options, we can manipulate the program to
+// behave in various ways. Example command lines:
+//
+// sudo ./explore
+// sudo ./explore --kill=false
+// sudo ./explore --kill=false --errno=0
+//
+// Supported Go toolchains are after go1.10. Those prior to go1.15
+// require this environment variable to be set to build successfully:
+//
+// export CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*"
+//
+// Go toolchains go1.16+ can be compiled CGO_ENABLED=0 too,
+// demonstrating native nocgo support for seccomp features.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "runtime"
+ "syscall"
+ "time"
+ "unsafe"
+
+ "kernel.org/pub/linux/libs/security/libcap/psx"
+)
+
+var (
+ withPSX = flag.Bool("psx", false, "use the psx mechanism to invoke prctl syscall")
+ delays = flag.Bool("delays", false, "use this to pause the program at various places")
+ kill = flag.Bool("kill", true, "kill the process if setuid attempted")
+ errno = flag.Int("errno", int(syscall.ENOTSUP), "if kill is false, block syscall and return this errno")
+)
+
+const (
+ prSetNoNewPrivs = 38
+
+ sysSeccomp = 317 // x86_64 syscall number
+ seccompSetModeFilter = 1 // uses user-supplied filter.
+ seccompFilterFlagTsync = (1 << 0) // mirror filtering on all threads.
+ seccompRetErrno = 0x00050000 // returns an errno
+ seccompRetData = 0x0000ffff // mask for RET data payload (ex. errno)
+ seccompRetKillProcess = 0x80000000 // kill the whole process immediately
+ seccompRetTrap = 0x00030000 // disallow and force a SIGSYS
+ seccompRetAllow = 0x7fff0000
+
+ bpfLd = 0x00
+ bpfJmp = 0x05
+ bpfRet = 0x06
+
+ bpfW = 0x00
+
+ bpfAbs = 0x20
+ bpfJeq = 0x10
+
+ bpfK = 0x00
+
+ auditArchX86_64 = 3221225534 // HACK: I don't understand this value
+ archNr = auditArchX86_64
+
+ syscallNr = 0
+)
+
+// SockFilter is a single filter block.
+type SockFilter struct {
+ // Code is the filter code instruction.
+ Code uint16
+ // Jt is the target for a true result from the code execution.
+ Jt uint8
+ // Jf is the target for a false result from the code execution.
+ Jf uint8
+ // K is a generic multiuse field
+ K uint32
+}
+
+// SockFProg is a
+type SockFProg struct {
+ // Len is the number of contiguous SockFilter blocks that can
+ // be found at *Filter.
+ Len uint16
+ // Filter is the address of the first SockFilter block of a
+ // program sequence.
+ Filter *SockFilter
+}
+
+// SockFilterSlice is a subprogram filter.
+type SockFilterSlice []SockFilter
+
+func bpfStmt(code uint16, k uint32) SockFilter {
+ return SockFilter{code, 0, 0, k}
+}
+
+func bpfJump(code uint16, k uint32, jt uint8, jf uint8) SockFilter {
+ return SockFilter{code, jt, jf, k}
+}
+
+func validateArchitecture() []SockFilter {
+ return []SockFilter{
+ bpfStmt(bpfLd+bpfW+bpfAbs, 4), // HACK: I don't understand this 4.
+ bpfJump(bpfJmp+bpfJeq+bpfK, archNr, 1, 0),
+ bpfStmt(bpfRet+bpfK, seccompRetKillProcess),
+ }
+}
+
+func examineSyscall() []SockFilter {
+ return []SockFilter{
+ bpfStmt(bpfLd+bpfW+bpfAbs, syscallNr),
+ }
+}
+
+func allowSyscall(syscallNum uint32) []SockFilter {
+ return []SockFilter{
+ bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
+ bpfStmt(bpfRet+bpfK, seccompRetAllow),
+ }
+}
+
+func disallowSyscall(syscallNum, errno uint32) []SockFilter {
+ return []SockFilter{
+ bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
+ bpfStmt(bpfRet+bpfK, seccompRetErrno|(errno&seccompRetData)),
+ }
+}
+
+func killProcess() []SockFilter {
+ return []SockFilter{
+ bpfStmt(bpfRet+bpfK, seccompRetKillProcess),
+ }
+}
+
+func notifyProcessAndDie() []SockFilter {
+ return []SockFilter{
+ bpfStmt(bpfRet+bpfK, seccompRetTrap),
+ }
+}
+
+func trapOnSyscall(syscallNum uint32) []SockFilter {
+ return []SockFilter{
+ bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
+ bpfStmt(bpfRet+bpfK, seccompRetTrap),
+ }
+}
+
+func allGood() []SockFilter {
+ return []SockFilter{
+ bpfStmt(bpfRet+bpfK, seccompRetAllow),
+ }
+}
+
+// prctl executes the prctl - unless the --psx commandline argument is
+// used, this is on a single thread.
+//go:uintptrescapes
+func prctl(option, arg1, arg2, arg3, arg4, arg5 uintptr) error {
+ var e syscall.Errno
+ if *withPSX {
+ _, _, e = psx.Syscall6(syscall.SYS_PRCTL, option, arg1, arg2, arg3, arg4, arg5)
+ } else {
+ _, _, e = syscall.RawSyscall6(syscall.SYS_PRCTL, option, arg1, arg2, arg3, arg4, arg5)
+ }
+ if e != 0 {
+ return e
+ }
+ if *delays {
+ fmt.Println("prctl'd - check now")
+ time.Sleep(1 * time.Minute)
+ }
+ return nil
+}
+
+// SeccompSetModeFilter is our wrapper for performing our seccomp system call.
+//go:uintptrescapes
+func SeccompSetModeFilter(prog *SockFProg) error {
+ if _, _, e := syscall.RawSyscall(sysSeccomp, seccompSetModeFilter, seccompFilterFlagTsync, uintptr(unsafe.Pointer(prog))); e != 0 {
+ return e
+ }
+ return nil
+}
+
+var empty func()
+
+func lockProcessThread(pick bool) {
+ // Make sure we are
+ pid := uintptr(syscall.Getpid())
+ runtime.LockOSThread()
+ for {
+ tid, _, _ := syscall.RawSyscall(syscall.SYS_GETTID, 0, 0, 0)
+ if (tid == pid) == pick {
+ fmt.Println("validated TID:", tid, "== PID:", pid, "is", pick)
+ break
+ }
+ runtime.UnlockOSThread()
+ go func() {
+ time.Sleep(1 * time.Microsecond)
+ }()
+ runtime.Gosched()
+ runtime.LockOSThread()
+ }
+}
+
+// applyPolicy uploads the program sequence.
+func applyPolicy(prog *SockFProg) {
+ // Without PSX we can't guarantee the thread we execute the
+ // seccomp call on will be the same one that we disabled new
+ // privs on. With PSX, the disabling of new privs is mirrored
+ // on all threads.
+ if !*withPSX {
+ lockProcessThread(false)
+ defer runtime.UnlockOSThread()
+ }
+
+ // This is required to load a filter without privilege.
+ if err := prctl(prSetNoNewPrivs, 1, 0, 0, 0, 0); err != nil {
+ log.Fatalf("Prctl(PR_SET_NO_NEW_PRIVS): %v", err)
+ }
+
+ fmt.Println("Applying syscall policy...")
+ if err := SeccompSetModeFilter(prog); err != nil {
+ log.Fatalf("seccomp_set_mode_filter: %v", err)
+ }
+ fmt.Println("...Policy applied")
+}
+
+func main() {
+ flag.Parse()
+
+ if *delays {
+ fmt.Println("check first", syscall.Getpid())
+ time.Sleep(60 * time.Second)
+ }
+
+ var filter []SockFilter
+ filter = append(filter, validateArchitecture()...)
+
+ // Grab the system call number.
+ filter = append(filter, examineSyscall()...)
+
+ // List disallowed syscalls.
+ for _, x := range []uint32{
+ syscall.SYS_SETUID,
+ } {
+ if *kill {
+ filter = append(filter, trapOnSyscall(x)...)
+ } else {
+ filter = append(filter, disallowSyscall(x, uint32(*errno))...)
+ }
+ }
+
+ filter = append(filter, allGood()...)
+
+ prog := &SockFProg{
+ Len: uint16(len(filter)),
+ Filter: &filter[0],
+ }
+
+ applyPolicy(prog)
+
+ // Ensure we are running on the TID=PID.
+ lockProcessThread(true)
+
+ log.Print("Now it is time to try to run something privileged...")
+ if _, _, e := syscall.RawSyscall(syscall.SYS_SETUID, 1, 0, 0); e != 0 {
+ log.Fatalf("setuid failed with an error: %v", e)
+ }
+ log.Print("Looked like that worked, but it really didn't: uid == ", syscall.Getuid(), " != 1")
+}
diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod
new file mode 100644
index 0000000..ecf18d9
--- /dev/null
+++ b/contrib/seccomp/go.mod
@@ -0,0 +1,5 @@
+module explore
+
+go 1.14
+
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69
diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile
new file mode 100644
index 0000000..df61ed5
--- /dev/null
+++ b/contrib/sucap/Makefile
@@ -0,0 +1,18 @@
+topdir=$(shell pwd)/../..
+include ../../Make.Rules
+
+# This line is here to link against the in-tree copy of libcap.so
+LINKEXTRA=-Wl,-rpath,$(topdir)/libcap
+DEPS=../../libcap/libcap.so
+
+all: su
+
+su: su.c $(DEPS)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -DPAM_APP_NAME=\"sucap\" $< -o $@ $(LINKEXTRA) -lpam -lpam_misc $(LIBCAPLIB)
+ # to permit all ambient capabilities, this needs all permitted.
+ # sudo setcap =p ./su
+ # to permit all inheritable, as CAP_PURE1E needs, we don't need as much
+ sudo setcap cap_chown,cap_setgid,cap_setuid,cap_dac_read_search,cap_setpcap=p ./su
+
+clean:
+ rm -f su su.o *~
diff --git a/contrib/sucap/README.md b/contrib/sucap/README.md
new file mode 100644
index 0000000..5cc0dcc
--- /dev/null
+++ b/contrib/sucap/README.md
@@ -0,0 +1,40 @@
+# A fully capable version of `su`
+
+This directory contains a port of the `SimplePAMApp` `su` one that can
+work in a `PURE1E` `libcap`-_mode_ environment.
+
+The point of developing this is to better test the full `libcap`
+implementation, and to also provide a non-setuid-root worked example
+for testing PAM interaction with `libcap` and `pam_cap.so`. The
+required expectations for `pam_unix.so` are that it include this
+commit:
+
+https://github.com/linux-pam/linux-pam/pull/373/commits/bf9b1d8ad909634000a7356af2d865a79d3f86f3
+
+The original sources for this version of `su` were found here:
+
+https://kernel.org/pub/linux/libs/pam/pre/applications/SimplePAMApps-0.60.tar.gz
+
+The `SimplePAMApps` contain the same License as `libcap` (they were
+originally started by the same authors!). The credited Authors in the
+above tarball were:
+
+- Andrew [G.] Morgan
+- Andrey V. Savochkin
+- Alexei V. Galatenko
+
+The code in this present directory is freely adapted from the above
+tar ball and is thus a derived work from that.
+
+**NOTE** As of the time of writing, this adaptation is likely rife
+ with bugs.
+
+Finally, Andrew would like to apologize to Andrey for removing all of
+the config support he worked to add all those decades ago..! I just
+wanted to make a quick tester for a potential workaround for this
+`pam_cap.so` issue:
+
+- https://bugzilla.kernel.org/show_bug.cgi?id=212945
+
+Andrew G. Morgan <morgan@kernel.org>
+2021-06-30
diff --git a/contrib/sucap/su.c b/contrib/sucap/su.c
new file mode 100644
index 0000000..e3dfe70
--- /dev/null
+++ b/contrib/sucap/su.c
@@ -0,0 +1,1638 @@
+/*
+ * Originally based on an implementation of `su' by
+ *
+ * Peter Orbaek <poe@daimi.aau.dk>
+ *
+ * obtained circa 1997 from ftp://ftp.daimi.aau.dk/pub/linux/poe/
+ *
+ * Rewritten for Linux-PAM by Andrew G. Morgan <morgan@linux.kernel.org>
+ * Modified by Andrey V. Savochkin <saw@msu.ru>
+ * Modified for use with libcap by Andrew G. Morgan <morgan@kernel.org>
+ */
+
+/* #define PAM_DEBUG */
+
+#include <sys/prctl.h>
+
+/* non-root user of convenience to block signals */
+#define TEMP_UID 1
+
+#ifndef PAM_APP_NAME
+#define PAM_APP_NAME "su"
+#endif /* ndef PAM_APP_NAME */
+
+#define DEFAULT_HOME "/"
+#define DEFAULT_SHELL "/bin/bash"
+#define SLEEP_TO_KILL_CHILDREN 3 /* seconds to wait after SIGTERM before
+ SIGKILL */
+#define SU_FAIL_DELAY 2000000 /* usec on authentication failure */
+
+#define RHOST_UNKNOWN_NAME "" /* perhaps "[from.where?]" */
+#define DEVICE_FILE_PREFIX "/dev/"
+#define WTMP_LOCK_TIMEOUT 3 /* in seconds */
+
+#ifndef UT_IDSIZE
+#define UT_IDSIZE 4 /* XXX - this is sizeof(struct utmp.ut_id) */
+#endif
+
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/wait.h>
+#include <utmp.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+#include <sys/capability.h>
+
+#include <security/_pam_macros.h>
+
+/* -------------------------------------------- */
+/* ------ declarations ------------------------ */
+/* -------------------------------------------- */
+
+extern char **environ;
+static pam_handle_t *pamh = NULL;
+
+static int wait_for_child_caught=0;
+static int need_job_control=0;
+static int is_terminal = 0;
+static struct termios stored_mode; /* initial terminal mode settings */
+static uid_t terminal_uid = (uid_t) -1;
+static uid_t invoked_uid = (uid_t) -1;
+
+/* -------------------------------------------- */
+/* ------ some local (static) functions ------- */
+/* -------------------------------------------- */
+
+/*
+ * We will attempt to transcribe the following env variables
+ * independent of whether we keep the whole environment. Others will
+ * be set elsewhere: either in modules; or after the identity of the
+ * user is known.
+ */
+
+static const char *posix_env[] = {
+ "LANG",
+ "LC_COLLATE",
+ "LC_CTYPE",
+ "LC_MONETARY",
+ "LC_NUMERIC",
+ "TZ",
+ NULL
+};
+
+/*
+ * make_environment transcribes a selection of environment variables
+ * from the invoking user.
+ */
+static int make_environment(int keep_env)
+{
+ const char *tmpe;
+ int i;
+ int retval;
+
+ if (keep_env) {
+ /* preserve the original environment */
+ return pam_misc_paste_env(pamh, (const char * const *)environ);
+ }
+
+ /* we always transcribe some variables anyway */
+ tmpe = getenv("TERM");
+ if (tmpe == NULL) {
+ tmpe = "dumb";
+ }
+ retval = pam_misc_setenv(pamh, "TERM", tmpe, 0);
+ if (retval == PAM_SUCCESS) {
+ retval = pam_misc_setenv(pamh, "PATH", "/bin:/usr/bin", 0);
+ }
+ if (retval != PAM_SUCCESS) {
+ tmpe = NULL;
+ D(("error setting environment variables"));
+ return retval;
+ }
+
+ /* also propagate the POSIX specific ones */
+ for (i=0; retval == PAM_SUCCESS && posix_env[i]; ++i) {
+ tmpe = getenv(posix_env[i]);
+ if (tmpe != NULL) {
+ retval = pam_misc_setenv(pamh, posix_env[i], tmpe, 0);
+ }
+ }
+ tmpe = NULL;
+
+ return retval;
+}
+
+/*
+ * checkfds ensures that stdout and stderr filedescriptors are
+ * defined. If all else fails, it directs them to /dev/null.
+ */
+static void checkfds(void)
+{
+ struct stat st;
+ int fd;
+
+ if (fstat(1, &st) == -1) {
+ fd = open("/dev/null", O_WRONLY);
+ if (fd == -1) goto badfds;
+ if (fd != 1) {
+ if (dup2(fd, 1) == -1) goto badfds;
+ if (close(fd) == -1) goto badfds;
+ }
+ }
+ if (fstat(2, &st) == -1) {
+ fd = open("/dev/null", O_WRONLY);
+ if (fd == -1) goto badfds;
+ if (fd != 2) {
+ if (dup2(fd, 2) == -1) goto badfds;
+ if (close(fd) == -1) goto badfds;
+ }
+ }
+
+ return;
+
+badfds:
+ perror("bad filedes");
+ exit(1);
+}
+
+/*
+ * store_terminal_modes captures the current state of the input
+ * terminal. Calling this at the start of the program, we ensure we
+ * can restore these default settings when su exits.
+ */
+static void store_terminal_modes(void)
+{
+ if (isatty(STDIN_FILENO)) {
+ is_terminal = 1;
+ if (tcgetattr(STDIN_FILENO, &stored_mode) != 0) {
+ fprintf(stderr, PAM_APP_NAME ": couldn't copy terminal mode");
+ exit(1);
+ }
+ return;
+ }
+ fprintf(stderr, PAM_APP_NAME ": must be run from a terminal\n");
+ exit(1);
+}
+
+/*
+ * restore_terminal_modes resets the terminal to the state it was in
+ * when the program started.
+ *
+ * Returns:
+ * 0 ok
+ * 1 error
+ */
+static int restore_terminal_modes(void)
+{
+ if (is_terminal && tcsetattr(STDIN_FILENO, TCSAFLUSH, &stored_mode) != 0) {
+ fprintf(stderr, PAM_APP_NAME ": cannot restore terminal mode: %s\n",
+ strerror(errno));
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* ------ unexpected signals ------------------ */
+
+struct sigaction old_int_act, old_quit_act, old_tstp_act, old_pipe_act;
+
+/*
+ * disable_terminal_signals attempts to make the process resistant to
+ * being stopped - it helps ensure that the PAM stack can complete
+ * session and auth failure logging etc.
+ */
+static void disable_terminal_signals(void)
+{
+ /*
+ * Protect the process from dangerous terminal signals.
+ * The protection is implemented via sigaction() because
+ * the signals are sent regardless of the process' uid.
+ */
+ struct sigaction act;
+
+ act.sa_handler = SIG_IGN; /* ignore the signal */
+ sigemptyset(&act.sa_mask); /* no signal blocking on handler
+ call needed */
+ act.sa_flags = SA_RESTART; /* do not reset after first signal
+ arriving, restart interrupted
+ system calls if possible */
+ sigaction(SIGINT, &act, &old_int_act);
+ sigaction(SIGQUIT, &act, &old_quit_act);
+ /*
+ * Ignore SIGTSTP signals. Why? attacker could otherwise stop
+ * a process and a. kill it, or b. wait for the system to
+ * shutdown - either way, nothing appears in syslogs.
+ */
+ sigaction(SIGTSTP, &act, &old_tstp_act);
+ /*
+ * Ignore SIGPIPE. The parent `su' process may print something
+ * on stderr. Killing of the process would be undesired.
+ */
+ sigaction(SIGPIPE, &act, &old_pipe_act);
+}
+
+static void enable_terminal_signals(void)
+{
+ sigaction(SIGINT, &old_int_act, NULL);
+ sigaction(SIGQUIT, &old_quit_act, NULL);
+ sigaction(SIGTSTP, &old_tstp_act, NULL);
+ sigaction(SIGPIPE, &old_pipe_act, NULL);
+}
+
+/* ------ terminal ownership ------------------ */
+
+/*
+ * change_terminal_owner changes the ownership of STDIN if needed.
+ * Returns:
+ * 0 ok,
+ * -1 fatal error (continuing is impossible),
+ * 1 non-fatal error.
+ * In the case of an error "err_descr" is set to the error message
+ * and "callname" to the name of the failed call.
+ */
+static int change_terminal_owner(uid_t uid, int is_login,
+ const char **callname, const char **err_descr)
+{
+ /* determine who owns the terminal line */
+ if (is_terminal && is_login) {
+ struct stat stat_buf;
+ cap_t current, working;
+ int status;
+ cap_value_t cchown = CAP_CHOWN;
+
+ if (fstat(STDIN_FILENO, &stat_buf) != 0) {
+ *callname = "fstat to STDIN";
+ *err_descr = strerror(errno);
+ return -1;
+ }
+
+ current = cap_get_proc();
+ working = cap_dup(current);
+ cap_set_flag(working, CAP_EFFECTIVE, 1, &cchown, CAP_SET);
+ status = cap_set_proc(working);
+ cap_free(working);
+
+ if (status != 0) {
+ *callname = "capset CHOWN";
+ } else if ((status = fchown(STDIN_FILENO, uid, -1)) != 0) {
+ *callname = "fchown of STDIN";
+ } else {
+ cap_set_proc(current);
+ }
+ cap_free(current);
+
+ if (status != 0) {
+ *err_descr = strerror(errno);
+ return 1;
+ }
+
+ terminal_uid = stat_buf.st_uid;
+ }
+ return 0;
+}
+
+/*
+ * restore_terminal_owner changes the terminal owner back to the value
+ * it had when su was started.
+ */
+static void restore_terminal_owner(void)
+{
+ if (terminal_uid != (uid_t) -1) {
+ cap_t current, working;
+ int status;
+ cap_value_t cchown = CAP_CHOWN;
+
+ current = cap_get_proc();
+ working = cap_dup(current);
+ cap_set_flag(working, CAP_EFFECTIVE, 1, &cchown, CAP_SET);
+ status = cap_set_proc(working);
+ cap_free(working);
+
+ if (status == 0) {
+ status = fchown(STDIN_FILENO, terminal_uid, -1);
+ cap_set_proc(current);
+ }
+ cap_free(current);
+
+ if (status != 0) {
+ openlog(PAM_APP_NAME, LOG_CONS|LOG_PERROR|LOG_PID, LOG_AUTHPRIV);
+ syslog(LOG_ALERT, "Terminal owner hasn\'t been restored: %s",
+ strerror(errno));
+ closelog();
+ }
+ terminal_uid = (uid_t) -1;
+ }
+}
+
+/*
+ * make_process_unkillable changes the uid of the process. TEMP_UID is
+ * used for this temporary state.
+ *
+ * Returns:
+ * 0 ok,
+ * -1 fatal error (continue of the work is impossible),
+ * 1 non-fatal error.
+ * In the case of an error "err_descr" is set to the error message
+ * and "callname" to the name of the failed call.
+ */
+static int make_process_unkillable(const char **callname,
+ const char **err_descr)
+{
+ invoked_uid = getuid();
+ if (invoked_uid == TEMP_UID) {
+ /* no change needed */
+ return 0;
+ }
+
+ if (cap_setuid(TEMP_UID) != 0) {
+ *callname = "setuid";
+ *err_descr = strerror(errno);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * make_process_killable restores the invoking uid to the current
+ * process.
+ */
+static void make_process_killable(void)
+{
+ (void) cap_setuid(invoked_uid);
+}
+
+/* ------ command line parser ----------------- */
+
+static void usage(int exit_val)
+{
+ fprintf(stderr,"usage: su [-] [-h] [-c \"command\"] [username]\n");
+ exit(exit_val);
+}
+
+/*
+ * parse_command_line extracts the options from the command line
+ * arguments.
+ */
+static void parse_command_line(int argc, char *argv[], int *is_login,
+ const char **user, const char **command)
+{
+ int username_present, command_present;
+
+ *is_login = 0;
+ *user = NULL;
+ *command = NULL;
+ username_present = command_present = 0;
+
+ while ( --argc > 0 ) {
+ const char *token;
+
+ token = *++argv;
+ if (*token == '-') {
+ switch (*++token) {
+ case '\0': /* su as a login shell for the user */
+ if (*is_login)
+ usage(1);
+ *is_login = 1;
+ break;
+ case 'c':
+ if (command_present) {
+ usage(1);
+ } else { /* indicate we are running commands */
+ if (*++token != '\0') {
+ command_present = 1;
+ *command = token;
+ } else if (--argc > 0) {
+ command_present = 1;
+ *command = *++argv;
+ } else
+ usage(1);
+ }
+ break;
+ case 'h':
+ usage(0);
+ default:
+ usage(1);
+ }
+ } else { /* must be username */
+ if (username_present) {
+ usage(1);
+ }
+ username_present = 1;
+ *user = *argv;
+ }
+ }
+
+ if (!username_present) {
+ fprintf(stderr, PAM_APP_NAME ": requires a username\n");
+ usage(1);
+ }
+}
+
+/*
+ * This following contains code that waits for a child process to die.
+ * It also chooses to intercept a couple of signals that it will
+ * kindly pass on a SIGTERM to the child ;^). Waiting again for the
+ * child to exit. If the child resists dying, it will SIGKILL it!
+ */
+
+static void wait_for_child_catch_sig(int ignore)
+{
+ wait_for_child_caught = 1;
+}
+
+static void prepare_for_job_control(int need_it)
+{
+ sigset_t ourset;
+
+ (void) sigfillset(&ourset);
+ if (sigprocmask(SIG_BLOCK, &ourset, NULL) != 0) {
+ fprintf(stderr,"[trouble blocking signals]\n");
+ wait_for_child_caught = 1;
+ return;
+ }
+ need_job_control = need_it;
+}
+
+static int wait_for_child(pid_t child)
+{
+ int retval, status, exit_code;
+ sigset_t ourset;
+
+ exit_code = -1; /* no exit code yet, exit codes could be from 0 to 255 */
+ if (child == -1) {
+ return exit_code;
+ }
+
+ /*
+ * set up signal handling
+ */
+
+ if (!wait_for_child_caught) {
+ struct sigaction action, defaction;
+
+ action.sa_handler = wait_for_child_catch_sig;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+
+ defaction.sa_handler = SIG_DFL;
+ sigemptyset(&defaction.sa_mask);
+ defaction.sa_flags = 0;
+
+ sigemptyset(&ourset);
+
+ if ( sigaddset(&ourset, SIGTERM)
+ || sigaction(SIGTERM, &action, NULL)
+ || sigaddset(&ourset, SIGHUP)
+ || sigaction(SIGHUP, &action, NULL)
+ || sigaddset(&ourset, SIGALRM) /* required by sleep(3) */
+ || (need_job_control && sigaddset(&ourset, SIGTSTP))
+ || (need_job_control && sigaction(SIGTSTP, &defaction, NULL))
+ || (need_job_control && sigaddset(&ourset, SIGTTIN))
+ || (need_job_control && sigaction(SIGTTIN, &defaction, NULL))
+ || (need_job_control && sigaddset(&ourset, SIGTTOU))
+ || (need_job_control && sigaction(SIGTTOU, &defaction, NULL))
+ || (need_job_control && sigaddset(&ourset, SIGCONT))
+ || (need_job_control && sigaction(SIGCONT, &defaction, NULL))
+ || sigprocmask(SIG_UNBLOCK, &ourset, NULL)
+ ) {
+ fprintf(stderr,"[trouble setting signal intercept]\n");
+ wait_for_child_caught = 1;
+ }
+
+ /* application should be ready for receiving a SIGTERM/HUP now */
+ }
+
+ /*
+ * This code waits for the process to actually die. If it stops,
+ * then the parent attempts to mimic the behavior of the
+ * child.. There is a slight bug in the code when the 'su'd user
+ * attempts to restart the child independently of the parent --
+ * the child dies.
+ */
+ while (!wait_for_child_caught) {
+ /* parent waits for child */
+ if ((retval = waitpid(child, &status, 0)) <= 0) {
+ if (errno == EINTR) {
+ continue; /* recovering from a 'fg' */
+ }
+ fprintf(stderr, "[error waiting child: %s]\n", strerror(errno));
+ /*
+ * Break the loop keeping exit_code undefined.
+ * Do we have a chance for a successful wait() call
+ * after kill()? (SAW)
+ */
+ wait_for_child_caught = 1;
+ break;
+ } else {
+ /* the child is terminated via exit() or a fatal signal */
+ if (WIFEXITED(status)) {
+ exit_code = WEXITSTATUS(status);
+ } else {
+ exit_code = 1;
+ }
+ break;
+ }
+ }
+
+ if (wait_for_child_caught) {
+ fprintf(stderr,"\nKilling shell...");
+ kill(child, SIGTERM);
+ }
+
+ /*
+ * do we need to wait for the child to catch up?
+ */
+ if (wait_for_child_caught) {
+ sleep(SLEEP_TO_KILL_CHILDREN);
+ kill(child, SIGKILL);
+ fprintf(stderr, "killed\n");
+ }
+
+ /*
+ * collect the zombie the shell was killed by ourself
+ */
+ if (exit_code == -1) {
+ do {
+ retval = waitpid(child, &status, 0);
+ } while (retval == -1 && errno == EINTR);
+ if (retval == -1) {
+ fprintf(stderr, PAM_APP_NAME ": the final wait failed: %s\n",
+ strerror(errno));
+ }
+ if (WIFEXITED(status)) {
+ exit_code = WEXITSTATUS(status);
+ } else {
+ exit_code = 1;
+ }
+ }
+
+ return exit_code;
+}
+
+
+/*
+ * Next some code that parses the spawned shell command line.
+ */
+
+static const char * const *build_shell_args(const char *pw_shell, int login,
+ const char *command)
+{
+ int use_default = 1; /* flag to signal we should use the default shell */
+ const char **args=NULL; /* array of PATH+ARGS+NULL pointers */
+
+ D(("called."));
+ if (login) {
+ command = NULL; /* command always ignored for login */
+ }
+
+ if (pw_shell && *pw_shell != '\0') {
+ char *line;
+ const char *tmp, *tmpb=NULL;
+ int arg_no=0,i;
+
+ /* first find the number of arguments */
+ D(("non-null shell"));
+ for (tmp=pw_shell; *tmp; ++arg_no) {
+
+ /* skip leading spaces */
+ while (isspace(*tmp))
+ ++tmp;
+
+ if (tmpb == NULL) /* mark beginning token */
+ tmpb = tmp;
+ if (*tmp == '\0') /* end of line with no token */
+ break;
+
+ /* skip token */
+ while (*tmp && !isspace(*tmp))
+ ++tmp;
+ }
+
+ /*
+ * We disallow shells:
+ * - without a full specified path;
+ * - when we are not logging in and the #args != 1
+ * (unlikely a simple shell)
+ */
+
+ D(("shell so far = %s, arg_no = %d", tmpb, arg_no));
+ if (tmpb != NULL && tmpb[0] == '/' /* something (full path) */
+ && ( login || arg_no == 1 ) /* login, or single arg shells */
+ ) {
+
+ use_default = 0; /* we will use this shell */
+ D(("committed to using user's shell"));
+ if (command) {
+ arg_no += 2; /* will append "-c" "command" */
+ }
+
+ /* allocate an array of pointers long enough */
+
+ D(("building array of size %d", 2+arg_no));
+ args = (const char **) calloc(2+arg_no, sizeof(const char *));
+ if (args == NULL)
+ return NULL;
+ /* get a string long enough for all the arguments */
+
+ D(("an array of size %d chars", 2+strlen(tmpb)
+ + ( command ? 4:0 )));
+ line = (char *) malloc(2+strlen(tmpb)
+ + ( command ? 4:0 ));
+ if (line == NULL) {
+ free(args);
+ return NULL;
+ }
+
+ /* fill array - tmpb points to start of first non-space char */
+
+ line[0] = '-';
+ strcpy(line+1, tmpb);
+
+ /* append " -c" to line? */
+ if (command) {
+ strcat(line, " -c");
+ }
+
+ D(("complete command: %s [+] %s", line, command));
+
+ tmp = strtok(line, " \t");
+ D(("command path=%s", line+1));
+ args[0] = line+1;
+
+ if (login) { /* standard procedure for login shell */
+ D(("argv[0]=%s", line));
+ args[i=1] = line;
+ } else { /* not a login shell -- for use with su */
+ D(("argv[0]=%s", line+1));
+ args[i=1] = line+1;
+ }
+
+ while ((tmp = strtok(NULL, " \t"))) {
+ D(("adding argument %d: %s",i,tmp));
+ args[++i] = tmp;
+ }
+ if (command) {
+ D(("appending command [%s]", command));
+ args[++i] = command;
+ }
+ D(("terminating args with NULL"));
+ args[++i] = NULL;
+ D(("list completed."));
+ }
+ }
+
+ /* should we use the default shell instead of specific one? */
+
+ if (use_default && !login) {
+ int last_arg;
+
+ D(("selecting default shell"));
+ last_arg = command ? 5:3;
+
+ args = (const char **) calloc(last_arg--, sizeof(const char *));
+ if (args == NULL) {
+ return NULL;
+ }
+ args[1] = DEFAULT_SHELL; /* mapped to argv[0] (NOT login shell) */
+ args[0] = args[1]; /* path to program */
+ if (command) {
+ args[2] = "-c"; /* should perform command and exit */
+ args[3] = command; /* the desired command */
+ }
+ args[last_arg] = NULL; /* terminate list of args */
+ }
+
+ D(("returning arg list"));
+ return (const char * const *) args;
+}
+
+
+/* ------ abnormal termination ---------------- */
+
+static void exit_now(int exit_code, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ if (pamh != NULL)
+ pam_end(pamh, exit_code ? PAM_ABORT:PAM_SUCCESS);
+
+ /* USER's shell may have completely broken terminal settings
+ restore the sane(?) initial conditions */
+ restore_terminal_modes();
+
+ exit(exit_code);
+}
+
+/* ------ PAM setup --------------------------- */
+
+static struct pam_conv conv = {
+ misc_conv, /* defined in <pam_misc/libmisc.h> */
+ NULL
+};
+
+static void do_pam_init(const char *user, int is_login)
+{
+ int retval;
+
+ retval = pam_start(PAM_APP_NAME, user, &conv, &pamh);
+ if (retval != PAM_SUCCESS) {
+ /*
+ * From my point of view failing of pam_start() means that
+ * pamh isn't a valid handler. Without a handler
+ * we couldn't call pam_strerror :-( 1998/03/29 (SAW)
+ */
+ fprintf(stderr, PAM_APP_NAME ": pam_start failed with code %d\n",
+ retval);
+ exit(1);
+ }
+
+ /*
+ * Fill in some blanks
+ */
+
+ retval = make_environment(!is_login);
+ D(("made_environment returned: %s", pam_strerror(pamh, retval)));
+
+ if (retval == PAM_SUCCESS && is_terminal) {
+ const char *terminal = ttyname(STDIN_FILENO);
+ if (terminal) {
+ retval = pam_set_item(pamh, PAM_TTY, (const void *)terminal);
+ } else {
+ retval = PAM_PERM_DENIED; /* how did we get here? */
+ }
+ terminal = NULL;
+ }
+
+ if (retval == PAM_SUCCESS && is_terminal) {
+ const char *ruser = getlogin(); /* Who is running this program? */
+ if (ruser) {
+ retval = pam_set_item(pamh, PAM_RUSER, (const void *)ruser);
+ } else {
+ retval = PAM_PERM_DENIED; /* must be known to system */
+ }
+ ruser = NULL;
+ }
+
+ if (retval == PAM_SUCCESS) {
+ retval = pam_set_item(pamh, PAM_RHOST, (const void *)"localhost");
+ }
+
+ if (retval != PAM_SUCCESS) {
+ exit_now(1, PAM_APP_NAME ": problem establishing environment\n");
+ }
+
+ /* have to pause on failure. At least this long (doubles..) */
+ retval = pam_fail_delay(pamh, SU_FAIL_DELAY);
+ if (retval != PAM_SUCCESS) {
+ exit_now(1, PAM_APP_NAME ": problem initializing failure delay\n");
+ }
+}
+
+/*
+ * authenticate_user arranges for the PAM authentication stack to run.
+ */
+static int authenticate_user(cap_t all, int *retval, const char **place,
+ const char **err_descr)
+{
+ *place = "pre-auth cap_set_proc";
+ if (cap_set_proc(all)) {
+ D(("failed to raise all capabilities"));
+ *err_descr = "cap_set_proc() failed";
+ *retval = PAM_SUCCESS;
+ return 1;
+ }
+
+ D(("attempt to authenticate user"));
+ *place = "pam_authenticate";
+ *retval = pam_authenticate(pamh, 0);
+ return (*retval != PAM_SUCCESS);
+}
+
+/*
+ * user_accounting confirms an authenticated user is permitted service.
+ */
+static int user_accounting(cap_t all, int *retval, const char **place,
+ const char **err_descr) {
+ *place = "user_accounting";
+ if (cap_set_proc(all)) {
+ D(("failed to raise all capabilities"));
+ *err_descr = "cap_set_proc() failed";
+ return 1;
+ }
+ *place = "pam_acct_mgmt";
+ *retval = pam_acct_mgmt(pamh, 0);
+ return (*retval != PAM_SUCCESS);
+}
+
+/*
+ * Find entry for this terminal (if there is one).
+ * Utmp file should have been opened and rewinded for the call.
+ *
+ * XXX: the search should be more or less compatible with libc one.
+ * The caller expects that pututline with the same arguments
+ * will replace the found entry.
+ */
+static const struct utmp *find_utmp_entry(const char *ut_line,
+ const char *ut_id)
+{
+ struct utmp *u_tmp_p;
+
+ while ((u_tmp_p = getutent()) != NULL)
+ if ((u_tmp_p->ut_type == INIT_PROCESS ||
+ u_tmp_p->ut_type == LOGIN_PROCESS ||
+ u_tmp_p->ut_type == USER_PROCESS ||
+ u_tmp_p->ut_type == DEAD_PROCESS) &&
+ !strncmp(u_tmp_p->ut_id, ut_id, UT_IDSIZE) &&
+ !strncmp(u_tmp_p->ut_line, ut_line, UT_LINESIZE))
+ break;
+
+ return u_tmp_p;
+}
+
+/*
+ * Identify the terminal name and the abbreviation we will use.
+ */
+static void set_terminal_name(const char *terminal, char *ut_line, char *ut_id)
+{
+ memset(ut_line, 0, UT_LINESIZE);
+ memset(ut_id, 0, UT_IDSIZE);
+
+ /* set the terminal entry */
+ if ( *terminal == '/' ) { /* now deal with filenames */
+ int o1, o2;
+
+ o1 = strncmp(DEVICE_FILE_PREFIX, terminal, 5) ? 0 : 5;
+ if (!strncmp("/dev/tty", terminal, 8)) {
+ o2 = 8;
+ } else {
+ o2 = strlen(terminal) - sizeof(UT_IDSIZE);
+ if (o2 < 0)
+ o2 = 0;
+ }
+
+ strncpy(ut_line, terminal + o1, UT_LINESIZE);
+ strncpy(ut_id, terminal + o2, UT_IDSIZE);
+ } else if (strchr(terminal, ':')) { /* deal with X-based session */
+ const char *suffix;
+
+ suffix = strrchr(terminal,':');
+ strncpy(ut_line, terminal, UT_LINESIZE);
+ strncpy(ut_id, suffix, UT_IDSIZE);
+ } else { /* finally deal with weird terminals */
+ strncpy(ut_line, terminal, UT_LINESIZE);
+ ut_id[0] = '?';
+ strncpy(ut_id + 1, terminal, UT_IDSIZE - 1);
+ }
+}
+
+/*
+ * Append an entry to wtmp. See utmp_open_session for the return convention.
+ * Be careful: the function uses alarm().
+ */
+
+#define WWTMP_STATE_BEGINNING 0
+#define WWTMP_STATE_FILE_OPENED 1
+#define WWTMP_STATE_SIGACTION_SET 2
+#define WWTMP_STATE_LOCK_TAKEN 3
+
+static int write_wtmp(struct utmp *u_tmp_p, const char **callname,
+ const char **err_descr)
+{
+ int w_tmp_fd;
+ struct flock w_lock;
+ struct sigaction act1, act2;
+ int state;
+ int retval;
+
+ state = WWTMP_STATE_BEGINNING;
+ retval = 1;
+
+ do {
+ D(("writing to wtmp"));
+ w_tmp_fd = open(_PATH_WTMP, O_APPEND|O_WRONLY);
+ if (w_tmp_fd == -1) {
+ *callname = "wtmp open";
+ *err_descr = strerror(errno);
+ break;
+ }
+ state = WWTMP_STATE_FILE_OPENED;
+
+ /* prepare for blocking operation... */
+ act1.sa_handler = SIG_DFL;
+ sigemptyset(&act1.sa_mask);
+ act1.sa_flags = 0;
+ if (sigaction(SIGALRM, &act1, &act2) == -1) {
+ *callname = "sigaction";
+ *err_descr = strerror(errno);
+ break;
+ }
+ alarm(WTMP_LOCK_TIMEOUT);
+ state = WWTMP_STATE_SIGACTION_SET;
+
+ /* now we try to lock this file-rcord exclusively; non-blocking */
+ memset(&w_lock, 0, sizeof(w_lock));
+ w_lock.l_type = F_WRLCK;
+ w_lock.l_whence = SEEK_END;
+ if (fcntl(w_tmp_fd, F_SETLK, &w_lock) < 0) {
+ D(("locking %s failed.", _PATH_WTMP));
+ *callname = "fcntl(F_SETLK)";
+ *err_descr = strerror(errno);
+ break;
+ }
+ alarm(0);
+ sigaction(SIGALRM, &act2, NULL);
+ state = WWTMP_STATE_LOCK_TAKEN;
+
+ if (write(w_tmp_fd, u_tmp_p, sizeof(struct utmp)) != -1) {
+ retval = 0;
+ }
+ } while(0); /* it's not a loop! */
+
+ if (state >= WWTMP_STATE_LOCK_TAKEN) {
+ w_lock.l_type = F_UNLCK; /* unlock wtmp file */
+ fcntl(w_tmp_fd, F_SETLK, &w_lock);
+ }else if (state >= WWTMP_STATE_SIGACTION_SET) {
+ alarm(0);
+ sigaction(SIGALRM, &act2, NULL);
+ }
+
+ if (state >= WWTMP_STATE_FILE_OPENED) {
+ close(w_tmp_fd); /* close wtmp file */
+ D(("wtmp written"));
+ }
+
+ return retval;
+}
+
+/*
+ * XXX - if this gets turned into a module, make this a
+ * pam_data item. You should put the pid in the name so we can
+ * "probably" nest calls more safely...
+ */
+struct utmp *login_stored_utmp=NULL;
+
+/*
+ * Returns:
+ * 0 ok,
+ * 1 non-fatal error
+ * -1 fatal error
+ * callname and err_descr will be set
+ * Be careful: the function indirectly uses alarm().
+ */
+static int utmp_do_open_session(const char *user, const char *terminal,
+ const char *rhost, pid_t pid,
+ const char **place, const char **err_descr)
+{
+ struct utmp u_tmp;
+ const struct utmp *u_tmp_p;
+ char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
+ int retval;
+
+ set_terminal_name(terminal, ut_line, ut_id);
+
+ utmpname(_PATH_UTMP);
+ setutent(); /* rewind file */
+ u_tmp_p = find_utmp_entry(ut_line, ut_id);
+
+ /* reset new entry */
+ memset(&u_tmp, 0, sizeof(u_tmp)); /* reset new entry */
+ if (u_tmp_p == NULL) {
+ D(("[NEW utmp]"));
+ } else {
+ D(("[OLD utmp]"));
+
+ /*
+ * here, we make a record of the former entry. If the
+ * utmp_close_session code is attached to the same process,
+ * the wtmp will be replaced, otherwise we leave init to pick
+ * up the pieces.
+ */
+ if (login_stored_utmp == NULL) {
+ login_stored_utmp = malloc(sizeof(struct utmp));
+ if (login_stored_utmp == NULL) {
+ *place = "malloc";
+ *err_descr = "fail";
+ endutent();
+ return -1;
+ }
+ }
+ memcpy(login_stored_utmp, u_tmp_p, sizeof(struct utmp));
+ }
+
+ /* we adjust the entry to reflect the current session */
+ {
+ strncpy(u_tmp.ut_line, ut_line, UT_LINESIZE);
+ memset(ut_line, 0, UT_LINESIZE);
+ strncpy(u_tmp.ut_id, ut_id, UT_IDSIZE);
+ memset(ut_id, 0, UT_IDSIZE);
+ strncpy(u_tmp.ut_user, user
+ , sizeof(u_tmp.ut_user));
+ strncpy(u_tmp.ut_host, rhost ? rhost : RHOST_UNKNOWN_NAME
+ , sizeof(u_tmp.ut_host));
+
+ /* try to fill the host address entry */
+ if (rhost != NULL) {
+ struct hostent *hptr;
+
+ /* XXX: it isn't good to do DNS lookup here... 1998/05/29 SAW */
+ hptr = gethostbyname(rhost);
+ if (hptr != NULL && hptr->h_addr_list) {
+ memcpy(&u_tmp.ut_addr, hptr->h_addr_list[0]
+ , sizeof(u_tmp.ut_addr));
+ }
+ }
+
+ /* we fill in the remaining info */
+ u_tmp.ut_type = USER_PROCESS; /* a user process starting */
+ u_tmp.ut_pid = pid; /* session identifier */
+ u_tmp.ut_time = time(NULL);
+ }
+
+ setutent(); /* rewind file (replace old) */
+ pututline(&u_tmp); /* write it to utmp */
+ endutent(); /* close the file */
+
+ retval = write_wtmp(&u_tmp, place, err_descr); /* write to wtmp file */
+ memset(&u_tmp, 0, sizeof(u_tmp)); /* reset entry */
+
+ return retval;
+}
+
+static int utmp_do_close_session(const char *terminal,
+ const char **place, const char **err_descr)
+{
+ struct utmp u_tmp;
+ const struct utmp *u_tmp_p;
+ char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
+
+ set_terminal_name(terminal, ut_line, ut_id);
+
+ utmpname(_PATH_UTMP);
+ setutent(); /* rewind file */
+
+ /*
+ * if there was a stored entry, return it to the utmp file, else
+ * if there is a session to close, we close that
+ */
+ if (login_stored_utmp) {
+ pututline(login_stored_utmp);
+
+ memcpy(&u_tmp, login_stored_utmp, sizeof(u_tmp));
+ u_tmp.ut_time = time(NULL); /* a new time to restart */
+
+ write_wtmp(&u_tmp, place, err_descr);
+
+ memset(login_stored_utmp, 0, sizeof(u_tmp)); /* reset entry */
+ free(login_stored_utmp);
+ } else {
+ u_tmp_p = find_utmp_entry(ut_line, ut_id);
+ if (u_tmp_p != NULL) {
+ memset(&u_tmp, 0, sizeof(u_tmp));
+ strncpy(u_tmp.ut_line, ut_line, UT_LINESIZE);
+ strncpy(u_tmp.ut_id, ut_id, UT_IDSIZE);
+ memset(&u_tmp.ut_user, 0, sizeof(u_tmp.ut_user));
+ memset(&u_tmp.ut_host, 0, sizeof(u_tmp.ut_host));
+ u_tmp.ut_addr = 0;
+ u_tmp.ut_type = DEAD_PROCESS; /* `old' login process */
+ u_tmp.ut_pid = 0;
+ u_tmp.ut_time = time(NULL);
+ setutent(); /* rewind file (replace old) */
+ pututline(&u_tmp); /* mark as dead */
+
+ write_wtmp(&u_tmp, place, err_descr);
+ }
+ }
+
+ /* clean up */
+ memset(ut_line, 0, UT_LINESIZE);
+ memset(ut_id, 0, UT_IDSIZE);
+
+ endutent(); /* close utmp file */
+ memset(&u_tmp, 0, sizeof(u_tmp)); /* reset entry */
+
+ return 0;
+}
+
+/*
+ * Returns:
+ * 0 ok,
+ * 1 non-fatal error
+ * -1 fatal error
+ * place and err_descr will be set
+ * Be careful: the function indirectly uses alarm().
+ */
+static int utmp_open_session(pid_t pid, int *retval,
+ const char **place, const char **err_descr)
+{
+ const char *user, *terminal, *rhost;
+
+ *retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
+ if (*retval != PAM_SUCCESS) {
+ return -1;
+ }
+ *retval = pam_get_item(pamh, PAM_TTY, (const void **)&terminal);
+ if (retval != PAM_SUCCESS) {
+ return -1;
+ }
+ *retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
+ if (retval != PAM_SUCCESS) {
+ rhost = NULL;
+ }
+
+ return utmp_do_open_session(user, terminal, rhost, pid, place, err_descr);
+}
+
+static int utmp_close_session(const char **place, const char **err_descr)
+{
+ int retval;
+ const char *terminal;
+
+ retval = pam_get_item(pamh, PAM_TTY, (const void **)&terminal);
+ if (retval != PAM_SUCCESS) {
+ *place = "pam_get_item(PAM_TTY)";
+ *err_descr = pam_strerror(pamh, retval);
+ return -1;
+ }
+
+ return utmp_do_close_session(terminal, place, err_descr);
+}
+
+/*
+ * set_credentials raises the process and PAM credentials.
+ */
+static int set_credentials(cap_t all, int login,
+ const char **user_p, uid_t *uid_p,
+ const char **pw_shell, int *retval,
+ const char **place, const char **err_descr)
+{
+ const char *user;
+ char *shell;
+ cap_value_t csetgid = CAP_SETGID;
+ cap_t current;
+ int status;
+ struct passwd *pw;
+ uid_t uid;
+
+ D(("get user from pam"));
+ *place = "set_credentials";
+ *retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
+ if (*retval != PAM_SUCCESS || user == NULL || *user == '\0') {
+ D(("error identifying user from PAM."));
+ *retval = PAM_USER_UNKNOWN;
+ return 1;
+ }
+ *user_p = user;
+
+ /*
+ * Add the LOGNAME and HOME environment variables.
+ */
+
+ pw = getpwnam(user);
+ if (pw == NULL || (user = x_strdup(pw->pw_name)) == NULL) {
+ D(("failed to identify user"));
+ *retval = PAM_USER_UNKNOWN;
+ return 1;
+ }
+
+ uid = pw->pw_uid;
+ if (uid == 0) {
+ D(("user is superuser: %s", user));
+ *retval = PAM_CRED_ERR;
+ return 1;
+ }
+ *uid_p = uid;
+
+ shell = x_strdup(pw->pw_shell);
+ if (shell == NULL) {
+ D(("user %s has no shell", user));
+ *retval = PAM_CRED_ERR;
+ return 1;
+ }
+
+ if (login) {
+ /* set LOGNAME, HOME */
+ if (pam_misc_setenv(pamh, "LOGNAME", user, 0) != PAM_SUCCESS) {
+ D(("failed to set LOGNAME"));
+ *retval = PAM_CRED_ERR;
+ return 1;
+ }
+ }
+
+ /* bash requires these be set to the target user values */
+ if (pam_misc_setenv(pamh, "HOME", pw->pw_dir, 0) != PAM_SUCCESS) {
+ D(("failed to set HOME"));
+ *retval = PAM_CRED_ERR;
+ return 1;
+ }
+ if (pam_misc_setenv(pamh, "USER", user, 0) != PAM_SUCCESS) {
+ D(("failed to set USER"));
+ *retval = PAM_CRED_ERR;
+ return 1;
+ }
+
+ current = cap_get_proc();
+ cap_set_flag(current, CAP_EFFECTIVE, 1, &csetgid, CAP_SET);
+ status = cap_set_proc(current);
+ cap_free(current);
+ if (status != 0) {
+ *err_descr = "unable to raise CAP_SETGID";
+ return 1;
+ }
+
+ /* initialize groups */
+ if (initgroups(pw->pw_name, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0) {
+ D(("failed to setgid etc"));
+ *retval = PAM_PERM_DENIED;
+ return 1;
+ }
+ *pw_shell = shell;
+
+ pw = NULL; /* be tidy */
+
+ D(("desired uid=%d", uid));
+
+ /* assume user's identity - but preserve the permitted set */
+ if (cap_setuid(uid) != 0) {
+ D(("failed to setuid: %v", strerror(errno)));
+ *retval = PAM_PERM_DENIED;
+ return 1;
+ }
+
+ /*
+ * Next, we call the PAM framework to add/enhance the credentials
+ * of this user [it may change the user's home directory in the
+ * pam_env, and add supplemental group memberships...].
+ */
+ D(("setting credentials"));
+ if (cap_set_proc(all)) {
+ D(("failed to raise all capabilities"));
+ *retval = PAM_PERM_DENIED;
+ return 1;
+ }
+
+ D(("calling pam_setcred to establish credentials"));
+ *retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+
+ return (*retval != PAM_SUCCESS);
+}
+
+/*
+ * open_session invokes the open session PAM stack.
+ */
+static int open_session(cap_t all, int *retval, const char **place,
+ const char **err_descr)
+{
+ /* Open the su-session */
+ *place = "pam_open_session";
+ if (cap_set_proc(all)) {
+ D(("failed to raise t_caps capabilities"));
+ *err_descr = "capability setting failed";
+ return 1;
+ }
+ *retval = pam_open_session(pamh, 0); /* Must take care to close */
+ if (*retval != PAM_SUCCESS) {
+ return 1;
+ }
+ return 0;
+}
+
+/* ------ shell invoker ----------------------- */
+
+static int launch_callback_fn(void *h)
+{
+ pam_handle_t *my_pamh = h;
+ int retval;
+
+ D(("pam_end"));
+ retval = pam_end(my_pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+ pamh = NULL;
+ if (retval != PAM_SUCCESS) {
+ return -1;
+ }
+
+ /*
+ * Restore a signal status: information if the signal is ignored
+ * is inherited across exec() call. (SAW)
+ */
+ enable_terminal_signals();
+
+#ifdef PAM_DEBUG
+ cap_iab_t iab = cap_iab_get_proc();
+ char *text = cap_iab_to_text(iab);
+ D(("iab = %s", text));
+ cap_free(text);
+ cap_free(iab);
+ cap_t cap = cap_get_proc();
+ text = cap_to_text(cap, NULL);
+ D(("cap = %s", text));
+ cap_free(text);
+ cap_free(cap);
+#endif
+
+ D(("about to launch"));
+ return 0;
+}
+
+/* Returns PAM_<STATUS>. */
+static int perform_launch_and_cleanup(cap_t all, int is_login, const char *user,
+ const char *shell, const char *command)
+{
+ int status;
+ const char *home;
+ const char * const * shell_args;
+ char * const * shell_env;
+ cap_launch_t launcher;
+ pid_t child;
+ cap_iab_t iab;
+
+ /*
+ * Break up the shell command into a command and arguments
+ */
+ shell_args = build_shell_args(shell, is_login, command);
+ if (shell_args == NULL) {
+ D(("failed to compute shell arguments"));
+ return PAM_SYSTEM_ERR;
+ }
+
+ home = pam_getenv(pamh, "HOME");
+ if ( !home || home[0] == '\0' ) {
+ fprintf(stderr, "setting home directory for %s to %s\n",
+ user, DEFAULT_HOME);
+ home = DEFAULT_HOME;
+ if (pam_misc_setenv(pamh, "HOME", home, 0) != PAM_SUCCESS) {
+ D(("unable to set $HOME"));
+ fprintf(stderr,
+ "Warning: unable to set HOME environment variable\n");
+ }
+ }
+ if (is_login) {
+ if (chdir(home) && chdir(DEFAULT_HOME)) {
+ D(("failed to change directory"));
+ return PAM_SYSTEM_ERR;
+ }
+ }
+
+ shell_env = pam_getenvlist(pamh);
+ if (shell_env == NULL) {
+ D(("failed to obtain environment for child"));
+ return PAM_SYSTEM_ERR;
+ }
+
+ iab = cap_iab_get_proc();
+ if (iab == NULL) {
+ D(("failed to read IAB value of process"));
+ return PAM_SYSTEM_ERR;
+ }
+
+ launcher = cap_new_launcher(shell_args[0],
+ (const char * const *) &shell_args[1],
+ (const char * const *) shell_env);
+ if (launcher == NULL) {
+ D(("failed to initialize launcher"));
+ return PAM_SYSTEM_ERR;
+ }
+ cap_launcher_callback(launcher, launch_callback_fn);
+
+ child = cap_launch(launcher, pamh);
+ cap_free(launcher);
+
+ if (cap_set_proc(all) != 0) {
+ D(("failed to restore process capabilities"));
+ return PAM_SYSTEM_ERR;
+ }
+
+ /* job control is off for login sessions */
+ prepare_for_job_control(!is_login && command != NULL);
+
+ if (cap_setuid(TEMP_UID) != 0) {
+ fprintf(stderr, "[failed to change monitor UID=%d]\n", TEMP_UID);
+ }
+
+ /* wait for child to terminate */
+ status = wait_for_child(child);
+ if (status != 0) {
+ D(("shell returned %d", status));
+ }
+ return status;
+}
+
+static void close_session(cap_t all)
+{
+ int retval;
+
+ D(("session %p closing", pamh));
+ if (cap_set_proc(all)) {
+ fprintf(stderr, "WARNING: could not raise all caps\n");
+ }
+ retval = pam_close_session(pamh, 0);
+ if (retval != PAM_SUCCESS) {
+ fprintf(stderr, "WARNING: could not close session\n\t%s\n",
+ pam_strerror(pamh,retval));
+ }
+}
+
+/* -------------------------------------------- */
+/* ------ the application itself -------------- */
+/* -------------------------------------------- */
+
+int main(int argc, char *argv[])
+{
+ int retcode, is_login, status;
+ int retval, final_retval; /* PAM_xxx return values */
+ const char *command, *shell;
+ uid_t uid;
+ const char *place = NULL, *err_descr = NULL;
+ cap_t all, t_caps;
+ const char *user;
+
+ all = cap_get_proc();
+ cap_fill(all, CAP_EFFECTIVE, CAP_PERMITTED);
+ cap_clear_flag(all, CAP_INHERITABLE);
+
+ checkfds();
+
+ /*
+ * Check whether stdin is a terminal and store terminal modes for later.
+ */
+ store_terminal_modes();
+
+ /* ---------- parse the argument list and --------- */
+ /* ------ initialize the Linux-PAM interface ------ */
+ {
+ parse_command_line(argc, argv, &is_login, &user, &command);
+ place = "do_pam_init";
+ do_pam_init(user, is_login); /* call pam_start and set PAM items */
+ user = NULL; /* transient until PAM_USER defined */
+ }
+
+ /*
+ * Turn off terminal signals - this is to be sure that su gets a
+ * chance to call pam_end() and restore the terminal modes in
+ * spite of the frustrated user pressing Ctrl-C.
+ */
+ disable_terminal_signals();
+
+ /*
+ * Random exits from here are strictly prohibited :-) (SAW) AGM
+ * achieves this with goto's and a single exit at the end of main.
+ */
+ status = 1; /* fake exit status of a child */
+ err_descr = NULL; /* errors haven't happened */
+
+ if (make_process_unkillable(&place, &err_descr) != 0) {
+ goto su_exit;
+ }
+
+ if (authenticate_user(all, &retval, &place, &err_descr) != 0) {
+ goto auth_exit;
+ }
+
+ /*
+ * The user is valid, but should they have access at this
+ * time?
+ */
+ if (user_accounting(all, &retval, &place, &err_descr) != 0) {
+ goto auth_exit;
+ }
+
+ D(("su attempt is confirmed as authorized"));
+
+ if (set_credentials(all, is_login, &user, &uid, &shell,
+ &retval, &place, &err_descr) != 0) {
+ D(("failed to set credentials"));
+ goto auth_exit;
+ }
+
+ /*
+ * ... setup terminal, ...
+ */
+ retcode = change_terminal_owner(uid, is_login, &place, &err_descr);
+ if (retcode > 0) {
+ fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+ err_descr = NULL; /* forget about the problem */
+ } else if (retcode < 0) {
+ D(("terminal owner to uid=%d change failed", uid));
+ goto auth_exit;
+ }
+
+ /*
+ * Here the IAB value is fixed and may differ from all's
+ * Inheritable value. So synthesize what we need to proceed in the
+ * child, for now, in this current process.
+ */
+ place = "preserving inheritable parts";
+ t_caps = cap_get_proc();
+ if (t_caps == NULL) {
+ D(("failed to read capabilities"));
+ err_descr = "capability read failed";
+ goto delete_cred;
+ }
+ if (cap_fill(t_caps, CAP_EFFECTIVE, CAP_PERMITTED)) {
+ D(("failed to fill effective bits"));
+ err_descr = "capability fill failed";
+ goto delete_cred;
+ }
+
+ /*
+ * ... make [uw]tmp entries.
+ */
+ if (is_login) {
+ /*
+ * Note: we use the parent pid as a session identifier for
+ * the logging.
+ */
+ retcode = utmp_open_session(getpid(), &retval, &place, &err_descr);
+ if (retcode > 0) {
+ fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+ err_descr = NULL; /* forget about this non-critical problem */
+ } else if (retcode < 0) {
+ goto delete_cred;
+ }
+ }
+
+#ifdef PAM_DEBUG
+ cap_iab_t iab = cap_iab_get_proc();
+ char *text = cap_iab_to_text(iab);
+ D(("pre-session open iab = %s", text));
+ cap_free(text);
+ cap_free(iab);
+#endif
+
+ if (open_session(t_caps, &retval, &place, &err_descr) != 0) {
+ goto utmp_closer;
+ }
+
+ status = perform_launch_and_cleanup(all, is_login, user, shell, command);
+ close_session(all);
+
+utmp_closer:
+ if (is_login) {
+ /* do [uw]tmp cleanup */
+ retcode = utmp_close_session(&place, &err_descr);
+ if (retcode) {
+ fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+ }
+ }
+
+delete_cred:
+ D(("delete credentials"));
+ if (cap_set_proc(all)) {
+ D(("failed to raise all capabilities"));
+ }
+ retcode = pam_setcred(pamh, PAM_DELETE_CRED);
+ if (retcode != PAM_SUCCESS) {
+ fprintf(stderr, "WARNING: could not delete credentials\n\t%s\n",
+ pam_strerror(pamh, retcode));
+ }
+
+ D(("return terminal to local control"));
+ restore_terminal_owner();
+
+auth_exit:
+ D(("for clean up we restore the launching user"));
+ make_process_killable();
+
+ D(("all done - closing down pam"));
+ if (retval != PAM_SUCCESS) { /* PAM has failed */
+ fprintf(stderr, PAM_APP_NAME ": %s\n", pam_strerror(pamh, retval));
+ final_retval = PAM_ABORT;
+ } else if (err_descr != NULL) { /* a system error has happened */
+ fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+ final_retval = PAM_ABORT;
+ } else {
+ final_retval = PAM_SUCCESS;
+ }
+ (void) pam_end(pamh, final_retval);
+ pamh = NULL;
+
+ if (restore_terminal_modes() != 0 && !status) {
+ status = 1;
+ }
+
+su_exit:
+ if (status != 0) {
+ perror(PAM_APP_NAME " failed");
+ }
+ exit(status); /* transparent exit */
+}
diff --git a/contrib/sucap/sucap.pamconfig b/contrib/sucap/sucap.pamconfig
new file mode 100644
index 0000000..02b70f2
--- /dev/null
+++ b/contrib/sucap/sucap.pamconfig
@@ -0,0 +1,6 @@
+#%PAM-1.0
+auth required pam_cap.so config=/etc/security/capability.conf
+auth required pam_unix.so
+account required pam_unix.so
+password required pam_unix.so
+session required pam_unix.so