Upgrade libcap to libcap-2.53 am: fbfa21662e am: ebeee9aaa2 am: 060326df86
Original change: https://android-review.googlesource.com/c/platform/external/libcap/+/2247493
Change-Id: Ibcfc4548c618bebf075ebcf5613e859ad8c75de5
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/.gitignore b/.gitignore
index 1971ad5..8698f19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
patches/
*.o
*~
+*.cf
diff --git a/License b/License
index 8a352bc..43a1297 100644
--- a/License
+++ b/License
@@ -1,8 +1,16 @@
Unless otherwise *explicitly* stated, the following text describes the
licensed conditions under which the contents of this libcap release
-may be used and distributed:
+may be used and distributed.
+
+The licensed conditions are one or the other of these two Licenses:
+
+ - BSD 3-clause
+ - GPL v2.0
-------------------------------------------------------------------------
+BSD 3-clause:
+-------------
+
Redistribution and use in source and binary forms of libcap, with
or without modification, are permitted provided that the following
conditions are met:
@@ -20,13 +28,6 @@
products derived from this software without their specific prior
written permission.
-ALTERNATIVELY, this product may be distributed under the terms of the
-GNU General Public License (v2.0 - see below), in which case the
-provisions of the GNU GPL are required INSTEAD OF the above
-restrictions. (This clause is necessary due to a potential conflict
-between the GNU GPL and the restrictions contained in a BSD-style
-copyright.)
-
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
@@ -38,7 +39,15 @@
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
+
-------------------------------------------------------------------------
+GPL v2.0:
+---------
+
+ALTERNATIVELY, this product may be distributed under the terms of the
+GNU General Public License (v2.0 - see below), in which case the
+provisions of the GNU GPL are required INSTEAD OF the above
+restrictions.
-------------------------
Full text of gpl-2.0.txt:
diff --git a/METADATA b/METADATA
index 052422f..d84738c 100644
--- a/METADATA
+++ b/METADATA
@@ -5,14 +5,11 @@
type: GIT
value: "https://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git"
}
- version: "libcap-2.48"
+ version: "libcap-2.53"
license_type: NOTICE
last_upgrade_date {
- year: 2021
- month: 2
- day: 5
- }
- security {
- tag: "NVD-CPE2.3:cpe:/a:tcpdump:libpcap:-"
+ year: 2022
+ month: 10
+ day: 10
}
}
diff --git a/Make.Rules b/Make.Rules
index ded9014..125f2aa 100644
--- a/Make.Rules
+++ b/Make.Rules
@@ -1,7 +1,7 @@
# Common version number defines for libcap
LIBTITLE=libcap
VERSION=2
-MINOR=48
+MINOR=53
#
## Optional prefixes:
@@ -43,11 +43,11 @@
PKGCONFIGDIR=$(LIBDIR)/pkgconfig
GOPKGDIR=$(prefix)/share/gocode/src
-# Once go1.16 is released, I plan to set this value to 1 and keep it
-# there. The Go packages should always remain backwardly compatible,
-# but I may have to up it if Go's syntax dramatically changes in a
-# backwards incompatible manner. (Let's hope not.)
-GOMAJOR=0
+# From here on out, the Go module packages should always remain
+# backwardly compatible. I will only resort to using major version 2
+# etc if Go's syntax dramatically changes in a backwards incompatible
+# manner. (Let's hope not.)
+GOMAJOR=1
# Compilation specifics
@@ -63,6 +63,7 @@
BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) $(IPATH)
AR := $(CROSS_COMPILE)ar
RANLIB := $(CROSS_COMPILE)ranlib
+OBJCOPY := $(CROSS_COMPILE)objcopy
DEBUG = -g #-DDEBUG
WARNINGS=-Wall -Wwrite-strings \
-Wpointer-arith -Wcast-qual -Wcast-align \
@@ -78,7 +79,6 @@
SYSTEM_HEADERS = /usr/include
INCS=$(topdir)/libcap/include/sys/capability.h
-LDFLAGS += -L$(topdir)/libcap
CFLAGS += -Dlinux $(WARNINGS) $(DEBUG)
INDENT := $(shell if [ -n "$$(which indent 2>/dev/null)" ]; then echo "| indent -kr" ; fi)
@@ -114,7 +114,7 @@
# Strictly speaking go1.15 doesn't need this, but 1.16 is when the
# real golang support arrives for non-cgo support, so drop the last
# vestige of legacy workarounds then.
-CGO_LDFLAGS_ALLOW := -Wl,-?-wrap[=,][^-.@][^,]*
+CGO_LDFLAGS_ALLOW := CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*"
endif
CGO_CFLAGS := -I$(topdir)/libcap/include
CGO_LDFLAGS := -L$(topdir)/libcap
@@ -155,7 +155,7 @@
#
# In the context of this tree, on such such systems, a yes setting will
# guarantee that every user, by default, is able to bless any binary with
-# any capability - a ready made local exploit machanism.
+# any capability - a ready made local exploit mechanism.
RAISE_SETFCAP := no
# If set to yes, this will cause the go "web" demo app to force the needed p
diff --git a/Makefile b/Makefile
index 7150b9b..d26af01 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@
# flags
#
-all install clean kdebug: %: %-here
+all install clean: %: %-here
$(MAKE) -C libcap $@
ifneq ($(PAM_CAP),no)
$(MAKE) -C pam_cap $@
@@ -32,7 +32,7 @@
distclean: clean
$(DISTCLEAN)
@echo "CONFIRM Go package cap has right version dependency on cap/psx:"
- for x in $$(find . -name go.mod); do grep -F -v "module" $$x | fgrep "kernel.org/pub/linux/libs/security/libcap" > /dev/null || continue ; grep -F "v$(GOMAJOR).$(VERSION).$(MINOR)" $$x > /dev/null && continue ; echo "$$x is not updated to v$(GOMAJOR).$(VERSION).$(MINOR)" ; exit 1 ; done
+ for x in $$(find . -name go.mod); do grep -F -v "module" $$x | fgrep "kernel.org/pub/linux/libs/security/libcap" > /dev/null || continue ; grep -F "v$(GOMAJOR).$(VERSION).$(MINOR)" $$x > /dev/null && continue ; echo "$$x is not updated. Try running: ./gomods.sh v$(GOMAJOR).$(VERSION).$(MINOR)" ; exit 1 ; done
@echo "ALL go.mod files updated"
@echo "Now validate that everything is checked in to a clean tree.."
test -z "$$(git status --ignored -s)"
@@ -52,6 +52,9 @@
endif
$(MAKE) -C progs $@
+ktest: all
+ $(MAKE) -C kdebug test
+
sudotest: all
$(MAKE) -C tests $@
ifneq ($(PAM_CAP),no)
@@ -65,14 +68,20 @@
distcheck:
./distcheck.sh
$(MAKE) DYNAMIC=yes clean all test sudotest
- $(MAKE) CC=/usr/local/musl/bin/musl-gcc clean all test sudotest
+ $(MAKE) DYNAMIC=no COPTS="-O2 -std=c89" clean all test sudotest
+ $(MAKE) PAM_CAP=no CC=/usr/local/musl/bin/musl-gcc clean all test sudotest
+ $(MAKE) CC=clang clean all test sudotest
$(MAKE) clean all test sudotest
$(MAKE) distclean
morgangodoc:
- @echo "Now the release is made, you want to remember to run:"
+ @echo "Now the release is made, you want to remember to run one of:"
@echo
- @echo "GOPROXY=https://proxy.golang.org GO111MODULE=on go get kernel.org/pub/linux/libs/security/libcap/cap@v$(GOMAJOR).$(VERSION).$(MINOR)"
+ @echo " GOPROXY=https://proxy.golang.org GO111MODULE=on go get kernel.org/pub/linux/libs/security/libcap/cap@v$(GOMAJOR).$(VERSION).$(MINOR)"
+ @echo
+ @echo or press the request button on this page:
+ @echo
+ @echo " https://pkg.go.dev/kernel.org/pub/linux/libs/security/libcap/cap@v$(GOMAJOR).$(VERSION).$(MINOR)"
@echo
@echo "This will cause a go.dev documentation update."
@@ -82,8 +91,8 @@
git tag -u E2CCF3F4 -s libcap-korg-$(VERSION).$(MINOR) -m "This is libcap-$(VERSION).$(MINOR)"
@echo "The following are for the Go module tracking."
git tag -u D41A6DF2 -s v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the version tag for the 'libcap' Go base directory associated with libcap-$(VERSION).$(MINOR)."
- git tag -u D41A6DF2 -s psx/v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the version tag for the 'psx' Go package associated with libcap-$(VERSION).$(MINOR)."
- git tag -u D41A6DF2 -s cap/v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the version tag for the 'cap' Go package associated with libcap-$(VERSION).$(MINOR)."
+ git tag -u D41A6DF2 -s psx/v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the (stable) version tag for the 'psx' Go package associated with libcap-$(VERSION).$(MINOR)."
+ git tag -u D41A6DF2 -s cap/v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the (stable) version tag for the 'cap' Go package associated with libcap-$(VERSION).$(MINOR)."
$(MAKE) release
@echo "sign the tar file using korg key"
cd .. && gpg -sba -u E2CCF3F4 libcap-$(VERSION).$(MINOR).tar
diff --git a/README b/README
index 6ba482c..9c4a3ea 100644
--- a/README
+++ b/README
@@ -5,45 +5,53 @@
This library would not have been possible without the help of
- Aleph1, Roland Buresund and Andrew Main, Alexander Kjeldaas.
+ Aleph1, Roland Buresund and Andrew Main, Alexander Kjeldaas.
More information on capabilities in the Linux kernel, links to the
-official git repostitory for libcap, release notes and how to report
+official git repository for libcap, release notes and how to report
bugs can be found at:
- http://sites.google.com/site/fullycapable/
+ http://sites.google.com/site/fullycapable/
+
+The primary upstream git repository is this one:
+
+ https://git.kernel.org/pub/scm/libs/libcap/libcap.git/
# BUILDING AND INSTALLATION
- $ make
+ $ make
- builds the library and the programs that are expected
- to work on your system. For example, if you have
- Linux-PAM installed, pam_cap is built. A golang
- installation is required to build the Go packages.
+ builds the library and the programs that are expected to work
+ on your system. For example, if you have Linux-PAM installed,
+ pam_cap is built. A golang installation is required to build
+ the Go packages.
- $ make test
+ $ make test
- runs all of the tests not requiring privilege
+ runs all of the tests not requiring privilege
- $ make sudotest
+ $ make sudotest
- runs all of the tests including those that require privilege.
+ runs all of the tests including those that require privilege.
- $ sudo make install
+ $ sudo make install
- default installs the library libcap.XX.Y in /lib[64]/
- the binaries in /sbin/
- the header files in /usr/include
- the {libcap,libpsx}.pc files in /usr/lib[64]/pkgconfig
- the Go packages (if built) under /usr/share/gocode/src
+ default installs the library libcap.XX.Y in /lib[64]/
+ the binaries in /sbin/
+ the header files in /usr/include
+ the {libcap,libpsx}.pc files in /usr/lib[64]/pkgconfig
+ the Go packages (if built) under /usr/share/gocode/src
-For some example C programs look in the progs/ directory. Specifically,
-capsh, getpcaps, setcap and getcap.
+For some example C programs look in the progs/ directory.
+Specifically, capsh, getpcaps, setcap and getcap. There are some C
+tests in the tests/ directory.
Go example programs are to be found in the goapps/ directory. There
are also some more complicated integration tests in the go/ directory.
+There are also some oddball experimental things in the contrib/
+directory, but they are mostly curiosities.
+
Cheers
Andrew G. Morgan <[email protected]>
diff --git a/cap/LICENSE b/cap/License
similarity index 98%
rename from cap/LICENSE
rename to cap/License
index 1c65641..a0ec04e 100644
--- a/cap/LICENSE
+++ b/cap/License
@@ -1,8 +1,16 @@
Unless otherwise *explicitly* stated, the following text describes the
licensed conditions under which the contents of this libcap/cap release
-may be used and distributed:
+may be used and distributed.
+
+The licensed conditions are one or the other of these two Licenses:
+
+ - BSD 3-clause
+ - GPL v2.0
-------------------------------------------------------------------------
+BSD 3-clause:
+-------------
+
Redistribution and use in source and binary forms of libcap/cap, with
or without modification, are permitted provided that the following
conditions are met:
@@ -20,13 +28,6 @@
products derived from this software without their specific prior
written permission.
-ALTERNATIVELY, this product may be distributed under the terms of the
-GNU General Public License (v2.0 - see below), in which case the
-provisions of the GNU GPL are required INSTEAD OF the above
-restrictions. (This clause is necessary due to a potential conflict
-between the GNU GPL and the restrictions contained in a BSD-style
-copyright.)
-
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
@@ -38,7 +39,17 @@
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
+
-------------------------------------------------------------------------
+GPL v2.0:
+---------
+
+ALTERNATIVELY, this product may be distributed under the terms of the
+GNU General Public License (v2.0 - see below), in which case the
+provisions of the GNU GPL are required INSTEAD OF the above
+restrictions. (This clause is necessary due to a potential conflict
+between the GNU GPL and the restrictions contained in a BSD-style
+copyright.)
-------------------------
Full text of gpl-2.0.txt:
diff --git a/cap/README b/cap/README
index 3ac8433..f257d44 100644
--- a/cap/README
+++ b/cap/README
@@ -4,7 +4,7 @@
https://sites.google.com/site/fullycapable/
Like libcap, the cap package is distributed with a "you choose"
-License. Specifically: BSD three clause, or GPL2. See the LICENSE
+License. Specifically: BSD three clause, or GPL2. See the License
file.
Andrew G. Morgan <[email protected]>
diff --git a/cap/cap.go b/cap/cap.go
index 5ccef59..908e2bb 100644
--- a/cap/cap.go
+++ b/cap/cap.go
@@ -28,27 +28,51 @@
// log.Fatalf("failed to drop privilege: %q -> %q: %v", old, empty, err)
// }
// now := cap.GetProc()
-// if cap.Differs(now.Compare(empty)) {
+// if cf, _ := now.Compare(empty); cf != 0 {
// log.Fatalf("failed to fully drop privilege: have=%q, wanted=%q", now, empty)
// }
//
+// The "cap" package operates with POSIX semantics for security
+// state. That is all OS threads are kept in sync at all times. The
+// package "kernel.org/pub/linux/libs/security/libcap/psx" is used to
+// implement POSIX semantics system calls that manipulate thread state
+// uniformly over the whole Go (and any CGo linked) process runtime.
+//
+// Note, if the Go runtime syscall interface contains the Linux
+// variant syscall.AllThreadsSyscall() API (it debuted in go1.16 see
+// https://github.com/golang/go/issues/1435 for its history) then the
+// "libcap/psx" package will use that to invoke Capability setting
+// system calls in pure Go binaries. With such an enhanced Go runtime,
+// to force this behavior, use the CGO_ENABLED=0 environment variable.
+//
+// POSIX semantics are more secure than trying to manage privilege at
+// a thread level when those threads share a common memory image as
+// they do under Linux: it is trivial to exploit a vulnerability in
+// one thread of a process to cause execution on any another
+// thread. So, any imbalance in security state, in such cases will
+// readily create an opportunity for a privilege escalation
+// vulnerability.
+//
+// POSIX semantics also work well with Go, which deliberately tries to
+// insulate the user from worrying about the number of OS threads that
+// are actually running in their program. Indeed, Go can efficiently
+// launch and manage tens of thousands of concurrent goroutines
+// without bogging the program or wider system down. It does this by
+// aggressively migrating idle threads to make progress on unblocked
+// goroutines. So, inconsistent security state across OS threads can
+// also lead to program misbehavior.
+//
+// The only exception to this process-wide common security state is
+// the cap.Launcher related functionality. This briefly locks an OS
+// thread to a goroutine in order to launch another executable - the
+// robust implementation of this kind of support is quite subtle, so
+// please read its documentation carefully, if you find that you need
+// it.
+//
// See https://sites.google.com/site/fullycapable/ for recent updates,
// some more complete walk-through examples of ways of using
// 'cap.Set's etc and information on how to file bugs.
//
-// For CGo linked binaries, behind the scenes, the package
-// "kernel.org/pub/linux/libs/security/libcap/psx" is used to perform
-// POSIX semantics system calls that manipulate thread state
-// uniformly over the whole Go (and CGo linked) process runtime.
-//
-// Note, if the Go runtime syscall interface contains the Linux
-// variant syscall.AllThreadsSyscall() API (it debuted in go1.16 see
-// https://github.com/golang/go/issues/1435 for its history) then
-// the "psx" package will use that to invoke Capability setting system
-// calls in pure Go binaries. In such an enhanced Go runtime, to force
-// this behavior, use the CGO_ENABLED=0 environment variable.
-//
-//
// Copyright (c) 2019-21 Andrew G. Morgan <[email protected]>
//
// The cap and psx packages are licensed with a (you choose) BSD
@@ -127,7 +151,7 @@
)
var (
- // starUp protects setting of the following values: magic,
+ // startUp protects setting of the following values: magic,
// words, maxValues.
startUp sync.Once
@@ -149,14 +173,6 @@
pid int32
}
-// scwMu is used to fully serialize the write system calls. Note, this
-// is generally not necesary, but in the case of Launch we get into a
-// situation where the launching thread is temporarily allowed to
-// deviate from the kernel state of the rest of the runtime and
-// allowing other threads to perform w* syscalls will potentially
-// interfere with the launching process.
-var scwMu sync.Mutex
-
// syscaller is a type for abstracting syscalls. The r* variants are
// for reading state, and can be parallelized, the w* variants need to
// be serialized so all OS threads can share state.
@@ -245,7 +261,7 @@
return int(r), nil
}
-// cInit perfoms the lazy identification of the capability vintage of
+// cInit performs the lazy identification of the capability vintage of
// the running system.
func (sc *syscaller) cInit() {
h := &header{
@@ -338,10 +354,15 @@
// process. The kernel will perform permission checks and an error
// will be returned if the attempt fails. Should the attempt fail
// no process capabilities will have been modified.
+//
+// Note, the general behavior of this call is to set the
+// process-shared capabilities. However, when called from a callback
+// function as part of a (*Launcher).Launch(), the call only sets the
+// capabilities of the thread being used to perform the launch.
func (c *Set) SetProc() error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.setProc(c)
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.setProc(c)
}
// defines from uapi/linux/prctl.h
@@ -381,9 +402,9 @@
// ill-defined state. The caller can determine where things went wrong
// using GetBound().
func DropBound(val ...Value) error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.dropBound(val...)
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.dropBound(val...)
}
// defines from uapi/linux/prctl.h
@@ -428,9 +449,9 @@
// captures all three inheritable vectors in a single type. Consider
// using that.
func SetAmbient(enable bool, val ...Value) error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.setAmbient(enable, val...)
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.setAmbient(enable, val...)
}
func (sc *syscaller) resetAmbient() error {
@@ -455,7 +476,7 @@
// already raised in both the Permitted and Inheritable Set is allowed
// to be raised by the kernel.
func ResetAmbient() error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.resetAmbient()
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.resetAmbient()
}
diff --git a/cap/cap_test.go b/cap/cap_test.go
index 017c565..d7c7970 100644
--- a/cap/cap_test.go
+++ b/cap/cap_test.go
@@ -106,6 +106,18 @@
return nil
}
+func confirmExpectedExport(t *testing.T, info string, c *Set, size uint) {
+ if ex, err := c.Export(); err != nil {
+ t.Fatalf("[%s] failed to export empty set: %v", info, err)
+ } else if n := 5 + 3*size; uint(len(ex)) != n {
+ t.Fatalf("[%s] wrong length: got=%d [%0x] want=%d", info, len(ex), ex, n)
+ } else if im, err := Import(ex); err != nil {
+ t.Fatalf("[%s] failed to import empty set: %v", info, err)
+ } else if got, want := im.String(), c.String(); got != want {
+ t.Fatalf("[%s] import != export: got=%q want=%q [%02x]", info, got, want, ex)
+ }
+}
+
func TestImportExport(t *testing.T) {
wantQ := "=ep cap_chown-e 63+ip"
if q, err := FromText(wantQ); err != nil {
@@ -116,15 +128,7 @@
// Sanity check empty import/export.
c := NewSet()
- if ex, err := c.Export(); err != nil {
- t.Fatalf("failed to export empty set: %v", err)
- } else if len(ex) != 5 {
- t.Fatalf("wrong length: got=%d want=%d", len(ex), 5)
- } else if im, err := Import(ex); err != nil {
- t.Fatalf("failed to import empty set: %v", err)
- } else if got, want := im.String(), c.String(); got != want {
- t.Fatalf("import != export: got=%q want=%q", got, want)
- }
+ confirmExpectedExport(t, "empty", c, MinExtFlagSize)
// Now keep flipping bits on and off and validate that all
// forms of import/export work.
for i := uint(0); i < 7000; i += 13 {
@@ -143,6 +147,24 @@
t.Fatalf("[%d] miscompare (%q vs. %q): %v", i, got, parsed, err)
}
}
+
+ oMin := MinExtFlagSize
+ for j := uint(0); j < 5; j++ {
+ t.Logf("exporting with min flag size %d", j)
+ MinExtFlagSize = j
+ c = NewSet()
+ for i := uint(0); i < maxValues; i++ {
+ s := Flag(i % 3)
+ v := Value(i)
+ c.SetFlag(s, true, v)
+ size := 1 + i/8
+ if size < MinExtFlagSize {
+ size = MinExtFlagSize
+ }
+ confirmExpectedExport(t, fmt.Sprintf("%d added %d %v %v", j, i, s, v), c, size)
+ }
+ }
+ MinExtFlagSize = oMin
}
func TestIAB(t *testing.T) {
@@ -212,3 +234,67 @@
}
}
}
+
+func TestFuncLaunch(t *testing.T) {
+ if _, err := FuncLauncher(func(data interface{}) error {
+ return nil
+ }).Launch(nil); err != nil {
+ t.Fatalf("trivial launcher failed: %v", err)
+ }
+
+ for i := 0; i < 100; i++ {
+ expect := i & 1
+ before, err := Prctl(prGetKeepCaps)
+ if err != nil {
+ t.Fatalf("failed to get PR_KEEP_CAPS: %v", err)
+ }
+ if before != expect {
+ t.Fatalf("invalid initial state: got=%d want=%d", before, expect)
+ }
+
+ if _, err := FuncLauncher(func(data interface{}) error {
+ was, ok := data.(int)
+ if !ok {
+ return fmt.Errorf("data was not an int: %v", data)
+ }
+ if _, err := Prctlw(prSetKeepCaps, uintptr(1-was)); err != nil {
+ return err
+ }
+ if v, err := Prctl(prGetKeepCaps); err != nil {
+ return err
+ } else if v == was {
+ return fmt.Errorf("PR_KEEP_CAPS unchanged: got=%d, want=%v", v, 1-was)
+ }
+ // All good.
+ return nil
+ }).Launch(before); err != nil {
+ t.Fatalf("trivial launcher failed: %v", err)
+ }
+
+ // Now validate that the main process is still OK.
+ if after, err := Prctl(prGetKeepCaps); err != nil {
+ t.Fatalf("failed to get PR_KEEP_CAPS: %v", err)
+ } else if before != after {
+ t.Fatalf("FuncLauncher leaked privileged state: got=%v want=%v", after, before)
+ }
+
+ // Now force the other way
+ if _, err := Prctlw(prSetKeepCaps, uintptr(1-expect)); err != nil {
+ t.Fatalf("[%d] attempt to flip PR_KEEP_CAPS failed: %v", i, err)
+ }
+ }
+}
+
+func TestFill(t *testing.T) {
+ c, err := FromText("cap_setfcap=p")
+ if err != nil {
+ t.Fatalf("failed to parse: %v", err)
+ }
+ c.Fill(Effective, Permitted)
+ c.ClearFlag(Permitted)
+ c.Fill(Inheritable, Effective)
+ c.ClearFlag(Effective)
+ if got, want := c.String(), "cap_setfcap=i"; got != want {
+ t.Errorf("Fill failed: got=%q want=%q", got, want)
+ }
+}
diff --git a/cap/convenience.go b/cap/convenience.go
index 9580903..d604ad1 100644
--- a/cap/convenience.go
+++ b/cap/convenience.go
@@ -2,6 +2,7 @@
import (
"errors"
+ "fmt"
"syscall"
"unsafe"
)
@@ -33,6 +34,7 @@
// defines from uapi/linux/prctl.h
const (
+ prGetKeepCaps = 7
prSetKeepCaps = 8
prGetSecureBits = 27
prSetSecureBits = 28
@@ -57,9 +59,9 @@
// will raise cap.SETPCAP in order to achieve this operation, and will
// completely lower the Effective vector of the process returning.
func (s Secbits) Set() error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.setSecbits(s)
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.setSecbits(s)
}
// Mode summarizes a complicated secure-bits and capability mode in a
@@ -181,9 +183,9 @@
// permission or because (some of) the Secbits are already locked for
// the current process.
func (m Mode) Set() error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.setMode(m)
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.setMode(m)
}
// String returns the libcap conventional string for this mode.
@@ -238,9 +240,9 @@
// performs a change of UID cap.SETUID is available, and the action
// does not alter the Permitted Flag of the process' Set.
func SetUID(uid int) error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.setUID(uid)
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.setUID(uid)
}
//go:uintptrescapes
@@ -286,7 +288,43 @@
// completely lower the Effective Flag of the process Set before
// returning.
func SetGroups(gid int, suppl ...int) error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.setGroups(gid, suppl)
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.setGroups(gid, suppl)
+}
+
+//go:uintptrescapes
+
+// Prctlw is a convenience function for performing a syscall.Prctl()
+// call that executes on all the threads of the process. It is called
+// Prctlw because it is only appropriate to call this function when it
+// is writing thread state that the caller wants to set on all OS
+// threads of the process to observe POSIX semantics when Linux
+// doesn't natively honor them. (Check prctl documentation for when it
+// is appropriate to use this vs. a normal syscall.Prctl() call.)
+func Prctlw(prVal uintptr, args ...uintptr) (int, error) {
+ if n := len(args); n > 5 {
+ return -1, fmt.Errorf("prctl supports up to 5 arguments (not %d)", n)
+ }
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ as := make([]uintptr, 5)
+ copy(as, args)
+ return sc.prctlwcall6(prVal, as[0], as[1], as[2], as[3], as[4])
+}
+
+//go:uintptrescapes
+
+// Prctl is a convenience function that performs a syscall.Prctl()
+// that either reads state using a single OS thread, or performs a
+// Prctl that is treated as a process wide setting. It is provided for
+// symmetry reasons, but is equivalent to simply calling the
+// corresponding syscall function.
+func Prctl(prVal uintptr, args ...uintptr) (int, error) {
+ if n := len(args); n > 5 {
+ return -1, fmt.Errorf("prctl supports up to 5 arguments (not %d)", n)
+ }
+ as := make([]uintptr, 5)
+ copy(as, args)
+ return singlesc.prctlrcall6(prVal, as[0], as[1], as[2], as[3], as[4])
}
diff --git a/cap/file.go b/cap/file.go
index 6658f1b..70dae92 100644
--- a/cap/file.go
+++ b/cap/file.go
@@ -65,6 +65,9 @@
// an irregular (non-executable) file.
var ErrBadPath = errors.New("file is not a regular executable")
+// ErrOutOfRange indicates an erroneous value for MinExtFlagSize.
+var ErrOutOfRange = errors.New("flag length invalid for export")
+
// digestFileCap unpacks a file capability and returns it in a *Set
// form.
func digestFileCap(d []byte, sz int, err error) (*Set, error) {
@@ -264,7 +267,7 @@
//go:uintptrescapes
-// SetFile attempts to set the file capabilities of the specfied
+// SetFile attempts to set the file capabilities of the specified
// filename. This function can also be used to delete a file's
// capabilities, by calling with c = nil.
//
@@ -311,7 +314,8 @@
const ExtMagic = uint32(0x5101c290)
// Import imports a Set from a byte array where it has been stored in
-// a portable (lossless) way.
+// a portable (lossless) way. That is values exported by
+// libcap.cap_copy_ext() and Export().
func Import(d []byte) (*Set, error) {
b := bytes.NewBuffer(d)
var m uint32
@@ -344,27 +348,45 @@
return c, nil
}
+// To strictly match libcap, this value defaults to 8. Setting it to
+// zero can generate smaller external representations. Such smaller
+// representations can be imported by libcap and the Go package just
+// fine, we just default to the default libcap representation for
+// legacy reasons.
+var MinExtFlagSize = uint(8)
+
// Export exports a Set into a lossless byte array format where it is
// stored in a portable way. Note, any namespace owner in the Set
// content is not exported by this function.
+//
+// Note, Export() generates exported byte streams that are importable
+// by libcap.cap_copy_int() as well as Import().
func (c *Set) Export() ([]byte, error) {
if c == nil {
return nil, ErrBadSet
}
+ if MinExtFlagSize > 255 {
+ return nil, ErrOutOfRange
+ }
b := new(bytes.Buffer)
binary.Write(b, binary.LittleEndian, ExtMagic)
c.mu.Lock()
defer c.mu.Unlock()
- var n = byte(0)
+ var n = uint(0)
for i, f := range c.flat {
- if u := f[Effective] | f[Permitted] | f[Inheritable]; u != 0 {
- n = 4 * byte(i)
- for ; u != 0; u >>= 8 {
- n++
+ if nn := 4 * uint(i); nn+4 > n {
+ if u := f[Effective] | f[Permitted] | f[Inheritable]; u != 0 {
+ n = nn
+ for ; u != 0; u >>= 8 {
+ n++
+ }
}
}
}
- b.Write([]byte{n})
+ if n < MinExtFlagSize {
+ n = MinExtFlagSize
+ }
+ b.Write([]byte{byte(n)})
for _, f := range c.flat {
if n == 0 {
break
@@ -382,5 +404,9 @@
inh >>= 8
}
}
+ for n > 0 {
+ n--
+ b.Write([]byte{0, 0, 0})
+ }
return b.Bytes(), nil
}
diff --git a/cap/flags.go b/cap/flags.go
index b800a2d..83a871a 100644
--- a/cap/flags.go
+++ b/cap/flags.go
@@ -75,6 +75,24 @@
return nil
}
+// Fill copies the from flag values into the to flag. With this
+// function, you can raise all of the permitted values in the
+// effective flag with c.Fill(cap.Effective, cap.Permitted).
+func (c *Set) Fill(to, from Flag) error {
+ if c == nil || len(c.flat) == 0 {
+ return ErrBadSet
+ }
+ if to > Inheritable || from > Inheritable {
+ return ErrBadValue
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ for i := range c.flat {
+ c.flat[i][to] = c.flat[i][from]
+ }
+ return nil
+}
+
// ErrBadValue indicates a bad capability value was specified.
var ErrBadValue = errors.New("bad capability value")
diff --git a/cap/go.mod b/cap/go.mod
index 45e38fa..a299f4c 100644
--- a/cap/go.mod
+++ b/cap/go.mod
@@ -2,4 +2,4 @@
go 1.11
-require kernel.org/pub/linux/libs/security/libcap/psx v0.2.48
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
diff --git a/cap/iab.go b/cap/iab.go
index 877ed12..77f2dbc 100644
--- a/cap/iab.go
+++ b/cap/iab.go
@@ -56,7 +56,7 @@
}
}
-// IABGetProc summarizes the Inh, Amb and Bound capabilty vectors of
+// IABGetProc summarizes the Inh, Amb and Bound capability vectors of
// the current process.
func IABGetProc() *IAB {
iab := IABInit()
@@ -188,14 +188,14 @@
}
// SetProc attempts to change the Inheritable, Ambient and Bounding
-// capabilty vectors of the current process using the content,
+// capability vectors of the current process using the content,
// iab. The Bounding vector strongly affects the potential for setting
// other bits, so this function carefully performs the the combined
// operation in the most flexible manner.
func (iab *IAB) SetProc() error {
- scwMu.Lock()
- defer scwMu.Unlock()
- return multisc.iabSetProc(iab)
+ state, sc := scwStateSC()
+ defer scwSetState(launchBlocked, state, -1)
+ return sc.iabSetProc(iab)
}
// GetVector returns the raised state of the specific capability bit
diff --git a/cap/launch.go b/cap/launch.go
index 4ae449c..6145f3e 100644
--- a/cap/launch.go
+++ b/cap/launch.go
@@ -8,19 +8,22 @@
"unsafe"
)
-// Launcher holds a configuration for launching a child process with
-// capability state different from (generally more restricted than)
-// the parent.
+// Launcher holds a configuration for executing an optional callback
+// function and/or launching a child process with capability state
+// different from the parent.
//
// Note, go1.10 is the earliest version of the Go toolchain that can
// support this abstraction.
type Launcher struct {
+ // Note, path and args must be set, or callbackFn. They cannot
+ // both be empty. In such cases .Launch() will error out.
path string
args []string
env []string
callbackFn func(pa *syscall.ProcAttr, data interface{}) error
+ // The following are only honored when path is non empty.
changeUIDs bool
uid int
@@ -46,14 +49,64 @@
}
}
-// Callback specifies a callback for Launch() to call before changing
-// privilege. The only thing that is assumed is that the OS thread in
-// use to call this callback function at launch time will be the one
-// that ultimately calls fork. Any returned error value of said
-// function will terminate the launch process. A nil callback (the
-// default) is ignored. The specified callback fn should not call any
-// "cap" package functions since this may deadlock or generate
-// undefined behavior for the parent process.
+// FuncLauncher returns a new launcher whose purpose is to only
+// execute fn in a disposable security context. This is a more bare
+// bones variant of the more elaborate program launcher returned by
+// cap.NewLauncher().
+//
+// Note, this launcher will fully ignore any overrides provided by the
+// (*Launcher).SetUID() etc. methods. Should your fn() code want to
+// run with a different capability state or other privilege, it should
+// use the cap.*() functions to set them directly. The cap package
+// will ensure that their effects are limited to the runtime of this
+// individual function invocation. Warning: executing non-cap.*()
+// syscall functions may corrupt the state of the program runtime and
+// lead to unpredictable results.
+//
+// The properties of fn are similar to those supplied via
+// (*Launcher).Callback(fn) method. However, this launcher is bare
+// bones because, when launching, all privilege management performed
+// by the fn() is fully discarded when the fn() completes
+// execution. That is, it does not end by exec()ing some program.
+func FuncLauncher(fn func(interface{}) error) *Launcher {
+ return &Launcher{
+ callbackFn: func(ignored *syscall.ProcAttr, data interface{}) error {
+ return fn(data)
+ },
+ }
+}
+
+// Callback changes the callback function for Launch() to call before
+// changing privilege. The only thing that is assumed is that the OS
+// thread in use to call this callback function at launch time will be
+// the one that ultimately calls fork to complete the launch of a path
+// specified executable. Any returned error value of said function
+// will terminate the launch process.
+//
+// A nil fn causes there to be no callback function invoked during a
+// Launch() sequence - it will remove any pre-existing callback.
+//
+// If the non-nil fn requires any effective capabilities in order to
+// run, they can be raised prior to calling .Launch() or inside the
+// callback function itself.
+//
+// If the specified callback fn should call any "cap" package
+// functions that change privilege state, these calls will only affect
+// the launch goroutine itself. While the launch is in progress, other
+// (non-launch) goroutines will block if they attempt to change
+// privilege state. These routines will unblock once there are no
+// in-flight launches.
+//
+// Note, the first argument provided to the callback function is the
+// *syscall.ProcAttr value to be used when a process launch is taking
+// place. A non-nil structure pointer can be modified by the callback
+// to enhance the launch. For example, the .Files field can be
+// overridden to affect how the launched process' stdin/out/err are
+// handled.
+//
+// Further, the 2nd argument to the callback function is provided at
+// Launch() invocation and can communicate contextual info to and from
+// the callback and the main process.
func (attr *Launcher) Callback(fn func(*syscall.ProcAttr, interface{}) error) {
attr.callbackFn = fn
}
@@ -93,7 +146,17 @@
// lResult is used to get the result from the doomed launcher thread.
type lResult struct {
+ // tid holds the tid of the locked launching thread which dies
+ // as the launch completes.
+ tid int
+
+ // pid is the pid of the launched program (path, args). In
+ // the case of a FuncLaunch() this value is zero on success.
+ // pid holds -1 in the case of error.
pid int
+
+ // err is nil on success, but otherwise holds the reason the
+ // launch failed.
err error
}
@@ -135,13 +198,21 @@
}
pid := syscall.Getpid()
- // Wait until we are not scheduled on the parent thread. We
- // will exit this thread once the child has launched, and
- // don't want other goroutines to use this thread afterwards.
+ // This code waits until we are not scheduled on the parent
+ // thread. We will exit this thread once the child has
+ // launched.
runtime.LockOSThread()
tid := syscall.Gettid()
if tid == pid {
- // Force the go runtime to find a new thread to run on.
+ // Force the go runtime to find a new thread to run
+ // on. (It is really awkward to have a process'
+ // PID=TID thread in effectively a zombie state. The
+ // Go runtime has support for it, but pstree gives
+ // ugly output since the prSetName value sticks around
+ // after launch completion...
+ //
+ // (Optimize for time to debug by reducing ugly spam
+ // like this.)
quit := make(chan struct{})
go launch(result, attr, data, quit)
@@ -154,6 +225,7 @@
// By never releasing the LockOSThread here, we guarantee that
// the runtime will terminate the current OS thread once this
// function returns.
+ scwSetState(launchIdle, launchActive, tid)
// Name the launcher thread - transient, but helps to debug if
// the callbackFn or something else hangs up.
@@ -163,22 +235,33 @@
// completing.
defer close(result)
- pa := &syscall.ProcAttr{
- Files: []uintptr{0, 1, 2},
- }
+ var pa *syscall.ProcAttr
var err error
var needChroot bool
- if len(attr.env) != 0 {
- pa.Env = attr.env
- } else {
- pa.Env = os.Environ()
+ // Only prepare a non-nil pa value if a path is provided.
+ if attr.path != "" {
+ // By default the following file descriptors are preserved for
+ // the child. The user should modify them in the callback for
+ // stdin/out/err redirection.
+ pa = &syscall.ProcAttr{
+ Files: []uintptr{0, 1, 2},
+ }
+ if len(attr.env) != 0 {
+ pa.Env = attr.env
+ } else {
+ pa.Env = os.Environ()
+ }
}
if attr.callbackFn != nil {
if err = attr.callbackFn(pa, data); err != nil {
goto abort
}
+ if attr.path == "" {
+ pid = 0
+ goto abort
+ }
}
if needChroot, err = validatePA(pa, attr.chroot); err != nil {
@@ -220,23 +303,51 @@
if err != nil {
pid = -1
}
- result <- lResult{pid: pid, err: err}
+ result <- lResult{
+ tid: tid,
+ pid: pid,
+ err: err,
+ }
}
-// Launch performs a new program launch with security state specified
-// in the supplied attr settings.
+// Launch performs a callback function and/or new program launch with
+// a disposable security state. The data object, when not nil, can be
+// used to communicate with the callback. It can also be used to
+// return details from the callback functions execution.
+//
+// If the attr was created with NewLauncher(), this present function
+// will return the pid of the launched process, or -1 and a non-nil
+// error.
+//
+// If the attr was created with FuncLauncher(), this present function
+// will return 0, nil if the callback function exits without
+// error. Otherwise it will return -1 and the non-nil error of the
+// callback return value.
+//
+// Note, while the disposable security state thread makes some
+// oprerations seem more isolated - they are *not securely
+// isolated*. Launching is inherently violating the POSIX semantics
+// maintained by the rest of the "libcap/cap" package, so think of
+// launching as a convenience wrapper around fork()ing.
+//
+// Advanced user note: if the caller of this function thinks they know
+// what they are doing by using runtime.LockOSThread() before invoking
+// this function, they should understand that the OS Thread invoking
+// (*Launcher).Launch() is *not guaranteed* to be the one used for the
+// disposable security state to perform the launch. If said caller
+// needs to run something on the disposable security state thread,
+// they should do it via the launch callback function mechanism. (The
+// Go runtime is complicated and this is why this Launch mechanism
+// provides the optional callback function.)
func (attr *Launcher) Launch(data interface{}) (int, error) {
- if attr.path == "" || len(attr.args) == 0 {
- return -1, ErrLaunchFailed
- }
if !LaunchSupported {
return -1, ErrNoLaunch
}
+ if attr.callbackFn == nil && (attr.path == "" || len(attr.args) == 0) {
+ return -1, ErrLaunchFailed
+ }
- scwMu.Lock()
- defer scwMu.Unlock()
result := make(chan lResult)
-
go launch(result, attr, data, nil)
for {
select {
@@ -244,6 +355,9 @@
if !ok {
return -1, ErrLaunchFailed
}
+ if v.tid != -1 {
+ defer scwSetState(launchActive, launchIdle, v.tid)
+ }
return v.pid, v.err
default:
runtime.Gosched()
diff --git a/cap/names.go b/cap/names.go
index 9e02cd1..8ee96d1 100644
--- a/cap/names.go
+++ b/cap/names.go
@@ -50,7 +50,7 @@
// file.
FSETID
- // KILL allows a process to sent a kill(2) signal to any other
+ // KILL allows a process to send a kill(2) signal to any other
// process - overriding the limitation that there be a
// [E]UID match between source and target process.
KILL
@@ -63,7 +63,7 @@
SETGID
// SETUID allows a process to freely manipulate its own UIDs:
- // - arbitraily set the UID, EUID, REUID and RESUID
+ // - arbitrarily set the UID, EUID, REUID and RESUID
// values
// - allows the forging of UID credentials passed over a
// socket
@@ -85,7 +85,7 @@
// default, as its unsuppressed behavior was not
// auditable: it could asynchronously grant its own
// Permitted capabilities to and remove capabilities from
- // other processes arbitraily. The former leads to
+ // other processes arbitrarily. The former leads to
// undefined behavior, and the latter is better served by
// the kill system call.]
SETPCAP
@@ -230,8 +230,6 @@
// - override the maximum number of consoles for console
// allocation
// - override the maximum number of keymaps
- //
- //
SYS_RESOURCE
// SYS_TIME allows a process to perform time manipulation of clocks:
@@ -261,6 +259,11 @@
AUDIT_CONTROL
// SETFCAP allows a process to set capabilities on files.
+ // Permits a process to uid_map the uid=0 of the
+ // parent user namespace into that of the child
+ // namespace. Also, permits a process to override
+ // securebits locks through user namespace
+ // creation.
SETFCAP
// MAC_OVERRIDE allows a process to override Manditory Access Control
diff --git a/cap/syscalls.go b/cap/syscalls.go
index ab4bcef..37121e0 100644
--- a/cap/syscalls.go
+++ b/cap/syscalls.go
@@ -1,6 +1,8 @@
package cap
import (
+ "runtime"
+ "sync"
"syscall"
"kernel.org/pub/linux/libs/security/libcap/psx"
@@ -25,3 +27,95 @@
r3: syscall.RawSyscall,
r6: syscall.RawSyscall6,
}
+
+// launchState is used to track which variant of the write syscalls
+// should execute.
+type launchState int
+
+// these states are used to understand when a launch is in progress.
+const (
+ launchIdle launchState = iota
+ launchActive
+ launchBlocked
+)
+
+// scwMu is used to fully serialize the write system calls. Note, this
+// would generally not be necessary, but in the case of Launch we get
+// into a situation where the launching thread is temporarily allowed
+// to deviate from the kernel state of the rest of the runtime and
+// allowing other threads to perform w* syscalls will potentially
+// interfere with the launching process. In pure Go binaries, this
+// will lead inevitably to a panic when the AllThreadsSyscall
+// discovers inconsistent thread state.
+//
+// scwMu protects scwTIDs and scwState
+var scwMu sync.Mutex
+
+// scwTIDs holds the thread IDs of the threads that are executing a
+// launch it is empty when no launches are occurring.
+var scwTIDs = make(map[int]bool)
+
+// scwState captures whether a launch is in progress or not.
+var scwState = launchIdle
+
+// scwCond is used to announce when scwState changes to other
+// goroutines waiting for it to change.
+var scwCond = sync.NewCond(&scwMu)
+
+// scwSetState blocks until a launch state change between states from
+// and to occurs. We use this for more context specific syscaller
+// use. In the case that the caller is requesting a launchActive ->
+// launchIdle transition they are declaring that tid is no longer
+// launching. If another thread is also launching the call will
+// complete, but the launchState will remain launchActive.
+func scwSetState(from, to launchState, tid int) {
+ scwMu.Lock()
+ for scwState != from {
+ if scwState == launchActive && from == launchIdle && to == launchActive {
+ break // This "transition" is also allowed.
+ }
+ scwCond.Wait()
+ }
+ if from == launchIdle && to == launchActive {
+ scwTIDs[tid] = true
+ } else if from == launchActive && to == launchIdle {
+ delete(scwTIDs, tid)
+ if len(scwTIDs) != 0 {
+ to = from // not actually idle
+ }
+ }
+ scwState = to
+ scwCond.Broadcast()
+ scwMu.Unlock()
+}
+
+// scwStateSC blocks until the current syscaller is available for
+// writes, and then marks launchBlocked. Use scwSetState to perform
+// the reverse transition (blocked->returned state value).
+func scwStateSC() (launchState, *syscaller) {
+ sc := multisc
+ scwMu.Lock()
+ for {
+ if scwState == launchIdle {
+ break
+ }
+ runtime.LockOSThread()
+ if scwState == launchActive && scwTIDs[syscall.Gettid()] {
+ sc = singlesc
+ // note, we don't runtime.UnlockOSThread()
+ // here because we have no reason to ever
+ // allow this thread to return to normal use -
+ // we need it dead before we can return to the
+ // launchIdle state.
+ break
+ }
+ runtime.UnlockOSThread()
+ scwCond.Wait()
+ }
+ old := scwState
+ scwState = launchBlocked
+ scwCond.Broadcast()
+ scwMu.Unlock()
+
+ return old, sc
+}
diff --git a/contrib/pcaps4convenience b/contrib/pcaps4convenience
index c46735d..b78a25b 100644
--- a/contrib/pcaps4convenience
+++ b/contrib/pcaps4convenience
@@ -63,22 +63,22 @@
# 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_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_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 @@
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 @@
# 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_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_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 @@
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
index 37fe97b..8203d4f 100644
--- a/contrib/seccomp/explore.go
+++ b/contrib/seccomp/explore.go
@@ -114,46 +114,46 @@
}
}
-func ExamineSyscall() []SockFilter {
+func examineSyscall() []SockFilter {
return []SockFilter{
bpfStmt(bpfLd+bpfW+bpfAbs, syscallNr),
}
}
-func AllowSyscall(syscallNum uint32) []SockFilter {
+func allowSyscall(syscallNum uint32) []SockFilter {
return []SockFilter{
bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
bpfStmt(bpfRet+bpfK, seccompRetAllow),
}
}
-func DisallowSyscall(syscallNum, errno uint32) []SockFilter {
+func disallowSyscall(syscallNum, errno uint32) []SockFilter {
return []SockFilter{
bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
bpfStmt(bpfRet+bpfK, seccompRetErrno|(errno&seccompRetData)),
}
}
-func KillProcess() []SockFilter {
+func killProcess() []SockFilter {
return []SockFilter{
bpfStmt(bpfRet+bpfK, seccompRetKillProcess),
}
}
-func NotifyProcessAndDie() []SockFilter {
+func notifyProcessAndDie() []SockFilter {
return []SockFilter{
bpfStmt(bpfRet+bpfK, seccompRetTrap),
}
}
-func TrapOnSyscall(syscallNum uint32) []SockFilter {
+func trapOnSyscall(syscallNum uint32) []SockFilter {
return []SockFilter{
bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
bpfStmt(bpfRet+bpfK, seccompRetTrap),
}
}
-func AllGood() []SockFilter {
+func allGood() []SockFilter {
return []SockFilter{
bpfStmt(bpfRet+bpfK, seccompRetAllow),
}
@@ -244,20 +244,20 @@
filter = append(filter, validateArchitecture()...)
// Grab the system call number.
- filter = append(filter, ExamineSyscall()...)
+ filter = append(filter, examineSyscall()...)
// List disallowed syscalls.
for _, x := range []uint32{
syscall.SYS_SETUID,
} {
if *kill {
- filter = append(filter, TrapOnSyscall(x)...)
+ filter = append(filter, trapOnSyscall(x)...)
} else {
- filter = append(filter, DisallowSyscall(x, uint32(*errno))...)
+ filter = append(filter, disallowSyscall(x, uint32(*errno))...)
}
}
- filter = append(filter, AllGood()...)
+ filter = append(filter, allGood()...)
prog := &SockFProg{
Len: uint16(len(filter)),
diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod
index 86e40c6..8f72409 100644
--- a/contrib/seccomp/go.mod
+++ b/contrib/seccomp/go.mod
@@ -2,4 +2,4 @@
go 1.14
-require kernel.org/pub/linux/libs/security/libcap/psx v0.2.48
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile
new file mode 100644
index 0000000..91947af
--- /dev/null
+++ b/contrib/sucap/Makefile
@@ -0,0 +1,9 @@
+all: su
+
+su: su.c
+ $(CC) -DPAM_APP_NAME=\"sucap\" -o $@ $< -lpam -lpam_misc -lcap
+ # to permit all ambient capabilities, this needs all permitted.
+ sudo setcap =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..586f017
--- /dev/null
+++ b/contrib/sucap/README.md
@@ -0,0 +1,40 @@
+This directory contains a port of the SimplePAMApp su to more
+aggressively use libcap.
+
+The Makefile builds a binary called `su` that registers with PAM as
+the application `sucap`. We've provided a sample `/etc/pam.d/sucap`
+file in this directory named `sucap.pamconfig`.
+
+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
+expectations for `pam_unix.so` are that it includes this commit:
+
+
+The original sources 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 issue:
+
+- https://bugzilla.kernel.org/show_bug.cgi?id=212945
+
+Andrew G. Morgan <[email protected]>
+2021-06-30
diff --git a/contrib/sucap/su.c b/contrib/sucap/su.c
new file mode 100644
index 0000000..5c98e5f
--- /dev/null
+++ b/contrib/sucap/su.c
@@ -0,0 +1,1606 @@
+/*
+ * Originally based on an implementation of `su' by
+ *
+ * Peter Orbaek <[email protected]>
+ *
+ * obtained circa 1997 from ftp://ftp.daimi.aau.dk/pub/linux/poe/
+ *
+ * Rewritten for Linux-PAM by Andrew G. Morgan <[email protected]>
+ * Modified by Andrey V. Savochkin <[email protected]>
+ * Modified for use with libcap by Andrew G. Morgan <[email protected]>
+ */
+
+/* #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 state;
+
+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(pam_handle_t *pamh, 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) exit(1);
+ if (fd != 1) {
+ if (dup2(fd, 1) == -1) exit(1);
+ if (close(fd) == -1) exit(1);
+ }
+ }
+ if (fstat(2, &st) == -1) {
+ fd = open("/dev/null", O_WRONLY);
+ if (fd == -1) exit(1);
+ if (fd != 2) {
+ if (dup2(fd, 2) == -1) exit(1);
+ if (close(fd) == -1) 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.
+ */
+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.
+ */
+void make_process_killable()
+{
+ (void) cap_setuid(invoked_uid);
+}
+
+/* ------ command line parser ----------------- */
+
+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.
+ */
+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;
+}
+
+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 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 (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);
+}
+
+static void exit_child_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) | PAM_DATA_SILENT);
+
+ 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(pamh, !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(pam_handle_t *pamh, 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(pam_handle_t *pamh, 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)
+{
+ int retval;
+ struct utmp u_tmp;
+ const struct utmp *u_tmp_p;
+ char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
+
+ retval = 0;
+
+ 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 */
+
+ retval = 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 */
+
+ retval = 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(pam_handle_t *pamh, 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(pam_handle_t *pamh
+ , 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 all of the process and PAM credentials.
+ */
+static int set_credentials(pam_handle_t *pamh, cap_t all, int login,
+ 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;
+ }
+
+ /*
+ * 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;
+ 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;
+ }
+ if (pam_misc_setenv(pamh, "HOME", pw->pw_dir, 0) != PAM_SUCCESS) {
+ D(("failed to set HOME"));
+ *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(pam_handle_t *pamh, 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 *pamh = h;
+ int retval;
+
+ D(("pam_end"));
+ retval = pam_end(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();
+
+ D(("about to launch"));
+ return 0;
+}
+
+/* Returns PAM_<STATUS>. */
+static int perform_launch_and_cleanup(cap_t all, int is_login,
+ const char *shell, const char *command)
+{
+ int retval, status;
+ const char *user, *home;
+ uid_t uid;
+ char * const * shell_args;
+ char * const * shell_env;
+ cap_launch_t launcher;
+ pid_t child;
+
+
+ /*
+ * 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;
+ }
+
+ 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_set_iab(launcher, cap_iab_get_proc());
+ cap_launcher_callback(launcher, launch_callback_fn);
+
+ child = cap_launch(launcher, pamh);
+ cap_free(launcher);
+
+ /* 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(pam_handle_t *pamh, 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;
+ pid_t child;
+ uid_t uid;
+ const char *place = NULL, *err_descr = NULL;
+ cap_t all, t_caps;
+
+ all = cap_get_proc();
+ cap_fill(all, CAP_EFFECTIVE, CAP_PERMITTED);
+
+ 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 ------ */
+ {
+ const char *user; /* transient until PAM_USER defined */
+ 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 */
+ }
+
+ /*
+ * 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(pamh, all, &retval, &place, &err_descr) != 0) {
+ goto auth_exit;
+ }
+
+ /*
+ * The user is valid, but should they have access at this
+ * time?
+ */
+ if (user_accounting(pamh, all, &retval, &place, &err_descr) != 0) {
+ goto auth_exit;
+ }
+
+ D(("su attempt is confirmed as authorized"));
+
+ /*
+ * ... 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;
+ }
+
+ if (set_credentials(pamh, all, is_login,
+ &shell, &retval, &place, &err_descr) != 0) {
+ D(("failed to set credentials"));
+ 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(pamh, 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;
+ }
+ }
+
+ if (open_session(pamh, t_caps, &retval, &place, &err_descr) != 0) {
+ goto utmp_closer;
+ }
+
+ status = perform_launch_and_cleanup(t_caps, is_login, shell, command);
+ close_session(pamh, all);
+
+utmp_closer:
+ if (is_login) {
+ /* do [uw]tmp cleanup */
+ retcode = utmp_close_session(pamh, &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));
+ }
+
+old_owner:
+ 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:
+ 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
diff --git a/doc/Makefile b/doc/Makefile
index e60f72d..a34cee0 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -10,13 +10,20 @@
cap_clear.3 cap_clear_flag.3 cap_get_flag.3 cap_set_flag.3 \
cap_compare.3 cap_get_proc.3 cap_get_pid.3 cap_set_proc.3 \
cap_get_file.3 cap_get_fd.3 cap_set_file.3 cap_set_fd.3 \
- cap_copy_ext.3 cap_size.3 cap_copy_int.3 \
+ cap_copy_ext.3 cap_size.3 cap_copy_int.3 cap_mode.3 \
cap_from_text.3 cap_to_text.3 cap_from_name.3 cap_to_name.3 \
capsetp.3 capgetp.3 libcap.3 \
cap_get_bound.3 cap_drop_bound.3 \
cap_get_mode.3 cap_set_mode.3 cap_mode_name.3 \
cap_get_secbits.3 cap_set_secbits.3 \
cap_setuid.3 cap_setgroups.3 \
+ cap_launch.3 cap_func_launcher.3 cap_launcher_callback.3 \
+ cap_launcher_set_chroot.3 cap_launcher_set_mode.3 \
+ cap_launcher_setgroups.3 cap_launcher_setuid.3 \
+ cap_launcher_set_iab.3 cap_new_launcher.3 \
+ cap_iab.3 cap_iab_init.3 cap_iab_get_proc.3 cap_iab_set_proc.3 \
+ cap_iab_to_text.3 cap_iab_from_text.3 cap_iab_get_vector.3 \
+ cap_iab_set_vector.3 cap_iab_fill.3 \
psx_syscall.3 psx_syscall3.3 psx_syscall6.3 libpsx.3
MAN8S = getcap.8 setcap.8 getpcaps.8
diff --git a/doc/cap_clear.3 b/doc/cap_clear.3
index 73aac61..6d06049 100644
--- a/doc/cap_clear.3
+++ b/doc/cap_clear.3
@@ -1,25 +1,21 @@
-.TH CAP_CLEAR 3 "2008-05-11" "" "Linux Programmer's Manual"
+.TH CAP_CLEAR 3 "2021-03-06" "" "Linux Programmer's Manual"
.SH NAME
-cap_clear, cap_clear_flag, cap_get_flag, cap_set_flag, cap_compare \- capability data object manipulation
+cap_clear, cap_clear_flag, cap_get_flag, cap_set_flag, cap_fill, cap_compare \- capability data object manipulation
.SH SYNOPSIS
.nf
-.B #include <sys/capability.h>
-.sp
-.BI "int cap_clear(cap_t " cap_p );
-.sp
-.BI "int cap_clear_flag(cap_t " cap_p ", cap_flag_t " flag ");"
-.sp
-.BI "int cap_get_flag(cap_t " cap_p ", cap_value_t " cap ,
-.BI " cap_flag_t " flag ", cap_flag_value_t *" value_p ");"
-.sp
-.BI "int cap_set_flag(cap_t " cap_p ", cap_flag_t " flag ", int " ncap ,
-.BI " const cap_value_t *" caps \
-", cap_flag_value_t " value ");"
-.sp
-.BI "int cap_compare(cap_t " cap_a ", cap_t " cap_b ");"
+#include <sys/capability.h>
+
+int cap_clear(cap_t cap_p);
+int cap_clear_flag(cap_t cap_p, cap_flag_t flag);
+int cap_get_flag(cap_t cap_p, cap_value_t cap,
+ cap_flag_t flag, cap_flag_value_t *value_p);
+int cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap,
+ const cap_value_t *caps, cap_flag_value_t value);
+int cap_fill(cap_t cap_p, cap_flag_t to, cap_flag_t from);
+int cap_compare(cap_t cap_a, cap_t cap_b);
+.fi
.sp
Link with \fI\-lcap\fP.
-.fi
.SH DESCRIPTION
These functions work on a capability state held in working storage.
A
@@ -85,6 +81,9 @@
is used to specify the number of capabilities in the array,
.IR caps .
.PP
+.BR cap_fill ()
+fills the to flag values by copying all of the from flag values.
+.PP
.BR cap_compare ()
compares two full capability sets and, in the spirit of
.BR memcmp (),
diff --git a/doc/cap_copy_ext.3 b/doc/cap_copy_ext.3
index acbb487..0965ad1 100644
--- a/doc/cap_copy_ext.3
+++ b/doc/cap_copy_ext.3
@@ -1,15 +1,15 @@
-.TH CAP_COPY_EXT 3 "2008-05-11" "" "Linux Programmer's Manual"
+.TH CAP_COPY_EXT 3 "2021-03-06" "" "Linux Programmer's Manual"
.SH NAME
cap_copy_ext, cap_size, cap_copy_int \- capability state
external representation translation
.SH SYNOPSIS
-.B #include <sys/capability.h>
-.sp
-.BI "ssize_t cap_size(cap_t " cap_p );
-.sp
-.BI "ssize_t cap_copy_ext(void *" ext_p ", cap_t " cap_p ", ssize_t " size );
-.sp
-.BI "cap_t cap_copy_int(const void *" ext_p );
+.nf
+#include <sys/capability.h>
+
+ssize_t cap_size(cap_t cap_p);
+ssize_t cap_copy_ext(void *ext_p, cap_t cap_p, ssize_t size);
+cap_t cap_copy_int(const void * ext_p);
+.fi
.sp
Link with \fI\-lcap\fP.
.SH DESCRIPTION
diff --git a/doc/cap_from_text.3 b/doc/cap_from_text.3
index 59724c7..9370e26 100644
--- a/doc/cap_from_text.3
+++ b/doc/cap_from_text.3
@@ -1,7 +1,7 @@
.\"
.\" written by Andrew Main <[email protected]>
.\"
-.TH CAP_FROM_TEXT 3 "2008-05-10" "" "Linux Programmer's Manual"
+.TH CAP_FROM_TEXT 3 "2021-03-06" "" "Linux Programmer's Manual"
.SH NAME
cap_from_text, cap_to_text, cap_to_name, cap_from_name \- capability
state textual representation translation
@@ -14,6 +14,7 @@
int cap_from_name(const char* name , cap_value_t* cap_p);
char *cap_to_name(cap_value_t cap);
.fi
+.sp
Link with \fI\-lcap\fP.
.SH DESCRIPTION
These functions translate a capability state between
diff --git a/doc/cap_func_launcher.3 b/doc/cap_func_launcher.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_func_launcher.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_get_file.3 b/doc/cap_get_file.3
index 3f73734..4c812fe 100644
--- a/doc/cap_get_file.3
+++ b/doc/cap_get_file.3
@@ -1,24 +1,21 @@
.\"
.\" written by Andrew Main <[email protected]>
.\"
-.TH CAP_GET_FILE 3 "2008-05-11" "" "Linux Programmer's Manual"
+.TH CAP_GET_FILE 3 "2021-03-06" "" "Linux Programmer's Manual"
.SH NAME
cap_get_file, cap_set_file, cap_get_fd, cap_set_fd \- capability
manipulation on files
.SH SYNOPSIS
-.B #include <sys/capability.h>
-.sp
-.BI "cap_t cap_get_file(const char *" path_p );
-.sp
-.BI "int cap_set_file(const char *" path_p ", cap_t " cap_p );
-.sp
-.BI "cap_t cap_get_fd(int " fd );
-.sp
-.BI "int cap_set_fd(int " fd ", cap_t " caps );
-.sp
-.BI "uid_t cap_get_nsowner(cap_t " caps );
-.sp
-.BI "int cap_set_nsowner(cap_t " caps ", uid_t " rootuid );
+.nf
+#include <sys/capability.h>
+
+cap_t cap_get_file(const char *path_p);
+int cap_set_file(const char *path_p, cap_t cap_p);
+cap_t cap_get_fd(int fd);
+int cap_set_fd(int fd, cap_t caps);
+uid_t cap_get_nsowner(cap_t caps);
+int cap_set_nsowner(cap_t caps, uid_t rootuid);
+.fi
.sp
Link with \fI\-lcap\fP.
.SH DESCRIPTION
diff --git a/doc/cap_get_proc.3 b/doc/cap_get_proc.3
index 74e5e8c..496c06e 100644
--- a/doc/cap_get_proc.3
+++ b/doc/cap_get_proc.3
@@ -1,49 +1,42 @@
-.TH CAP_GET_PROC 3 "2019-12-21" "" "Linux Programmer's Manual"
+.TH CAP_GET_PROC 3 "2021-03-06" "" "Linux Programmer's Manual"
.SH NAME
cap_get_proc, cap_set_proc, capgetp, cap_get_bound, cap_drop_bound, \
cap_get_ambient, cap_set_ambient, cap_reset_ambient, \
cap_get_secbits, cap_set_secbits, cap_get_mode, cap_set_mode, \
-cap_mode_name, cap_get_pid, cap_setuid, cap_setgroups \
+cap_mode_name, cap_get_pid, cap_setuid, cap_prctl, cap_prctlw, cap_setgroups \
\- capability manipulation on processes
.SH SYNOPSIS
-.B #include <sys/capability.h>
-.sp
-.B "cap_t cap_get_proc(void);"
-.sp
-.BI "int cap_set_proc(cap_t " cap_p );
-.sp
-.BI "int cap_get_bound(cap_value_t " cap );
-.sp
-.BI "CAP_IS_SUPPORTED(cap_value_t " cap );
-.sp
-.BI "int cap_drop_bound(cap_value_t " cap );
-.sp
-.BI "int cap_get_ambient(cap_value_t " cap );
-.sp
-.BI "int cap_set_ambient(cap_value_t " cap ", cap_flag_value_t " value );
-.sp
-.B int cap_reset_ambient(void);
-.sp
-.BI CAP_AMBIENT_SUPPORTED();
-.sp
-.B "unsigned cap_get_secbits(void);"
-.sp
-.BI "int cap_set_secbits(unsigned " bits );
-.sp
-.B "cap_mode_t cap_get_mode(void);"
-.sp
-.BI "const char *cap_mode_name(cap_mode_t " mode );
-.sp
-.BI "int cap_set_mode(cap_mode_t " mode );
-.sp
-.B #include <sys/types.h>
-.sp
-.BI "cap_t cap_get_pid(pid_t " pid );
-.sp
-.BI "int cap_setuid(uid_t " uid );
-.sp
-.BI "int cap_setgroups(gid_t " gid ", size_t " ngroups ", const gid_t " \
-groups );
+.nf
+#include <sys/capability.h>
+
+cap_t cap_get_proc(void);
+int cap_set_proc(cap_t cap_p);
+
+int cap_get_bound(cap_value_t cap);
+CAP_IS_SUPPORTED(cap_value_t cap);
+
+int cap_drop_bound(cap_value_t cap);
+int cap_get_ambient(cap_value_t cap);
+int cap_set_ambient(cap_value_t cap, cap_flag_value_t value);
+int cap_reset_ambient(void);
+CAP_AMBIENT_SUPPORTED();
+
+unsigned cap_get_secbits(void);
+int cap_set_secbits(unsigned bits);
+cap_mode_t cap_get_mode(void);
+const char *cap_mode_name(cap_mode_t mode);
+int cap_prctl(long int pr_cmd, long int arg1, long int arg2,
+ long int arg3, long int arg4, long int arg5);
+int cap_prctlw(long int pr_cmd, long int arg1, long int arg2,
+ long int arg3, long int arg4, long int arg5);
+int cap_set_mode(cap_mode_t mode);
+
+#include <sys/types.h>
+
+cap_t cap_get_pid(pid_t pid);
+int cap_setuid(uid_t uid);
+int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups);
+.fi
.sp
Link with \fI\-lcap\fP.
.SH DESCRIPTION
@@ -172,6 +165,12 @@
Supported modes are:
.BR CAP_MODE_NOPRIV ", " CAP_MODE_PURE1E_INIT " and " CAP_MODE_PURE1E .
.PP
+.BR cap_prctl ()
+can be used to read state via the \fBprctl\fI()\fP system call.
+.PP
+.BR cap_prctlw ()
+can be used to write state via the \fBprctl\fI()\fP system call.
+.PP
.BR cap_set_mode ()
can be used to set the desired mode. The permitted capability
.B CAP_SETPCAP
@@ -250,7 +249,10 @@
.sp
When linked this way, due to linker magic, libcap uses
.BR psx_syscall "(3) and " psx_syscall6 (3)
-to perform state setting system calls.
+to perform state setting system calls. Notably, this also ensures that
+.BI cap_prctlw ()
+can be used to ensure process control bits are shared over all threads
+of a single process.
.SS capgetp() and capsetp()
The library also supports the deprecated functions:
.PP
@@ -269,7 +271,7 @@
.BR cap_get_pid ().
.PP
.BR capsetp ()
-attempts to set the capabilities of the calling porcess or of
+attempts to set the capabilities of the calling process or of
some other process(es),
.IR pid .
Note that setting capabilities of another process is only possible on older
diff --git a/doc/cap_iab.3 b/doc/cap_iab.3
new file mode 100644
index 0000000..a453428
--- /dev/null
+++ b/doc/cap_iab.3
@@ -0,0 +1,164 @@
+.TH CAP_IAB 3 "2021-03-10" "" "Linux Programmer's Manual"
+.SH NAME
+.nf
+#include <sys/capability.h>
+
+cap_iab_t cap_iab_init(void);
+
+cap_iab_t cap_iab_get_proc(void);
+
+int cap_iab_set_proc(cap_iab_t iab);
+
+char *cap_iab_to_text(cap_iab_t iab);
+
+cap_iab_t cap_iab_from_text(const char *text);
+
+cap_flag_value_t cap_iab_get_vector(cap_iab_t iab, cap_iab_vector_t vec,
+ cap_value_t val);
+
+int cap_iab_set_vector(cap_iab_t iab, cap_iab_vector_t vec, cap_value_t val,
+ cap_flag_value_t enable);
+
+int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec,
+ cap_t set, cap_flag_t flag);
+
+.fi
+.sp
+Link with \fI\-lcap\fP.
+.SH "DESCRIPTION"
+The functions defined in this man page concern the three naively
+inheritable process capability vectors: Inh, Amb and Bound. This
+\fIIAB\fP 3-tuple of capability vectors, captured in type
+\fIcap_iab_t\fP combine to pass capabilities from one process to
+another through
+.BR execve (2)
+system calls. The convolution rules using the IAB set are a fail over
+set of rules when the executed file has no configured
+\fIfile-capabilities\fP.
+.PP
+There are some constraints enforced by the kernel with respect to the
+three components of an IAB set and the Permitted process capability
+flag. They are: the Inh vector is entirely equal to the process
+Inheritable flag at all times; the the Amb vector contains no more
+capability values than the intersection of the Inh vector and the
+Permitted flag for the process; no Amb value blocked in the Bound
+Vector will survive
+.BR execve (2);
+and the Bound (or \fIblocked\fP) vector is the twos-complement of the
+process bounding set.
+.PP
+In some environments, it is considered desirable to naively inherit
+capabilities. That is pass capabilities, independent of the status of
+the executed binary, from parent to child through exec* system
+calls. The surviving capabilities become the Permitted flag for the
+post-exec process. This method of inheritance differs significantly
+from the handshake inheritance between pre-exec* process and
+file-capability bestowed executable of the traditional capability
+mechanism.
+.PP
+The convolution rules for IAB style inheritance are: I'=I; A'= A & ~B;
+P'=A & ~B. Where P etc are the pre-exec values and P' etc are the
+post-exec values.
+.PP
+With an understanding of these convolution rules, we can explain how
+.BR libcap (3)
+support for the IAB set is managed: the IAB API.
+.PP
+.BR cap_iab_init ()
+returns an empty IAB value. That is a \fImostly-harmless\fP tuple. It
+will not block and capabilities through exec, but it won't bestow any
+either. The returned cap_iab_t should be freed with
+.BR cap_free (3).
+.sp
+.BR cap_iab_get_proc ()
+returns a copy of the IAB value for the current process. The returned
+cap_iab_t should be freed with
+.BR cap_free (3).
+.sp
+.BR cap_iab_set_proc ()
+can be used to set the IAB value carried by the current process. Such
+a setting will fail if the process is insufficiently capable. The
+process requires CAP_SETPCAP and a superset of P values over the A and
+I vectors.
+.sp
+.BR cap_iab_to_text ()
+will convert an IAB set to a canonical text representation. The
+representation is slightly redundant but libcap will try to generate
+as short a representation as it is able.
+.sp
+.BR cap_iab_from_text ()
+generates an IAB set from a text string (likely generated by the
+previous function). The returned IAB set should be freed with
+.BR cap_free (3).
+.sp
+The text format accepted by
+.BR cap_iab_from_text ()
+is a comma separated list of capability values. Each capability is
+prefixed by nothing (or %) (Inh); ! (Bound); ^ (Amb). Or, some
+combination thereof. Since the Amb vector is constrained to be no
+greater than the Inh set, ^ is equivalent to %^. Further, unless B is
+non-zero, % can be omitted. The following are legal text
+representations: "!%cap_chown" (Bound but Inh),
+"!cap_setuid,^cap_chown" (Bound, Inh+Amb). "cap_setuid,!cap_chown"
+(Inh, Bound). As noted above, this text representation is the syntax
+for the \fIpam_cap.so\fP config file.
+.sp
+.BR cap_iab_get_vector ()
+can be used to determine the specific capability value of an IAB
+vector.
+.sp
+.BR cap_iab_set_vector ()
+can be used to set a specific vector value to the enable setting.
+.BR cap_iab_fill ()
+can be used to wholesale copy a cap_t flag value into the vec vector
+of the IAB set. Copying into Amb in this way may implicitly raise Inh
+values in the IAB set. Similarly copying into the Inh vector may
+implicitly lower Amb values that are not present in the resulting Inh
+vector.
+.SH "ERRORS"
+The functions returning \fIcap_iab_t\fP values or allocated memory in
+the form of a string return NULL on error.
+
+Integer return values are -1 on error and 0 on success.
+
+In the case of error consult \fIerrno\fP.
+.SH "NOTES"
+.PP
+Unlike the traditional \fIcap_t\fP capability set, the
+IAB set, taken together, is incompatible with filesystem capabilities
+created via tools like
+.BR setcap (8).
+That is, the Amb vector of the IAB set is rendered moot when an
+executable with a file capability is executed.
+.PP
+Further, there are libcap
+.BR cap_mode (3)s
+that render the Amb vector and its method of process inheritance
+disabled.
+
+.SH "HISTORY"
+The IAB format for inheritable variants of capabilities was first
+developed as the configuration syntax for the \fIpam_cap.so\fP
+Linux-PAM module in libcap-2.29. It was introduced to extend the
+\fIsimple\fP comma separated list of process Inheritable capabilities,
+that the module could besow on an authenticated process tree, to
+include enforced limits on the Bounding set and introduce support for
+the Amibient set of capability bits.
+
+While the Inheritable and Bounding sets were anticipated by the
+POSIX.1e draft that introduced capabilities, the Ambient set is a
+Linux invention, and incompatible with the POSIX.1e file capability
+model. As such, it was felt that trying to meld together all of the 5
+capability vectors into one text representation was not going to
+work. Instead the \fIpam_cap.so\fP config syntax was generalized into
+a whole set of libcap functions for bundling together all three
+naively inheritable capabilities: the IAB set. The support for this
+debuted in libcap-2.33.
+
+.SH "SEE ALSO"
+.BR libcap (3),
+.BR cap_launch (3),
+.BR cap_init (3),
+.BR capabilities (7)
+and
+.BR errno (3).
diff --git a/doc/cap_iab_fill.3 b/doc/cap_iab_fill.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_fill.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_from_text.3 b/doc/cap_iab_from_text.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_from_text.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_get_proc.3 b/doc/cap_iab_get_proc.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_get_proc.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_get_vector.3 b/doc/cap_iab_get_vector.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_get_vector.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_init.3 b/doc/cap_iab_init.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_init.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_set_proc.3 b/doc/cap_iab_set_proc.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_set_proc.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_set_vector.3 b/doc/cap_iab_set_vector.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_set_vector.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_to_text.3 b/doc/cap_iab_to_text.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_to_text.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_init.3 b/doc/cap_init.3
index 362db66..125b529 100644
--- a/doc/cap_init.3
+++ b/doc/cap_init.3
@@ -1,17 +1,17 @@
.\"
.\" written by Andrew Main <[email protected]>
.\"
-.TH CAP_INIT 3 "2008-05-11" "" "Linux Programmer's Manual"
+.TH CAP_INIT 3 "2021-03-06" "" "Linux Programmer's Manual"
.SH NAME
cap_init, cap_free, cap_dup \- capability data object storage management
.SH SYNOPSIS
-.B #include <sys/capability.h>
-.sp
-.B cap_t cap_init(void);
-.sp
-.BI "int cap_free(void *" obj_d );
-.sp
-.BI "cap_t cap_dup(cap_t " cap_p );
+.nf
+#include <sys/capability.h>
+
+cap_t cap_init(void);
+int cap_free(void *obj_d);
+cap_t cap_dup(cap_t cap_p);
+.fi
.sp
Link with \fI\-lcap\fP.
.SH DESCRIPTION
diff --git a/doc/cap_launch.3 b/doc/cap_launch.3
new file mode 100644
index 0000000..6d9b8f7
--- /dev/null
+++ b/doc/cap_launch.3
@@ -0,0 +1,182 @@
+.TH CAP_LAUNCH 3 "2021-08-01" "" "Linux Programmer's Manual"
+.SH NAME
+.nf
+#include <sys/capability.h>
+
+cap_launch_t cap_new_launcher(const char *arg0, const char * const *argv,
+ const char * const *envp);
+
+cap_launch_t cap_func_launcher(int (callback_fn)(void *detail));
+
+void cap_launcher_callback(cap_launch_t attr,
+ int (callback_fn)(void *detail));
+void cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor);
+cap_iab_t cap_launcher_set_iab(cap_launch_t attr, cap_iab_t iab);
+void cap_launcher_set_chroot(cap_launch_t attr, const char *chroot);
+
+#include <sys/types.h>
+
+pid_t cap_launch(cap_launch_t attr, void *detail);
+void cap_launcher_setuid(cap_launch_t attr, uid_t uid);
+void cap_launcher_setgroups(cap_launch_t attr, gid_t gid,
+.fi
+.sp
+Link with \fI\-lcap\fP.
+.SH DESCRIPTION
+A launcher provides a mechanism for code to execute a callback
+function and/or a program executable in an environment with a modified
+security context. Essentially it provides a mechanism for a program to
+.BR fork (2)
+a new context from that of the main program manipulate capability and other privileged state in that
+.BR fork (2)d
+process before (optionally)
+.BR execve (2)ing
+a new program. When the application links to \fI\-lpsx\fP this support
+is needed to robustly execute the launched code without modifying the
+privilge of the whole (POSIX semantics honoring) main application.
+.PP
+A launcher is defined by one of these two functions:
+.BR cap_new_launcher ()
+or
+.BR cap_func_launcher ().
+The return value being of opaque type
+.B cap_launch_t
+a return value of NULL implies an error has occurred.
+.PP
+Once defined, a
+.B cap_launch_t
+value can be used with
+.BR cap_launch ()
+to execute that \fIlauncher\fP. In such cases, a non-negative return
+value indicates success: zero meaning success without a program being
+invoked; non-zero being equal to the process ID
+.RB ( pid_t )
+of the newly launched program.
+.PP
+A
+.B cap_launch_t
+occupies allocated memory and should be freed with
+.BR cap_free (3).
+Before being
+.BR cap_free (3)d
+a
+.B cap_value_t
+value may be reused for multiple independent launches. The
+.I detail
+argument to
+.BR cap_launch (),
+in conjunction with the launcher's callback function, can be used to
+customize the invocation of the launch. Care must be taken to leverage
+custom shared memory (see
+.BR mmap (2))
+or some other IPC to return values to the main program via
+.I detail
+since the callback and any subsequent program execution will occur
+outside the main process of the calling application. An example of
+this would be to allocate detail as follows:
+.nf
+
+ const *char[] args = { "echo", "hello", NULL };
+ cap_launch_t cmd = cap_new_launcher("/usr/bin/echo", args, NULL);
+ int *detail = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ cap_launcher_callback(cmd, &answer_detail_fn);
+ *detail = 41;
+ pid_t pid = cap_launch(cmd, detail);
+ printf("launcher callback set detail to %d\\n", *detail);
+ munmap(detail, sizeof(int));
+
+.if
+.PP
+Unless modified by the callback function, the launched code will
+execute with the capability and other security context of the
+application.
+
+If the callback function returns anything other than zero, a
+.BR cap_launch ()
+will be aborted. If detail of the failure is important to the caller,
+it should be communicated via the
+.I detail
+argument.
+
+The following functions can be used to instruct the launcher to modify
+the security state of the invoked program without altering the state
+of the calling program. Such modifications must be performed prior to
+calling \fBcap_launch\fP() if they are to have the desired
+effect. Further, they are only invoked after any installed callback
+has completed. For example, one can drop or modify capabilities,
+\fIjust\fP for executing a file.
+.PP
+The following functions instruct the launcher to do some common tasks
+of this sort (note some require permitted capability bits to succeed):
+.sp
+.BR cap_launcher_callback ()
+can be used to install or replace the currently installed callback
+function of the launcher.
+.sp
+.BR cap_launcher_set_mode ()
+can be used to ensure that the libcap-mode of the launched program is
+the desired one.
+.sp
+.BR cap_launcher_set_iab ()
+This function returns the \fBcap_iab_t\fP previously associated with
+the launcher. Calling this function with an IAB value of NULL will
+configure the launcher to not set an IAB value (the default). See
+\fBcap_iab\fP(3) for details on the IAB set. Note, the launcher is
+associated directly with the supplied \fIiab\fP value, and does not
+make a copy of it. Set with NULL to regain control over the memory
+associated with that IAB value, otherwise the IAB value will be
+\fBcap_free\fI()\fP'd when the launcher is.
+.sp
+.BR cap_launcher_set_chroot ()
+This function causes the launched program executable to be invoked
+inside a chroot \fIroot\fP directory.
+.sp
+.BR cap_launcher_setuid ()
+This function causes the launched program executable to be invoked
+with the specified user identifier (\fIuid_t\fP).
+.sp
+.BR cap_launcher_setgroups ()
+This function causes the launched program executable to be invoked
+with the specified primary and supplementary group IDs.
+.sp
+.PP
+Note, if any of the launcher enhancements made by the above functions
+should fail to take effect (typically for a lack of sufficient
+privilege), the launch will fail and return -1.
+
+.SH "ERRORS"
+A return of NULL for a
+.B cap_launch_t
+should be considered an error.
+.PP
+.BR cap_launch ()
+returns -1 in the case of an error.
+.PP
+In all such cases consult
+.BR errno (3)
+for further details.
+.SH "HISTORY"
+The \fBcap_launch\fP() family of functions were introduced in libcap
+2.33. It primarily addresses a complexity with \fI-lpsx\fP linked
+pthreads(7) applications that use capabilities but also honor POSIX
+semantics.
+
+Using \fI\-lcap\fP and \fI\-lpthread\fP together without the POSIX
+semantics support from \fI\-lpsx\fP introduces a subtle class of
+exposure to privilege escalation. (See
+https://sites.google.com/site/fullycapable/who-ordered-libpsx for an
+explanation.)
+.SH "SEE ALSO"
+.BR libpsx (3),
+.BR psx_syscall (3),
+.BR libcap (3),
+.BR cap_mode (3),
+.BR cap_iab (3),
+.BR capabilities (7),
+.BR errno (3),
+.BR fork (2),
+.BR mmap (2),
+.BR chroot (2),
+and
+.BR munmap (2).
diff --git a/doc/cap_launcher_callback.3 b/doc/cap_launcher_callback.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_callback.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_set_chroot.3 b/doc/cap_launcher_set_chroot.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_set_chroot.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_set_iab.3 b/doc/cap_launcher_set_iab.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_set_iab.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_set_mode.3 b/doc/cap_launcher_set_mode.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_set_mode.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_setgroups.3 b/doc/cap_launcher_setgroups.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_setgroups.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_setuid.3 b/doc/cap_launcher_setuid.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_setuid.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_mode.3 b/doc/cap_mode.3
new file mode 100644
index 0000000..65ea3e4
--- /dev/null
+++ b/doc/cap_mode.3
@@ -0,0 +1 @@
+.so man3/cap_get_proc.3
diff --git a/doc/cap_new_launcher.3 b/doc/cap_new_launcher.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_new_launcher.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/capability.notes b/doc/capability.notes
index b1e5245..4087c80 100644
--- a/doc/capability.notes
+++ b/doc/capability.notes
@@ -22,7 +22,7 @@
user can be made the owner of all of the system directories on your
system and critical system binaries too.
-Why is this a good idea? In a simple case, the CAP_FUSER capabilty is
+Why is this a good idea? In a simple case, the CAP_FUSER capability is
required for the superuser to delete files owned by a non-root user in
a 'sticky-bit' protected non-root owned directory. Thus, the sticky
bit can help you protect the /lib/ directory from an compromized
diff --git a/doc/capsh.1 b/doc/capsh.1
index ab20c44..e309438 100644
--- a/doc/capsh.1
+++ b/doc/capsh.1
@@ -1,4 +1,4 @@
-.TH CAPSH 1 "2020-10-27" "libcap 2" "User Commands"
+.TH CAPSH 1 "2021-07-01" "libcap 2" "User Commands"
.SH NAME
capsh \- capability shell wrapper
.SH SYNOPSIS
@@ -21,6 +21,9 @@
.B \-\-print
Display prevailing capability and related state.
.TP
+.B \-\-current
+Display prevailing capability state, 1e capabilities and IAB vector.
+.TP
.BI \-\- " [args]"
Execute
.B /bin/bash
@@ -40,7 +43,7 @@
occurs after a
.BI \-\-chroot= /some/path
argument the PATH located binary may not be resolve to the same binary
-as that running initially. This behavior is an intented feature as it
+as that running initially. This behavior is an intended feature as it
can complete the chroot transition.
.TP
.BI \-\-caps= cap-set
@@ -214,6 +217,16 @@
.B capsh
program exits with status 1.
.TP
+.BI \-\-explain= cap_xxx
+Give a brief textual description of what privileges the specified
+capability makes available to a running program. Note, instead of
+\fIcap_xxx\fP, one can provide a decimal number and \fBcapsh\fP will
+look up the corresponding capability's description.
+.TP
+.BI \-\-suggest= phrase
+Scan each of the textual descriptions of capabilities, known to
+\fBcapsh\fP, and display all descriptions that include \fIphrase\fP.
+.TP
.BI \-\-decode= N
This is a convenience feature. If you look at
.B /proc/1/status
@@ -266,6 +279,11 @@
.B xxx
raised.
.TP
+.BI \-\-iab= xxx
+Attempts to set the IAB tuple of inheritable capability vectors.
+The text conventions used for \fIxxx\fP are those of
+.BR cap_iab_from_text (3).
+.TP
.BI \-\-addamb= xxx
Adds the specified ambient capability to the running process.
.TP
@@ -290,6 +308,8 @@
.SH "SEE ALSO"
.BR libcap (3),
.BR getcap (8),
-.BR setcap (8)
+.BR setcap (8),
+.BR cap_from_text (3),
+.BR cap_iab (3)
and
.BR capabilities (7).
diff --git a/doc/libcap.3 b/doc/libcap.3
index 730e275..b8c8520 100644
--- a/doc/libcap.3
+++ b/doc/libcap.3
@@ -1,4 +1,4 @@
-.TH LIBCAP 3 "2020-01-07" "" "Linux Programmer's Manual"
+.TH LIBCAP 3 "2021-03-06" "" "Linux Programmer's Manual"
.SH NAME
cap_clear, cap_clear_flag, cap_compare, cap_copy_ext, cap_copy_int, \
cap_free, cap_from_name, cap_from_text, cap_get_fd, cap_get_file, \
@@ -7,54 +7,36 @@
cap_get_pid, cap_dup \- capability data object manipulation
.SH SYNOPSIS
.nf
-.B #include <sys/capability.h>
-.sp
-.BI "int cap_clear(cap_t " cap_p );
-.sp
-.BI "int cap_clear_flag(cap_t " cap_p ", cap_flag_t " flag ");"
-.sp
-.BI "int cap_compare(cap_t " cap_a ", cap_t " cap_b ");"
-.sp
-.BI "ssize_t cap_copy_ext(void *" ext_p ", cap_t " cap_p ", ssize_t " size );
-.sp
-.BI "cap_t cap_copy_int(const void *" ext_p );
-.sp
-.BI "int cap_free(void *" obj_d );
-.sp
-.BI "int cap_from_name(const char *" name ", cap_value_t *" cap_p );
-.sp
-.BI "cap_t cap_from_text(const char *" buf_p );
-.sp
-.BI "cap_t cap_get_fd(int " fd );
-.sp
-.BI "cap_t cap_get_file(const char *" path_p );
-.sp
-.BI "int cap_get_flag(cap_t " cap_p ", cap_value_t " cap ,
-.BI " cap_flag_t " flag ", cap_flag_value_t *" value_p ");"
-.sp
-.B #include <sys/types.h>
-.BI "cap_t cap_get_pid(pid_t " pid );
-.sp
-.B "cap_t cap_get_proc(void);"
-.sp
-.BI "int cap_set_fd(int " fd ", cap_t " caps );
-.sp
-.BI "int cap_set_file(const char *" path_p ", cap_t " cap_p );
-.sp
-.sp
-.BI "int cap_set_flag(cap_t " cap_p ", cap_flag_t " flag ", int " ncap ,
-.BI " const cap_value_t *" caps ", cap_flag_value_t " value ");"
-.BI "int cap_set_proc(cap_t " cap_p );
-.sp
-.BI "ssize_t cap_size(cap_t " cap_p );
-.sp
-.BI "char *cap_to_name(cap_value_t " cap );
-.sp
-.BI "char *cap_to_text(cap_t " caps ", ssize_t *" length_p );
-.sp
-.BI "cap_t cap_get_pid(pid_t " pid );
-.sp
-.BI "cap_t cap_dup(cap_t " cap_p );
+#include <sys/capability.h>
+
+int cap_clear(cap_t cap_p);
+int cap_clear_flag(cap_t cap_p, cap_flag_t flag);
+int cap_compare(cap_t cap_a, cap_t cap_b);
+ssize_t cap_copy_ext(void *ext_p, cap_t cap_p, ssize_t size);
+cap_t cap_copy_int(const void *ext_p);
+int cap_free(void *obj_d);
+int cap_from_name(const char *name, cap_value_t *cap_p);
+cap_t cap_from_text(const char *buf_p);
+cap_t cap_get_fd(int fd);
+cap_t cap_get_file(const char *path_p);
+int cap_get_flag(cap_t cap_p, cap_value_t cap ,
+ cap_flag_t flag, cap_flag_value_t *value_p);
+
+#include <sys/types.h>
+
+cap_t cap_get_pid(pid_t pid);
+cap_t cap_get_proc(void);
+int cap_set_fd(int fd, cap_t caps);
+int cap_set_file(const char *path_p, cap_t cap_p);
+int cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap ,
+ const cap_value_t *caps, cap_flag_value_t value);
+int cap_set_proc(cap_t cap_p);
+ssize_t cap_size(cap_t cap_p);
+char *cap_to_name(cap_value_t cap);
+char *cap_to_text(cap_t caps, ssize_t *length_p);
+cap_t cap_get_pid(pid_t pid);
+cap_t cap_dup(cap_t cap_p);
+.fi
.sp
Link with \fI\-lcap\fP.
.fi
@@ -102,9 +84,14 @@
and
.BR cap_compare ().
.SH "REPORTING BUGS"
-Please report bugs via:
+The
+.B libcap
+library is distributed from
+https://sites.google.com/site/fullycapable/ where the release notes
+may already cover recent issues. Please report newly discovered bugs
+via:
.TP
-https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1047723&product=Tools&resolution=---
+https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1090757
.SH "SEE ALSO"
.BR cap_clear (3),
.BR cap_copy_ext (3),
@@ -113,5 +100,7 @@
.BR cap_get_proc (3),
.BR cap_init (3),
.BR capabilities (7),
-.BR getpid (2)
+.BR getpid (2),
.BR capsh (1)
+and
+.BR libpsx (3).
diff --git a/doc/libpsx.3 b/doc/libpsx.3
index 61baa88..4ba306b 100644
--- a/doc/libpsx.3
+++ b/doc/libpsx.3
@@ -1,13 +1,13 @@
-.TH LIBPSX 3 "2021-01-31" "" "Linux Programmer's Manual"
+.TH LIBPSX 3 "2021-03-06" "" "Linux Programmer's Manual"
.SH NAME
psx_syscall3, psx_syscall6 \- POSIX semantics for system calls
.SH SYNOPSIS
.nf
-.B #include <sys/psx_syscall.h>
-.sp
-.BI "long int psx_syscall3(long int" " syscall_nr, " "long int" " arg1, " "long int" " arg2, " "long int" " arg3);"
-.sp
-.BI "long int psx_syscall6(long int" " syscall_nr, " "long int" " arg1, " "long int" " arg2, " "long int" " arg3, " "long int" " arg4, " "long int" " arg5, " "long int" " arg6);"
+#include <sys/psx_syscall.h>
+
+long int psx_syscall3(long int syscall_nr, long int arg1, long int arg2, long int arg3);
+long int psx_syscall6(long int syscall_nr, long int arg1, long int arg2, long int arg3, long int arg4, long int arg5, long int arg6);
+.fi
.sp
Link with one of these:
.sp
@@ -80,9 +80,14 @@
.TP
https://sites.google.com/site/fullycapable/who-ordered-libpsx
.SH "REPORTING BUGS"
-Please report bugs via:
+The
+.B libpsx
+library is distributed from
+https://sites.google.com/site/fullycapable/ where the release notes
+may already cover recent issues. Please report newly discovered bugs
+via:
.TP
-https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1047723&product=Tools&resolution=---
+https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1090757
.SH SEE ALSO
.BR libcap (3),
.BR pthreads "(7) and"
diff --git a/doc/old/_setfilecap.2 b/doc/old/_setfilecap.2
index 6a0538c..3c9e374 100644
--- a/doc/old/_setfilecap.2
+++ b/doc/old/_setfilecap.2
@@ -93,7 +93,7 @@
.TP
.SB ELOOP
.I filename
-containes a circular reference (via symlinks).
+contains a circular reference (via symlinks).
.TP
.SB EBADF
.I fd
diff --git a/doc/values/24.txt b/doc/values/24.txt
index bb3bac7..4911e50 100644
--- a/doc/values/24.txt
+++ b/doc/values/24.txt
@@ -12,5 +12,3 @@
- override the maximum number of consoles for console
allocation
- override the maximum number of keymaps
-
-
diff --git a/doc/values/31.txt b/doc/values/31.txt
index 163b048..ae97df2 100644
--- a/doc/values/31.txt
+++ b/doc/values/31.txt
@@ -1 +1,6 @@
Allows a process to set capabilities on files.
+Permits a process to uid_map the uid=0 of the
+parent user namespace into that of the child
+namespace. Also, permits a process to override
+securebits locks through user namespace
+creation.
diff --git a/doc/values/5.txt b/doc/values/5.txt
index 1097fe0..c4ded8e 100644
--- a/doc/values/5.txt
+++ b/doc/values/5.txt
@@ -1,3 +1,3 @@
-Allows a process to sent a kill(2) signal to any other
+Allows a process to send a kill(2) signal to any other
process - overriding the limitation that there be a
[E]UID match between source and target process.
diff --git a/doc/values/7.txt b/doc/values/7.txt
index 432a97e..fbc1240 100644
--- a/doc/values/7.txt
+++ b/doc/values/7.txt
@@ -1,5 +1,5 @@
Allows a process to freely manipulate its own UIDs:
- - arbitraily set the UID, EUID, REUID and RESUID
+ - arbitrarily set the UID, EUID, REUID and RESUID
values
- allows the forging of UID credentials passed over a
socket
diff --git a/doc/values/8.txt b/doc/values/8.txt
index d6d7c1f..d7654f0 100644
--- a/doc/values/8.txt
+++ b/doc/values/8.txt
@@ -14,6 +14,6 @@
default, as its unsuppressed behavior was not
auditable: it could asynchronously grant its own
Permitted capabilities to and remove capabilities from
-other processes arbitraily. The former leads to
+other processes arbitrarily. The former leads to
undefined behavior, and the latter is better served by
the kill system call.]
diff --git a/go/.gitignore b/go/.gitignore
index c0a9737..7b811c9 100644
--- a/go/.gitignore
+++ b/go/.gitignore
@@ -10,5 +10,7 @@
setid
gowns
ok
-pkg
-src
+vendor
+go.sum
+PSXGOPACKAGE
+CAPGOPACKAGE
diff --git a/go/Makefile b/go/Makefile
index 757844a..6b69cbe 100644
--- a/go/Makefile
+++ b/go/Makefile
@@ -1,24 +1,21 @@
# Building the libcap/{cap.psx} Go packages, and examples.
#
-# Note, we use symlinks to construct a GOPATH friendly src tree. The
+# Note, we use symlinks to construct a go.mod build friendly tree. The
# packages themselves are intended to be (ultimately) found via proxy
# as "kernel.org/pub/linux/libs/security/libcap/cap" and
# "kernel.org/pub/linux/libs/security/libcap/psx". However, to
# validate their use on these paths, we fake such a structure in the
-# build tree with symlinks.
+# build tree with symlinks and a vendor directory.
topdir=$(realpath ..)
include $(topdir)/Make.Rules
-GOPATH=$(realpath .)
IMPORTDIR=kernel.org/pub/linux/libs/security/libcap
PKGDIR=pkg/$(GOOSARCH)/$(IMPORTDIR)
-PSXGOPACKAGE=$(PKGDIR)/psx.a
-CAPGOPACKAGE=$(PKGDIR)/cap.a
DEPS=../libcap/libcap.a ../libcap/libpsx.a
-all: $(PSXGOPACKAGE) $(CAPGOPACKAGE) web setid gowns compare-cap try-launching psx-signals
+all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns compare-cap try-launching psx-signals
$(DEPS):
make -C ../libcap all
@@ -26,71 +23,80 @@
../progs/tcapsh-static:
make -C ../progs tcapsh-static
-src/$(IMPORTDIR)/psx:
- mkdir -p "src/$(IMPORTDIR)"
- ln -s $(topdir)/psx $@
+vendor/$(IMPORTDIR) vendor/modules.txt:
+ mkdir -p "vendor/$(IMPORTDIR)"
+ echo "# $(IMPORTDIR)/psx v$(GOMAJOR).$(VERSION).$(MINOR)" > vendor/modules.txt
+ echo "$(IMPORTDIR)/psx" >> vendor/modules.txt
+ echo "# $(IMPORTDIR)/cap v$(GOMAJOR).$(VERSION).$(MINOR)" >> vendor/modules.txt
+ echo "$(IMPORTDIR)/cap" >> vendor/modules.txt
-src/$(IMPORTDIR)/cap:
- mkdir -p "src/$(IMPORTDIR)"
- ln -s $(topdir)/cap $@
+vendor/$(IMPORTDIR)/psx: vendor/modules.txt
+ ln -sf $(topdir)/psx vendor/$(IMPORTDIR)
+ touch ../psx
-$(topdir)/libcap/cap_names.h: $(DEPS)
- make -C $(topdir)/libcap all
+vendor/$(IMPORTDIR)/cap: vendor/modules.txt
+ ln -sf $(topdir)/cap vendor/$(IMPORTDIR)
+ touch ../cap
-good-names.go: $(topdir)/libcap/cap_names.h src/$(IMPORTDIR)/cap mknames.go
- $(GO) run mknames.go --header=$< --textdir=$(topdir)/doc/values | gofmt > $@ || rm -f $@
+$(topdir)/libcap/cap_names.h:
+ make -C $(topdir)/libcap cap_names.h
+
+good-names.go: $(topdir)/libcap/cap_names.h vendor/$(IMPORTDIR)/cap mknames.go
+ CC="$(CC)" $(GO) run -mod=vendor mknames.go --header=$< --textdir=$(topdir)/doc/values | gofmt > $@ || rm -f $@
diff -u ../cap/names.go $@
-$(PSXGOPACKAGE): src/$(IMPORTDIR)/psx ../psx/*.go $(DEPS)
- mkdir -p pkg
- GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) install $(IMPORTDIR)/psx
+PSXGOPACKAGE: vendor/$(IMPORTDIR)/psx ../psx/*.go $(DEPS)
+ touch $@
-$(CAPGOPACKAGE): src/$(IMPORTDIR)/cap ../cap/*.go good-names.go $(PSXGOPACKAGE)
- GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) install $(IMPORTDIR)/cap
+CAPGOPACKAGE: vendor/$(IMPORTDIR)/cap ../cap/*.go good-names.go $(PSXGOPACKAGE)
+ touch $@
# Compiles something with this package to compare it to libcap. This
# tests more when run under sudotest (see ../progs/quicktest.sh for that).
-compare-cap: compare-cap.go $(CAPGOPACKAGE)
- GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $<
+compare-cap: compare-cap.go CAPGOPACKAGE
+ CC="$(CC)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor $<
-web: ../goapps/web/web.go $(CAPGOPACKAGE)
- GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $<
+web: ../goapps/web/web.go CAPGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
ifeq ($(RAISE_GO_FILECAP),yes)
make -C ../progs setcap
sudo ../progs/setcap cap_setpcap,cap_net_bind_service=p web
@echo "NOTE: RAISED cap_setpcap,cap_net_bind_service ON web binary"
endif
-setid: ../goapps/setid/setid.go $(CAPGOPACKAGE) $(PSXGOPACKAGE)
- GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $<
+setid: ../goapps/setid/setid.go CAPGOPACKAGE PSXGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
-gowns: ../goapps/gowns/gowns.go $(CAPGOPACKAGE)
- GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $<
+gowns: ../goapps/gowns/gowns.go CAPGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
ok: ok.go
- GO111MODULE=off CGO_ENABLED=0 GOPATH=$(GOPATH) $(GO) build $<
+ CC="$(CC)" CGO_ENABLED=0 $(GO) build -mod=vendor $<
-try-launching: try-launching.go $(CAPGOPACKAGE) ok
- GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build $<
+try-launching: try-launching.go CAPGOPACKAGE ok
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor $<
ifeq ($(CGO_REQUIRED),0)
- GO111MODULE=off CGO_ENABLED="1" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@-cgo $<
+ CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@-cgo $<
endif
-psx-signals: psx-signals.go $(PSXGOPACKAGE)
- GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $<
+psx-signals: psx-signals.go PSXGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor $<
+
ifeq ($(CGO_REQUIRED),0)
- GO111MODULE=off CGO_ENABLED="1" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build -o $@-cgo $<
+psx-signals-cgo: psx-signals.go PSXGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor -o $@ $<
endif
-b210613: b210613.go $(CAPGOPACKAGE)
- GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $<
+b210613: b210613.go CAPGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor $<
test: all
- GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/psx
- GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/cap
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) test -mod=vendor $(IMPORTDIR)/psx
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) test -mod=vendor $(IMPORTDIR)/cap
LD_LIBRARY_PATH=../libcap ./compare-cap
./psx-signals
ifeq ($(CGO_REQUIRED),0)
+ $(MAKE) psx-signals-cgo
./psx-signals-cgo
endif
./setid --caps=false
@@ -114,10 +120,10 @@
install: all
rm -rf $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
mkdir -p $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
- install -m 0644 src/$(IMPORTDIR)/psx/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
+ install -m 0644 vendor/$(IMPORTDIR)/psx/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
mkdir -p $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap
rm -rf $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap/*
- install -m 0644 src/$(IMPORTDIR)/cap/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap
+ install -m 0644 vendor/$(IMPORTDIR)/cap/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap
clean:
rm -f *.o *.so *~ mknames ok good-names.go
@@ -125,4 +131,4 @@
rm -f compare-cap try-launching try-launching-cgo
rm -f $(topdir)/cap/*~ $(topdir)/psx/*~
rm -f b210613 psx-signals psx-signals-cgo
- rm -fr pkg src
+ rm -fr vendor CAPGOPACKAGE PSXGOPACKAGE go.sum
diff --git a/go/compare-cap.go b/go/compare-cap.go
index 4424ebe..f2a7d6b 100644
--- a/go/compare-cap.go
+++ b/go/compare-cap.go
@@ -184,7 +184,7 @@
}
}
- // The current process is now without any access to privelege.
+ // The current process is now without any access to privilege.
}
func main() {
@@ -257,12 +257,12 @@
}
// Validate that it can be imported in binary in C
- iC := C.cap_copy_int(unsafe.Pointer(&iE[0]))
+ iC := C.cap_copy_int_check(unsafe.Pointer(&iE[0]), C.ssize_t(len(iE)))
if iC == nil {
log.Fatal("c failed to import go binary")
}
defer C.cap_free(unsafe.Pointer(iC))
- fC := C.cap_to_text(cC, &tCLen)
+ fC := C.cap_to_text(iC, &tCLen)
if fC == nil {
log.Fatal("basic c init caps -> text failure")
}
diff --git a/go/go.mod b/go/go.mod
new file mode 100644
index 0000000..4c49252
--- /dev/null
+++ b/go/go.mod
@@ -0,0 +1,8 @@
+module main
+
+go 1.11
+
+require (
+ kernel.org/pub/linux/libs/security/libcap/cap v1.2.53
+ kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
+)
diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod
index bc534af..92de262 100644
--- a/goapps/gowns/go.mod
+++ b/goapps/gowns/go.mod
@@ -2,4 +2,4 @@
go 1.15
-require kernel.org/pub/linux/libs/security/libcap/cap v0.2.48
+require kernel.org/pub/linux/libs/security/libcap/cap v1.2.53
diff --git a/goapps/gowns/gowns.go b/goapps/gowns/gowns.go
index b9a14cd..3d26c34 100644
--- a/goapps/gowns/gowns.go
+++ b/goapps/gowns/gowns.go
@@ -1,5 +1,10 @@
// Program gowns is a small program to explore and demonstrate using
// Go to Wrap a child in a NameSpace under Linux.
+//
+// Note, this program is under active development and should not be
+// considered stable. That is, it is more a worked example and may
+// change command line arguments and behavior from release to release.
+// Should it become stable, I'll remove this comment.
package main
import (
@@ -119,7 +124,6 @@
base++
for _, next := range ranges(ids) {
- fmt.Println("next:", next)
list = append(list,
syscall.SysProcIDMap{
ContainerID: base,
diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod
index cd2282d..02061c2 100644
--- a/goapps/setid/go.mod
+++ b/goapps/setid/go.mod
@@ -3,6 +3,6 @@
go 1.11
require (
- kernel.org/pub/linux/libs/security/libcap/cap v0.2.48
- kernel.org/pub/linux/libs/security/libcap/psx v0.2.48
+ kernel.org/pub/linux/libs/security/libcap/cap v1.2.53
+ kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
)
diff --git a/goapps/web/README b/goapps/web/README
index cc3c609..cbabd5d 100644
--- a/goapps/web/README
+++ b/goapps/web/README
@@ -10,7 +10,7 @@
A more complete walk through of what this code does is provided here:
- https://sites.google.com/site/fullycapable/building-go-programs-that-manipulate-capabilities
+ https://sites.google.com/site/fullycapable/getting-started-with-go/building-go-programs-that-manipulate-capabilities
Go compilers prior to go1.11.13 are not expected to work. Report more
recent issues to:
diff --git a/goapps/web/go.mod b/goapps/web/go.mod
index f7ae28b..4ca6b0e 100644
--- a/goapps/web/go.mod
+++ b/goapps/web/go.mod
@@ -2,4 +2,4 @@
go 1.11
-require kernel.org/pub/linux/libs/security/libcap/cap v0.2.48
+require kernel.org/pub/linux/libs/security/libcap/cap v1.2.53
diff --git a/goapps/web/web.go b/goapps/web/web.go
index d184e97..c96e745 100644
--- a/goapps/web/web.go
+++ b/goapps/web/web.go
@@ -1,26 +1,17 @@
-// Progam web provides an example of a webserver using capabilities to
+// Program web provides an example of a webserver using capabilities to
// bind to a privileged port, and then drop all capabilities before
// handling the first web request.
//
-// This program cannot work reliably as a pure Go application without
-// the equivalent of the Go runtime patch that adds a POSIX semantics
-// wrapper around the system calls that change per-thread security
-// state. A patch for the pure Go compiler/runtime to add this support
-// is available here [2019-12-14]:
+// This program can be compiled CGO_ENABLED=0 with the go1.16+
+// toolchain.
//
-// https://go-review.googlesource.com/c/go/+/210639/
+// Go versions prior to 1.16 use some cgo support provided by the
+// "kernel.org/pub/linux/libs/security/libcap/psx" package.
//
-// Until that patch, or something like it, is absorbed into the Go
-// runtime the only way to get capabilities to work reliably on the Go
-// runtime is to use something like libpsx via CGo to do capability
-// setting syscalls in C with POSIX semantics. As of this build of the
-// Go "kernel.org/pub/linux/libs/security/libcap/cap" package,
-// courtesy of the "kernel.org/pub/linux/libs/security/libcap/psx"
-// package, this is how things work.
-//
-// To set this up, compile and empower this binary as follows (read
-// over the detail in the psx package description if this doesn't
-// 'just' work):
+// To set this up, compile and empower this binary as follows (the
+// README contains a pointer to a full writeup for building this
+// package - go versions prior to 1.15 need some environment variable
+// workarounds):
//
// go build web.go
// sudo setcap cap_setpcap,cap_net_bind_service=p web
diff --git a/gomods.sh b/gomods.sh
new file mode 100755
index 0000000..890cccd
--- /dev/null
+++ b/gomods.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+version="${1}"
+if [[ -z "${version}" ]]; then
+ echo "usage: supply a cap/psx module version to target"
+ exit 1
+fi
+
+for x in $(find . -name 'go.mod'); do
+ sed -i -e '[email protected]/\([^ ]*\) v.*[email protected]/\1 '"${version}@" "${x}"
+done
diff --git a/kdebug/Makefile b/kdebug/Makefile
index c710050..0e8c11f 100644
--- a/kdebug/Makefile
+++ b/kdebug/Makefile
@@ -1,9 +1,17 @@
topdir=$(shell pwd)/..
include ../Make.Rules
-test:
+test: exit
+ rm -f interactive
./test-kernel.sh
+shell: exit
+ touch interactive
+ ./test-kernel.sh
+
+exit: exit.c
+ $(CC) -O2 $< -o $@ --static
+
all:
@echo cd to kdebug to test a kernel build
@@ -11,4 +19,4 @@
clean:
$(LOCALCLEAN)
- rm -f fs.conf initramfs.img
+ rm -f fs.conf initramfs.img exit interactive
diff --git a/kdebug/exit.c b/kdebug/exit.c
new file mode 100644
index 0000000..a83232d
--- /dev/null
+++ b/kdebug/exit.c
@@ -0,0 +1,36 @@
+/*
+ * See https://stackoverflow.com/questions/42208228/how-to-automatically-close-the-execution-of-the-qemu-after-end-of-process
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/io.h>
+#include <unistd.h>
+
+#define SHUTDOWN_PORT 0x604
+#define EXIT_PORT 0x501
+
+static void clean_exit(void) {
+ ioperm(SHUTDOWN_PORT, 16, 1);
+ outw(0x2000, SHUTDOWN_PORT);
+}
+
+int main(int argc, char **argv) {
+ int status;
+ if (argc != 2) {
+ clean_exit();
+ }
+ status = atoi(argv[1]);
+ printf("exiting with status %d (in three seconds)\n", status);
+ sleep(3);
+ if (!status) {
+ clean_exit();
+ }
+ ioperm(EXIT_PORT, 8, 1);
+ /*
+ * status returned is 1+(2*orig_status)
+ */
+ outb(status-1, EXIT_PORT);
+ printf("didn't exit.. did you include '-device isa-debug-exit'"
+ " in qemu command?\n");
+ exit(1);
+}
diff --git a/kdebug/test-init.sh b/kdebug/test-init.sh
index 4b55b51..849d9c7 100644
--- a/kdebug/test-init.sh
+++ b/kdebug/test-init.sh
@@ -10,5 +10,10 @@
echo Hello, World
cd /root
-./quicktest.sh
-sh -i
+if [ -f ./interactive ]; then
+ ./quicktest.sh
+ sh -i
+else
+ ./quicktest.sh || ./exit 1
+fi
+./exit
diff --git a/kdebug/test-kernel.sh b/kdebug/test-kernel.sh
index 1326cd7..38f5584 100755
--- a/kdebug/test-kernel.sh
+++ b/kdebug/test-kernel.sh
@@ -47,6 +47,8 @@
file /root/capsh $HERE/../progs/capsh 0755 0 0
file /root/getpcaps $HERE/../progs/getpcaps 0755 0 0
file /root/tcapsh-static $HERE/../progs/tcapsh-static 0755 0 0
+file /root/exit $HERE/exit 0755 0 0
+file /root/uns_test $HERE/../tests/uns_test 0755 0 0
EOF
# convenience for some local experiments
@@ -55,6 +57,10 @@
. "$HERE/extras.sh"
fi
+if [ -f "$HERE/interactive" ]; then
+ echo "file /root/interactive $HERE/interactive 0755 0 0" >> fs.conf
+fi
+
COMMANDS="awk cat chmod cp dmesg fgrep id less ln ls mkdir mount pwd rm rmdir sh sort umount uniq vi"
for f in $COMMANDS; do
echo slink /bin/$f /sbin/busybox 0755 0 0 >> fs.conf
@@ -73,4 +79,5 @@
-kernel $KERNEL \
-initrd initramfs.img \
-append "$APPEND" \
- -smp sockets=2,dies=1,cores=4
+ -smp sockets=2,dies=1,cores=4 \
+ -device isa-debug-exit
diff --git a/libcap/.gitignore b/libcap/.gitignore
index 8f77a0e..a0771d4 100644
--- a/libcap/.gitignore
+++ b/libcap/.gitignore
@@ -9,3 +9,7 @@
cap_test
libcap.pc
libpsx.pc
+empty
+loader.txt
+cap_magic.o
+psx_magic.o
diff --git a/libcap/Makefile b/libcap/Makefile
index 9563d88..b5689d2 100644
--- a/libcap/Makefile
+++ b/libcap/Makefile
@@ -9,11 +9,18 @@
CAPLIBNAME=$(LIBTITLE).so
STACAPLIBNAME=$(LIBTITLE).a
#
-PSXLIBNAME=libpsx.so
-STAPSXLIBNAME=libpsx.a
+PSXTITLE=libpsx
+PSXLIBNAME=$(PSXTITLE).so
+STAPSXLIBNAME=$(PSXTITLE).a
CAPFILES=cap_alloc cap_proc cap_extint cap_flag cap_text cap_file
+CAPMAGICOBJ=cap_magic.o
PSXFILES=../psx/psx
+PSXMAGICOBJ=psx_magic.o
+
+# The linker magic needed to build a dynamic library as independently
+# executable
+MAGIC=-Wl,-e,__so_start
INCLS=libcap.h cap_names.h $(INCS)
GPERF_OUTPUT = _caps_output.gperf
@@ -37,9 +44,9 @@
endif
endif
-pcs: libcap.pc
+pcs: $(LIBTITLE).pc
ifeq ($(PTHREADS),yes)
- $(MAKE) libpsx.pc
+ $(MAKE) $(PSXTITLE).pc
endif
ifeq ($(BUILD_GPERF),yes)
@@ -47,7 +54,7 @@
INCLUDE_GPERF_OUTPUT = -DINCLUDE_GPERF_OUTPUT='"$(GPERF_OUTPUT)"'
endif
-libcap.pc: libcap.pc.in
+$(LIBTITLE).pc: $(LIBTITLE).pc.in
sed -e 's,@prefix@,$(prefix),' \
-e 's,@exec_prefix@,$(exec_prefix),' \
-e 's,@libdir@,$(LIBDIR),' \
@@ -56,7 +63,7 @@
-e 's,@deps@,$(DEPS),' \
$< >$@
-libpsx.pc: libpsx.pc.in
+$(PSXTITLE).pc: $(PSXTITLE).pc.in
sed -e 's,@prefix@,$(prefix),' \
-e 's,@exec_prefix@,$(exec_prefix),' \
-e 's,@libdir@,$(LIBDIR),' \
@@ -93,13 +100,26 @@
$(RANLIB) $@
ifeq ($(SHARED),yes)
-$(CAPLIBNAME) $(MAJCAPLIBNAME) $(MINCAPLIBNAME): $(CAPOBJS)
- $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJCAPLIBNAME) -o $(MINCAPLIBNAME) $^
+
+empty: empty.c
+ $(CC) -o $@ $<
+
+loader.txt: empty
+ $(OBJCOPY) --dump-section .interp=$@ $<
+
+cap_magic.o: execable.h execable.c loader.txt
+ $(CC) $(CFLAGS) $(IPATH) -DLIBRARY_VERSION=\"$(LIBTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c execable.c -o $@
+
+$(CAPLIBNAME) $(MAJCAPLIBNAME) $(MINCAPLIBNAME): $(CAPOBJS) $(CAPMAGICOBJ)
+ $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJCAPLIBNAME) -o $(MINCAPLIBNAME) $^ $(MAGIC)
ln -sf $(MINCAPLIBNAME) $(MAJCAPLIBNAME)
ln -sf $(MAJCAPLIBNAME) $(CAPLIBNAME)
-$(PSXLIBNAME) $(MAJPSXLIBNAME) $(MINPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h
- $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJPSXLIBNAME) -o $(MINPSXLIBNAME) $(PSXOBJS) $(PSXLINKFLAGS)
+psx_magic.o: execable.h execable.c loader.txt
+ $(CC) $(CFLAGS) $(IPATH) -DLIBRARY_VERSION=\"$(PSXTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c execable.c -o $@
+
+$(PSXLIBNAME) $(MAJPSXLIBNAME) $(MINPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h $(PSXMAGICOBJ)
+ $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJPSXLIBNAME) -o $(MINPSXLIBNAME) $(PSXOBJS) $(PSXMAGICOBJ) $(MAGIC) $(PSXLINKFLAGS)
ln -sf $(MINPSXLIBNAME) $(MAJPSXLIBNAME)
ln -sf $(MAJPSXLIBNAME) $(PSXLIBNAME)
endif
@@ -110,11 +130,23 @@
cap_text.o: cap_text.c $(USE_GPERF_OUTPUT) $(INCLS)
$(CC) $(CFLAGS) $(IPATH) $(INCLUDE_GPERF_OUTPUT) -c $< -o $@
-cap_test: cap_test.c libcap.h
- $(CC) $(CFLAGS) $(IPATH) $< -o $@
+cap_test: cap_test.c libcap.h $(CAPOBJS)
+ $(CC) $(CFLAGS) $(IPATH) $< $(CAPOBJS) -o $@
+
+libcapsotest: $(CAPLIBNAME)
+ ./$(CAPLIBNAME)
+
+libpsxsotest: $(PSXLIBNAME)
+ ./$(PSXLIBNAME)
test: cap_test
./cap_test
+ifeq ($(SHARED),yes)
+ $(MAKE) libcapsotest
+ifeq ($(PTHREADS),yes)
+ $(MAKE) libpsxsotest
+endif
+endif
install: install-static
ifeq ($(SHARED),yes)
@@ -163,17 +195,17 @@
-/sbin/ldconfig
endif
-install-common-cap: install-common libcap.pc
+install-common-cap: install-common $(LIBTITLE).pc
install -m 0644 include/sys/capability.h $(FAKEROOT)$(INCDIR)/sys
- install -m 0644 libcap.pc $(FAKEROOT)$(PKGCONFIGDIR)/libcap.pc
+ install -m 0644 $(LIBTITLE).pc $(FAKEROOT)$(PKGCONFIGDIR)/$(LIBTITLE).pc
include/sys/psx_syscall.h: ../psx/psx_syscall.h
rm -f $@
ln -s ../../../psx/psx_syscall.h $@
-install-common-psx: install-common libpsx.pc include/sys/psx_syscall.h
+install-common-psx: install-common $(PSXTITLE).pc include/sys/psx_syscall.h
install -m 0644 include/sys/psx_syscall.h $(FAKEROOT)$(INCDIR)/sys
- install -m 0644 libpsx.pc $(FAKEROOT)$(PKGCONFIGDIR)/libpsx.pc
+ install -m 0644 $(PSXTITLE).pc $(FAKEROOT)$(PKGCONFIGDIR)/$(PSXTITLE).pc
install-common:
mkdir -p -m 0755 $(FAKEROOT)$(INCDIR)/sys
@@ -182,8 +214,9 @@
clean:
$(LOCALCLEAN)
- rm -f $(CAPOBJS) $(CAPLIBNAME)* $(STACAPLIBNAME) libcap.pc
- rm -f $(PSXOBJS) $(PSXLIBNAME)* $(STAPSXLIBNAME) libpsx.pc
+ rm -f $(CAPOBJS) $(CAPLIBNAME)* $(STACAPLIBNAME) $(LIBTITLE).pc
+ rm -f $(PSXOBJS) $(PSXLIBNAME)* $(STAPSXLIBNAME) $(PSXTITLE).pc
rm -f cap_names.h cap_names.list.h _makenames $(GPERF_OUTPUT) cap_test
rm -f include/sys/psx_syscall.h
+ rm -f $(CAPMAGICOBJ) $(PSXMAGICOBJ) empty loader.txt
cd include/sys && $(LOCALCLEAN)
diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c
index 6dab4e6..88ba6da 100644
--- a/libcap/cap_alloc.c
+++ b/libcap/cap_alloc.c
@@ -147,6 +147,22 @@
}
/*
+ * cap_func_launcher allocates some memory for a launcher and
+ * initializes it. The purpose of this launcher, unlike one created
+ * with cap_new_launcher(), is to execute some function code from a
+ * forked copy of the program. The forked process will exit when the
+ * callback function, func, returns.
+ */
+cap_launch_t cap_func_launcher(int (callback_fn)(void *detail))
+{
+ __u32 *data = calloc(1, sizeof(__u32) + sizeof(struct cap_launch_s));
+ *(data++) = CAP_LAUNCH_MAGIC;
+ struct cap_launch_s *attr = (struct cap_launch_s *) data;
+ attr->custom_setup_fn = callback_fn;
+ return attr;
+}
+
+/*
* Scrub and then liberate an internal capability set.
*/
diff --git a/libcap/cap_extint.c b/libcap/cap_extint.c
index 7d6e7ad..bf0967b 100644
--- a/libcap/cap_extint.c
+++ b/libcap/cap_extint.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997-8 Andrew G Morgan <[email protected]>
+ * Copyright (c) 1997-8,2021 Andrew G. Morgan <[email protected]>
*
* This file deals with exchanging internal and external
* representations of capability sets.
@@ -15,6 +15,11 @@
#define CAP_EXT_MAGIC_SIZE 4
const static __u8 external_magic[CAP_EXT_MAGIC_SIZE+1] = CAP_EXT_MAGIC;
+/*
+ * This is the largest size libcap can currently export.
+ * cap_size() may return something smaller depending on the
+ * content of its argument cap_t.
+ */
struct cap_ext_struct {
__u8 magic[CAP_EXT_MAGIC_SIZE];
__u8 length_of_capset;
@@ -26,11 +31,44 @@
};
/*
+ * minimum exported flag size: libcap2 has always exported with flags
+ * this size.
+ */
+static size_t _libcap_min_ext_flag_size = CAP_SET_SIZE < 8 ? CAP_SET_SIZE : 8;
+
+/*
* return size of external capability set
*/
-
-ssize_t cap_size(cap_t caps)
+ssize_t cap_size(cap_t cap_d)
{
+ if (good_cap_t(cap_d)) {
+ size_t j, used;
+ for (j=used=0; j<CAP_SET_SIZE; j+=sizeof(__u32)) {
+ int i;
+ __u32 val = 0;
+ for (i=0; i<NUMBER_OF_CAP_SETS; ++i) {
+ val |= cap_d->u[j/sizeof(__u32)].flat[i];
+ }
+ if (val == 0) {
+ continue;
+ }
+ if (val > 0x0000ffff) {
+ if (val > 0x00ffffff) {
+ used = j+4;
+ } else {
+ used = j+3;
+ }
+ } else if (val > 0x000000ff) {
+ used = j+2;
+ } else {
+ used = j+1;
+ }
+ }
+ if (used < _libcap_min_ext_flag_size) {
+ used = _libcap_min_ext_flag_size;
+ }
+ return (ssize_t)(CAP_EXT_MAGIC_SIZE + 1+ NUMBER_OF_CAP_SETS * used);
+ }
return ssizeof(struct cap_ext_struct);
}
@@ -43,42 +81,57 @@
ssize_t cap_copy_ext(void *cap_ext, cap_t cap_d, ssize_t length)
{
struct cap_ext_struct *result = (struct cap_ext_struct *) cap_ext;
+ ssize_t csz, len_set;
int i;
/* valid arguments? */
- if (!good_cap_t(cap_d) || length < ssizeof(struct cap_ext_struct)
- || cap_ext == NULL) {
+ if (!good_cap_t(cap_d) || cap_ext == NULL) {
errno = EINVAL;
return -1;
}
+ csz = cap_size(cap_d);
+ if (csz > length) {
+ errno = EINVAL;
+ return -1;
+ }
+ len_set = (csz - (CAP_EXT_MAGIC_SIZE+1))/NUMBER_OF_CAP_SETS;
+
/* fill external capability set */
memcpy(&result->magic, external_magic, CAP_EXT_MAGIC_SIZE);
- result->length_of_capset = CAP_SET_SIZE;
+ result->length_of_capset = len_set;
for (i=0; i<NUMBER_OF_CAP_SETS; ++i) {
size_t j;
- for (j=0; j<CAP_SET_SIZE; ) {
+ for (j=0; j<len_set; ) {
__u32 val;
val = cap_d->u[j/sizeof(__u32)].flat[i];
- result->bytes[j++][i] = val & 0xFF;
- result->bytes[j++][i] = (val >>= 8) & 0xFF;
- result->bytes[j++][i] = (val >>= 8) & 0xFF;
- result->bytes[j++][i] = (val >> 8) & 0xFF;
+ result->bytes[j++][i] = val & 0xFF;
+ if (j < len_set) {
+ result->bytes[j++][i] = (val >>= 8) & 0xFF;
+ }
+ if (j < len_set) {
+ result->bytes[j++][i] = (val >>= 8) & 0xFF;
+ }
+ if (j < len_set) {
+ result->bytes[j++][i] = (val >> 8) & 0xFF;
+ }
}
}
/* All done: return length of external representation */
- return (ssizeof(struct cap_ext_struct));
+ return csz;
}
/*
* Import an external representation to produce an internal rep.
* the internal rep should be liberated with cap_free().
+ *
+ * Note, this function assumes that cap_ext has a valid length. That
+ * is, feeding garbage to this function will likely crash the program.
*/
-
cap_t cap_copy_int(const void *cap_ext)
{
const struct cap_ext_struct *export =
@@ -121,3 +174,24 @@
return cap_d;
}
+/*
+ * This function is the same as cap_copy_int() although it requires an
+ * extra argument that is the length of the cap_ext data. Before
+ * running cap_copy_int() the function validates that length is
+ * consistent with the stated length. It returns NULL on error.
+ */
+cap_t cap_copy_int_check(const void *cap_ext, ssize_t length)
+{
+ const struct cap_ext_struct *export =
+ (const struct cap_ext_struct *) cap_ext;
+
+ if (length < 1+CAP_EXT_MAGIC_SIZE) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (length < 1+CAP_EXT_MAGIC_SIZE + export->length_of_capset * NUMBER_OF_CAP_SETS) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return cap_copy_int(cap_ext);
+}
diff --git a/libcap/cap_flag.c b/libcap/cap_flag.c
index c1ffa0d..51799b0 100644
--- a/libcap/cap_flag.c
+++ b/libcap/cap_flag.c
@@ -147,6 +147,31 @@
}
/*
+ * cap_fill copies a bit-vector of capability state in a cap_t from
+ * one flag to another.
+ */
+int cap_fill(cap_t cap_d, cap_flag_t to, cap_flag_t from)
+{
+ if (!good_cap_t(cap_d)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (to < CAP_EFFECTIVE || to > CAP_INHERITABLE ||
+ from < CAP_EFFECTIVE || from > CAP_INHERITABLE) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ int i;
+ for (i = 0; i < _LIBCAP_CAPABILITY_U32S; i++) {
+ cap_d->u[i].flat[to] = cap_d->u[i].flat[from];
+ }
+
+ return 0;
+}
+
+/*
* cap_iab_get_vector reads the single bit value from an IAB vector set.
*/
cap_flag_value_t cap_iab_get_vector(cap_iab_t iab, cap_iab_vector_t vec,
diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c
index 1329f94..e12c8e6 100644
--- a/libcap/cap_proc.c
+++ b/libcap/cap_proc.c
@@ -406,6 +406,29 @@
}
/*
+ * cap_prctl performs a prctl() 6 argument call on the current
+ * thread. Use cap_prctlw() if you want to perform a POSIX semantics
+ * prctl() system call.
+ */
+int cap_prctl(long int pr_cmd, long int arg1, long int arg2,
+ long int arg3, long int arg4, long int arg5)
+{
+ return prctl(pr_cmd, arg1, arg2, arg3, arg4, arg5);
+}
+
+/*
+ * cap_prctlw performs a POSIX semantics prctl() call. That is a 6 arg
+ * prctl() call that executes on all available threads when libpsx is
+ * linked. The suffix 'w' refers to the fact one only ever needs to
+ * invoke this is if the call will write some kernel state.
+ */
+int cap_prctlw(long int pr_cmd, long int arg1, long int arg2,
+ long int arg3, long int arg4, long int arg5)
+{
+ return _libcap_wprctl6(&multithread, pr_cmd, arg1, arg2, arg3, arg4, arg5);
+}
+
+/*
* Some predefined constants
*/
#define CAP_SECURED_BITS_BASIC \
@@ -848,16 +871,22 @@
/*
* _cap_launch is invoked in the forked child, it cannot return but is
- * required to exit. If the execve fails, it will write the errno value
- * over the filedescriptor, fd, and exit with status 0.
+ * required to exit, if the execve fails. It will write the errno
+ * value for any failure over the filedescriptor, fd, and exit with
+ * status 1.
*/
__attribute__ ((noreturn))
static void _cap_launch(int fd, cap_launch_t attr, void *detail) {
struct syscaller_s *sc = &singlethread;
+ int my_errno;
if (attr->custom_setup_fn && attr->custom_setup_fn(detail)) {
goto defer;
}
+ if (attr->arg0 == NULL) {
+ /* handle the successful cap_func_launcher completion */
+ exit(0);
+ }
if (attr->change_uids && _cap_setuid(sc, attr->uid)) {
goto defer;
@@ -891,8 +920,9 @@
* getting here means an error has occurred and errno is
* communicated to the parent
*/
+ my_errno = errno;
for (;;) {
- int n = write(fd, &errno, sizeof(errno));
+ int n = write(fd, &my_errno, sizeof(my_errno));
if (n < 0 && errno == EAGAIN) {
continue;
}
@@ -903,36 +933,50 @@
}
/*
- * cap_launch performs a wrapped fork+exec that works in both an
- * unthreaded environment and also where libcap is linked with
- * psx+pthreads. The function supports dropping privilege in the
- * forked thread, but retaining privilege in the parent thread(s).
+ * cap_launch performs a wrapped fork+(callback and/or exec) that
+ * works in both an unthreaded environment and also where libcap is
+ * linked with psx+pthreads. The function supports dropping privilege
+ * in the forked thread, but retaining privilege in the parent
+ * thread(s).
*
- * Since the ambient set is fragile with respect to changes in I or P,
- * the function carefully orders setting of these inheritable
- * characteristics, to make sure they stick, or return an error
- * of -1 setting errno because the launch failed.
+ * When applying the IAB vector inside the fork, since the ambient set
+ * is fragile with respect to changes in I or P, the function
+ * carefully orders setting of these inheritable characteristics, to
+ * make sure they stick.
+ *
+ * This function will return an error of -1 setting errno if the
+ * launch failed.
*/
-pid_t cap_launch(cap_launch_t attr, void *data) {
+pid_t cap_launch(cap_launch_t attr, void *detail) {
int my_errno;
int ps[2];
+ pid_t child;
+
+ /* The launch must have a purpose */
+ if (attr->custom_setup_fn == NULL &&
+ (attr->arg0 == NULL || attr->argv == NULL)) {
+ errno = EINVAL;
+ return -1;
+ }
if (pipe2(ps, O_CLOEXEC) != 0) {
return -1;
}
- int child = fork();
+ child = fork();
my_errno = errno;
+ if (!child) {
+ close(ps[0]);
+ prctl(PR_SET_NAME, "cap-launcher", 0, 0, 0);
+ _cap_launch(ps[1], attr, detail);
+ /* no return from this function */
+ _exit(1);
+ }
close(ps[1]);
if (child < 0) {
goto defer;
}
- if (!child) {
- close(ps[0]);
- /* noreturn from this function: */
- _cap_launch(ps[1], attr, data);
- }
/*
* Extend this function's return codes to include setup failures
@@ -956,5 +1000,5 @@
defer:
close(ps[0]);
errno = my_errno;
- return (pid_t) child;
+ return child;
}
diff --git a/libcap/cap_test.c b/libcap/cap_test.c
index 4ea83c8..a717217 100644
--- a/libcap/cap_test.c
+++ b/libcap/cap_test.c
@@ -29,11 +29,55 @@
return failed;
}
+static int test_cap_flags(void) {
+ cap_t c, d;
+ cap_flag_t f = CAP_INHERITABLE, t;
+ cap_value_t v;
+
+ c = cap_init();
+ if (c == NULL) {
+ printf("test_flags failed to allocate a set\n");
+ return -1;
+ }
+
+ for (v = 0; v < __CAP_MAXBITS; v += 3) {
+ if (cap_set_flag(c, CAP_INHERITABLE, 1, &v, CAP_SET)) {
+ printf("unable to set inheritable bit %d\n", v);
+ return -1;
+ }
+ }
+
+ d = cap_dup(c);
+ for (t = CAP_EFFECTIVE; t <= CAP_INHERITABLE; t++) {
+ if (cap_fill(c, t, f)) {
+ printf("cap_fill failed %d -> %d\n", f, t);
+ return -1;
+ }
+ if (cap_clear_flag(c, f)) {
+ printf("cap_fill unable to clear flag %d\n", f);
+ return -1;
+ }
+ f = t;
+ }
+ if (cap_compare(c, d)) {
+ printf("permuted cap_fill()ing failed to perform net no-op\n");
+ return -1;
+ }
+ cap_free(d);
+ cap_free(c);
+ return 0;
+}
+
int main(int argc, char **argv) {
int result = 0;
+
result = test_cap_bits() | result;
+ result = test_cap_flags() | result;
+
if (result) {
- printf("test FAILED\n");
+ printf("cap_test FAILED\n");
exit(1);
}
+ printf("cap_test PASS\n");
+ exit(0);
}
diff --git a/libcap/cap_text.c b/libcap/cap_text.c
index b0fad9d..87e0838 100644
--- a/libcap/cap_text.c
+++ b/libcap/cap_text.c
@@ -315,7 +315,7 @@
#endif
char *tmp, *result;
- asprintf(&tmp, "%u", cap);
+ (void) asprintf(&tmp, "%u", cap);
result = _libcap_strdup(tmp);
free(tmp);
diff --git a/libcap/empty.c b/libcap/empty.c
new file mode 100644
index 0000000..0314ff1
--- /dev/null
+++ b/libcap/empty.c
@@ -0,0 +1 @@
+int main(int argc, char **argv) { return 0; }
diff --git a/libcap/execable.c b/libcap/execable.c
new file mode 100644
index 0000000..be18914
--- /dev/null
+++ b/libcap/execable.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+#include "execable.h"
+
+SO_MAIN(int argc, char **argv)
+{
+ const char *cmd = "This library";
+ if (argv != NULL) {
+ cmd = argv[0];
+ }
+ printf("%s is the shared library version: " LIBRARY_VERSION ".\n"
+ "See the License file for distribution information.\n"
+ "More information on this library is available from:\n"
+ "\n"
+ " https://sites.google.com/site/fullycapable/\n", cmd);
+}
diff --git a/libcap/execable.h b/libcap/execable.h
new file mode 100644
index 0000000..0bcc5d4
--- /dev/null
+++ b/libcap/execable.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2021 Andrew G. Morgan <[email protected]>
+ *
+ * Some header magic to help make a shared object run-able as a stand
+ * alone executable binary.
+ *
+ * This is a slightly more sophisticated implementation than the
+ * answer I posted here:
+ *
+ * https://stackoverflow.com/a/68339111/14760867
+ *
+ * Compile your shared library with:
+ *
+ * -DSHARED_LOADER="\"ld-linux...\"" (loader for your target system)
+ * ...
+ * --entry=__so_start
+ */
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __EXECABLE_H
+#error "only include execable.h once"
+#endif
+#define __EXECABLE_H
+
+const char __execable_dl_loader[] __attribute((section(".interp"))) =
+ SHARED_LOADER ;
+
+static void __execable_parse_args(int *argc_p, char ***argv_p)
+{
+ int argc = 0;
+ char **argv = NULL;
+ FILE *f = fopen("/proc/self/cmdline", "rb");
+ if (f != NULL) {
+ char *mem = NULL, *p;
+ size_t size = 32, offset;
+ for (offset=0; ; size *= 2) {
+ char *new_mem = realloc(mem, size+1);
+ if (new_mem == NULL) {
+ perror("unable to parse arguments");
+ if (mem != NULL) {
+ free(mem);
+ }
+ exit(1);
+ }
+ mem = new_mem;
+ offset += fread(mem+offset, 1, size-offset, f);
+ if (offset < size) {
+ size = offset;
+ mem[size] = '\0';
+ break;
+ }
+ }
+ fclose(f);
+ for (argc=1, p=mem+size-2; p >= mem; p--) {
+ argc += (*p == '\0');
+ }
+ argv = calloc(argc+1, sizeof(char *));
+ if (argv == NULL) {
+ perror("failed to allocate memory for argv");
+ free(mem);
+ exit(1);
+ }
+ for (p=mem, argc=0, offset=0; offset < size; argc++) {
+ argv[argc] = mem+offset;
+ offset += strlen(mem+offset)+1;
+ }
+ }
+ *argc_p = argc;
+ *argv_p = argv;
+}
+
+/*
+ * Note, to avoid any runtime confusion, SO_MAIN is a void static
+ * function.
+ */
+
+#define SO_MAIN \
+static void __execable_main(int, char**); \
+extern void __so_start(void); \
+void __so_start(void) \
+{ \
+ int argc; \
+ char **argv; \
+ __execable_parse_args(&argc, &argv); \
+ __execable_main(argc, argv); \
+ if (argc != 0) { \
+ free(argv[0]); \
+ free(argv); \
+ } \
+ exit(0); \
+} \
+static void __execable_main
diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h
index ac13c12..d172ddc 100644
--- a/libcap/include/sys/capability.h
+++ b/libcap/include/sys/capability.h
@@ -115,6 +115,10 @@
cap_flag_value_t);
extern int cap_clear(cap_t);
extern int cap_clear_flag(cap_t, cap_flag_t);
+extern int cap_fill(cap_t, cap_flag_t, cap_flag_t);
+
+#define CAP_DIFFERS(result, flag) (((result) & (1 << (flag))) != 0)
+extern int cap_compare(cap_t, cap_t);
extern cap_flag_value_t cap_iab_get_vector(cap_iab_t, cap_iab_vector_t,
cap_value_t);
@@ -145,9 +149,10 @@
#define CAP_AMBIENT_SUPPORTED() (cap_get_ambient(CAP_CHOWN) >= 0)
/* libcap/cap_extint.c */
-extern ssize_t cap_size(cap_t);
-extern ssize_t cap_copy_ext(void *, cap_t, ssize_t);
-extern cap_t cap_copy_int(const void *);
+extern ssize_t cap_size(cap_t cap_d);
+extern ssize_t cap_copy_ext(void *cap_ext, cap_t cap_d, ssize_t length);
+extern cap_t cap_copy_int(const void *cap_ext);
+extern cap_t cap_copy_int_check(const void *cap_ext, ssize_t length);
/* libcap/cap_text.c */
extern cap_t cap_from_text(const char *);
@@ -158,9 +163,6 @@
extern char * cap_iab_to_text(cap_iab_t iab);
extern cap_iab_t cap_iab_from_text(const char *text);
-#define CAP_DIFFERS(result, flag) (((result) & (1 << (flag))) != 0)
-extern int cap_compare(cap_t, cap_t);
-
/* libcap/cap_proc.c */
extern void cap_set_syscall(long int (*new_syscall)(long int,
long int, long int, long int),
@@ -175,6 +177,10 @@
extern unsigned cap_get_secbits(void);
extern int cap_set_secbits(unsigned bits);
+extern int cap_prctl(long int pr_cmd, long int arg1, long int arg2,
+ long int arg3, long int arg4, long int arg5);
+extern int cap_prctlw(long int pr_cmd, long int arg1, long int arg2,
+ long int arg3, long int arg4, long int arg5);
extern int cap_setuid(uid_t uid);
extern int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups[]);
@@ -185,6 +191,7 @@
extern cap_launch_t cap_new_launcher(const char *arg0, const char * const *argv,
const char * const *envp);
+extern cap_launch_t cap_func_launcher(int (callback_fn)(void *detail));
extern void cap_launcher_callback(cap_launch_t attr,
int (callback_fn)(void *detail));
extern void cap_launcher_setuid(cap_launch_t attr, uid_t uid);
@@ -193,7 +200,7 @@
extern void cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor);
extern cap_iab_t cap_launcher_set_iab(cap_launch_t attr, cap_iab_t iab);
extern void cap_launcher_set_chroot(cap_launch_t attr, const char *chroot);
-extern pid_t cap_launch(cap_launch_t attr, void *data);
+extern pid_t cap_launch(cap_launch_t attr, void *detail);
/*
* system calls - look to libc for function to system call
diff --git a/pam_cap/.gitignore b/pam_cap/.gitignore
index 05e9bbf..ef9e57f 100644
--- a/pam_cap/.gitignore
+++ b/pam_cap/.gitignore
@@ -1,3 +1,5 @@
pam_cap.so
testlink
test_pam_cap
+lazylink.so
+pam_cap_linkopts
diff --git a/pam_cap/Makefile b/pam_cap/Makefile
index 56604fd..689239e 100644
--- a/pam_cap/Makefile
+++ b/pam_cap/Makefile
@@ -10,25 +10,52 @@
mkdir -p -m 0755 $(FAKEROOT)$(LIBDIR)/security
install -m 0755 pam_cap.so $(FAKEROOT)$(LIBDIR)/security
-# Note (as the author of much of the Linux-PAM library, I am confident
-# that this next line does *not* require -lpam on it.) If you think it
-# does, *verify that it does*, and if you observe that it fails as
-# written (and you know why it fails), email me and explain why. Thanks!
+../libcap/loader.txt:
+ $(MAKE) -C ../libcap loader.txt
-pam_cap.so: pam_cap.o
- $(LD) -o pam_cap.so $< $(LIBCAPLIB) $(LDFLAGS)
+execable.o: execable.c ../libcap/execable.h ../libcap/loader.txt
+ $(CC) $(CFLAGS) $(IPATH) -DLIBCAP_VERSION=\"libcap-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" -c execable.c -o $@
+
+pam_cap.so: pam_cap.o execable.o pam_cap_linkopts
+ cat pam_cap_linkopts | xargs -e $(LD) -o $@ pam_cap.o execable.o $(LIBCAPLIB) $(LDFLAGS)
+
+# Some distributions force link everything at compile time, and don't
+# take advantage of libpam's dlopen runtime options to resolve ill
+# defined symbols from its own linkage as needed. (As the original
+# author of that part of libpam, I consider this force linking
+# premature optimization.) We debugged its consequences to pam_cap.so
+# as part of:
+#
+# https://bugzilla.kernel.org/show_bug.cgi?id=214023
+#
+# If the current build environment is one of those, extend the link
+# options for pam_cap.so to force linkage against libpam and the
+# gazillion other things libpam is linked against...
+pam_cap_linkopts: lazylink.so
+ echo "-Wl,-e,__so_start" > $@
+ ./lazylink.so || echo "-lpam" >> $@
+
+lazylink.so: lazylink.c ../libcap/execable.h ../libcap/loader.txt
+ $(LD) -o $@ $(CFLAGS) $(IPATH) lazylink.c -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" $(LDFLAGS) -Wl,-e,__so_start
pam_cap.o: pam_cap.c
$(CC) $(CFLAGS) $(IPATH) -c $< -o $@
-test_pam_cap: test_pam_cap.c pam_cap.c
+../libcap/libcap.a:
+ $(MAKE) -C ../libcap libcap.a
+
+test_pam_cap: test_pam_cap.c pam_cap.c ../libcap/libcap.a
$(CC) $(CFLAGS) $(IPATH) -o $@ test_pam_cap.c $(LIBCAPLIB) $(LDFLAGS) --static
testlink: test.c pam_cap.o
$(CC) $(CFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) $(LDFLAGS)
-test: pam_cap.so
- make testlink
+test: testlink test_pam_cap pam_cap.so
+ $(MAKE) testlink
+ ./test_pam_cap
+ LD_LIBRARY_PATH=../libcap ./pam_cap.so
+ LD_LIBRARY_PATH=../libcap ./pam_cap.so --help
+ @echo "module can be run as an executable!"
sudotest: test test_pam_cap
sudo ./test_pam_cap root 0x0 0x0 0x0 config=./capability.conf
@@ -40,4 +67,4 @@
sudo ./test_pam_cap delta 0x41 0x80 0x41 config=./sudotest.conf
clean:
- rm -f *.o *.so testlink test_pam_cap *~
+ rm -f *.o *.so testlink lazylink.so test_pam_cap pam_cap_linkopts *~
diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
index 09517f8..08c01e1 100644
--- a/pam_cap/capability.conf
+++ b/pam_cap/capability.conf
@@ -6,14 +6,26 @@
#
# In order to use this module, it must have been linked with libcap
# and thus you'll know about Linux's capability support.
-# [If you don't know about libcap, the sources for it are here:
+# [If you don't know about libcap, read more about it here:
#
-# http://www.kernel.org/pub/linux/libs/security/linux-privs/
+# https://sites.google.com/site/fullycapable/
+#
+# There is a page devoted to pam_cap.so here:
+#
+# https://sites.google.com/site/fullycapable/pam_cap-so
#
# .]
#
# Here are some sample lines (remove the preceding '#' if you want to
-# use them
+# use them.
+#
+# The pam_cap.so module accepts the following arguments:
+#
+# debug - be more verbose logging things (unused by pam_cap for now)
+# config=<file> - override the default config for the module with file
+# keepcaps - workaround for applications that setuid without this
+# autoauth - if you want pam_cap.so to always succeed for the auth phase
+# default=<iab> - provide a fallback IAB value if there is no '*' rule
## user 'morgan' gets the CAP_SETFCAP inheritable capability (commented out!)
#cap_setfcap morgan
@@ -24,20 +36,23 @@
## 'everyone else' gets no inheritable capabilities (restrictive config)
none *
-## if there is no '*' entry, all users not explicitly mentioned will
-## get all available capabilities. This is a permissive default, and
-## possibly not what you want... On first reading, you might think this
-## is a security problem waiting to happen, but it defaults to not being
-## so in this sample file! Further, by 'get', we mean 'get in their inheritable
-## set'. That is, if you look at a random process, even one run by root,
-## you will see it has no inheritable capabilities (by default):
+## if there is no '*' entry, and no "default=<iab>" pam_cap.so module
+## argument to fallback on, all users not explicitly mentioned will
+## get all currently available inheritable capabilities. This is a
+## permissive default, and possibly not what you want... On first
+## reading, you might think this is a security problem waiting to
+## happen, but it defaults to not being so in this sample file!
+## Further, by 'get', we mean 'get in their IAB sets'. That is, if you
+## look at a random process, even one run by root, you will see it has
+## no IAB capabilities (by default):
##
## $ /sbin/capsh --decode=$(grep CapInh /proc/1/status|awk '{print $2}')
## 0000000000000000=
##
-## The pam_cap module simply alters the value of this capability
-## set. Including the 'none *' forces use of this module with an
-## unspecified user to have their inheritable set forced to zero.
+## The pam_cap module simply alters the value of the inheritable
+## capability vactors (IAB). Including the 'none *' forces use of this
+## module with an unspecified user to have their inheritable set
+## forced to zero.
##
## Omitting the line will cause the inheritable set to be unmodified
## from what the parent process had (which is generally 0 unless the
diff --git a/pam_cap/execable.c b/pam_cap/execable.c
new file mode 100644
index 0000000..0bf42d3
--- /dev/null
+++ b/pam_cap/execable.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021 Andrew G. Morgan <[email protected]>
+ *
+ * The purpose of this file is to provide an executable mode for the
+ * pam_cap.so binary. If you run it directly, all it does is print
+ * version information.
+ *
+ * It accepts the optional --help argument which causes the executable
+ * to display a summary of all the supported, pam stacked, module
+ * arguments.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../libcap/execable.h"
+
+SO_MAIN(int argc, char **argv)
+{
+ const char *cmd = "<pam_cap.so>";
+ if (argv != NULL) {
+ cmd = argv[0];
+ }
+
+ printf(
+ "%s (version " LIBCAP_VERSION ") is a PAM module to specify\n"
+ "inheritable (IAB) capabilities via the libpam authentication\n"
+ "abstraction. See the libcap License file for licensing information.\n"
+ "\n"
+ "Release notes and feature documentation for libcap and pam_cap.so\n"
+ "can be found at:\n"
+ "\n"
+ " https://sites.google.com/site/fullycapable/\n", cmd);
+ if (argc <= 1) {
+ return;
+ }
+
+ if (argc > 2 || strcmp(argv[1], "--help")) {
+ printf("\n%s only supports the optional argument --help\n", cmd);
+ exit(1);
+ }
+
+ printf("\n"
+ "%s supports the following module arguments:\n"
+ "\n"
+ "debug - verbose logging (ignored for now)\n"
+ "config=<file> - override the default config with file\n"
+ "keepcaps - workaround for apps that setuid without this\n"
+ "autoauth - pam_cap.so to always succeed for the 'auth' phase\n"
+ "default=<iab> - fallback IAB value if there is no '*' rule\n",
+ cmd);
+}
diff --git a/pam_cap/lazylink.c b/pam_cap/lazylink.c
new file mode 100644
index 0000000..969c92d
--- /dev/null
+++ b/pam_cap/lazylink.c
@@ -0,0 +1,20 @@
+/*
+ * Test if the provided LDFLAGS support lazy linking
+ */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../libcap/execable.h"
+
+extern int nothing_sets_this(void);
+extern void nothing_uses_this(void);
+
+void nothing_uses_this(void)
+{
+ nothing_sets_this();
+}
+
+SO_MAIN(int argc, char **argv)
+{
+ exit(0);
+}
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 6927f7b..162e1f5 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -1,11 +1,11 @@
/*
- * Copyright (c) 1999,2007,19,20 Andrew G. Morgan <[email protected]>
+ * Copyright (c) 1999,2007,2019-21 Andrew G. Morgan <[email protected]>
*
* The purpose of this module is to enforce inheritable, bounding and
* ambient capability sets for a specified user.
*/
-/* #define DEBUG */
+/* #define PAM_DEBUG */
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
@@ -21,6 +21,7 @@
#include <string.h>
#include <syslog.h>
#include <sys/capability.h>
+#include <sys/prctl.h>
#include <sys/types.h>
#include <linux/limits.h>
@@ -31,10 +32,16 @@
#define CAP_FILE_BUFFER_SIZE 4096
#define CAP_FILE_DELIMITERS " \t\n"
+/*
+ * pam_cap_s is used to summarize argument values in a parsed form.
+ */
struct pam_cap_s {
int debug;
+ int keepcaps;
+ int autoauth;
const char *user;
const char *conf_filename;
+ const char *fallback;
};
/*
@@ -74,7 +81,7 @@
return 0;
}
-/* obtain the inheritable capabilities for the current user */
+/* obtain the desired IAB capabilities for the current user */
static char *read_capabilities_for_user(const char *user, const char *source)
{
@@ -198,7 +205,11 @@
? cs->conf_filename:USER_CAP_FILE );
if (conf_caps == NULL) {
D(("no capabilities found for user [%s]", cs->user));
- goto cleanup_cap_s;
+ if (cs->fallback == NULL) {
+ goto cleanup_cap_s;
+ }
+ conf_caps = strdup(cs->fallback);
+ D(("user [%s] received fallback caps [%s]", cs->user, conf_caps));
}
ssize_t conf_caps_length = strlen(conf_caps);
@@ -218,7 +229,7 @@
if (!cap_set_proc(cap_s)) {
ok = 1;
}
- goto cleanup_cap_s;
+ goto cleanup_conf;
}
iab = cap_iab_from_text(conf_caps);
@@ -233,15 +244,24 @@
}
cap_free(iab);
+ if (cs->keepcaps) {
+ /*
+ * Best effort to set keep caps - this may help work around
+ * situations where applications are using a capabilities
+ * unaware setuid() call.
+ */
+ D(("setting keepcaps"));
+ (void) cap_prctlw(PR_SET_KEEPCAPS, 1, 0, 0, 0, 0);
+ }
+
cleanup_conf:
memset(conf_caps, 0, conf_caps_length);
_pam_drop(conf_caps);
cleanup_cap_s:
- if (cap_s) {
- cap_free(cap_s);
- cap_s = NULL;
- }
+ cap_free(cap_s);
+ cap_s = NULL;
+
return ok;
}
@@ -260,12 +280,22 @@
static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
{
+ D(("parsing %d module arg(s)", argc));
+
+ memset(pcs, 0, sizeof(*pcs));
+
/* step through arguments */
for (; argc-- > 0; ++argv) {
if (!strcmp(*argv, "debug")) {
pcs->debug = 1;
} else if (!strncmp(*argv, "config=", 7)) {
pcs->conf_filename = 7 + *argv;
+ } else if (!strcmp(*argv, "keepcaps")) {
+ pcs->keepcaps = 1;
+ } else if (!strcmp(*argv, "autoauth")) {
+ pcs->autoauth = 1;
+ } else if (!strncmp(*argv, "default=", 8)) {
+ pcs->fallback = 8 + *argv;
} else {
_pam_log(LOG_ERR, "unknown option; %s", *argv);
}
@@ -284,7 +314,6 @@
struct pam_cap_s pcs;
char *conf_caps;
- memset(&pcs, 0, sizeof(pcs));
parse_args(argc, argv, &pcs);
retval = pam_get_user(pamh, &pcs.user, NULL);
@@ -294,6 +323,12 @@
return PAM_INCOMPLETE;
}
+ if (pcs.autoauth) {
+ D(("pam_sm_authenticate autoauth = success"));
+ memset(&pcs, 0, sizeof(pcs));
+ return PAM_SUCCESS;
+ }
+
if (retval != PAM_SUCCESS) {
D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
memset(&pcs, 0, sizeof(pcs));
@@ -310,9 +345,11 @@
conf_caps));
/* We could also store this as a pam_[gs]et_data item for use
- by the setcred call to follow. As it is, there is a small
- race associated with a redundant read. Oh well, if you
- care, send me a patch.. */
+ by the setcred call to follow. However, this precludes
+ using pam_cap as just a cred module, and requires that the
+ 'auth' component be called first. As it is, there is a
+ small race associated with a redundant read of the
+ config. */
_pam_overwrite(conf_caps);
_pam_drop(conf_caps);
@@ -342,7 +379,6 @@
return PAM_IGNORE;
}
- memset(&pcs, 0, sizeof(pcs));
parse_args(argc, argv, &pcs);
retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
@@ -354,5 +390,5 @@
retval = set_capabilities(&pcs);
memset(&pcs, 0, sizeof(pcs));
- return (retval ? PAM_SUCCESS:PAM_IGNORE );
+ return (retval ? PAM_SUCCESS:PAM_IGNORE);
}
diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c
index 452a27f..4c09a5d 100644
--- a/pam_cap/test_pam_cap.c
+++ b/pam_cap/test_pam_cap.c
@@ -5,6 +5,11 @@
* it.
*/
+#define _DEFAULT_SOURCE
+
+#include <unistd.h>
+#include <sys/types.h>
+
#include "./pam_cap.c"
const char *test_groups[] = {
@@ -121,12 +126,106 @@
cap_free(prev);
}
+struct vargs {
+ struct pam_cap_s cs;
+ const char *args[5];
+};
+
+static int test_arg_parsing(void) {
+ static struct vargs vs[] = {
+ {
+ { 1, 0, 0, NULL, NULL, NULL },
+ { "debug", NULL }
+ },
+ {
+ { 0, 1, 0, NULL, NULL, NULL },
+ { "keepcaps", NULL }
+ },
+ {
+ { 0, 0, 1, NULL, NULL, NULL },
+ { "autoauth", NULL }
+ },
+ {
+ { 1, 0, 1, NULL, NULL, NULL },
+ { "autoauth", "debug", NULL }
+ },
+ {
+ { 0, 0, 0, NULL, "/over/there", NULL },
+ { "config=/over/there", NULL }
+ },
+ {
+ { 0, 0, 0, NULL, NULL, "^cap_setfcap" },
+ { "default=^cap_setfcap", NULL }
+ },
+ {
+ { 0, 0, 0, NULL, NULL, NULL },
+ { NULL }
+ }
+ };
+ int i;
+
+ for (i=0; ; i++) {
+ int argc;
+ const char **argv;
+ struct vargs *v;
+
+ v = &vs[i];
+ argv = v->args;
+
+ for (argc = 0; argv[argc] != NULL; argc++);
+
+ struct pam_cap_s cs;
+ parse_args(argc, argv, &cs);
+
+ if (cs.debug != v->cs.debug) {
+ printf("test_arg_parsing[%d]: debug=%d, wanted debug=%d\n",
+ i, cs.debug, v->cs.debug);
+ return 1;
+ }
+ if (cs.keepcaps != v->cs.keepcaps) {
+ printf("test_arg_parsing[%d]: keepcaps=%d, wanted keepcaps=%d\n",
+ i, cs.keepcaps, v->cs.keepcaps);
+ return 1;
+ }
+ if (cs.autoauth != v->cs.autoauth) {
+ printf("test_arg_parsing[%d]: autoauth=%d, wanted autoauth=%d\n",
+ i, cs.autoauth, v->cs.autoauth);
+ return 1;
+ }
+ if (cs.conf_filename != v->cs.conf_filename &&
+ strcmp(cs.conf_filename, v->cs.conf_filename)) {
+ printf("test_arg_parsing[%d]: conf_filename=[%s], wanted=[%s]\n",
+ i, cs.conf_filename, v->cs.conf_filename);
+ return 1;
+ }
+ if (cs.fallback != v->cs.fallback &&
+ strcmp(cs.fallback, v->cs.fallback)) {
+ printf("test_arg_parsing[%d]: fallback=[%s], wanted=[%s]\n",
+ i, cs.fallback, v->cs.fallback);
+ return 1;
+ }
+
+ if (argc == 0) {
+ break;
+ }
+ }
+ return 0;
+}
+
/*
* args: user a b i config-args...
*/
int main(int argc, char *argv[]) {
unsigned long int before[3], change[3], after[3];
+ if (test_arg_parsing()) {
+ printf("failed to parse arguments\n");
+ exit(1);
+ }
+ if (read_capabilities_for_user("morgan", "/dev/null") != NULL) {
+ printf("/dev/null is not a valid config file\n");
+ }
+
/*
* Start out with a cleared inheritable set.
*/
@@ -134,6 +233,12 @@
cap_clear_flag(orig, CAP_INHERITABLE);
cap_set_proc(orig);
+ if (getuid() != 0) {
+ cap_free(orig);
+ printf("test_pam_cap: OK! (Skipping privileged tests (uid!=0))\n");
+ exit(0);
+ }
+
change[A] = strtoul(argv[2], NULL, 0);
change[B] = strtoul(argv[3], NULL, 0);
change[I] = strtoul(argv[4], NULL, 0);
diff --git a/progs/.gitignore b/progs/.gitignore
index 978229e..eed1982 100644
--- a/progs/.gitignore
+++ b/progs/.gitignore
@@ -5,3 +5,4 @@
setcap
verify-caps
compare-cap
+uns_test
diff --git a/progs/Makefile b/progs/Makefile
index 1d7fc7a..2c3c993 100644
--- a/progs/Makefile
+++ b/progs/Makefile
@@ -4,17 +4,17 @@
#
# Programs: all of the examples that we will compile
#
-PROGS=getpcaps capsh getcap setcap
+PROGS=getpcaps getcap setcap
BUILD=$(PROGS)
-all: $(BUILD)
+all: $(BUILD) capsh
ifeq ($(DYNAMIC),yes)
LDPATH = LD_LIBRARY_PATH=../libcap
DEPS = ../libcap/libcap.so
else
-LDFLAGS += --static
+LDSTATIC = --static
DEPS = ../libcap/libcap.a
endif
@@ -25,28 +25,40 @@
make -C ../libcap libcap.so
$(BUILD): %: %.o $(DEPS)
- $(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS)
+ $(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC)
%.o: %.c $(INCS)
- $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -c $< -o $@
+ $(CC) $(IPATH) $(CFLAGS) -c $< -o $@
install: all
mkdir -p -m 0755 $(FAKEROOT)$(SBINDIR)
- for p in $(PROGS) ; do \
+ for p in $(PROGS) capsh ; do \
install -m 0755 $$p $(FAKEROOT)$(SBINDIR) ; \
done
ifeq ($(RAISE_SETFCAP),yes)
$(FAKEROOT)$(SBINDIR)/setcap cap_setfcap=i $(FAKEROOT)$(SBINDIR)/setcap
endif
-test: $(PROGS)
+test: $(PROGS) capsh
-tcapsh-static: capsh.c $(DEPS)
- $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS) --static
+capshdoc.h.cf: capshdoc.h ./mkcapshdoc.sh
+ ./mkcapshdoc.sh > $@
+ diff -u capshdoc.h $@ || (rm $@ ; exit 1)
-sudotest: test tcapsh-static
+capsh: capsh.c capshdoc.h.cf $(DEPS)
+ $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC)
+
+tcapsh-static: capsh.c capshdoc.h.cf $(DEPS)
+ $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) --static
+
+uns_test: ../tests/uns_test.c
+ $(MAKE) -C ../tests uns_test
+ cp ../tests/uns_test .
+
+sudotest: test tcapsh-static uns_test
sudo $(LDPATH) ./quicktest.sh
clean:
$(LOCALCLEAN)
- rm -f *.o $(BUILD) tcapsh* privileged ping hack.sh compare-cap
+ rm -f *.o $(BUILD) privileged ping hack.sh compare-cap uns_test
+ rm -f capsh tcapsh* capshdoc.h.cf
diff --git a/progs/capsh.c b/progs/capsh.c
index a39ceeb..50c2c99 100644
--- a/progs/capsh.c
+++ b/progs/capsh.c
@@ -14,6 +14,10 @@
#define _DEFAULT_SOURCE
#endif
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
@@ -32,6 +36,8 @@
#define SHELL "/bin/bash"
#endif /* ndef SHELL */
+#include "./capshdoc.h"
+
#define MAX_GROUPS 100 /* max number of supplementary groups for user */
static char *binary(unsigned long value)
@@ -77,33 +83,45 @@
}
}
-/* arg_print displays the current capability state of the process */
-static void arg_print(void)
+static void display_current(void)
{
- long set;
- int status, j;
cap_t all;
char *text;
- const char *sep;
- struct group *g;
- gid_t groups[MAX_GROUPS], gid;
- uid_t uid, euid;
- struct passwd *u, *eu;
- cap_iab_t iab;
all = cap_get_proc();
text = cap_to_text(all, NULL);
printf("Current: %s\n", text);
cap_free(text);
cap_free(all);
+}
- display_prctl_set("Bounding", cap_get_bound);
- display_prctl_set("Ambient", cap_get_ambient);
+static void display_current_iab(void)
+{
+ cap_iab_t iab;
+ char *text;
+
iab = cap_iab_get_proc();
text = cap_iab_to_text(iab);
printf("Current IAB: %s\n", text);
cap_free(text);
cap_free(iab);
+}
+
+/* arg_print displays the current capability state of the process */
+static void arg_print(void)
+{
+ long set;
+ int status, j;
+ const char *sep;
+ struct group *g;
+ gid_t groups[MAX_GROUPS], gid;
+ uid_t uid, euid;
+ struct passwd *u, *eu;
+
+ display_current();
+ display_prctl_set("Bounding", cap_get_bound);
+ display_prctl_set("Ambient", cap_get_ambient);
+ display_current_iab();
set = cap_get_secbits();
if (set >= 0) {
@@ -336,8 +354,8 @@
*/
static char *find_self(const char *arg0)
{
- int i;
- char *parts, *dir, *scratch;
+ int i, status=1;
+ char *p = NULL, *parts, *dir, *scratch;
const char *path;
for (i = strlen(arg0)-1; i >= 0 && arg0[i] != '/'; i--);
@@ -352,21 +370,61 @@
}
parts = strdup(path);
- scratch = malloc(2+strlen(path)+strlen(arg0));
- if (parts == NULL || scratch == NULL) {
- fprintf(stderr, "insufficient memory for path building\n");
+ if (parts == NULL) {
+ fprintf(stderr, "insufficient memory for parts of path\n");
exit(1);
}
- for (i=0; (dir = strtok(parts, ":")); parts = NULL) {
- sprintf(scratch, "%s/%s", dir, arg0);
- if (access(scratch, X_OK) == 0) {
- return scratch;
- }
+ scratch = malloc(2+strlen(path)+strlen(arg0));
+ if (scratch == NULL) {
+ fprintf(stderr, "insufficient memory for path building\n");
+ goto free_parts;
}
- fprintf(stderr, "unable to find executable '%s' in PATH\n", arg0);
- exit(1);
+ for (p = parts; (dir = strtok(p, ":")); p = NULL) {
+ sprintf(scratch, "%s/%s", dir, arg0);
+ if (access(scratch, X_OK) == 0) {
+ status = 0;
+ break;
+ }
+ }
+ if (status) {
+ fprintf(stderr, "unable to find executable '%s' in PATH\n", arg0);
+ free(scratch);
+ }
+
+free_parts:
+ free(parts);
+ if (status) {
+ exit(status);
+ }
+ return scratch;
+}
+
+static long safe_sysconf(int name)
+{
+ long ans = sysconf(name);
+ if (ans <= 0) {
+ fprintf(stderr, "sysconf(%d) returned a non-positive number: %ld\n", name, ans);
+ exit(1);
+ }
+ return ans;
+}
+
+static void describe(cap_value_t cap) {
+ int j;
+ const char **lines = explanations[cap];
+ char *name = cap_to_name(cap);
+ if (cap < cap_max_bits()) {
+ printf("%s (%d)", name, cap);
+ } else {
+ printf("<reserved for> %s (%d)", name, cap);
+ }
+ cap_free(name);
+ printf(" [/proc/self/status:CapXXX: 0x%016llx]\n\n", 1ULL<<cap);
+ for (j=0; lines[j]; j++) {
+ printf(" %s\n", lines[j]);
+ }
}
int main(int argc, char *argv[], char *envp[])
@@ -617,7 +675,9 @@
* Given we are now in a new directory tree, its good practice
* to start off in a sane location
*/
- status = chdir("/");
+ if (status == 0) {
+ status = chdir("/");
+ }
cap_free(orig);
@@ -718,14 +778,14 @@
gid_t *group_list;
int g_count;
- length = sysconf(_SC_GETGR_R_SIZE_MAX);
+ length = safe_sysconf(_SC_GETGR_R_SIZE_MAX);
buf = calloc(1, length);
if (NULL == buf) {
fprintf(stderr, "No memory for [%s] operation\n", argv[i]);
exit(1);
}
- max_groups = sysconf(_SC_NGROUPS_MAX);
+ max_groups = safe_sysconf(_SC_NGROUPS_MAX);
group_list = calloc(max_groups, sizeof(gid_t));
if (NULL == group_list) {
fprintf(stderr, "No memory for gid list\n");
@@ -741,8 +801,7 @@
}
if (!isdigit(*ptr)) {
struct group *g, grp;
- getgrnam_r(ptr, &grp, buf, length, &g);
- if (NULL == g) {
+ if (getgrnam_r(ptr, &grp, buf, length, &g) || NULL == g) {
fprintf(stderr, "Failed to identify gid for group [%s]\n", ptr);
exit(1);
}
@@ -835,6 +894,7 @@
argv[argc] = NULL;
execve(argv[i], argv+i, envp);
fprintf(stderr, "execve '%s' failed!\n", argv[i]);
+ free(argv[i]);
exit(1);
} else if (!strncmp("--shell=", argv[i], 8)) {
shell = argv[i]+8;
@@ -923,26 +983,77 @@
}
} else if (!strcmp("--license", argv[i])) {
printf(
- "%s has a you choose license: BSD 3-clause or GPL2\n"
- "Copyright (c) 2008-11,16,19,2020 Andrew G. Morgan"
+ "%s see LICENSE file for details.\n"
+ "Copyright (c) 2008-11,16,19-21 Andrew G. Morgan"
" <[email protected]>\n", argv[0]);
exit(0);
+ } else if (!strncmp("--explain=", argv[i], 10)) {
+ cap_value_t cap;
+ if (cap_from_name(argv[i]+10, &cap) != 0) {
+ fprintf(stderr, "unrecognised value '%s'\n", argv[i]+10);
+ exit(1);
+ }
+ if (cap < 0) {
+ fprintf(stderr, "negative capability (%d) invalid\n", cap);
+ exit(1);
+ }
+ if (cap < CAPSH_DOC_LIMIT) {
+ describe(cap);
+ continue;
+ }
+ if (cap < cap_max_bits()) {
+ printf("<unnamed in libcap> (%d)", cap);
+ } else {
+ printf("<unsupported> (%d)", cap);
+ }
+ printf(" [/proc/self/status:CapXXX: 0x%016llx]\n", 1ULL<<cap);
+ } else if (!strncmp("--suggest=", argv[i], 10)) {
+ cap_value_t cap;
+ int hits = 0;
+ for (cap=0; cap < CAPSH_DOC_LIMIT; cap++) {
+ const char **lines = explanations[cap];
+ int j;
+ char *name = cap_to_name(cap);
+ char *match = strcasestr(name, argv[i]+10);
+ cap_free(name);
+ if (match != NULL) {
+ if (hits++) {
+ printf("\n");
+ }
+ describe(cap);
+ continue;
+ }
+ for (j=0; lines[j]; j++) {
+ if (strcasestr(lines[j], argv[i]+10) != NULL) {
+ if (hits++) {
+ printf("\n");
+ }
+ describe(cap);
+ break;
+ }
+ }
+ }
+ } else if (strcmp("--current", argv[i]) == 0) {
+ display_current();
+ display_current_iab();
} else {
usage:
printf("usage: %s [args ...]\n"
- " --has-a=xxx exit 1 if capability xxx not ambient\n"
- " --has-ambient exit 1 unless ambient vector supported\n"
" --addamb=xxx add xxx,... capabilities to ambient set\n"
" --cap-uid=<n> use libcap cap_setuid() to change uid\n"
" --caps=xxx set caps as per cap_from_text()\n"
" --chroot=path chroot(2) to this path\n"
+ " --current show current caps and IAB vectors\n"
" --decode=xxx decode a hex string to a list of caps\n"
" --delamb=xxx remove xxx,... capabilities from ambient\n"
+ " --explain=xxx explain what capability xxx permits\n"
" --forkfor=<n> fork and make child sleep for <n> sec\n"
" --gid=<n> set gid to <n> (hint: id <username>)\n"
" --groups=g,... set the supplemental groups\n"
- " --has-p=xxx exit 1 if capability xxx not permitted\n"
+ " --has-a=xxx exit 1 if capability xxx not ambient\n"
+ " --has-ambient exit 1 unless ambient vector supported\n"
" --has-i=xxx exit 1 if capability xxx not inheritable\n"
+ " --has-p=xxx exit 1 if capability xxx not permitted\n"
" --has-no-new-privs exit 1 if privs not limited\n"
" --help, -h this message (or try 'man capsh')\n"
" --iab=... use cap_iab_from_text() to set iab\n"
@@ -960,6 +1071,7 @@
" --print display capability relevant state\n"
" --secbits=<n> write a new value for securebits\n"
" --shell=/xx/yy use /xx/yy instead of " SHELL " for --\n"
+ " --suggest=text search cap descriptions for text\n"
" --supports=xxx exit 1 if capability xxx unsupported\n"
" --uid=<n> set uid to <n> (hint: id <username>)\n"
" --user=<name> set uid,gid and groups to that of user\n"
diff --git a/progs/capshdoc.h b/progs/capshdoc.h
new file mode 100644
index 0000000..c182144
--- /dev/null
+++ b/progs/capshdoc.h
@@ -0,0 +1,413 @@
+#ifdef CAPSHDOC
+#error "don't include this twice"
+#endif
+#define CAPSHDOC
+
+/*
+ * A line by line explanation of each named capability value
+ */
+static const char *explanation0[] = { /* cap_chown = 0 */
+ "Allows a process to arbitrarily change the user and",
+ "group ownership of a file.",
+ NULL
+};
+static const char *explanation1[] = { /* cap_dac_override = 1 */
+ "Allows a process to override of all Discretionary",
+ "Access Control (DAC) access, including ACL execute",
+ "access. That is read, write or execute files that the",
+ "process would otherwise not have access to. This",
+ "excludes DAC access covered by CAP_LINUX_IMMUTABLE.",
+ NULL
+};
+static const char *explanation2[] = { /* cap_dac_read_search = 2 */
+ "Allows a process to override all DAC restrictions",
+ "limiting the read and search of files and",
+ "directories. This excludes DAC access covered by",
+ "CAP_LINUX_IMMUTABLE.",
+ NULL
+};
+static const char *explanation3[] = { /* cap_fowner = 3 */
+ "Allows a process to perform operations on files, even",
+ "where file owner ID should otherwise need be equal to",
+ "the UID, except where CAP_FSETID is applicable. It",
+ "doesn't override MAC and DAC restrictions.",
+ NULL
+};
+static const char *explanation4[] = { /* cap_fsetid = 4 */
+ "Allows a process to set the S_ISUID and S_ISUID bits of",
+ "the file permissions, even when the process' effective",
+ "UID or GID/supplementary GIDs do not match that of the",
+ "file.",
+ NULL
+};
+static const char *explanation5[] = { /* cap_kill = 5 */
+ "Allows a process to send a kill(2) signal to any other",
+ "process - overriding the limitation that there be a",
+ "[E]UID match between source and target process.",
+ NULL
+};
+static const char *explanation6[] = { /* cap_setgid = 6 */
+ "Allows a process to freely manipulate its own GIDs:",
+ " - arbitrarily set the GID, EGID, REGID, RESGID values",
+ " - arbitrarily set the supplementary GIDs",
+ " - allows the forging of GID credentials passed over a",
+ " socket",
+ NULL
+};
+static const char *explanation7[] = { /* cap_setuid = 7 */
+ "Allows a process to freely manipulate its own UIDs:",
+ " - arbitrarily set the UID, EUID, REUID and RESUID",
+ " values",
+ " - allows the forging of UID credentials passed over a",
+ " socket",
+ NULL
+};
+static const char *explanation8[] = { /* cap_setpcap = 8 */
+ "Allows a process to freely manipulate its inheritable",
+ "capabilities. Linux supports the POSIX.1e Inheritable",
+ "set, as well as Bounding and Ambient Linux extension",
+ "vectors. This capability permits dropping bits from the",
+ "Bounding vector. It also permits the process to raise",
+ "Ambient vector bits that are both raised in the",
+ "Permitted and Inheritable sets of the process. This",
+ "capability cannot be used to raise Permitted bits, or",
+ "Effective bits beyond those already present in the",
+ "process' permitted set.",
+ "",
+ "[Historical note: prior to the advent of file",
+ "capabilities (2008), this capability was suppressed by",
+ "default, as its unsuppressed behavior was not",
+ "auditable: it could asynchronously grant its own",
+ "Permitted capabilities to and remove capabilities from",
+ "other processes arbitrarily. The former leads to",
+ "undefined behavior, and the latter is better served by",
+ "the kill system call.]",
+ NULL
+};
+static const char *explanation9[] = { /* cap_linux_immutable = 9 */
+ "Allows a process to modify the S_IMMUTABLE and",
+ "S_APPEND file attributes.",
+ NULL
+};
+static const char *explanation10[] = { /* cap_net_bind_service = 10 */
+ "Allows a process to bind to privileged ports:",
+ " - TCP/UDP sockets below 1024",
+ " - ATM VCIs below 32",
+ NULL
+};
+static const char *explanation11[] = { /* cap_net_broadcast = 11 */
+ "Allows a process to broadcast to the network and to",
+ "listen to multicast.",
+ NULL
+};
+static const char *explanation12[] = { /* cap_net_admin = 12 */
+ "Allows a process to perform network configuration",
+ "operations:",
+ " - interface configuration",
+ " - administration of IP firewall, masquerading and",
+ " accounting",
+ " - setting debug options on sockets",
+ " - modification of routing tables",
+ " - setting arbitrary process, and process group",
+ " ownership on sockets",
+ " - binding to any address for transparent proxying",
+ " (this is also allowed via CAP_NET_RAW)",
+ " - setting TOS (Type of service)",
+ " - setting promiscuous mode",
+ " - clearing driver statistics",
+ " - multicasing",
+ " - read/write of device-specific registers",
+ " - activation of ATM control sockets",
+ NULL
+};
+static const char *explanation13[] = { /* cap_net_raw = 13 */
+ "Allows a process to use raw networking:",
+ " - RAW sockets",
+ " - PACKET sockets",
+ " - binding to any address for transparent proxying",
+ " (also permitted via CAP_NET_ADMIN)",
+ NULL
+};
+static const char *explanation14[] = { /* cap_ipc_lock = 14 */
+ "Allows a process to lock shared memory segments for IPC",
+ "purposes. Also enables mlock and mlockall system",
+ "calls.",
+ NULL
+};
+static const char *explanation15[] = { /* cap_ipc_owner = 15 */
+ "Allows a process to override IPC ownership checks.",
+ NULL
+};
+static const char *explanation16[] = { /* cap_sys_module = 16 */
+ "Allows a process to initiate the loading and unloading",
+ "of kernel modules. This capability can effectively",
+ "modify kernel without limit.",
+ NULL
+};
+static const char *explanation17[] = { /* cap_sys_rawio = 17 */
+ "Allows a process to perform raw IO:",
+ " - permit ioper/iopl access",
+ " - permit sending USB messages to any device via",
+ " /dev/bus/usb",
+ NULL
+};
+static const char *explanation18[] = { /* cap_sys_chroot = 18 */
+ "Allows a process to perform a chroot syscall to change",
+ "the effective root of the process' file system:",
+ "redirect to directory \"/\" to some other location.",
+ NULL
+};
+static const char *explanation19[] = { /* cap_sys_ptrace = 19 */
+ "Allows a process to perform a ptrace() of any other",
+ "process.",
+ NULL
+};
+static const char *explanation20[] = { /* cap_sys_pacct = 20 */
+ "Allows a process to configure process accounting.",
+ NULL
+};
+static const char *explanation21[] = { /* cap_sys_admin = 21 */
+ "Allows a process to perform a somewhat arbitrary",
+ "grab-bag of privileged operations. Over time, this",
+ "capability should weaken as specific capabilities are",
+ "created for subsets of CAP_SYS_ADMINs functionality:",
+ " - configuration of the secure attention key",
+ " - administration of the random device",
+ " - examination and configuration of disk quotas",
+ " - setting the domainname",
+ " - setting the hostname",
+ " - calling bdflush()",
+ " - mount() and umount(), setting up new SMB connection",
+ " - some autofs root ioctls",
+ " - nfsservctl",
+ " - VM86_REQUEST_IRQ",
+ " - to read/write pci config on alpha",
+ " - irix_prctl on mips (setstacksize)",
+ " - flushing all cache on m68k (sys_cacheflush)",
+ " - removing semaphores",
+ " - Used instead of CAP_CHOWN to \"chown\" IPC message",
+ " queues, semaphores and shared memory",
+ " - locking/unlocking of shared memory segment",
+ " - turning swap on/off",
+ " - forged pids on socket credentials passing",
+ " - setting readahead and flushing buffers on block",
+ " devices",
+ " - setting geometry in floppy driver",
+ " - turning DMA on/off in xd driver",
+ " - administration of md devices (mostly the above, but",
+ " some extra ioctls)",
+ " - tuning the ide driver",
+ " - access to the nvram device",
+ " - administration of apm_bios, serial and bttv (TV)",
+ " device",
+ " - manufacturer commands in isdn CAPI support driver",
+ " - reading non-standardized portions of PCI",
+ " configuration space",
+ " - DDI debug ioctl on sbpcd driver",
+ " - setting up serial ports",
+ " - sending raw qic-117 commands",
+ " - enabling/disabling tagged queuing on SCSI",
+ " controllers and sending arbitrary SCSI commands",
+ " - setting encryption key on loopback filesystem",
+ " - setting zone reclaim policy",
+ NULL
+};
+static const char *explanation22[] = { /* cap_sys_boot = 22 */
+ "Allows a process to initiate a reboot of the system.",
+ NULL
+};
+static const char *explanation23[] = { /* cap_sys_nice = 23 */
+ "Allows a process to maipulate the execution priorities",
+ "of arbitrary processes:",
+ " - those involving different UIDs",
+ " - setting their CPU affinity",
+ " - alter the FIFO vs. round-robin (realtime)",
+ " scheduling for itself and other processes.",
+ NULL
+};
+static const char *explanation24[] = { /* cap_sys_resource = 24 */
+ "Allows a process to adjust resource related parameters",
+ "of processes and the system:",
+ " - set and override resource limits",
+ " - override quota limits",
+ " - override the reserved space on ext2 filesystem",
+ " (this can also be achieved via CAP_FSETID)",
+ " - modify the data journaling mode on ext3 filesystem,",
+ " which uses journaling resources",
+ " - override size restrictions on IPC message queues",
+ " - configure more than 64Hz interrupts from the",
+ " real-time clock",
+ " - override the maximum number of consoles for console",
+ " allocation",
+ " - override the maximum number of keymaps",
+ NULL
+};
+static const char *explanation25[] = { /* cap_sys_time = 25 */
+ "Allows a process to perform time manipulation of clocks:",
+ " - alter the system clock",
+ " - enable irix_stime on MIPS",
+ " - set the real-time clock",
+ NULL
+};
+static const char *explanation26[] = { /* cap_sys_tty_config = 26 */
+ "Allows a process to manipulate tty devices:",
+ " - configure tty devices",
+ " - perform vhangup() of a tty",
+ NULL
+};
+static const char *explanation27[] = { /* cap_mknod = 27 */
+ "Allows a process to perform privileged operations with",
+ "the mknod() system call.",
+ NULL
+};
+static const char *explanation28[] = { /* cap_lease = 28 */
+ "Allows a process to take leases on files.",
+ NULL
+};
+static const char *explanation29[] = { /* cap_audit_write = 29 */
+ "Allows a process to write to the audit log via a",
+ "unicast netlink socket.",
+ NULL
+};
+static const char *explanation30[] = { /* cap_audit_control = 30 */
+ "Allows a process to configure audit logging via a",
+ "unicast netlink socket.",
+ NULL
+};
+static const char *explanation31[] = { /* cap_setfcap = 31 */
+ "Allows a process to set capabilities on files.",
+ "Permits a process to uid_map the uid=0 of the",
+ "parent user namespace into that of the child",
+ "namespace. Also, permits a process to override",
+ "securebits locks through user namespace",
+ "creation.",
+ NULL
+};
+static const char *explanation32[] = { /* cap_mac_override = 32 */
+ "Allows a process to override Manditory Access Control",
+ "(MAC) access. Not all kernels are configured with a MAC",
+ "mechanism, but this is the capability reserved for",
+ "overriding them.",
+ NULL
+};
+static const char *explanation33[] = { /* cap_mac_admin = 33 */
+ "Allows a process to configure the Mandatory Access",
+ "Control (MAC) policy. Not all kernels are configured",
+ "with a MAC enabled, but if they are this capability is",
+ "reserved for code to perform administration tasks.",
+ NULL
+};
+static const char *explanation34[] = { /* cap_syslog = 34 */
+ "Allows a process to configure the kernel's syslog",
+ "(printk) behavior.",
+ NULL
+};
+static const char *explanation35[] = { /* cap_wake_alarm = 35 */
+ "Allows a process to trigger something that can wake the",
+ "system up.",
+ NULL
+};
+static const char *explanation36[] = { /* cap_block_suspend = 36 */
+ "Allows a process to block system suspends - prevent the",
+ "system from entering a lower power state.",
+ NULL
+};
+static const char *explanation37[] = { /* cap_audit_read = 37 */
+ "Allows a process to read the audit log via a multicast",
+ "netlink socket.",
+ NULL
+};
+static const char *explanation38[] = { /* cap_perfmon = 38 */
+ "Allows a process to enable observability of privileged",
+ "operations related to performance. The mechanisms",
+ "include perf_events, i915_perf and other kernel",
+ "subsystems.",
+ NULL
+};
+static const char *explanation39[] = { /* cap_bpf = 39 */
+ "Allows a process to manipulate aspects of the kernel",
+ "enhanced Berkeley Packet Filter (BPF) system. This is",
+ "an execution subsystem of the kernel, that manages BPF",
+ "programs. CAP_BPF permits a process to:",
+ " - create all types of BPF maps",
+ " - advanced verifier features:",
+ " - indirect variable access",
+ " - bounded loops",
+ " - BPF to BPF function calls",
+ " - scalar precision tracking",
+ " - larger complexity limits",
+ " - dead code elimination",
+ " - potentially other features",
+ "",
+ "Other capabilities can be used together with CAP_BFP to",
+ "further manipulate the BPF system:",
+ " - CAP_PERFMON relaxes the verifier checks as follows:",
+ " - BPF programs can use pointer-to-integer",
+ " conversions",
+ " - speculation attack hardening measures can be",
+ " bypassed",
+ " - bpf_probe_read to read arbitrary kernel memory is",
+ " permitted",
+ " - bpf_trace_printk to print the content of kernel",
+ " memory",
+ " - CAP_SYS_ADMIN permits the following:",
+ " - use of bpf_probe_write_user",
+ " - iteration over the system-wide loaded programs,",
+ " maps, links BTFs and convert their IDs to file",
+ " descriptors.",
+ " - CAP_PERFMON is required to load tracing programs.",
+ " - CAP_NET_ADMIN is required to load networking",
+ " programs.",
+ NULL
+};
+static const char *explanation40[] = { /* cap_checkpoint_restore = 40 */
+ "Allows a process to perform checkpoint",
+ "and restore operations. Also permits",
+ "explicit PID control via clone3() and",
+ "also writing to ns_last_pid.",
+ NULL
+};
+static const char **explanations[] = {
+ explanation0,
+ explanation1,
+ explanation2,
+ explanation3,
+ explanation4,
+ explanation5,
+ explanation6,
+ explanation7,
+ explanation8,
+ explanation9,
+ explanation10,
+ explanation11,
+ explanation12,
+ explanation13,
+ explanation14,
+ explanation15,
+ explanation16,
+ explanation17,
+ explanation18,
+ explanation19,
+ explanation20,
+ explanation21,
+ explanation22,
+ explanation23,
+ explanation24,
+ explanation25,
+ explanation26,
+ explanation27,
+ explanation28,
+ explanation29,
+ explanation30,
+ explanation31,
+ explanation32,
+ explanation33,
+ explanation34,
+ explanation35,
+ explanation36,
+ explanation37,
+ explanation38,
+ explanation39,
+ explanation40,
+};
+#define CAPSH_DOC_LIMIT 41
diff --git a/progs/getcap.c b/progs/getcap.c
index 208bd6a..eec733b 100644
--- a/progs/getcap.c
+++ b/progs/getcap.c
@@ -96,8 +96,8 @@
case 'h':
usage(0);
case 'l':
- printf("%s has a you choose license: BSD 3-clause or GPL2\n"
- "Copyright (c) 1997,2007 Andrew G. Morgan"
+ printf("%s see LICENSE file for details.\n"
+ "Copyright (c) 1997,2007,2021 Andrew G. Morgan"
" <[email protected]>\n", argv[0]);
exit(0);
default:
diff --git a/progs/getpcaps.c b/progs/getpcaps.c
index 5bc511e..5e78487 100644
--- a/progs/getpcaps.c
+++ b/progs/getpcaps.c
@@ -44,8 +44,9 @@
!strcmp(argv[0], "-h")) {
usage(0);
} else if (!strcmp(argv[0], "--license")) {
- printf("%s has a you choose license: BSD 3-clause or GPL2\n"
-"[Copyright (c) 1997-8,2007,2019 Andrew G. Morgan <[email protected]>]\n",
+ printf("%s see LICENSE file for details.\n"
+ "[Copyright (c) 1997-8,2007,19,21"
+ " Andrew G. Morgan <[email protected]>]\n",
argv[0]);
exit(0);
} else if (!strcmp(argv[0], "--verbose")) {
diff --git a/progs/mkcapshdoc.sh b/progs/mkcapshdoc.sh
new file mode 100755
index 0000000..705d526
--- /dev/null
+++ b/progs/mkcapshdoc.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# This script generates some C code for inclusion in the capsh binary.
+# The Makefile generally only generates the .h code and compares it
+# with the checked in code in the progs directory.
+
+cat<<EOF
+#ifdef CAPSHDOC
+#error "don't include this twice"
+#endif
+#define CAPSHDOC
+
+/*
+ * A line by line explanation of each named capability value
+ */
+EOF
+
+let x=0
+while [ -f "../doc/values/${x}.txt" ]; do
+ name=$(fgrep ",${x}}" ../libcap/cap_names.list.h|sed -e 's/{"//' -e 's/",/ = /' -e 's/},//')
+ echo "static const char *explanation${x}[] = { /* ${name} */"
+ sed -e 's/"/\\"/g' -e 's/^/ "/' -e 's/$/",/' "../doc/values/${x}.txt"
+ let x=1+${x}
+ echo " NULL"
+ echo "};"
+done
+
+cat<<EOF
+static const char **explanations[] = {
+EOF
+let y=0
+while [ "${y}" -lt "${x}" ]; do
+ echo " explanation${y},"
+ let y=1+${y}
+done
+cat<<EOF
+};
+#define CAPSH_DOC_LIMIT ${x}
+EOF
diff --git a/progs/quicktest.sh b/progs/quicktest.sh
index 6aa2598..ba64ab5 100755
--- a/progs/quicktest.sh
+++ b/progs/quicktest.sh
@@ -1,7 +1,7 @@
#!/bin/bash
#
# Run through a series of tests to try out the various capability
-# manipulations posible through exec.
+# manipulations possible through exec.
#
# [Run this as root in a root-enabled process tree.]
@@ -43,6 +43,7 @@
}
pass_capsh --print
+pass_capsh --current
# Validate that PATH expansion works
PATH=$(/bin/pwd)/junk:$(/bin/pwd) capsh == == == --modes
@@ -89,7 +90,7 @@
/bin/chmod u+s tcapsh
/bin/ls -l tcapsh
-# leverage keep caps to maintain capabilities accross a change of euid
+# leverage keep caps to maintain capabilities across a change of euid
# from setuid root to capable luser (as per wireshark/dumpcap 0.99.7)
# This test is subtle. It is testing that a change to self, dropping
# euid=0 back to that of the luser keeps capabilities.
@@ -204,7 +205,7 @@
# Next force the privileged binary to have an empty capability set.
# This is sort of the opposite of privileged - it should ensure that
- # the file can never aquire privilege by the ambient method.
+ # the file can never acquire privilege by the ambient method.
./setcap = ./privileged
fail_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- -c "./privileged --print --uid=1"
@@ -259,4 +260,11 @@
fi
rm -f compare-cap
+echo "attempt to exploit kernel bug"
+./uns_test
+if [ $? -ne 0 ]; then
+ echo "upgrade your kernel"
+ exit 1
+fi
+
echo "ALL TESTS PASSED!"
diff --git a/progs/setcap.c b/progs/setcap.c
index 930429a..e28b1c7 100644
--- a/progs/setcap.c
+++ b/progs/setcap.c
@@ -94,7 +94,7 @@
}
if (!strcmp("--license", *argv)) {
printf(
- "%s has a you choose license: BSD 3-clause or GPL2\n"
+ "%s see LICENSE file for details.\n"
"Copyright (c) 1997,2007-8,2020 Andrew G. Morgan"
" <[email protected]>\n", argv[0]);
exit(0);
diff --git a/psx/LICENSE b/psx/License
similarity index 98%
rename from psx/LICENSE
rename to psx/License
index e2574a7..2645a87 100644
--- a/psx/LICENSE
+++ b/psx/License
@@ -1,8 +1,16 @@
Unless otherwise *explicitly* stated, the following text describes the
licensed conditions under which the contents of this libcap/psx release
-may be used and distributed:
+may be used and distributed.
+
+The licensed conditions are one or the other of these two Licenses:
+
+ - BSD 3-clause
+ - GPL v2.0
-------------------------------------------------------------------------
+BSD 3-clause:
+-------------
+
Redistribution and use in source and binary forms of libcap/psx, with
or without modification, are permitted provided that the following
conditions are met:
@@ -20,13 +28,6 @@
products derived from this software without their specific prior
written permission.
-ALTERNATIVELY, this product may be distributed under the terms of the
-GNU General Public License (v2.0 - see below), in which case the
-provisions of the GNU GPL are required INSTEAD OF the above
-restrictions. (This clause is necessary due to a potential conflict
-between the GNU GPL and the restrictions contained in a BSD-style
-copyright.)
-
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
@@ -38,7 +39,17 @@
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
+
-------------------------------------------------------------------------
+GPL v2.0:
+---------
+
+ALTERNATIVELY, this product may be distributed under the terms of the
+GNU General Public License (v2.0 - see below), in which case the
+provisions of the GNU GPL are required INSTEAD OF the above
+restrictions. (This clause is necessary due to a potential conflict
+between the GNU GPL and the restrictions contained in a BSD-style
+copyright.)
-------------------------
Full text of gpl-2.0.txt:
diff --git a/psx/README b/psx/README
index cd9c651..e4f9001 100644
--- a/psx/README
+++ b/psx/README
@@ -23,6 +23,6 @@
Like libcap/libpsx itself, the "psx" package is distributed with a
"you choose" License. Specifically: BSD three clause, or GPL2. See the
-LICENSE file.
+License file.
Andrew G. Morgan <[email protected]>
diff --git a/psx/doc.go b/psx/doc.go
index e6f9013..c4ba829 100644
--- a/psx/doc.go
+++ b/psx/doc.go
@@ -1,5 +1,5 @@
// Package psx provides support for system calls that are run
-// simultanously on all threads under Linux.
+// simultaneously on all threads under Linux.
//
// This property can be used to work around a historical lack of
// native Go support for such a feature. Something that is the subject
@@ -12,9 +12,9 @@
//
// In the former case, psx is a low overhead wrapper for the two
// native go calls: syscall.AllThreadsSyscall() and
-// syscall.AllThreadsSyscall6() [expected to be] introduced in
-// go1.16. We provide this wrapping to minimize client source code
-// changes when compiling with or without CGo enabled.
+// syscall.AllThreadsSyscall6() introduced in go1.16. We provide this
+// wrapping to minimize client source code changes when compiling with
+// or without CGo enabled.
//
// In the latter case, and toolchains prior to go1.16, it works via
// CGo wrappers for system call functions that call the C [lib]psx
diff --git a/psx/psx.c b/psx/psx.c
index 4de3653..90dcc50 100644
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -229,7 +229,7 @@
psx_tracker.psx_sig = SIGSYS;
psx_confirm_sigaction();
- psx_do_registration(); // register the main thread.
+ psx_do_registration(); /* register the main thread. */
psx_tracker.initialized = 1;
}
@@ -454,6 +454,10 @@
int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg) {
psx_starter_t *starter = calloc(1, sizeof(psx_starter_t));
+ if (starter == NULL) {
+ perror("failed at thread creation");
+ exit(1);
+ }
starter->fn = start_routine;
starter->arg = arg;
/*
diff --git a/psx/psx.go b/psx/psx.go
index 529f19d..77648e2 100644
--- a/psx/psx.go
+++ b/psx/psx.go
@@ -3,13 +3,17 @@
package psx // import "kernel.org/pub/linux/libs/security/libcap/psx"
-import (
- "syscall"
-)
+import "syscall"
-// Syscall3 and Syscall6 are aliases for syscall.AllThreadsSyscall*
-// when compiled CGO_ENABLED=0.
-var (
- Syscall3 = syscall.AllThreadsSyscall
- Syscall6 = syscall.AllThreadsSyscall6
-)
+// Documentation for these functions are provided in the psx_cgo.go
+// file.
+
+//go:uintptrescapes
+func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) {
+ return syscall.AllThreadsSyscall(syscallnr, arg1, arg2, arg3)
+}
+
+//go:uintptrescapes
+func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) {
+ return syscall.AllThreadsSyscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6)
+}
diff --git a/psx/psx_cgo.go b/psx/psx_cgo.go
index c17b4f3..26aa15a 100644
--- a/psx/psx_cgo.go
+++ b/psx/psx_cgo.go
@@ -32,11 +32,21 @@
return int(C.__errno_too(C.long(v)))
}
-// Syscall3 performs a 3 argument syscall using the libpsx C function
-// psx_syscall3(). Syscall3 differs from syscall.[Raw]Syscall()
-// insofar as it is simultaneously executed on every pthread of the
-// combined Go and CGo runtimes.
+//go:uintptrescapes
+
+// Syscall3 performs a 3 argument syscall. Syscall3 differs from
+// syscall.[Raw]Syscall() insofar as it is simultaneously executed on
+// every thread of the combined Go and CGo runtimes. It works
+// differently depending on whether CGO_ENABLED is 1 or 0 at compile
+// time.
+//
+// If CGO_ENABLED=1 it uses the libpsx function C.psx_syscall3().
+//
+// If CGO_ENABLED=0 it redirects to the go1.16+
+// syscall.AllThreadsSyscall() function.
func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) {
+ // We lock to the OSThread here because we may need errno to
+ // be the one for this thread.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@@ -48,11 +58,15 @@
return uintptr(v), uintptr(v), errno
}
-// Syscall6 performs a 6 argument syscall using the libpsx C function
-// psx_syscall6(). Syscall6 differs from syscall.[Raw]Syscall6() insofar as
-// it is simultaneously executed on every pthread of the combined Go
-// and CGo runtimes.
+//go:uintptrescapes
+
+// Syscall6 performs a 6 argument syscall on every thread of the
+// combined Go and CGo runtimes. Other than the number of syscall
+// arguments, its behavior is identical to that of Syscall3() - see
+// above for the full documentation.
func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) {
+ // We lock to the OSThread here because we may need errno to
+ // be the one for this thread.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
diff --git a/psx/psx_syscall.h b/psx/psx_syscall.h
index 4aacfab..3987d59 100644
--- a/psx/psx_syscall.h
+++ b/psx/psx_syscall.h
@@ -59,7 +59,7 @@
* is to define this function as weak in a library that can optionally
* use libpsx and then, should the caller link -lpsx, that library can
* implicitly use these POSIX semantics syscalls. See libcap for an
- * example of this useage.
+ * example of this usage.
*/
void psx_load_syscalls(long int (**syscall_fn)(long int,
long int, long int, long int),
diff --git a/tests/.gitignore b/tests/.gitignore
index ac7ffb0..d0b3f15 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -5,3 +5,4 @@
libcap_psx_launch_test
exploit
noexploit
+uns_test
diff --git a/tests/Makefile b/tests/Makefile
index 1e7039d..7ce8776 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -8,9 +8,9 @@
#
all:
- make libcap_launch_test
+ $(MAKE) libcap_launch_test uns_test
ifeq ($(PTHREADS),yes)
- make psx_test libcap_psx_test libcap_psx_launch_test
+ $(MAKE) psx_test libcap_psx_test libcap_psx_launch_test
endif
install: all
@@ -22,7 +22,7 @@
DEPS += ../libcap/libpsx.so
endif
else
-LDFLAGS += --static
+LDSTATIC = --static
DEPS=../libcap/libcap.a
ifeq ($(PTHREADS),yes)
DEPS += ../libcap/libpsx.a
@@ -30,31 +30,32 @@
endif
../libcap/libcap.so:
- make -C ../libcap libcap.so
+ $(MAKE) -C ../libcap libcap.so
../libcap/libcap.a:
- make -C ../libcap libcap.a
+ $(MAKE) -C ../libcap libcap.a
ifeq ($(PTHREADS),yes)
../libcap/libpsx.so:
- make -C ../libcap libpsx.so
+ $(MAKE) -C ../libcap libpsx.so
../libcap/libpsx.a:
- make -C ../libcap libpsx.a
+ $(MAKE) -C ../libcap libpsx.a
endif
../progs/tcapsh-static:
- make -C ../progs tcapsh-static
+ $(MAKE) -C ../progs tcapsh-static
test:
ifeq ($(PTHREADS),yes)
- make run_psx_test run_libcap_psx_test
+ $(MAKE) run_psx_test run_libcap_psx_test
endif
sudotest: test
- make run_libcap_launch_test
+ $(MAKE) run_uns_test
+ $(MAKE) run_libcap_launch_test
ifeq ($(PTHREADS),yes)
- make run_libcap_psx_launch_test run_exploit_test
+ $(MAKE) run_libcap_psx_launch_test run_exploit_test
endif
# unprivileged
@@ -62,15 +63,21 @@
./psx_test
psx_test: psx_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDFLAGS)
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDSTATIC)
run_libcap_psx_test: libcap_psx_test
./libcap_psx_test
libcap_psx_test: libcap_psx_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS)
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC)
# privileged
+uns_test: uns_test.c $(DEPS)
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC)
+
+run_uns_test: uns_test
+ echo exit | sudo ./uns_test
+
run_libcap_launch_test: libcap_launch_test noop ../progs/tcapsh-static
sudo ./libcap_launch_test
@@ -78,13 +85,13 @@
sudo ./libcap_psx_launch_test
libcap_launch_test: libcap_launch_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS)
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC)
# This varies only slightly from the above insofar as it currently
# only links in the pthreads fork support. TODO() we need to change
# the source to do something interesting with pthreads.
libcap_psx_launch_test: libcap_launch_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS)
+ $(CC) $(CFLAGS) $(IPATH) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC)
# This test demonstrates that libpsx is needed to secure multithreaded
@@ -99,18 +106,18 @@
$(CC) $(CFLAGS) $(IPATH) -c $<
exploit: exploit.o $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDFLAGS)
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDSTATIC)
# Note, for some reason, the order of libraries is important to avoid
# the exploit working for dynamic linking.
noexploit: exploit.o $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDFLAGS)
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDSTATIC)
# This one runs in a chroot with no shared library files.
noop: noop.c
$(CC) $(CFLAGS) $< -o $@ --static
clean:
- rm -f psx_test libcap_psx_test libcap_launch_test *~
+ rm -f psx_test libcap_psx_test libcap_launch_test uns_test *~
rm -f libcap_launch_test libcap_psx_launch_test core noop
rm -f exploit noexploit exploit.o
diff --git a/tests/exploit.c b/tests/exploit.c
index 28bac88..814337c 100644
--- a/tests/exploit.c
+++ b/tests/exploit.c
@@ -16,6 +16,10 @@
* to execute arbitrary code. As such, if all but one thread drops
* privilege, privilege escalation is somewhat trivial.
*/
+
+/* as per "man sigaction" */
+#define _POSIX_C_SOURCE 200809L
+
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
@@ -148,7 +152,8 @@
if (greatest_len != 1) {
printf("exploit succeeded\n");
exit(1);
- } else {
- printf("exploit failed\n");
}
+
+ printf("exploit failed\n");
+ exit(0);
}
diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c
index bba38c6..f45b2b7 100644
--- a/tests/libcap_launch_test.c
+++ b/tests/libcap_launch_test.c
@@ -24,7 +24,9 @@
const char **envp;
const char *iab;
cap_mode_t mode;
+ int launch_abort;
int result;
+ int (*callback_fn)(void *detail);
};
#ifdef WITH_PTHREADS
@@ -32,6 +34,17 @@
#else /* WITH_PTHREADS */
#endif /* WITH_PTHREADS */
+/*
+ * clean_out drops all process capabilities.
+ */
+static int clean_out(void *data) {
+ cap_t empty;
+ empty = cap_init();
+ cap_set_proc(empty);
+ cap_free(empty);
+ return 0;
+}
+
int main(int argc, char **argv) {
static struct test_case_s vs[] = {
{
@@ -39,13 +52,32 @@
.result = 0
},
{
+ .args = { "../progs/tcapsh-static", "--", "-c", "echo hello" },
+ .callback_fn = &clean_out,
+ .result = 0
+ },
+ {
+ .callback_fn = &clean_out,
+ .result = 0
+ },
+ {
.args = { "../progs/tcapsh-static", "--is-uid=123" },
.result = 256
},
{
+ .args = { "/", "won't", "work" },
+ .launch_abort = 1,
+ },
+ {
.args = { "../progs/tcapsh-static", "--is-uid=123" },
- .result = 0,
.uid = 123,
+ .result = 0,
+ },
+ {
+ .args = { "../progs/tcapsh-static", "--is-uid=123" },
+ .callback_fn = &clean_out,
+ .uid = 123,
+ .launch_abort = 1,
},
{
.args = { "../progs/tcapsh-static", "--is-gid=123" },
@@ -91,8 +123,16 @@
for (i=0; vs[i].pass_on != NO_MORE; i++) {
const struct test_case_s *v = &vs[i];
printf("[%d] test should %s\n", i,
- v->result ? "generate error" : "work");
- cap_launch_t attr = cap_new_launcher(v->args[0], v->args, v->envp);
+ v->result || v->launch_abort ? "generate error" : "work");
+ cap_launch_t attr;
+ if (v->args[0] != NULL) {
+ attr = cap_new_launcher(v->args[0], v->args, v->envp);
+ if (v->callback_fn != NULL) {
+ cap_launcher_callback(attr, v->callback_fn);
+ }
+ } else {
+ attr = cap_func_launcher(v->callback_fn);
+ }
if (v->chroot) {
cap_launcher_set_chroot(attr, v->chroot);
}
@@ -125,28 +165,30 @@
pid_t child = cap_launch(attr, NULL);
if (child <= 0) {
- fprintf(stderr, "[%d] failed to launch", i);
- perror(":");
- success = 0;
+ fprintf(stderr, "[%d] failed to launch: ", i);
+ perror("");
+ if (!v->launch_abort) {
+ success = 0;
+ }
continue;
}
if (cap_free(attr)) {
- fprintf(stderr, "[%d] failed to free launcher", i);
- perror(":");
+ fprintf(stderr, "[%d] failed to free launcher: ", i);
+ perror("");
success = 0;
}
int result;
int ret = waitpid(child, &result, 0);
if (ret != child) {
- fprintf(stderr, "[%d] failed to wait", i);
- perror(":");
+ fprintf(stderr, "[%d] failed to wait: ", i);
+ perror("");
success = 0;
continue;
}
if (result != v->result) {
- fprintf(stderr, "[%d] bad result: got=%d want=%d", i, result,
+ fprintf(stderr, "[%d] bad result: got=%d want=%d: ", i, result,
v->result);
- perror(":");
+ perror("");
success = 0;
continue;
}
@@ -164,10 +206,10 @@
cap_free(final);
cap_free(orig);
- if (success) {
- printf("cap_launch_test: PASSED\n");
- } else {
+ if (!success) {
printf("cap_launch_test: FAILED\n");
exit(1);
}
+ printf("cap_launch_test: PASSED\n");
+ exit(0);
}
diff --git a/tests/uns_test.c b/tests/uns_test.c
new file mode 100644
index 0000000..d8f5415
--- /dev/null
+++ b/tests/uns_test.c
@@ -0,0 +1,158 @@
+/*
+ * Try unsharing where we remap the root user by rotating uids (0,1,2)
+ * and the corresponding gids too.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define STACK_RESERVED 10*1024
+
+struct my_pipe {
+ int to[2];
+ int from[2];
+};
+
+static int child(void *data) {
+ struct my_pipe *fdsp = data;
+ static const char * const args[] = {"bash", NULL};
+
+ close(fdsp->to[1]);
+ close(fdsp->from[0]);
+ if (write(fdsp->from[1], "1", 1) != 1) {
+ fprintf(stderr, "failed to confirm setuid(1)\n");
+ exit(1);
+ }
+ close(fdsp->from[1]);
+
+ char datum[1];
+ if (read(fdsp->to[0], datum, 1) != 1) {
+ fprintf(stderr, "failed to wait for parent\n");
+ exit(1);
+ }
+ close(fdsp->to[0]);
+ if (datum[0] == '!') {
+ /* parent failed */
+ exit(0);
+ }
+
+ setsid();
+
+ execv("/bin/bash", (const void *) args);
+ perror("execv failed");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ static const char *file_formats[] = {
+ "/proc/%d/uid_map",
+ "/proc/%d/gid_map"
+ };
+ static const char id_map[] = "0 1 1\n1 2 1\n2 0 1\n3 3 49999997\n";
+ cap_value_t fscap = CAP_SETFCAP;
+ cap_t orig = cap_get_proc();
+
+ /* Run with this one lowered */
+ cap_set_flag(orig, CAP_EFFECTIVE, 1, &fscap, CAP_CLEAR);
+
+ struct my_pipe fds;
+ if (pipe(&fds.from[0]) || pipe(&fds.to[0])) {
+ perror("no pipes");
+ exit(1);
+ }
+
+ char *stack = mmap(NULL, STACK_RESERVED, PROT_READ|PROT_WRITE,
+ MAP_ANONYMOUS|MAP_PRIVATE|MAP_STACK, -1, 0);
+ if (stack == MAP_FAILED) {
+ perror("no map for stack");
+ exit(1);
+ }
+
+ if (cap_setuid(1)) {
+ perror("failed to cap_setuid(1)");
+ exit(1);
+ }
+
+ if (cap_set_proc(orig)) {
+ perror("failed to raise caps again");
+ exit(1);
+ }
+
+ pid_t pid = clone(&child, stack+STACK_RESERVED, CLONE_NEWUSER|SIGCHLD, &fds);
+ if (pid == -1) {
+ perror("clone failed");
+ exit(1);
+ }
+
+ close(fds.from[1]);
+ close(fds.to[0]);
+
+ if (cap_setuid(0)) {
+ perror("failed to cap_setuid(0)");
+ exit(1);
+ }
+
+ if (cap_set_proc(orig)) {
+ perror("failed to raise caps again");
+ exit(1);
+ }
+
+ char datum[1];
+ if (read(fds.from[0], datum, 1) != 1 || datum[0] != '1') {
+ fprintf(stderr, "failed to read child status\n");
+ exit(1);
+ }
+ close(fds.from[0]);
+
+ int i;
+ for (i=0; i<2; i++) {
+ char *map_file;
+ if (asprintf(&map_file, file_formats[i], pid) < 0) {
+ perror("allocate string");
+ exit(1);
+ }
+
+ FILE *f = fopen(map_file, "w");
+ free(map_file);
+ if (f == NULL) {
+ perror("fopen failed");
+ exit(1);
+ }
+ int len = fwrite(id_map, 1, strlen(id_map), f);
+ if (len != strlen(id_map)) {
+ goto bailok;
+ }
+ if (fclose(f)) {
+ goto bailok;
+ }
+ }
+
+ if (write(fds.to[1], ".", 1) != 1) {
+ perror("failed to write '.'");
+ exit(1);
+ }
+ close(fds.to[1]);
+
+ fprintf(stderr, "user namespace launched exploit worked - upgrade kernel\n");
+ if (wait(NULL) == pid) {
+ exit(1);
+ }
+ perror("launch failed");
+ exit(1);
+
+bailok:
+ fprintf(stderr, "exploit attempt failed\n");
+ (void) write(fds.to[1], "!", 1);
+ exit(0);
+}