Merge pull request #3671 from sipsma/fix-merge-file-caps

diffapply: do chown before xattrs
This commit is contained in:
Erik Sipsma
2023-02-24 15:56:29 -08:00
committed by GitHub
27 changed files with 5411 additions and 19 deletions

2
go.mod
View File

@@ -87,6 +87,7 @@ require (
google.golang.org/genproto v0.0.0-20230131230820-1c016267d619
google.golang.org/grpc v1.52.3
google.golang.org/protobuf v1.28.1
kernel.org/pub/linux/libs/security/libcap/cap v1.2.67
)
require (
@@ -156,4 +157,5 @@ require (
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.5.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 // indirect
)

4
go.sum
View File

@@ -1890,6 +1890,10 @@ k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuB
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 h1:sPQ9qlSNR26fToTKbxe/HDWJlXvBLqGmt84LGCQkOy0=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.67/go.mod h1:GkntoBuwffz19qtdFVB+k2NtWNN+yCKnC/Ykv/hMiTU=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 h1:NxbXJ7pDVq0FKBsqjieT92QDXI2XaqH2HAi4QcCOHt8=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.67/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=

View File

@@ -379,6 +379,18 @@ func (a *applier) applyCopy(ctx context.Context, ca *changeApply) error {
return errors.Errorf("unhandled file type %d during merge at path %q", ca.srcStat.Mode&unix.S_IFMT, ca.srcPath)
}
// NOTE: it's important that chown happens before setting xattrs due to the fact that chown will
// reset the security.capabilities xattr which results in file capabilities being lost.
if err := os.Lchown(ca.dstPath, int(ca.srcStat.Uid), int(ca.srcStat.Gid)); err != nil {
return errors.Wrap(err, "failed to chown during apply")
}
if ca.srcStat.Mode&unix.S_IFMT != unix.S_IFLNK {
if err := unix.Chmod(ca.dstPath, ca.srcStat.Mode); err != nil {
return errors.Wrapf(err, "failed to chmod path %q during apply", ca.dstPath)
}
}
if ca.srcPath != "" {
xattrs, err := sysx.LListxattr(ca.srcPath)
if err != nil {
@@ -410,16 +422,6 @@ func (a *applier) applyCopy(ctx context.Context, ca *changeApply) error {
}
}
if err := os.Lchown(ca.dstPath, int(ca.srcStat.Uid), int(ca.srcStat.Gid)); err != nil {
return errors.Wrap(err, "failed to chown during apply")
}
if ca.srcStat.Mode&unix.S_IFMT != unix.S_IFLNK {
if err := unix.Chmod(ca.dstPath, ca.srcStat.Mode); err != nil {
return errors.Wrapf(err, "failed to chmod path %q during apply", ca.dstPath)
}
}
atimeSpec := unix.Timespec{Sec: ca.srcStat.Atim.Sec, Nsec: ca.srcStat.Atim.Nsec}
mtimeSpec := unix.Timespec{Sec: ca.srcStat.Mtim.Sec, Nsec: ca.srcStat.Mtim.Nsec}
if ca.srcStat.Mode&unix.S_IFMT != unix.S_IFDIR {

View File

@@ -26,6 +26,7 @@ import (
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
bolt "go.etcd.io/bbolt"
libcap "kernel.org/pub/linux/libs/security/libcap/cap"
)
func newSnapshotter(ctx context.Context, t *testing.T, snapshotterName string) (_ context.Context, _ *mergeSnapshotter, rerr error) {
@@ -372,6 +373,41 @@ func TestHardlinks(t *testing.T) {
}
}
func TestMergeFileCapabilities(t *testing.T) {
requireRoot(t)
for _, snName := range []string{"overlayfs", "native", "native-nohardlink"} {
snName := snName
t.Run(snName, func(t *testing.T) {
t.Parallel()
ctx, sn, err := newSnapshotter(context.Background(), t, snName)
require.NoError(t, err)
setCaps := "cap_net_bind_service=+ep"
base1Snap := committedKey(ctx, t, sn, identity.NewID(), "",
fstest.CreateFile("hasCaps", []byte("capable"), 0700),
fstest.Chown("hasCaps", 1000, 1000),
setFileCap("hasCaps", setCaps),
)
base2Snap := committedKey(ctx, t, sn, identity.NewID(), "",
fstest.CreateFile("foo", []byte("bar"), 0600),
)
mergeSnap := mergeKey(ctx, t, sn, identity.NewID(), []Diff{
{"", base1Snap.Name},
{"", base2Snap.Name},
})
actualCaps := getFileCap(ctx, t, sn, mergeSnap.Name, "hasCaps")
require.Equal(t, "cap_net_bind_service=ep", actualCaps)
stat := statPath(ctx, t, sn, mergeSnap.Name, "hasCaps")
require.EqualValues(t, 1000, stat.Uid)
require.EqualValues(t, 1000, stat.Gid)
require.EqualValues(t, 0700, stat.Mode&0777)
})
}
}
func TestUsage(t *testing.T) {
for _, snName := range []string{"overlayfs", "native", "native-nohardlink"} {
snName := snName
@@ -559,7 +595,7 @@ func requireMtime(t *testing.T, path string, mtime time.Time) {
require.Equal(t, mtime.UnixNano(), stat.Mtim.Nano())
}
func tryStatPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string) (st *syscall.Stat_t) {
func pathCallback[T any](ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string, cb func(t *testing.T, path string) *T) *T {
t.Helper()
mounts, cleanup := getMounts(ctx, t, sn, key)
defer cleanup()
@@ -577,24 +613,32 @@ func tryStatPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, p
}
}
if upperdir != "" {
st = trySyscallStat(t, filepath.Join(upperdir, path))
if st != nil {
return st
r := cb(t, filepath.Join(upperdir, path))
if r != nil {
return r
}
}
for _, lowerdir := range lowerdirs {
st = trySyscallStat(t, filepath.Join(lowerdir, path))
if st != nil {
return st
r := cb(t, filepath.Join(lowerdir, path))
if r != nil {
return r
}
}
return nil
}
var r *T
withMount(ctx, t, sn, key, func(root string) {
st = trySyscallStat(t, filepath.Join(root, path))
r = cb(t, filepath.Join(root, path))
})
return r
}
func tryStatPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string) *syscall.Stat_t {
t.Helper()
return pathCallback(ctx, t, sn, key, path, func(t *testing.T, path string) *syscall.Stat_t {
return trySyscallStat(t, path)
})
return st
}
func statPath(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string) (st *syscall.Stat_t) {
@@ -610,3 +654,36 @@ func requireRoot(t *testing.T) {
t.Skip("test requires root")
}
}
func setFileCap(path string, caps string) fstest.Applier {
return applyFn(func(root string) error {
path := filepath.Join(root, path)
capSet, err := libcap.FromText(caps)
if err != nil {
return err
}
return capSet.SetFile(path)
})
}
func getFileCap(ctx context.Context, t *testing.T, sn *mergeSnapshotter, key, path string) string {
t.Helper()
caps := pathCallback(ctx, t, sn, key, path, func(t *testing.T, path string) *string {
t.Helper()
capSet, err := libcap.GetFile(path)
if err != nil {
require.ErrorIs(t, err, os.ErrNotExist)
return nil
}
caps := capSet.String()
return &caps
})
require.NotNil(t, caps)
return *caps
}
type applyFn func(root string) error
func (a applyFn) Apply(root string) error {
return a(root)
}

View File

@@ -0,0 +1,398 @@
/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-only */
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.
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:
1. Redistributions of source code must retain any existing copyright
notice, and this entire permission notice in its entirety,
including the disclaimer of warranties.
2. Redistributions in binary form must reproduce all prior and current
copyright notices, this list of conditions, and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
3. The name of any author may not be used to endorse or promote
products derived from this software without their specific prior
written permission.
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.
IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
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:
-------------------------
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -0,0 +1,10 @@
Package cap is the libcap API for Linux Capabilities written in
Go. The official release announcement site for libcap is:
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
file.
Andrew G. Morgan <morgan@kernel.org>

View File

@@ -0,0 +1,503 @@
// Package cap provides all the Linux Capabilities userspace library API
// bindings in native Go.
//
// Capabilities are a feature of the Linux kernel that allow fine
// grain permissions to perform privileged operations. Privileged
// operations are required to do irregular system level operations
// from code. You can read more about how Capabilities are intended to
// work here:
//
// https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/33528.pdf
//
// This package supports native Go bindings for all the features
// described in that paper as well as supporting subsequent changes to
// the kernel for other styles of inheritable Capability.
//
// Some simple things you can do with this package are:
//
// // Read and display the capabilities of the running process
// c := cap.GetProc()
// log.Printf("this process has these caps:", c)
//
// // Drop any privilege a process might have (including for root,
// // but note root 'owns' a lot of system files so a cap-limited
// // root can still do considerable damage to a running system).
// old := cap.GetProc()
// empty := cap.NewSet()
// if err := empty.SetProc(); err != nil {
// log.Fatalf("failed to drop privilege: %q -> %q: %v", old, empty, err)
// }
// now := cap.GetProc()
// if cf, _ := now.Cf(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.
//
// Copyright (c) 2019-21 Andrew G. Morgan <morgan@kernel.org>
//
// The cap and psx packages are licensed with a (you choose) BSD
// 3-clause or GPL2. See LICENSE file for details.
package cap // import "kernel.org/pub/linux/libs/security/libcap/cap"
import (
"errors"
"sort"
"sync"
"syscall"
"unsafe"
)
// Value is the type of a single capability (or permission) bit.
type Value uint
// Flag is the type of one of the three Value dimensions held in a
// Set. It is also used in the (*IAB).Fill() method for changing the
// Bounding and Ambient Vectors.
type Flag uint
// Effective, Permitted, Inheritable are the three Flags of Values
// held in a Set.
const (
Effective Flag = iota
Permitted
Inheritable
)
// Diff summarizes the result of the (*Set).Cf() function.
type Diff uint
const (
effectiveDiff Diff = 1 << Effective
permittedDiff Diff = 1 << Permitted
inheritableDiff Diff = 1 << Inheritable
)
// String identifies a Flag value by its conventional "e", "p" or "i"
// string abbreviation.
func (f Flag) String() string {
switch f {
case Effective:
return "e"
case Permitted:
return "p"
case Inheritable:
return "i"
default:
return "<Error>"
}
}
// data holds a 32-bit slice of the compressed bitmaps of capability
// sets as understood by the kernel.
type data [Inheritable + 1]uint32
// Set is an opaque capabilities container for a set of system
// capbilities. It holds individually addressable capability Value's
// for the three capability Flag's. See GetFlag() and SetFlag() for
// how to adjust them individually, and Clear() and ClearFlag() for
// how to do bulk operations.
//
// For admin tasks associated with managing namespace specific file
// capabilities, Set can also support a namespace-root-UID value which
// defaults to zero. See GetNSOwner() and SetNSOwner().
type Set struct {
// mu protects all other members of a Set.
mu sync.RWMutex
// flat holds Flag Value bitmaps for all capabilities
// associated with this Set.
flat []data
// Linux specific
nsRoot int
}
// Various known kernel magic values.
const (
kv1 = 0x19980330 // First iteration of process capabilities (32 bits).
kv2 = 0x20071026 // First iteration of process and file capabilities (64 bits) - deprecated.
kv3 = 0x20080522 // Most recently supported process and file capabilities (64 bits).
)
var (
// startUp protects setting of the following values: magic,
// words, maxValues.
startUp sync.Once
// magic holds the preferred magic number for the kernel ABI.
magic uint32
// words holds the number of uint32's associated with each
// capability Flag for this session.
words int
// maxValues holds the number of bit values that are named by
// the running kernel. This is generally expected to match
// ValueCount which is autogenerated at packaging time.
maxValues uint
)
type header struct {
magic uint32
pid int32
}
// 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.
type syscaller struct {
r3 func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno)
w3 func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno)
r6 func(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
w6 func(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
}
// caprcall provides a pointer etc wrapper for the system calls
// associated with getcap.
//go:uintptrescapes
func (sc *syscaller) caprcall(call uintptr, h *header, d []data) error {
x := uintptr(0)
if d != nil {
x = uintptr(unsafe.Pointer(&d[0]))
}
_, _, err := sc.r3(call, uintptr(unsafe.Pointer(h)), x, 0)
if err != 0 {
return err
}
return nil
}
// capwcall provides a pointer etc wrapper for the system calls
// associated with setcap.
//go:uintptrescapes
func (sc *syscaller) capwcall(call uintptr, h *header, d []data) error {
x := uintptr(0)
if d != nil {
x = uintptr(unsafe.Pointer(&d[0]))
}
_, _, err := sc.w3(call, uintptr(unsafe.Pointer(h)), x, 0)
if err != 0 {
return err
}
return nil
}
// prctlrcall provides a wrapper for the prctl systemcalls that only
// read kernel state. There is a limited number of arguments needed
// and the caller should use 0 for those not needed.
func (sc *syscaller) prctlrcall(prVal, v1, v2 uintptr) (int, error) {
r, _, err := sc.r3(syscall.SYS_PRCTL, prVal, v1, v2)
if err != 0 {
return int(r), err
}
return int(r), nil
}
// prctlrcall6 provides a wrapper for the prctl systemcalls that only
// read kernel state and require 6 arguments - ambient cap API, I'm
// looking at you. There is a limited number of arguments needed and
// the caller should use 0 for those not needed.
func (sc *syscaller) prctlrcall6(prVal, v1, v2, v3, v4, v5 uintptr) (int, error) {
r, _, err := sc.r6(syscall.SYS_PRCTL, prVal, v1, v2, v3, v4, v5)
if err != 0 {
return int(r), err
}
return int(r), nil
}
// prctlwcall provides a wrapper for the prctl systemcalls that
// write/modify kernel state. Where available, these will use the
// POSIX semantics fixup system calls. There is a limited number of
// arguments needed and the caller should use 0 for those not needed.
func (sc *syscaller) prctlwcall(prVal, v1, v2 uintptr) (int, error) {
r, _, err := sc.w3(syscall.SYS_PRCTL, prVal, v1, v2)
if err != 0 {
return int(r), err
}
return int(r), nil
}
// prctlwcall6 provides a wrapper for the prctl systemcalls that
// write/modify kernel state and require 6 arguments - ambient cap
// API, I'm looking at you. (Where available, these will use the POSIX
// semantics fixup system calls). There is a limited number of
// arguments needed and the caller should use 0 for those not needed.
func (sc *syscaller) prctlwcall6(prVal, v1, v2, v3, v4, v5 uintptr) (int, error) {
r, _, err := sc.w6(syscall.SYS_PRCTL, prVal, v1, v2, v3, v4, v5)
if err != 0 {
return int(r), err
}
return int(r), nil
}
// cInit performs the lazy identification of the capability vintage of
// the running system.
func (sc *syscaller) cInit() {
h := &header{
magic: kv3,
}
sc.caprcall(syscall.SYS_CAPGET, h, nil)
magic = h.magic
switch magic {
case kv1:
words = 1
case kv2, kv3:
words = 2
default:
// Fall back to a known good version.
magic = kv3
words = 2
}
// Use the bounding set to evaluate which capabilities exist.
maxValues = uint(sort.Search(32*words, func(n int) bool {
_, err := GetBound(Value(n))
return err != nil
}))
if maxValues == 0 {
// Fall back to using the largest value defined at build time.
maxValues = NamedCount
}
}
// MaxBits returns the number of kernel-named capabilities discovered
// at runtime in the current system.
func MaxBits() Value {
startUp.Do(multisc.cInit)
return Value(maxValues)
}
// NewSet returns an empty capability set.
func NewSet() *Set {
startUp.Do(multisc.cInit)
return &Set{
flat: make([]data, words),
}
}
// ErrBadSet indicates a nil pointer was used for a *Set, or the
// request of the Set is invalid in some way.
var ErrBadSet = errors.New("bad capability set")
// good confirms that c looks valid.
func (c *Set) good() error {
if c == nil || len(c.flat) == 0 {
return ErrBadSet
}
return nil
}
// Dup returns a copy of the specified capability set.
func (c *Set) Dup() (*Set, error) {
if err := c.good(); err != nil {
return nil, err
}
n := NewSet()
c.mu.RLock()
defer c.mu.RUnlock()
copy(n.flat, c.flat)
n.nsRoot = c.nsRoot
return n, nil
}
// GetPID returns the capability set associated with the target process
// id; pid=0 is an alias for current.
func GetPID(pid int) (*Set, error) {
v := NewSet()
if err := multisc.caprcall(syscall.SYS_CAPGET, &header{magic: magic, pid: int32(pid)}, v.flat); err != nil {
return nil, err
}
return v, nil
}
// GetProc returns the capability Set of the current process. If the
// kernel is unable to determine the Set associated with the current
// process, the function panic()s.
func GetProc() *Set {
c, err := GetPID(0)
if err != nil {
panic(err)
}
return c
}
// setProc uses syscaller to set process capabilities. Note, c is
// either private to or (read) locked by the caller.
func (sc *syscaller) setProc(c *Set) error {
return sc.capwcall(syscall.SYS_CAPSET, &header{magic: magic}, c.flat)
}
// SetProc attempts to set the capability Set of the current
// 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 {
if err := c.good(); err != nil {
return err
}
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
c.mu.RLock()
defer c.mu.RUnlock()
return sc.setProc(c)
}
// defines from uapi/linux/prctl.h
const (
prCapBSetRead = 23
prCapBSetDrop = 24
)
// GetBound determines if a specific capability is currently part of
// the local bounding set. On systems where the bounding set Value is
// not present, this function returns an error.
func GetBound(val Value) (bool, error) {
v, err := multisc.prctlrcall(prCapBSetRead, uintptr(val), 0)
if err != nil {
return false, err
}
return v > 0, nil
}
//go:uintptrescapes
func (sc *syscaller) dropBound(val ...Value) error {
for _, v := range val {
if _, err := sc.prctlwcall(prCapBSetDrop, uintptr(v), 0); err != nil {
return err
}
}
return nil
}
// DropBound attempts to suppress bounding set Values. The kernel will
// never allow a bounding set Value bit to be raised once successfully
// dropped. However, dropping requires the current process is
// sufficiently capable (usually via cap.SETPCAP being raised in the
// Effective flag of the process' Set). Note, the drops are performed
// in order and if one bounding value cannot be dropped, the function
// returns immediately with an error which may leave the system in an
// ill-defined state. The caller can determine where things went wrong
// using GetBound().
func DropBound(val ...Value) error {
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
return sc.dropBound(val...)
}
// defines from uapi/linux/prctl.h
const (
prCapAmbient = 47
prCapAmbientIsSet = 1
prCapAmbientRaise = 2
prCapAmbientLower = 3
prCapAmbientClearAll = 4
)
// GetAmbient determines if a specific capability is currently part of
// the local ambient set. On systems where the ambient set Value is
// not present, this function returns an error.
func GetAmbient(val Value) (bool, error) {
r, err := multisc.prctlrcall6(prCapAmbient, prCapAmbientIsSet, uintptr(val), 0, 0, 0)
return r > 0, err
}
//go:uintptrescapes
func (sc *syscaller) setAmbient(enable bool, val ...Value) error {
dir := uintptr(prCapAmbientLower)
if enable {
dir = prCapAmbientRaise
}
for _, v := range val {
_, err := sc.prctlwcall6(prCapAmbient, dir, uintptr(v), 0, 0, 0)
if err != nil {
return err
}
}
return nil
}
// SetAmbient attempts to set a specific Value bit to the state,
// enable. This function will return an error if insufficient
// permission is available to perform this task. The settings are
// performed in order and the function returns immediately an error is
// detected. Use GetAmbient() to unravel where things went
// wrong. Note, the cap package manages an abstraction IAB that
// captures all three inheritable vectors in a single type. Consider
// using that.
func SetAmbient(enable bool, val ...Value) error {
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
return sc.setAmbient(enable, val...)
}
func (sc *syscaller) resetAmbient() error {
var v bool
var err error
for c := Value(0); !v; c++ {
if v, err = GetAmbient(c); err != nil {
// no non-zero values found.
return nil
}
}
_, err = sc.prctlwcall6(prCapAmbient, prCapAmbientClearAll, 0, 0, 0, 0)
return err
}
// ResetAmbient attempts to ensure the Ambient set is fully
// cleared. It works by first reading the set and if it finds any bits
// raised it will attempt a reset. The test before attempting a reset
// behavior is a workaround for situations where the Ambient API is
// locked, but a reset is not actually needed. No Ambient bit not
// already raised in both the Permitted and Inheritable Set is allowed
// to be raised by the kernel.
func ResetAmbient() error {
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
return sc.resetAmbient()
}

View File

@@ -0,0 +1,340 @@
package cap
import (
"errors"
"fmt"
"syscall"
"unsafe"
)
// This file contains convenience functions for libcap, to help
// users do the right thing with respect to capabilities for
// common actions.
// Secbits capture the prctl settable secure-bits of a process.
type Secbits uint
// SecbitNoRoot etc are the bitmasks associated with the supported
// Secbit masks. Source: uapi/linux/securebits.h
const (
SecbitNoRoot Secbits = 1 << iota
SecbitNoRootLocked
SecbitNoSetUIDFixup
SecbitNoSetUIDFixupLocked
SecbitKeepCaps
SecbitKeepCapsLocked
SecbitNoCapAmbientRaise
SecbitNoCapAmbientRaiseLocked
)
const (
securedBasicBits = SecbitNoRoot | SecbitNoRootLocked | SecbitNoSetUIDFixup | SecbitNoSetUIDFixupLocked | SecbitKeepCapsLocked
securedAmbientBits = securedBasicBits | SecbitNoCapAmbientRaise | SecbitNoCapAmbientRaiseLocked
)
// defines from uapi/linux/prctl.h
const (
prGetKeepCaps = 7
prSetKeepCaps = 8
prGetSecureBits = 27
prSetSecureBits = 28
prSetNoNewPrivs = 38
)
// GetSecbits returns the current setting of the process' Secbits.
func GetSecbits() Secbits {
v, err := multisc.prctlrcall(prGetSecureBits, 0, 0)
if err != nil {
panic(err)
}
return Secbits(v)
}
func (sc *syscaller) setSecbits(s Secbits) error {
_, err := sc.prctlwcall(prSetSecureBits, uintptr(s), 0)
return err
}
// Set attempts to force the process Secbits to a value. This function
// will raise cap.SETPCAP in order to achieve this operation, and will
// completely lower the Effective Flag of the process upon returning.
func (s Secbits) Set() error {
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
return sc.setSecbits(s)
}
// Mode summarizes a complicated secure-bits and capability mode in a
// libcap preferred way.
type Mode uint
// ModeUncertain etc are how libcap summarizes security modes
// involving capabilities and secure-bits.
const (
ModeUncertain Mode = iota
ModeNoPriv
ModePure1EInit
ModePure1E
ModeHybrid
)
// GetMode assesses the current process state and summarizes it as
// a Mode. This function always succeeds. Unfamiliar modes are
// declared ModeUncertain.
func GetMode() Mode {
b := GetSecbits()
if b == 0 {
return ModeHybrid
}
if b&securedBasicBits != securedBasicBits {
return ModeUncertain
}
for c := Value(0); ; c++ {
v, err := GetAmbient(c)
if err != nil {
if c != 0 && b != securedAmbientBits {
return ModeUncertain
}
break
}
if v {
return ModeUncertain
}
}
w := GetProc()
e := NewSet()
cf, _ := w.Cf(e)
if cf.Has(Inheritable) {
return ModePure1E
}
if cf.Has(Permitted) || cf.Has(Effective) {
return ModePure1EInit
}
for c := Value(0); ; c++ {
v, err := GetBound(c)
if err != nil {
break
}
if v {
return ModePure1EInit
}
}
return ModeNoPriv
}
// ErrBadMode is the error returned when an attempt is made to set an
// unrecognized libcap security mode.
var ErrBadMode = errors.New("unsupported mode")
func (sc *syscaller) setMode(m Mode) error {
w := GetProc()
defer func() {
w.ClearFlag(Effective)
sc.setProc(w)
}()
if err := w.SetFlag(Effective, true, SETPCAP); err != nil {
return err
}
if err := sc.setProc(w); err != nil {
return err
}
if m == ModeHybrid {
return sc.setSecbits(0)
}
if m == ModeNoPriv || m == ModePure1EInit {
w.ClearFlag(Inheritable)
} else if m != ModePure1E {
return ErrBadMode
}
sb := securedAmbientBits
if _, err := GetAmbient(0); err != nil {
sb = securedBasicBits
} else if err := sc.resetAmbient(); err != nil {
return err
}
if err := sc.setSecbits(sb); err != nil {
return err
}
if m != ModeNoPriv {
return nil
}
for c := Value(0); sc.dropBound(c) == nil; c++ {
}
w.ClearFlag(Permitted)
// For good measure.
sc.prctlwcall6(prSetNoNewPrivs, 1, 0, 0, 0, 0)
return nil
}
// Set attempts to enter the specified mode. An attempt is made to
// enter the mode, so if you prefer this operation to be a no-op if
// entering the same mode, call only if CurrentMode() disagrees with
// the desired mode.
//
// This function will raise cap.SETPCAP in order to achieve this
// operation, and will completely lower the Effective Flag of the
// process' Set before returning. This function may fail for lack of
// permission or because (some of) the Secbits are already locked for
// the current process.
func (m Mode) Set() error {
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
return sc.setMode(m)
}
// String returns the libcap conventional string for this mode.
func (m Mode) String() string {
switch m {
case ModeUncertain:
return "UNCERTAIN"
case ModeNoPriv:
return "NOPRIV"
case ModePure1EInit:
return "PURE1E_INIT"
case ModePure1E:
return "PURE1E"
case ModeHybrid:
return "HYBRID"
default:
return "UNKNOWN"
}
}
func (sc *syscaller) setUID(uid int) error {
w := GetProc()
defer func() {
w.ClearFlag(Effective)
sc.setProc(w)
}()
if err := w.SetFlag(Effective, true, SETUID); err != nil {
return err
}
// these may or may not work depending on whether or not they
// are locked. We try them just in case.
sc.prctlwcall(prSetKeepCaps, 1, 0)
defer sc.prctlwcall(prSetKeepCaps, 0, 0)
if err := sc.setProc(w); err != nil {
return err
}
if _, _, err := sc.w3(syscall.SYS_SETUID, uintptr(uid), 0, 0); err != 0 {
return err
}
return nil
}
// SetUID is a convenience function for robustly setting the UID and
// all other variants of UID (EUID etc) to the specified value without
// dropping the privilege of the current process. This function will
// raise cap.SETUID in order to achieve this operation, and will
// completely lower the Effective Flag of the process before
// returning. Unlike the traditional method of dropping privilege when
// changing from [E]UID=0 to some other UID, this function only can
// perform any change of UID if cap.SETUID is available, and this
// operation will not alter the Permitted Flag of the process' Set.
func SetUID(uid int) error {
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
return sc.setUID(uid)
}
//go:uintptrescapes
func (sc *syscaller) setGroups(gid int, suppl []int) error {
w := GetProc()
defer func() {
w.ClearFlag(Effective)
sc.setProc(w)
}()
if err := w.SetFlag(Effective, true, SETGID); err != nil {
return err
}
if err := sc.setProc(w); err != nil {
return err
}
if _, _, err := sc.w3(syscall.SYS_SETGID, uintptr(gid), 0, 0); err != 0 {
return err
}
if len(suppl) == 0 {
if _, _, err := sc.w3(sysSetGroupsVariant, 0, 0, 0); err != 0 {
return err
}
return nil
}
// On linux gid values are 32-bits.
gs := make([]uint32, len(suppl))
for i, g := range suppl {
gs[i] = uint32(g)
}
if _, _, err := sc.w3(sysSetGroupsVariant, uintptr(len(suppl)), uintptr(unsafe.Pointer(&gs[0])), 0); err != 0 {
return err
}
return nil
}
// SetGroups is a convenience function for robustly setting the GID
// and all other variants of GID (EGID etc) to the specified value, as
// well as setting all of the supplementary groups. This function will
// raise cap.SETGID in order to achieve this operation, and will
// completely lower the Effective Flag of the process Set before
// returning.
func SetGroups(gid int, suppl ...int) error {
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])
}

View File

@@ -0,0 +1,419 @@
package cap
import (
"bytes"
"encoding/binary"
"errors"
"io"
"os"
"syscall"
"unsafe"
)
// uapi/linux/xattr.h defined.
var (
xattrNameCaps, _ = syscall.BytePtrFromString("security.capability")
)
// uapi/linux/capability.h defined.
const (
vfsCapRevisionMask = uint32(0xff000000)
vfsCapFlagsMask = ^vfsCapRevisionMask
vfsCapFlagsEffective = uint32(1)
vfsCapRevision1 = uint32(0x01000000)
vfsCapRevision2 = uint32(0x02000000)
vfsCapRevision3 = uint32(0x03000000)
)
// Data types stored in little-endian order.
type vfsCaps1 struct {
MagicEtc uint32
Data [1]struct {
Permitted, Inheritable uint32
}
}
type vfsCaps2 struct {
MagicEtc uint32
Data [2]struct {
Permitted, Inheritable uint32
}
}
type vfsCaps3 struct {
MagicEtc uint32
Data [2]struct {
Permitted, Inheritable uint32
}
RootID uint32
}
// ErrBadSize indicates the loaded file capability has
// an invalid number of bytes in it.
var ErrBadSize = errors.New("filecap bad size")
// ErrBadMagic indicates that the kernel preferred magic number for
// capability Set values is not supported by this package. This
// generally implies you are using an exceptionally old
// "../libcap/cap" package. An upgrade is needed, or failing that see
// https://sites.google.com/site/fullycapable/ for how to file a bug.
var ErrBadMagic = errors.New("unsupported magic")
// ErrBadPath indicates a failed attempt to set a file capability on
// 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) {
if err != nil {
return nil, err
}
var raw1 vfsCaps1
var raw2 vfsCaps2
var raw3 vfsCaps3
if sz < binary.Size(raw1) || sz > binary.Size(raw3) {
return nil, ErrBadSize
}
b := bytes.NewReader(d[:sz])
var magicEtc uint32
if err = binary.Read(b, binary.LittleEndian, &magicEtc); err != nil {
return nil, err
}
c := NewSet()
b.Seek(0, io.SeekStart)
switch magicEtc & vfsCapRevisionMask {
case vfsCapRevision1:
if err = binary.Read(b, binary.LittleEndian, &raw1); err != nil {
return nil, err
}
data := raw1.Data[0]
c.flat[0][Permitted] = data.Permitted
c.flat[0][Inheritable] = data.Inheritable
if raw1.MagicEtc&vfsCapFlagsMask == vfsCapFlagsEffective {
c.flat[0][Effective] = data.Inheritable | data.Permitted
}
case vfsCapRevision2:
if err = binary.Read(b, binary.LittleEndian, &raw2); err != nil {
return nil, err
}
for i, data := range raw2.Data {
c.flat[i][Permitted] = data.Permitted
c.flat[i][Inheritable] = data.Inheritable
if raw2.MagicEtc&vfsCapFlagsMask == vfsCapFlagsEffective {
c.flat[i][Effective] = data.Inheritable | data.Permitted
}
}
case vfsCapRevision3:
if err = binary.Read(b, binary.LittleEndian, &raw3); err != nil {
return nil, err
}
for i, data := range raw3.Data {
c.flat[i][Permitted] = data.Permitted
c.flat[i][Inheritable] = data.Inheritable
if raw3.MagicEtc&vfsCapFlagsMask == vfsCapFlagsEffective {
c.flat[i][Effective] = data.Inheritable | data.Permitted
}
}
c.nsRoot = int(raw3.RootID)
default:
return nil, ErrBadMagic
}
return c, nil
}
//go:uintptrescapes
// GetFd returns the file capabilities of an open (*os.File).Fd().
func GetFd(file *os.File) (*Set, error) {
var raw3 vfsCaps3
d := make([]byte, binary.Size(raw3))
sz, _, oErr := multisc.r6(syscall.SYS_FGETXATTR, uintptr(file.Fd()), uintptr(unsafe.Pointer(xattrNameCaps)), uintptr(unsafe.Pointer(&d[0])), uintptr(len(d)), 0, 0)
var err error
if oErr != 0 {
err = oErr
}
return digestFileCap(d, int(sz), err)
}
//go:uintptrescapes
// GetFile returns the file capabilities of a named file.
func GetFile(path string) (*Set, error) {
p, err := syscall.BytePtrFromString(path)
if err != nil {
return nil, err
}
var raw3 vfsCaps3
d := make([]byte, binary.Size(raw3))
sz, _, oErr := multisc.r6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(xattrNameCaps)), uintptr(unsafe.Pointer(&d[0])), uintptr(len(d)), 0, 0)
if oErr != 0 {
err = oErr
}
return digestFileCap(d, int(sz), err)
}
// GetNSOwner returns the namespace owner UID of the capability Set.
func (c *Set) GetNSOwner() (int, error) {
if magic < kv3 {
return 0, ErrBadMagic
}
c.mu.RLock()
defer c.mu.RUnlock()
return c.nsRoot, nil
}
// SetNSOwner adds an explicit namespace owner UID to the capability
// Set. This is only honored when generating file capabilities, and is
// generally for use by a setup process when installing binaries that
// use file capabilities to become capable inside a namespace to be
// administered by that UID. If capability aware code within that
// namespace writes file capabilities without explicitly setting such
// a UID, the kernel will fix-up the capabilities to be specific to
// that owner. In this way, the kernel prevents filesystem
// capabilities from leaking out of that restricted namespace.
func (c *Set) SetNSOwner(uid int) {
c.mu.Lock()
defer c.mu.Unlock()
c.nsRoot = uid
}
// packFileCap transforms a system capability into a VFS form. Because
// of the way Linux stores capabilities in the file extended
// attributes, the process is a little lossy with respect to effective
// bits.
func (c *Set) packFileCap() ([]byte, error) {
c.mu.RLock()
defer c.mu.RUnlock()
var magic uint32
switch words {
case 1:
if c.nsRoot != 0 {
return nil, ErrBadSet // nsRoot not supported for single DWORD caps.
}
magic = vfsCapRevision1
case 2:
if c.nsRoot == 0 {
magic = vfsCapRevision2
break
}
magic = vfsCapRevision3
}
if magic == 0 {
return nil, ErrBadSize
}
eff := uint32(0)
for _, f := range c.flat {
eff |= (f[Permitted] | f[Inheritable]) & f[Effective]
}
if eff != 0 {
magic |= vfsCapFlagsEffective
}
b := new(bytes.Buffer)
binary.Write(b, binary.LittleEndian, magic)
for _, f := range c.flat {
binary.Write(b, binary.LittleEndian, f[Permitted])
binary.Write(b, binary.LittleEndian, f[Inheritable])
}
if c.nsRoot != 0 {
binary.Write(b, binary.LittleEndian, c.nsRoot)
}
return b.Bytes(), nil
}
//go:uintptrescapes
// SetFd attempts to set the file capabilities of an open
// (*os.File).Fd(). This function can also be used to delete a file's
// capabilities, by calling with c = nil.
//
// Note, Linux does not store the full Effective Flag in the metadata
// for the file. Only a single Effective bit is stored in this
// metadata. This single bit is non-zero if the Effective Flag has any
// overlapping bits with the Permitted or Inheritable Flags of c. This
// may appear suboptimal, but the reasoning behind it is sound.
// Namely, the purpose of the Effective bit it to support capabability
// unaware binaries that will only work if they magically launch with
// the needed Values already raised (this bit is sometimes referred to
// simply as the 'legacy' bit).
//
// Historical note: without *full* support for runtime capability
// manipulation, as it is provided in this "../libcap/cap" package,
// this was previously the only way for Go programs to make use of
// file capabilities.
//
// The preferred way that a binary will actually manipulate its
// file-acquired capabilities is to carefully and deliberately use
// this package (or libcap, assisted by libpsx, for threaded C/C++
// family code).
func (c *Set) SetFd(file *os.File) error {
if c == nil {
if _, _, err := multisc.r6(syscall.SYS_FREMOVEXATTR, uintptr(file.Fd()), uintptr(unsafe.Pointer(xattrNameCaps)), 0, 0, 0, 0); err != 0 {
return err
}
return nil
}
c.mu.RLock()
defer c.mu.RUnlock()
d, err := c.packFileCap()
if err != nil {
return err
}
if _, _, err := multisc.r6(syscall.SYS_FSETXATTR, uintptr(file.Fd()), uintptr(unsafe.Pointer(xattrNameCaps)), uintptr(unsafe.Pointer(&d[0])), uintptr(len(d)), 0, 0); err != 0 {
return err
}
return nil
}
//go:uintptrescapes
// 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.
//
// Note, see the comment for SetFd() for some non-obvious behavior of
// Linux for the Effective Flag on the modified file.
func (c *Set) SetFile(path string) error {
fi, err := os.Stat(path)
if err != nil {
return err
}
mode := fi.Mode()
if mode&os.ModeType != 0 {
return ErrBadPath
}
if mode&os.FileMode(0111) == 0 {
return ErrBadPath
}
p, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
if c == nil {
if _, _, err := multisc.r6(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(xattrNameCaps)), 0, 0, 0, 0); err != 0 {
return err
}
return nil
}
c.mu.RLock()
defer c.mu.RUnlock()
d, err := c.packFileCap()
if err != nil {
return err
}
if _, _, err := multisc.r6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(xattrNameCaps)), uintptr(unsafe.Pointer(&d[0])), uintptr(len(d)), 0, 0); err != 0 {
return err
}
return nil
}
// ExtMagic is the 32-bit (little endian) magic for an external
// capability set. It can be used to transmit capabilities in binary
// format in a Linux portable way. The format is:
// <ExtMagic><byte:length><length-bytes*3-of-cap-data>.
const ExtMagic = uint32(0x5101c290)
// Import imports a Set from a byte array where it has been stored in
// 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
if err := binary.Read(b, binary.LittleEndian, &m); err != nil {
return nil, ErrBadSize
} else if m != ExtMagic {
return nil, ErrBadMagic
}
var n byte
if err := binary.Read(b, binary.LittleEndian, &n); err != nil {
return nil, ErrBadSize
}
c := NewSet()
if int(n) > 4*words {
return nil, ErrBadSize
}
f := make([]byte, 3)
for i := 0; i < words; i++ {
for j := uint(0); n > 0 && j < 4; j++ {
n--
if x, err := b.Read(f); err != nil || x != 3 {
return nil, ErrBadSize
}
sh := 8 * j
c.flat[i][Effective] |= uint32(f[0]) << sh
c.flat[i][Permitted] |= uint32(f[1]) << sh
c.flat[i][Inheritable] |= uint32(f[2]) << sh
}
}
return c, nil
}
// MinExtFlagSize defaults to 8 in order to be equivalent to libcap
// defaults. 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 err := c.good(); err != nil {
return nil, err
}
if MinExtFlagSize > 255 {
return nil, ErrOutOfRange
}
b := new(bytes.Buffer)
binary.Write(b, binary.LittleEndian, ExtMagic)
c.mu.RLock()
defer c.mu.RUnlock()
var n = uint(0)
for i, f := range c.flat {
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++
}
}
}
}
if n < MinExtFlagSize {
n = MinExtFlagSize
}
b.Write([]byte{byte(n)})
for _, f := range c.flat {
if n == 0 {
break
}
eff, per, inh := f[Effective], f[Permitted], f[Inheritable]
for i := 0; n > 0 && i < 4; i++ {
n--
b.Write([]byte{
byte(eff & 0xff),
byte(per & 0xff),
byte(inh & 0xff),
})
eff >>= 8
per >>= 8
inh >>= 8
}
}
for n > 0 {
n--
b.Write([]byte{0, 0, 0})
}
return b.Bytes(), nil
}

View File

@@ -0,0 +1,271 @@
package cap
import "errors"
// GetFlag determines if the requested Value is enabled in the
// specified Flag of the capability Set.
func (c *Set) GetFlag(vec Flag, val Value) (bool, error) {
if err := c.good(); err != nil {
// Checked this first, because otherwise we are sure
// cInit has been called.
return false, err
}
offset, mask, err := bitOf(vec, val)
if err != nil {
return false, err
}
c.mu.RLock()
defer c.mu.RUnlock()
return c.flat[offset][vec]&mask != 0, nil
}
// SetFlag sets the requested bits to the indicated enable state. This
// function does not perform any security checks, so values can be set
// out-of-order. Only when the Set is used to SetProc() etc., will the
// bits be checked for validity and permission by the kernel. If the
// function returns an error, the Set will not be modified.
func (c *Set) SetFlag(vec Flag, enable bool, val ...Value) error {
if err := c.good(); err != nil {
// Checked this first, because otherwise we are sure
// cInit has been called.
return err
}
c.mu.Lock()
defer c.mu.Unlock()
// Make a backup.
replace := make([]uint32, words)
for i := range replace {
replace[i] = c.flat[i][vec]
}
var err error
for _, v := range val {
offset, mask, err2 := bitOf(vec, v)
if err2 != nil {
err = err2
break
}
if enable {
c.flat[offset][vec] |= mask
} else {
c.flat[offset][vec] &= ^mask
}
}
if err == nil {
return nil
}
// Clean up.
for i, bits := range replace {
c.flat[i][vec] = bits
}
return err
}
// Clear fully clears a capability set.
func (c *Set) Clear() error {
if err := c.good(); err != nil {
return err
}
// startUp.Do(cInit) is not called here because c cannot be
// initialized except via this package and doing that will
// perform that call at least once (sic).
c.mu.Lock()
defer c.mu.Unlock()
c.flat = make([]data, words)
c.nsRoot = 0
return nil
}
// FillFlag copies the from flag values of ref into the to flag of
// c. With this function, you can raise all of the permitted values in
// the c Set from those in ref with c.Fill(cap.Permitted, ref,
// cap.Permitted).
func (c *Set) FillFlag(to Flag, ref *Set, from Flag) error {
if err := c.good(); err != nil {
return err
}
if err := ref.good(); err != nil {
return err
}
if to > Inheritable || from > Inheritable {
return ErrBadValue
}
// Avoid deadlock by using a copy.
if c != ref {
var err error
ref, err = ref.Dup()
if err != nil {
return err
}
}
c.mu.Lock()
defer c.mu.Unlock()
for i := range c.flat {
c.flat[i][to] = ref.flat[i][from]
}
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 {
return c.FillFlag(to, c, from)
}
// ErrBadValue indicates a bad capability value was specified.
var ErrBadValue = errors.New("bad capability value")
// bitOf converts from a Value into the offset and mask for a specific
// Value bit in the compressed (kernel ABI) representation of a
// capabilities. If the requested bit is unsupported, an error is
// returned.
func bitOf(vec Flag, val Value) (uint, uint32, error) {
if vec > Inheritable || val > Value(words*32) {
return 0, 0, ErrBadValue
}
u := uint(val)
return u / 32, uint32(1) << (u % 32), nil
}
// allMask returns the mask of valid bits in the all mask for index.
func allMask(index uint) (mask uint32) {
if maxValues == 0 {
panic("uninitialized package")
}
base := 32 * uint(index)
if maxValues <= base {
return
}
if maxValues >= 32+base {
mask = ^mask
return
}
mask = uint32((uint64(1) << (maxValues % 32)) - 1)
return
}
// forceFlag sets 'all' capability values (supported by the kernel) of
// a specified Flag to enable.
func (c *Set) forceFlag(vec Flag, enable bool) error {
if err := c.good(); err != nil {
return err
}
if vec > Inheritable {
return ErrBadSet
}
m := uint32(0)
if enable {
m = ^m
}
c.mu.Lock()
defer c.mu.Unlock()
for i := range c.flat {
c.flat[i][vec] = m & allMask(uint(i))
}
return nil
}
// ClearFlag clears all the Values associated with the specified Flag.
func (c *Set) ClearFlag(vec Flag) error {
return c.forceFlag(vec, false)
}
// Cf returns 0 if c and d are identical. A non-zero Diff value
// captures a simple macroscopic summary of how they differ. The
// (Diff).Has() function can be used to determine how the two
// capability sets differ.
func (c *Set) Cf(d *Set) (Diff, error) {
if err := c.good(); err != nil {
return 0, err
}
if c == d {
return 0, nil
}
d, err := d.Dup()
if err != nil {
return 0, err
}
c.mu.RLock()
defer c.mu.RUnlock()
var cf Diff
for i := 0; i < words; i++ {
if c.flat[i][Effective]^d.flat[i][Effective] != 0 {
cf |= effectiveDiff
}
if c.flat[i][Permitted]^d.flat[i][Permitted] != 0 {
cf |= permittedDiff
}
if c.flat[i][Inheritable]^d.flat[i][Inheritable] != 0 {
cf |= inheritableDiff
}
}
return cf, nil
}
// Compare returns 0 if c and d are identical in content.
//
// Deprecated: Replace with (*Set).Cf().
//
// Example, replace this:
//
// diff, err := a.Compare(b)
// if err != nil {
// return err
// }
// if diff == 0 {
// return nil
// }
// if diff & (1 << Effective) {
// log.Print("a and b difference includes Effective values")
// }
//
// with this:
//
// diff, err := a.Cf(b)
// if err != nil {
// return err
// }
// if diff == 0 {
// return nil
// }
// if diff.Has(Effective) {
// log.Print("a and b difference includes Effective values")
// }
func (c *Set) Compare(d *Set) (uint, error) {
u, err := c.Cf(d)
return uint(u), err
}
// Differs processes the result of Compare and determines if the
// Flag's components were different.
//
// Deprecated: Replace with (Diff).Has().
//
// Example, replace this:
//
// diff, err := a.Compare(b)
// ...
// if diff & (1 << Effective) {
// ... different effective capabilities ...
// }
//
// with this:
//
// diff, err := a.Cf(b)
// ...
// if diff.Has(Effective) {
// ... different effective capabilities ...
// }
func Differs(cf uint, vec Flag) bool {
return cf&(1<<vec) != 0
}
// Has processes the Diff result of (*Set).Cf() and determines if the
// Flag's components were different in that result.
func (cf Diff) Has(vec Flag) bool {
return uint(cf)&(1<<vec) != 0
}

View File

@@ -0,0 +1,483 @@
package cap
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
"sync"
)
// omask returns the offset and mask for a specific capability.
func omask(c Value) (uint, uint32) {
u := uint(c)
return u >> 5, uint32(1) << (u & 31)
}
// IAB holds a summary of all of the inheritable capability vectors:
// Inh, Amb and Bound. The Bound vector is the logical inverse (two's
// complement) of the process' Bounding set. That is, raising a Value
// in the Bound (think blocked) vector is equivalent to dropping that
// Value from the process' Bounding set. This convention is used to
// support the empty IAB as being mostly harmless.
type IAB struct {
mu sync.RWMutex
a, i, nb []uint32
}
// Vector enumerates which of the inheritable IAB capability vectors
// is being manipulated.
type Vector uint
// Inh, Amb, Bound enumerate the IAB vector components. (Vector) Inh
// is equivalent to (Flag) Inheritable. They are named differently for
// syntax/type checking reasons.
const (
Inh Vector = iota
Amb
Bound
)
// IABDiff holds the non-error result of an (*IAB).Cf()
// function call. It can be interpreted with the function
// (IABDiff).Has().
type IABDiff uint
// iBits, iBits and bBits track the (semi-)independent parts of an
// IABDiff.
const (
iBits IABDiff = 1 << Inh
aBits IABDiff = 1 << Amb
bBits IABDiff = 1 << Bound
)
// Has determines if an IAB comparison differs in a specific vector.
func (d IABDiff) Has(v Vector) bool {
return d&(1<<v) != 0
}
// String identifies a Vector value by its conventional I A or B
// string abbreviation.
func (v Vector) String() string {
switch v {
case Inh:
return "I"
case Amb:
return "A"
case Bound:
return "B"
default:
return "<Error>"
}
}
// NewIAB returns an empty IAB.
func NewIAB() *IAB {
startUp.Do(multisc.cInit)
return &IAB{
i: make([]uint32, words),
a: make([]uint32, words),
nb: make([]uint32, words),
}
}
// good confirms the iab looks to be initialized.
func (iab *IAB) good() error {
if iab == nil || len(iab.i) == 0 || len(iab.i) != words || len(iab.a) != words || len(iab.nb) != words {
return ErrBadValue
}
return nil
}
// Dup returns a duplicate copy of the IAB.
func (iab *IAB) Dup() (*IAB, error) {
if err := iab.good(); err != nil {
return nil, err
}
v := NewIAB()
iab.mu.RLock()
defer iab.mu.RUnlock()
copy(v.i, iab.i)
copy(v.a, iab.a)
copy(v.nb, iab.nb)
return v, nil
}
// IABInit allocates a new IAB tuple.
//
// Deprecated: Replace with NewIAB.
//
// Example, replace this:
//
// iab := IABInit()
//
// with this:
//
// iab := NewIAB()
func IABInit() *IAB {
return NewIAB()
}
// IABGetProc summarizes the Inh, Amb and Bound capability vectors of
// the current process.
func IABGetProc() *IAB {
iab := NewIAB()
current := GetProc()
iab.Fill(Inh, current, Inheritable)
for c := MaxBits(); c > 0; {
c--
offset, mask := omask(c)
if a, _ := GetAmbient(c); a {
iab.a[offset] |= mask
}
if b, err := GetBound(c); err == nil && !b {
iab.nb[offset] |= mask
}
}
return iab
}
// IABFromText parses a string representing an IAB, as generated
// by IAB.String(), to generate an IAB.
func IABFromText(text string) (*IAB, error) {
iab := NewIAB()
if len(text) == 0 {
return iab, nil
}
for _, f := range strings.Split(text, ",") {
var i, a, nb bool
var j int
for j = 0; j < len(f); j++ {
switch f[j : j+1] {
case "!":
nb = true
case "^":
i = true
a = true
case "%":
i = true
default:
goto done
}
}
done:
c, err := FromName(f[j:])
if err != nil {
return nil, err
}
offset, mask := omask(c)
if i || !nb {
iab.i[offset] |= mask
}
if a {
iab.a[offset] |= mask
}
if nb {
iab.nb[offset] |= mask
}
}
return iab, nil
}
// String serializes an IAB to a string format.
func (iab *IAB) String() string {
if err := iab.good(); err != nil {
return "<invalid>"
}
var vs []string
iab.mu.RLock()
defer iab.mu.RUnlock()
for c := Value(0); c < Value(maxValues); c++ {
offset, mask := omask(c)
i := (iab.i[offset] & mask) != 0
a := (iab.a[offset] & mask) != 0
nb := (iab.nb[offset] & mask) != 0
var cs []string
if nb {
cs = append(cs, "!")
}
if a {
cs = append(cs, "^")
} else if nb && i {
cs = append(cs, "%")
}
if nb || a || i {
vs = append(vs, strings.Join(cs, "")+c.String())
}
}
return strings.Join(vs, ",")
}
// iabSetProc uses a syscaller to apply an IAB tuple to the process.
// The iab is known to be locked by the caller.
func (sc *syscaller) iabSetProc(iab *IAB) (err error) {
temp := GetProc()
var raising uint32
for i := 0; i < words; i++ {
newI := iab.i[i]
oldIP := temp.flat[i][Inheritable] | temp.flat[i][Permitted]
raising |= (newI & ^oldIP) | iab.a[i] | iab.nb[i]
temp.flat[i][Inheritable] = newI
}
working, err2 := temp.Dup()
if err2 != nil {
err = err2
return
}
if raising != 0 {
if err = working.SetFlag(Effective, true, SETPCAP); err != nil {
return
}
if err = sc.setProc(working); err != nil {
return
}
}
defer func() {
if err2 := sc.setProc(temp); err == nil {
err = err2
}
}()
if err = sc.resetAmbient(); err != nil {
return
}
for c := Value(maxValues); c > 0; {
c--
offset, mask := omask(c)
if iab.a[offset]&mask != 0 {
err = sc.setAmbient(true, c)
}
if err == nil && iab.nb[offset]&mask != 0 {
err = sc.dropBound(c)
}
if err != nil {
return
}
}
return
}
// SetProc attempts to change the Inheritable, Ambient and Bounding
// 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 combined
// operation in the most flexible manner.
func (iab *IAB) SetProc() error {
if err := iab.good(); err != nil {
return err
}
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
iab.mu.RLock()
defer iab.mu.RUnlock()
return sc.iabSetProc(iab)
}
// GetVector returns the raised state of the specific capability bit
// of the indicated vector.
func (iab *IAB) GetVector(vec Vector, val Value) (bool, error) {
if err := iab.good(); err != nil {
return false, err
}
if val >= MaxBits() {
return false, ErrBadValue
}
iab.mu.RLock()
defer iab.mu.RUnlock()
offset, mask := omask(val)
switch vec {
case Inh:
return (iab.i[offset] & mask) != 0, nil
case Amb:
return (iab.a[offset] & mask) != 0, nil
case Bound:
return (iab.nb[offset] & mask) != 0, nil
default:
return false, ErrBadValue
}
}
// SetVector sets all of the vals in the specified vector to the
// raised value. Note, the Ambient vector cannot contain values not raised
// in the Inh vector, so setting values directly in one vector may have
// the side effect of mirroring the value in the other vector to
// maintain this constraint. Note, raising a Bound vector bit is
// equivalent to lowering the Bounding vector of the process (when
// successfully applied with (*IAB).SetProc()).
func (iab *IAB) SetVector(vec Vector, raised bool, vals ...Value) error {
if err := iab.good(); err != nil {
return err
}
iab.mu.Lock()
defer iab.mu.Unlock()
for _, val := range vals {
if val >= Value(maxValues) {
return ErrBadValue
}
offset, mask := omask(val)
switch vec {
case Inh:
if raised {
iab.i[offset] |= mask
} else {
iab.i[offset] &= ^mask
iab.a[offset] &= ^mask
}
case Amb:
if raised {
iab.a[offset] |= mask
iab.i[offset] |= mask
} else {
iab.a[offset] &= ^mask
}
case Bound:
if raised {
iab.nb[offset] |= mask
} else {
iab.nb[offset] &= ^mask
}
default:
return ErrBadValue
}
}
return nil
}
// Fill fills one of the Inh, Amb and Bound capability vectors from
// one of the flag vectors of a Set. Note, filling the Inh vector
// will mask the Amb vector, and filling the Amb vector may raise
// entries in the Inh vector. Further, when filling the Bound vector,
// the bits are inverted from what you might expect - that is lowered
// bits from the Set will be raised in the Bound vector.
func (iab *IAB) Fill(vec Vector, c *Set, flag Flag) error {
if err := iab.good(); err != nil {
return err
}
// work with a copy to avoid potential deadlock.
s, err := c.Dup()
if err != nil {
return err
}
iab.mu.Lock()
defer iab.mu.Unlock()
for i := 0; i < words; i++ {
flat := s.flat[i][flag]
switch vec {
case Inh:
iab.i[i] = flat
iab.a[i] &= flat
case Amb:
iab.a[i] = flat
iab.i[i] |= flat
case Bound:
iab.nb[i] = ^flat
default:
return ErrBadSet
}
}
return nil
}
// Cf compares two IAB values. Its return value is 0 if the compared
// tuples are considered identical. The macroscopic differences can be
// investigated with (IABDiff).Has().
func (iab *IAB) Cf(alt *IAB) (IABDiff, error) {
if err := iab.good(); err != nil {
return 0, err
}
if iab == alt {
return 0, nil
}
// Avoid holding two locks at once.
ref, err := alt.Dup()
if err != nil {
return 0, err
}
iab.mu.RLock()
defer iab.mu.RUnlock()
var cf IABDiff
for i := 0; i < words; i++ {
if iab.i[i] != ref.i[i] {
cf |= iBits
}
if iab.a[i] != ref.a[i] {
cf |= aBits
}
if iab.nb[i] != ref.nb[i] {
cf |= bBits
}
}
return cf, nil
}
// parseHex converts the /proc/*/status string into an array of
// uint32s suitable for storage in an IAB structure.
func parseHex(hex string, invert bool) []uint32 {
if len(hex) != 8*words {
// Invalid string
return nil
}
var result []uint32
for i := 0; i < words; i++ {
upper := 8 * (words - i)
raw, err := strconv.ParseUint(hex[upper-8:upper], 16, 32)
if err != nil {
return nil
}
if invert {
raw = ^raw
}
bits := allMask(uint(i)) & uint32(raw)
result = append(result, bits)
}
return result
}
var procRoot = "/proc"
// ProcRoot sets the local mount point for the Linux /proc filesystem.
// It defaults to "/proc", but might be mounted elsewhere on any given
// system. The function returns the previous value of the local mount
// point. If the user attempts to set it to "", the value is left
// unchanged.
func ProcRoot(path string) string {
was := procRoot
if path != "" {
procRoot = path
}
return was
}
// IABGetPID returns the IAB tuple of a specified process. The kernel
// ABI does not support this query via system calls, so the function
// works by parsing the /proc/<pid>/status file content.
func IABGetPID(pid int) (*IAB, error) {
tf := fmt.Sprintf("%s/%d/status", procRoot, pid)
d, err := ioutil.ReadFile(tf)
if err != nil {
return nil, err
}
iab := &IAB{}
for _, line := range strings.Split(string(d), "\n") {
if !strings.HasPrefix(line, "Cap") {
continue
}
flavor := line[3:]
if strings.HasPrefix(flavor, "Inh:\t") {
iab.i = parseHex(line[8:], false)
continue
}
if strings.HasPrefix(flavor, "Bnd:\t") {
iab.nb = parseHex(line[8:], true)
continue
}
if strings.HasPrefix(flavor, "Amb:\t") {
iab.a = parseHex(line[8:], false)
continue
}
}
if len(iab.i) != words || len(iab.a) != words || len(iab.nb) != words {
return nil, ErrBadValue
}
return iab, nil
}

View File

@@ -0,0 +1,423 @@
package cap
import (
"errors"
"os"
"runtime"
"sync"
"syscall"
"unsafe"
)
// 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 {
mu sync.RWMutex
// 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
changeGIDs bool
gid int
groups []int
changeMode bool
mode Mode
iab *IAB
chroot string
}
// NewLauncher returns a new launcher for the specified program path
// and args with the specified environment.
func NewLauncher(path string, args []string, env []string) *Launcher {
return &Launcher{
path: path,
args: args,
env: env,
}
}
// 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) {
if attr == nil {
return
}
attr.mu.Lock()
defer attr.mu.Unlock()
attr.callbackFn = fn
}
// SetUID specifies the UID to be used by the launched command.
func (attr *Launcher) SetUID(uid int) {
if attr == nil {
return
}
attr.mu.Lock()
defer attr.mu.Unlock()
attr.changeUIDs = true
attr.uid = uid
}
// SetGroups specifies the GID and supplementary groups for the
// launched command.
func (attr *Launcher) SetGroups(gid int, groups []int) {
if attr == nil {
return
}
attr.mu.Lock()
defer attr.mu.Unlock()
attr.changeGIDs = true
attr.gid = gid
attr.groups = groups
}
// SetMode specifies the libcap Mode to be used by the launched command.
func (attr *Launcher) SetMode(mode Mode) {
if attr == nil {
return
}
attr.mu.Lock()
defer attr.mu.Unlock()
attr.changeMode = true
attr.mode = mode
}
// SetIAB specifies the IAB capability vectors to be inherited by the
// launched command. A nil value means the prevailing vectors of the
// parent will be inherited. Note, a duplicate of the provided IAB
// tuple is actually stored, so concurrent modification of the iab
// value does not affect the launcher.
func (attr *Launcher) SetIAB(iab *IAB) {
if attr == nil {
return
}
attr.mu.Lock()
defer attr.mu.Unlock()
attr.iab, _ = iab.Dup()
}
// SetChroot specifies the chroot value to be used by the launched
// command. An empty value means no-change from the prevailing value.
func (attr *Launcher) SetChroot(root string) {
if attr == nil {
return
}
attr.mu.Lock()
defer attr.mu.Unlock()
attr.chroot = root
}
// lResult is used to get the result from the doomed launcher thread.
type lResult struct {
// tgid holds the thread group id, which is an alias for the
// shared process id of the parent program.
tgid int
// 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
}
// ErrLaunchFailed is returned if a launch was aborted with no more
// specific error.
var ErrLaunchFailed = errors.New("launch failed")
// ErrNoLaunch indicates the go runtime available to this binary does
// not reliably support launching. See cap.LaunchSupported.
var ErrNoLaunch = errors.New("launch not supported")
// ErrAmbiguousChroot indicates that the Launcher is being used in
// addition to a callback supplied Chroot. The former should be used
// exclusively for this.
var ErrAmbiguousChroot = errors.New("use Launcher for chroot")
// ErrAmbiguousIDs indicates that the Launcher is being used in
// addition to a callback supplied Credentials. The former should be
// used exclusively for this.
var ErrAmbiguousIDs = errors.New("use Launcher for uids and gids")
// ErrAmbiguousAmbient indicates that the Launcher is being used in
// addition to a callback supplied ambient set and the former should
// be used exclusively in a Launch call.
var ErrAmbiguousAmbient = errors.New("use Launcher for ambient caps")
// lName is the name we temporarily give to the launcher thread. Note,
// this will likely stick around in the process tree if the Go runtime
// is not cleaning up locked launcher OS threads.
var lName = []byte("cap-launcher\000")
// <uapi/linux/prctl.h>
const prSetName = 15
//go:uintptrescapes
func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<- struct{}) {
if quit != nil {
defer close(quit)
}
// Thread group ID is the process ID.
tgid := syscall.Getpid()
// 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 == tgid {
// 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)
// Wait for that go routine to complete.
<-quit
runtime.UnlockOSThread()
return
}
// Provide a way to serialize the caller on the thread
// completing. This should be done by the one locked tid that
// does the ForkExec(). All the other threads have a different
// security context.
defer close(result)
// 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.
singlesc.prctlrcall(prSetName, uintptr(unsafe.Pointer(&lName[0])), 0)
var pa *syscall.ProcAttr
var err error
var needChroot bool
// 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()
}
}
var pid int
if attr.callbackFn != nil {
if err = attr.callbackFn(pa, data); err != nil {
goto abort
}
if attr.path == "" {
goto abort
}
}
if needChroot, err = validatePA(pa, attr.chroot); err != nil {
goto abort
}
if attr.changeUIDs {
if err = singlesc.setUID(attr.uid); err != nil {
goto abort
}
}
if attr.changeGIDs {
if err = singlesc.setGroups(attr.gid, attr.groups); err != nil {
goto abort
}
}
if attr.changeMode {
if err = singlesc.setMode(attr.mode); err != nil {
goto abort
}
}
if attr.iab != nil {
// Note, since .iab is a private copy we don't need to
// lock it around this call.
if err = singlesc.iabSetProc(attr.iab); err != nil {
goto abort
}
}
if needChroot {
c := GetProc()
if err = c.SetFlag(Effective, true, SYS_CHROOT); err != nil {
goto abort
}
if err = singlesc.setProc(c); err != nil {
goto abort
}
}
pid, err = syscall.ForkExec(attr.path, attr.args, pa)
abort:
if err != nil {
pid = -1
}
result <- lResult{
tgid: tgid,
tid: tid,
pid: pid,
err: err,
}
}
// pollForThreadExit waits for a thread to terminate. Only after the
// thread has safely exited is it safe to resume POSIX semantics
// security state mirroring for the rest of the process threads.
func (v lResult) pollForThreadExit() {
if v.tid == -1 {
return
}
for syscall.Tgkill(v.tgid, v.tid, 0) == nil {
runtime.Gosched()
}
scwSetState(launchActive, launchIdle, v.tid)
}
// 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 function's 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
// operations 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 !LaunchSupported {
return -1, ErrNoLaunch
}
if attr == nil {
return -1, ErrLaunchFailed
}
attr.mu.RLock()
defer attr.mu.RUnlock()
if attr.callbackFn == nil && (attr.path == "" || len(attr.args) == 0) {
return -1, ErrLaunchFailed
}
result := make(chan lResult)
go launch(result, attr, data, nil)
v, ok := <-result
if !ok {
return -1, ErrLaunchFailed
}
<-result // blocks until the launch() goroutine exits
v.pollForThreadExit()
return v.pid, v.err
}

View File

@@ -0,0 +1,7 @@
// +build linux,arm linux,386
package cap
import "syscall"
var sysSetGroupsVariant = uintptr(syscall.SYS_SETGROUPS32)

View File

@@ -0,0 +1,8 @@
// +build linux,!arm
// +build linux,!386
package cap
import "syscall"
var sysSetGroupsVariant = uintptr(syscall.SYS_SETGROUPS)

View File

@@ -0,0 +1,436 @@
package cap
/* ** DO NOT EDIT THIS FILE. IT WAS AUTO-GENERATED BY LIBCAP'S GO BUILDER (mknames.go) ** */
// NamedCount holds the number of capability values with official
// names known at the time this libcap/cap version, was released. The
// "../libcap/cap" package is fully able to manipulate higher numbered
// capability values by numerical value. However, if you find
// cap.NamedCount < cap.MaxBits(), it is probably time to upgrade this
// package on your system.
//
// FWIW the userspace tool '/sbin/capsh' also contains a runtime check
// for the condition that libcap is behind the running kernel in this
// way.
const NamedCount = 41
// CHOWN etc., are the named capability values of the Linux
// kernel. The canonical source for each name is the
// "uapi/linux/capabilities.h" file. Some values may not be available
// (yet) where the kernel is older. The actual number of capabities
// supported by the running kernel can be obtained using the
// cap.MaxBits() function.
const (
// CHOWN allows a process to arbitrarily change the user and
// group ownership of a file.
CHOWN Value = iota
// DAC_OVERRIDE 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.
DAC_OVERRIDE
// DAC_READ_SEARCH 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.
DAC_READ_SEARCH
// FOWNER 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.
FOWNER
// FSETID 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.
FSETID
// 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
// SETGID 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
SETGID
// SETUID 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
SETUID
// SETPCAP allows a process to freely manipulate its inheritable
// capabilities.
//
// Linux supports the POSIX.1e Inheritable set, the POXIX.1e (X
// vector) known in Linux as the Bounding vector, as well as
// the Linux extension Ambient vector.
//
// This capability permits dropping bits from the Bounding
// vector (ie. raising B bits in the libcap IAB
// representation). 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, Effective bits beyond those
// already present in the process' permitted set, or
// Inheritable bits beyond those present in the Bounding
// vector.
//
// [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.]
SETPCAP
// LINUX_IMMUTABLE allows a process to modify the S_IMMUTABLE and
// S_APPEND file attributes.
LINUX_IMMUTABLE
// NET_BIND_SERVICE allows a process to bind to privileged ports:
// - TCP/UDP sockets below 1024
// - ATM VCIs below 32
NET_BIND_SERVICE
// NET_BROADCAST allows a process to broadcast to the network and to
// listen to multicast.
NET_BROADCAST
// NET_ADMIN 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
NET_ADMIN
// NET_RAW allows a process to use raw networking:
// - RAW sockets
// - PACKET sockets
// - binding to any address for transparent proxying
// (also permitted via cap.NET_ADMIN)
NET_RAW
// IPC_LOCK allows a process to lock shared memory segments for IPC
// purposes. Also enables mlock and mlockall system
// calls.
IPC_LOCK
// IPC_OWNER allows a process to override IPC ownership checks.
IPC_OWNER
// SYS_MODULE allows a process to initiate the loading and unloading
// of kernel modules. This capability can effectively
// modify kernel without limit.
SYS_MODULE
// SYS_RAWIO allows a process to perform raw IO:
// - permit ioper/iopl access
// - permit sending USB messages to any device via
// /dev/bus/usb
SYS_RAWIO
// SYS_CHROOT 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.
SYS_CHROOT
// SYS_PTRACE allows a process to perform a ptrace() of any other
// process.
SYS_PTRACE
// SYS_PACCT allows a process to configure process accounting.
SYS_PACCT
// SYS_ADMIN 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
SYS_ADMIN
// SYS_BOOT allows a process to initiate a reboot of the system.
SYS_BOOT
// SYS_NICE 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.
SYS_NICE
// SYS_RESOURCE 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
SYS_RESOURCE
// SYS_TIME allows a process to perform time manipulation of clocks:
// - alter the system clock
// - enable irix_stime on MIPS
// - set the real-time clock
SYS_TIME
// SYS_TTY_CONFIG allows a process to manipulate tty devices:
// - configure tty devices
// - perform vhangup() of a tty
SYS_TTY_CONFIG
// MKNOD allows a process to perform privileged operations with
// the mknod() system call.
MKNOD
// LEASE allows a process to take leases on files.
LEASE
// AUDIT_WRITE allows a process to write to the audit log via a
// unicast netlink socket.
AUDIT_WRITE
// AUDIT_CONTROL allows a process to configure audit logging via a
// unicast netlink socket.
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
// (MAC) access. Not all kernels are configured with a MAC
// mechanism, but this is the capability reserved for
// overriding them.
MAC_OVERRIDE
// MAC_ADMIN 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.
MAC_ADMIN
// SYSLOG allows a process to configure the kernel's syslog
// (printk) behavior.
SYSLOG
// WAKE_ALARM allows a process to trigger something that can wake the
// system up.
WAKE_ALARM
// BLOCK_SUSPEND allows a process to block system suspends - prevent the
// system from entering a lower power state.
BLOCK_SUSPEND
// AUDIT_READ allows a process to read the audit log via a multicast
// netlink socket.
AUDIT_READ
// PERFMON allows a process to enable observability of privileged
// operations related to performance. The mechanisms
// include perf_events, i915_perf and other kernel
// subsystems.
PERFMON
// BPF 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.
BPF
// CHECKPOINT_RESTORE allows a process to perform checkpoint
// and restore operations. Also permits
// explicit PID control via clone3() and
// also writing to ns_last_pid.
CHECKPOINT_RESTORE
)
var names = map[Value]string{
CHOWN: "cap_chown",
DAC_OVERRIDE: "cap_dac_override",
DAC_READ_SEARCH: "cap_dac_read_search",
FOWNER: "cap_fowner",
FSETID: "cap_fsetid",
KILL: "cap_kill",
SETGID: "cap_setgid",
SETUID: "cap_setuid",
SETPCAP: "cap_setpcap",
LINUX_IMMUTABLE: "cap_linux_immutable",
NET_BIND_SERVICE: "cap_net_bind_service",
NET_BROADCAST: "cap_net_broadcast",
NET_ADMIN: "cap_net_admin",
NET_RAW: "cap_net_raw",
IPC_LOCK: "cap_ipc_lock",
IPC_OWNER: "cap_ipc_owner",
SYS_MODULE: "cap_sys_module",
SYS_RAWIO: "cap_sys_rawio",
SYS_CHROOT: "cap_sys_chroot",
SYS_PTRACE: "cap_sys_ptrace",
SYS_PACCT: "cap_sys_pacct",
SYS_ADMIN: "cap_sys_admin",
SYS_BOOT: "cap_sys_boot",
SYS_NICE: "cap_sys_nice",
SYS_RESOURCE: "cap_sys_resource",
SYS_TIME: "cap_sys_time",
SYS_TTY_CONFIG: "cap_sys_tty_config",
MKNOD: "cap_mknod",
LEASE: "cap_lease",
AUDIT_WRITE: "cap_audit_write",
AUDIT_CONTROL: "cap_audit_control",
SETFCAP: "cap_setfcap",
MAC_OVERRIDE: "cap_mac_override",
MAC_ADMIN: "cap_mac_admin",
SYSLOG: "cap_syslog",
WAKE_ALARM: "cap_wake_alarm",
BLOCK_SUSPEND: "cap_block_suspend",
AUDIT_READ: "cap_audit_read",
PERFMON: "cap_perfmon",
BPF: "cap_bpf",
CHECKPOINT_RESTORE: "cap_checkpoint_restore",
}
var bits = map[string]Value{
"cap_chown": CHOWN,
"cap_dac_override": DAC_OVERRIDE,
"cap_dac_read_search": DAC_READ_SEARCH,
"cap_fowner": FOWNER,
"cap_fsetid": FSETID,
"cap_kill": KILL,
"cap_setgid": SETGID,
"cap_setuid": SETUID,
"cap_setpcap": SETPCAP,
"cap_linux_immutable": LINUX_IMMUTABLE,
"cap_net_bind_service": NET_BIND_SERVICE,
"cap_net_broadcast": NET_BROADCAST,
"cap_net_admin": NET_ADMIN,
"cap_net_raw": NET_RAW,
"cap_ipc_lock": IPC_LOCK,
"cap_ipc_owner": IPC_OWNER,
"cap_sys_module": SYS_MODULE,
"cap_sys_rawio": SYS_RAWIO,
"cap_sys_chroot": SYS_CHROOT,
"cap_sys_ptrace": SYS_PTRACE,
"cap_sys_pacct": SYS_PACCT,
"cap_sys_admin": SYS_ADMIN,
"cap_sys_boot": SYS_BOOT,
"cap_sys_nice": SYS_NICE,
"cap_sys_resource": SYS_RESOURCE,
"cap_sys_time": SYS_TIME,
"cap_sys_tty_config": SYS_TTY_CONFIG,
"cap_mknod": MKNOD,
"cap_lease": LEASE,
"cap_audit_write": AUDIT_WRITE,
"cap_audit_control": AUDIT_CONTROL,
"cap_setfcap": SETFCAP,
"cap_mac_override": MAC_OVERRIDE,
"cap_mac_admin": MAC_ADMIN,
"cap_syslog": SYSLOG,
"cap_wake_alarm": WAKE_ALARM,
"cap_block_suspend": BLOCK_SUSPEND,
"cap_audit_read": AUDIT_READ,
"cap_perfmon": PERFMON,
"cap_bpf": BPF,
"cap_checkpoint_restore": CHECKPOINT_RESTORE,
}

View File

@@ -0,0 +1,33 @@
// +build !go1.10
package cap
import "syscall"
// LaunchSupported indicates that is safe to return from a locked OS
// Thread and have that OS Thread be terminated by the runtime. The
// Launch functionality really needs to rely on the fact that an
// excess of runtime.LockOSThread() vs. runtime.UnlockOSThread() calls
// in a returning go routine will cause the underlying locked OSThread
// to terminate. That feature was added to the Go runtime in version
// 1.10.
//
// See these bugs for the discussion and feature assumed by the code
// in this Launch() functionality:
//
// https://github.com/golang/go/issues/20395
// https://github.com/golang/go/issues/20458
//
// A value of false for this constant causes the Launch functionality
// to fail with an error: cap.ErrNoLaunch. If this value is false you
// have two choices with respect to the Launch functionality:
//
// 1) don't use cap.(*Launcher).Launch()
// 2) upgrade your Go toolchain to 1.10+ (ie., do this one).
const LaunchSupported = false
// validatePA confirms that the pa.Sys entry is not incompatible with
// Launch.
func validatePA(pa *syscall.ProcAttr, chroot string) (bool, error) {
return false, ErrNoLaunch
}

View File

@@ -0,0 +1,51 @@
// +build go1.10
package cap
import "syscall"
// LaunchSupported indicates that is safe to return from a locked
// OS Thread and have that OS Thread be terminated by the runtime. The
// Launch functionality really needs to rely on the fact that an
// excess of runtime.LockOSThread() vs. runtime.UnlockOSThread() calls
// in a returning go routine will cause the underlying locked OSThread
// to terminate. That feature was added to the Go runtime in version
// 1.10.
//
// See these bugs for the discussion and feature assumed by the code
// in this Launch() functionality:
//
// https://github.com/golang/go/issues/20395
// https://github.com/golang/go/issues/20458
//
// A value of false for this constant causes the Launch functionality
// to fail with an error: cap.ErrNoLaunch. If this value is false you
// have two choices with respect to the Launch functionality:
//
// 1) don't use cap.(*Launcher).Launch()
// 2) upgrade your Go toolchain to 1.10+ (ie., do this one).
const LaunchSupported = true
// validatePA confirms that the pa.Sys entry is not incompatible with
// Launch and loads up the chroot value.
func validatePA(pa *syscall.ProcAttr, chroot string) (bool, error) {
s := pa.Sys
if s == nil {
if chroot == "" {
return false, nil
}
s = &syscall.SysProcAttr{
Chroot: chroot,
}
pa.Sys = s
} else if s.Chroot != "" {
return false, ErrAmbiguousChroot
}
if s.Credential != nil {
return false, ErrAmbiguousIDs
}
if len(s.AmbientCaps) != 0 {
return false, ErrAmbiguousAmbient
}
return s != nil && s.Chroot != "", nil
}

View File

@@ -0,0 +1,121 @@
package cap
import (
"runtime"
"sync"
"syscall"
"kernel.org/pub/linux/libs/security/libcap/psx"
)
// multisc provides syscalls overridable for testing purposes that
// support a single kernel security state for all OS threads.
// We use this version when we are cgo compiling because
// we need to manage the native C pthreads too.
var multisc = &syscaller{
w3: psx.Syscall3,
w6: psx.Syscall6,
r3: syscall.RawSyscall,
r6: syscall.RawSyscall6,
}
// singlesc provides a single threaded implementation. Users should
// take care to ensure the thread is locked and marked nogc.
var singlesc = &syscaller{
w3: syscall.RawSyscall,
w6: syscall.RawSyscall6,
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
}

View File

@@ -0,0 +1,326 @@
package cap
import (
"bufio"
"errors"
"strconv"
"strings"
)
// String converts a capability Value into its canonical text
// representation.
func (v Value) String() string {
name, ok := names[v]
if ok {
return name
}
// Un-named capabilities are referred to numerically (in decimal).
return strconv.Itoa(int(v))
}
// FromName converts a named capability Value to its binary
// representation.
func FromName(name string) (Value, error) {
startUp.Do(multisc.cInit)
v, ok := bits[name]
if ok {
if v >= Value(words*32) {
return 0, ErrBadValue
}
return v, nil
}
i, err := strconv.Atoi(name)
if err != nil {
return 0, err
}
if i >= 0 && i < int(words*32) {
return Value(i), nil
}
return 0, ErrBadValue
}
const (
eBin uint = (1 << Effective)
pBin = (1 << Permitted)
iBin = (1 << Inheritable)
)
var combos = []string{"", "e", "p", "ep", "i", "ei", "ip", "eip"}
// histo generates a histogram of flag state combinations.
// Note: c is locked by or private to the caller.
func (c *Set) histo(bins []int, patterns []uint, from, limit Value) uint {
for v := from; v < limit; v++ {
b := uint(v & 31)
u, bit, err := bitOf(0, v)
if err != nil {
break
}
x := uint((c.flat[u][Effective]&bit)>>b) * eBin
x |= uint((c.flat[u][Permitted]&bit)>>b) * pBin
x |= uint((c.flat[u][Inheritable]&bit)>>b) * iBin
bins[x]++
patterns[uint(v)] = x
}
// Note, in the loop, we use >= to pick the smallest value for
// m with the highest bin value. That is ties break towards
// m=0.
m := uint(7)
for t := m; t > 0; {
t--
if bins[t] >= bins[m] {
m = t
}
}
return m
}
// String converts a full capability Set into a single short readable
// string representation (which may contain spaces). See the
// cap.FromText() function for an explanation of its return values.
//
// Note (*cap.Set).String() may evolve to generate more compact
// strings representing the a given Set over time, but it should
// maintain compatibility with the libcap:cap_to_text() function for
// any given release. Further, it will always be an inverse of
// cap.FromText().
func (c *Set) String() string {
if err := c.good(); err != nil {
return "<invalid>"
}
bins := make([]int, 8)
patterns := make([]uint, maxValues)
c.mu.RLock()
defer c.mu.RUnlock()
// Note, in order to have a *Set pointer, startUp.Do(cInit)
// must have been called which sets maxValues.
m := c.histo(bins, patterns, 0, Value(maxValues))
// Background state is the most popular of the named bits.
vs := []string{"=" + combos[m]}
for i := uint(8); i > 0; {
i--
if i == m || bins[i] == 0 {
continue
}
var list []string
for j, p := range patterns {
if p != i {
continue
}
list = append(list, Value(j).String())
}
x := strings.Join(list, ",")
var y, z string
if cf := i & ^m; cf != 0 {
op := "+"
if len(vs) == 1 && vs[0] == "=" {
// Special case "= foo+..." == "foo=...".
// Prefer because it
vs = nil
op = "="
}
y = op + combos[cf]
}
if cf := m & ^i; cf != 0 {
z = "-" + combos[cf]
}
vs = append(vs, x+y+z)
}
// The unnamed bits can only add to the above named ones since
// unnamed ones are always defaulted to lowered.
uBins := make([]int, 8)
uPatterns := make([]uint, 32*words)
c.histo(uBins, uPatterns, Value(maxValues), 32*Value(words))
for i := uint(7); i > 0; i-- {
if uBins[i] == 0 {
continue
}
var list []string
for j, p := range uPatterns {
if p != i {
continue
}
list = append(list, Value(j).String())
}
vs = append(vs, strings.Join(list, ",")+"+"+combos[i])
}
return strings.Join(vs, " ")
}
// ErrBadText is returned if the text for a capability set cannot be parsed.
var ErrBadText = errors.New("bad text")
// FromText converts the canonical text representation for a Set into
// a freshly allocated Set.
//
// The format follows the following pattern: a set of space separated
// sequences. Each sequence applies over the previous sequence to
// build up a Set. The format of a sequence is:
//
// [comma list of cap_values][[ops][flags]]*
//
// Examples:
//
// "all=ep"
// "cap_chown,cap_setuid=ip cap_setuid+e"
// "=p cap_setpcap-p+i"
//
// Here "all" refers to all named capabilities known to the hosting
// kernel, and "all" is assumed if no capabilities are listed before
// an "=".
//
// The ops values, "=", "+" and "-" imply "reset and raise", "raise"
// and "lower" respectively. The "e", "i" and "p" characters
// correspond to the capabilities of the corresponding Flag: "e"
// (Effective); "i" (Inheritable); "p" (Permitted).
//
// This syntax is overspecified and there are many ways of building
// the same final Set state. Any sequence that includes a '=' resets
// the accumulated state of all Flags ignoring earlier sequences. On
// each of the following lines we give three or more examples of ways
// to specify a common Set. The last entry on each line is the one
// generated by (*cap.Set).String() from that Set.
//
// "=p all+ei" "all=pie" "=pi all+e" "=eip"
//
// "cap_setuid=p cap_chown=i" "cap_chown=ip-p" "cap_chown=i"
//
// "cap_chown=-p" "all=" "cap_setuid=pie-pie" "="
//
// Note: FromText() is tested at release time to completely match the
// import ability of the libcap:cap_from_text() function.
func FromText(text string) (*Set, error) {
c := NewSet()
scanner := bufio.NewScanner(strings.NewReader(text))
scanner.Split(bufio.ScanWords)
chunks := 0
for scanner.Scan() {
chunks++
// Parsing for xxx([-+=][eip]+)+
t := scanner.Text()
i := strings.IndexAny(t, "=+-")
if i < 0 {
return nil, ErrBadText
}
var vs []Value
sep := t[i]
if vals := t[:i]; vals == "all" {
for v := Value(0); v < Value(maxValues); v++ {
vs = append(vs, v)
}
} else if vals != "" {
for _, name := range strings.Split(vals, ",") {
v, err := FromName(name)
if err != nil {
return nil, ErrBadText
}
vs = append(vs, v)
}
} else if sep != '=' {
if vals == "" {
// Only "=" supports ""=="all".
return nil, ErrBadText
}
} else if j := i + 1; j+1 < len(t) {
switch t[j] {
case '+':
sep = 'P'
i++
case '-':
sep = 'M'
i++
}
}
i++
// There are 5 ways to set: =, =+, =-, +, -. We call
// the 2nd and 3rd of these 'P' and 'M'.
for {
// read [eip]+ setting flags.
var fE, fP, fI bool
for ok := true; ok && i < len(t); i++ {
switch t[i] {
case 'e':
fE = true
case 'i':
fI = true
case 'p':
fP = true
default:
ok = false
}
if !ok {
break
}
}
if !(fE || fI || fP) {
if sep != '=' {
return nil, ErrBadText
}
}
switch sep {
case '=', 'P', 'M', '+':
if sep != '+' {
c.Clear()
if sep == 'M' {
break
}
}
if keep := len(vs) == 0; keep {
if sep != '=' {
return nil, ErrBadText
}
c.forceFlag(Effective, fE)
c.forceFlag(Permitted, fP)
c.forceFlag(Inheritable, fI)
break
}
// =, + and P for specific values are left.
if fE {
c.SetFlag(Effective, true, vs...)
}
if fP {
c.SetFlag(Permitted, true, vs...)
}
if fI {
c.SetFlag(Inheritable, true, vs...)
}
case '-':
if fE {
c.SetFlag(Effective, false, vs...)
}
if fP {
c.SetFlag(Permitted, false, vs...)
}
if fI {
c.SetFlag(Inheritable, false, vs...)
}
}
if i == len(t) {
break
}
switch t[i] {
case '+', '-':
sep = t[i]
i++
default:
return nil, ErrBadText
}
}
}
if chunks == 0 {
return nil, ErrBadText
}
return c, nil
}

View File

@@ -0,0 +1,398 @@
/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-only */
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.
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:
1. Redistributions of source code must retain any existing copyright
notice, and this entire permission notice in its entirety,
including the disclaimer of warranties.
2. Redistributions in binary form must reproduce all prior and current
copyright notices, this list of conditions, and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
3. The name of any author may not be used to endorse or promote
products derived from this software without their specific prior
written permission.
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.
IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
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:
-------------------------
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -0,0 +1,28 @@
Package "psx" provides an API for invoking system calls in a way that
each system call is mirrored on all OS threads of the combined Go/CGo
runtime. Since the Go runtime treats OS threads as interchangeable, a
feature like this is needed to meaningfully change process privilege
(including dropping privilege) in a Go program running on Linux. This
package is required by:
"kernel.org/pub/linux/libs/security/libcap/cap"
When compiled CGO_ENABLED=0, the functionality requires go1.16+ to
build. That release of Go introduced syscall.AllThreadsSyscall*()
APIs. When compiled this way, the "psx" package functions
psx.Syscall3() and psx.Syscall6() are aliased to
syscall.AllThreadsSyscall() and syscall.AllThreadsSyscall6()
respectively.
When compiled CGO_ENABLED=1, the functionality is implemented by C
code, [lib]psx, which is distributed with libcap.
The official release announcement site for libcap and libpsx is:
https://sites.google.com/site/fullycapable/
Like libcap/libpsx itself, the "psx" package is distributed with a
"you choose" License. Specifically: BSD three clause, or GPL2. See the
License file.
Andrew G. Morgan <morgan@kernel.org>

View File

@@ -0,0 +1,60 @@
// Package psx provides support for system calls that are run
// 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
// of:
//
// https://github.com/golang/go/issues/1435
//
// The package works differently depending on whether or not
// CGO_ENABLED is 0 or 1.
//
// In the former case, psx is a low overhead wrapper for the two
// native go calls: syscall.AllThreadsSyscall() and
// 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
// functions of these names. This ensures that the system calls
// execute simultaneously on all the pthreads of the Go (and CGo)
// combined runtime.
//
// With CGo, the psx support works in the following way: the pthread
// that is first asked to execute the syscall does so, and determines
// if it succeeds or fails. If it fails, it returns immediately
// without attempting the syscall on other pthreads. If the initial
// attempt succeeds, however, then the runtime is stopped in order for
// the same system call to be performed on all the remaining pthreads
// of the runtime. Once all pthreads have completed the syscall, the
// return codes are those obtained by the first pthread's invocation
// of the syscall.
//
// Note, there is no need to use this variant of syscall where the
// syscalls only read state from the kernel. However, since Go's
// runtime freely migrates code execution between pthreads, support of
// this type is required for any successful attempt to fully drop or
// modify the privilege of a running Go program under Linux.
//
// More info on how Linux privilege works and examples of using this
// package can be found here:
//
// https://sites.google.com/site/fullycapable
//
// WARNING: For older go toolchains (prior to go1.15), correct
// compilation of this package may require an extra workaround step:
//
// The workaround is to build with the following CGO_LDFLAGS_ALLOW in
// effect (here the syntax is that of bash for defining an environment
// variable):
//
// export CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*"
//
//
// Copyright (c) 2019,20 Andrew G. Morgan <morgan@kernel.org>
//
// The psx package is licensed with a (you choose) BSD 3-clause or
// GPL2. See LICENSE file for details.
package psx // import "kernel.org/pub/linux/libs/security/libcap/psx"

View File

@@ -0,0 +1,765 @@
/*
* Copyright (c) 2019-21 Andrew G Morgan <morgan@kernel.org>
*
* This file contains a collection of routines that perform thread
* synchronization to ensure that a whole process is running as a
* single privilege entity - independent of the number of pthreads.
*
* The whole file would be unnecessary if glibc exported an explicit
* psx_syscall()-like function that leveraged the nptl:setxid
* mechanism to synchronize thread state over the whole process.
*/
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 199309L
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "psx_syscall.h"
#ifdef _PSX_DEBUG_MEMORY
static void *_psx_calloc(const char *file, const int line,
size_t nmemb, size_t size) {
void *ptr = calloc(nmemb, size);
fprintf(stderr, "psx:%d:%s:%d: calloc(%ld, %ld) -> %p\n", gettid(),
file, line, (long int)nmemb, (long int)size, ptr);
return ptr;
}
static void _psx_free(const char *file, const int line, void *ptr) {
fprintf(stderr, "psx:%d:%s:%d: free(%p)\n", gettid(), file, line, ptr);
return free(ptr);
}
#define calloc(a, b) _psx_calloc(__FILE__, __LINE__, a, b)
#define free(a) _psx_free(__FILE__, __LINE__, a)
#endif /* def _PSX_DEBUG_MEMORY */
/*
* psx_load_syscalls() can be weakly defined in dependent libraries to
* provide a mechanism for a library to optionally leverage this psx
* mechanism. Specifically, when libcap calls psx_load_sycalls() it
* provides a weakly declared default that maps its system calls to
* the regular system call functions. However, when linked with psx,
* this function here overrides the syscalls to be the psx ones.
*/
void psx_load_syscalls(long int (**syscall_fn)(long int,
long int, long int, long int),
long int (**syscall6_fn)(long int,
long int, long int, long int,
long int, long int, long int))
{
*syscall_fn = psx_syscall3;
*syscall6_fn = psx_syscall6;
}
/*
* type to keep track of registered threads.
*/
typedef struct registered_thread_s {
struct registered_thread_s *next, *prev;
pthread_t thread;
pthread_mutex_t mu;
int pending;
int gone;
long int retval;
pid_t tid;
} registered_thread_t;
static pthread_once_t psx_tracker_initialized = PTHREAD_ONCE_INIT;
typedef enum {
_PSX_IDLE = 0,
_PSX_SETUP = 1,
_PSX_SYSCALL = 2,
_PSX_CREATE = 3,
_PSX_INFORK = 4,
_PSX_EXITING = 5,
} psx_tracker_state_t;
/*
* This global structure holds the global coordination state for
* libcap's psx_posix_syscall() support.
*/
static struct psx_tracker_s {
int has_forked;
pthread_mutex_t state_mu;
pthread_cond_t cond; /* this is only used to wait on 'state' changes */
psx_tracker_state_t state;
int initialized;
int psx_sig;
psx_sensitivity_t sensitivity;
struct {
long syscall_nr;
long arg1, arg2, arg3, arg4, arg5, arg6;
int six;
int active;
} cmd;
struct sigaction sig_action;
struct sigaction chained_action;
registered_thread_t *root;
} psx_tracker;
/*
* psx_action_key is used for thread local storage of the thread's
* registration.
*/
pthread_key_t psx_action_key;
/*
* psx_do_registration called locked and creates a tracker entry for
* the current thread with a TLS specific key pointing at the threads
* specific tracker.
*/
static void *psx_do_registration(void) {
registered_thread_t *node = calloc(1, sizeof(registered_thread_t));
if (node == NULL) {
perror("unable to register psx handler");
_exit(1);
}
pthread_mutex_init(&node->mu, NULL);
node->thread = pthread_self();
pthread_setspecific(psx_action_key, node);
node->next = psx_tracker.root;
if (node->next) {
node->next->prev = node;
}
psx_tracker.root = node;
return node;
}
/*
* psx_posix_syscall_actor performs the system call on the targeted
* thread and signals it is no longer pending.
*/
static void psx_posix_syscall_actor(int signum, siginfo_t *info, void *ignore) {
/* bail early if this isn't something we recognize */
if (signum != psx_tracker.psx_sig || !psx_tracker.cmd.active ||
info == NULL || info->si_code != SI_TKILL || info->si_pid != getpid()) {
if (psx_tracker.chained_action.sa_sigaction != 0) {
psx_tracker.chained_action.sa_sigaction(signum, info, ignore);
}
return;
}
long int retval;
if (!psx_tracker.cmd.six) {
retval = syscall(psx_tracker.cmd.syscall_nr,
psx_tracker.cmd.arg1,
psx_tracker.cmd.arg2,
psx_tracker.cmd.arg3);
} else {
retval = syscall(psx_tracker.cmd.syscall_nr,
psx_tracker.cmd.arg1,
psx_tracker.cmd.arg2,
psx_tracker.cmd.arg3,
psx_tracker.cmd.arg4,
psx_tracker.cmd.arg5,
psx_tracker.cmd.arg6);
}
/*
* This handler can only be called on registered threads which
* have had this specific defined at start-up. (But see the
* subsequent test.)
*/
registered_thread_t *ref = pthread_getspecific(psx_action_key);
if (ref) {
pthread_mutex_lock(&ref->mu);
ref->pending = 0;
ref->retval = retval;
ref->tid = syscall(SYS_gettid);
pthread_mutex_unlock(&ref->mu);
} /*
* else thread must be dying and its psx_action_key has already
* been cleaned up.
*/
}
/*
* Some forward declarations for the initialization
* psx_syscall_start() routine.
*/
static void _psx_cleanup(void);
static void _psx_prepare_fork(void);
static void _psx_fork_completed(void);
static void _psx_forked_child(void);
int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
/*
* psx requires this function to be provided by the linkage wrapping.
*/
extern int __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
/*
* psx_confirm_sigaction reconfirms that the psx handler is the first
* handler to respond to the psx signal. It assumes that
* psx_tracker.psx_sig has been set.
*/
static void psx_confirm_sigaction(void) {
sigset_t mask, orig;
struct sigaction existing_sa;
/*
* Block interrupts while potentially rewriting the handler.
*/
sigemptyset(&mask);
sigaddset(&mask, psx_tracker.psx_sig);
sigprocmask(SIG_BLOCK, &mask, &orig);
sigaction(psx_tracker.psx_sig, NULL, &existing_sa);
if (existing_sa.sa_sigaction != psx_posix_syscall_actor) {
memcpy(&psx_tracker.chained_action, &existing_sa, sizeof(struct sigaction));
psx_tracker.sig_action.sa_sigaction = psx_posix_syscall_actor;
sigemptyset(&psx_tracker.sig_action.sa_mask);
psx_tracker.sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
sigaction(psx_tracker.psx_sig, &psx_tracker.sig_action, NULL);
}
sigprocmask(SIG_SETMASK, &orig, NULL);
}
/*
* psx_syscall_start initializes the subsystem including initializing
* the mutex.
*/
static void psx_syscall_start(void) {
pthread_mutex_init(&psx_tracker.state_mu, NULL);
pthread_cond_init(&psx_tracker.cond, NULL);
pthread_key_create(&psx_action_key, NULL);
pthread_atfork(_psx_prepare_fork, _psx_fork_completed, _psx_forked_child);
/*
* All sorts of things are assumed by Linux and glibc and/or musl
* about signal handlers and which can be blocked. Go has its own
* idiosyncrasies too. We tried SIGRTMAX until
*
* https://bugzilla.kernel.org/show_bug.cgi?id=210533
*
* Our current strategy is to aggressively intercept SIGSYS.
*/
psx_tracker.psx_sig = SIGSYS;
psx_confirm_sigaction();
psx_do_registration(); /* register the main thread. */
atexit(_psx_cleanup);
psx_tracker.initialized = 1;
}
/*
* This is the only way this library globally locks. Note, this is not
* to be confused with psx_sig (interrupt) blocking - which is
* performed around thread creation and when the signal handler is
* being confirmed.
*/
static void psx_lock(void)
{
pthread_once(&psx_tracker_initialized, psx_syscall_start);
pthread_mutex_lock(&psx_tracker.state_mu);
}
/*
* This is the only way this library unlocks.
*/
static void psx_unlock(void)
{
pthread_mutex_unlock(&psx_tracker.state_mu);
}
/*
* under lock perform a state transition. Changing state is generally
* done via this function. However, there is a single exception in
* _psx_cleanup().
*/
static void psx_new_state(psx_tracker_state_t was, psx_tracker_state_t is)
{
psx_lock();
while (psx_tracker.state != was) {
pthread_cond_wait(&psx_tracker.cond, &psx_tracker.state_mu);
}
psx_tracker.state = is;
if (is == _PSX_IDLE) {
/* only announce newly idle states since that is all we wait for */
pthread_cond_signal(&psx_tracker.cond);
}
psx_unlock();
}
long int psx_syscall3(long int syscall_nr,
long int arg1, long int arg2, long int arg3) {
return psx_syscall(syscall_nr, arg1, arg2, 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) {
return psx_syscall(syscall_nr, arg1, arg2, arg3, arg4, arg5, arg6);
}
static void _psx_prepare_fork(void) {
/*
* obtain global lock - we don't want any syscalls while the fork
* is occurring since it may interfere with the preparation for
* the fork.
*/
psx_new_state(_PSX_IDLE, _PSX_INFORK);
}
static void _psx_fork_completed(void) {
/*
* The only way we can get here is if state is _PSX_INFORK and was
* previously _PSX_IDLE. Now that the fork has completed, the
* parent can continue as if it hadn't happened - the forked child
* does not tie its security state to that of the parent process
* and threads.
*
* We don't strictly need to change the psx_tracker.state since we
* hold the mutex over the fork, but we do to make deadlock
* debugging easier.
*/
psx_new_state(_PSX_INFORK, _PSX_IDLE);
}
static void _psx_forked_child(void) {
/*
* The only way we can get here is if state is _PSX_INFORK and was
* previously _PSX_IDLE. However, none of the registered threads
* exist in this newly minted child process, so we have to reset
* the tracking structure to avoid any confusion. We also scuttle
* any chance of the PSX API working on more than one thread in
* the child by leaving the state as _PSX_INFORK. We do support
* all psx_syscall()s by reverting to them being direct in the
* fork()ed child.
*
* We do this because the glibc man page for fork() suggests that
* only a subset of things will work post fork(). Specifically,
* only a "async-signal-safe functions (see signal-safety(7))
* until such time as it calls execve(2)" can be relied upon. That
* man page suggests that you can't expect mutexes to work: "not
* async-signal-safe because it uses pthread_mutex_lock(3)
* internally.".
*/
registered_thread_t *next, *old_root;
old_root = psx_tracker.root;
psx_tracker.root = NULL;
psx_tracker.has_forked = 1;
for (; old_root; old_root = next) {
next = old_root->next;
memset(old_root, 0, sizeof(*old_root));
free(old_root);
}
}
/*
* called locked to unregister a node from the tracker.
*/
static void psx_do_unregister(registered_thread_t *node) {
if (psx_tracker.root == node) {
psx_tracker.root = node->next;
}
if (node->next) {
node->next->prev = node->prev;
}
if (node->prev) {
node->prev->next = node->next;
}
pthread_mutex_destroy(&node->mu);
memset(node, 0, sizeof(*node));
free(node);
}
typedef struct {
void *(*fn)(void *);
void *arg;
sigset_t sigbits;
} psx_starter_t;
/*
* _psx_exiting is used to cleanup the node for the thread on its exit
* path. This is needed for musl libc:
*
* https://bugzilla.kernel.org/show_bug.cgi?id=208477
*
* and likely wise for glibc too:
*
* https://sourceware.org/bugzilla/show_bug.cgi?id=12889
*/
static void _psx_exiting(void *node) {
/*
* Until we are in the _PSX_EXITING state, we must not block the
* psx_sig interrupt for this dying thread. That is, until this
* exiting thread can set ref->gone to 1, this dying thread is
* still participating in the psx syscall distribution.
*
* See https://github.com/golang/go/issues/42494 for a situation
* where this code is called with psx_tracker.psx_sig blocked.
*/
sigset_t sigbit, orig_sigbits;
sigemptyset(&sigbit);
pthread_sigmask(SIG_UNBLOCK, &sigbit, &orig_sigbits);
sigaddset(&sigbit, psx_tracker.psx_sig);
pthread_sigmask(SIG_UNBLOCK, &sigbit, NULL);
/*
* With psx_tracker.psx_sig unblocked we can wait until this
* thread can enter the _PSX_EXITING state.
*/
psx_new_state(_PSX_IDLE, _PSX_EXITING);
/*
* We now indicate that this thread is no longer participating in
* the psx mechanism.
*/
registered_thread_t *ref = node;
pthread_mutex_lock(&ref->mu);
ref->gone = 1;
pthread_mutex_unlock(&ref->mu);
/*
* At this point, we can restore the calling sigmask to whatever
* the caller thought was appropriate for a dying thread to have.
*/
pthread_sigmask(SIG_SETMASK, &orig_sigbits, NULL);
/*
* Allow the rest of the psx system to carry on as per normal.
*/
psx_new_state(_PSX_EXITING, _PSX_IDLE);
}
/*
* _psx_start_fn is a trampoline for the intended start function, it
* is called blocked (_PSX_CREATE), but releases the block before
* calling starter->fn. Before releasing the block, the TLS specific
* attributes are initialized for use by the interrupt handler under
* the psx mutex, so it doesn't race with an interrupt received by
* this thread and the interrupt handler does not need to poll for
* that specific attribute to be present (which is problematic during
* thread shutdown).
*/
static void *_psx_start_fn(void *data) {
void *node = psx_do_registration();
psx_new_state(_PSX_CREATE, _PSX_IDLE);
psx_starter_t *starter = data;
pthread_sigmask(SIG_SETMASK, &starter->sigbits, NULL);
void *(*fn)(void *) = starter->fn;
void *arg = starter->arg;
memset(data, 0, sizeof(*starter));
free(data);
void *ret;
pthread_cleanup_push(_psx_exiting, node);
ret = fn(arg);
pthread_cleanup_pop(1);
return ret;
}
/*
* __wrap_pthread_create is the wrapped destination of all regular
* pthread_create calls.
*/
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;
/*
* Until we are in the _PSX_IDLE state and locked, we must not
* block the psx_sig interrupt for this parent thread. Arrange
* that parent thread and newly created one can restore signal
* mask.
*/
sigset_t sigbit, orig_sigbits;
sigemptyset(&sigbit);
pthread_sigmask(SIG_UNBLOCK, &sigbit, &starter->sigbits);
sigaddset(&sigbit, psx_tracker.psx_sig);
pthread_sigmask(SIG_UNBLOCK, &sigbit, &orig_sigbits);
psx_new_state(_PSX_IDLE, _PSX_CREATE);
/*
* until the child thread has been blessed with its own TLS
* specific attribute(s) we prevent either the parent thread or
* the new one from experiencing a PSX interrupt.
*/
pthread_sigmask(SIG_BLOCK, &sigbit, NULL);
int ret = __real_pthread_create(thread, attr, _psx_start_fn, starter);
if (ret == -1) {
psx_new_state(_PSX_CREATE, _PSX_IDLE);
memset(starter, 0, sizeof(*starter));
free(starter);
} /* else unlock happens in _psx_start_fn */
/* the parent can once again receive psx interrupt signals */
pthread_sigmask(SIG_SETMASK, &orig_sigbits, NULL);
return ret;
}
/*
* __psx_immediate_syscall does one syscall using the current
* process.
*/
static long int __psx_immediate_syscall(long int syscall_nr,
int count, long int *arg) {
psx_tracker.cmd.syscall_nr = syscall_nr;
psx_tracker.cmd.arg1 = count > 0 ? arg[0] : 0;
psx_tracker.cmd.arg2 = count > 1 ? arg[1] : 0;
psx_tracker.cmd.arg3 = count > 2 ? arg[2] : 0;
if (count > 3) {
psx_tracker.cmd.six = 1;
psx_tracker.cmd.arg4 = arg[3];
psx_tracker.cmd.arg5 = count > 4 ? arg[4] : 0;
psx_tracker.cmd.arg6 = count > 5 ? arg[5] : 0;
return syscall(syscall_nr,
psx_tracker.cmd.arg1,
psx_tracker.cmd.arg2,
psx_tracker.cmd.arg3,
psx_tracker.cmd.arg4,
psx_tracker.cmd.arg5,
psx_tracker.cmd.arg6);
}
psx_tracker.cmd.six = 0;
return syscall(syscall_nr, psx_tracker.cmd.arg1,
psx_tracker.cmd.arg2, psx_tracker.cmd.arg3);
}
/*
* __psx_syscall performs the syscall on the current thread and if no
* error is detected it ensures that the syscall is also performed on
* all (other) registered threads. The return code is the value for
* the first invocation. It uses a trick to figure out how many
* arguments the user has supplied. The other half of the trick is
* provided by the macro psx_syscall() in the <sys/psx_syscall.h>
* file. The trick is the 7th optional argument (8th over all) to
* __psx_syscall is the count of arguments supplied to psx_syscall.
*
* User:
* psx_syscall(nr, a, b);
* Expanded by macro to:
* __psx_syscall(nr, a, b, 6, 5, 4, 3, 2, 1, 0);
* The eighth arg is now ------------------------------------^
*/
long int __psx_syscall(long int syscall_nr, ...) {
long int arg[7];
int i;
va_list aptr;
va_start(aptr, syscall_nr);
for (i = 0; i < 7; i++) {
arg[i] = va_arg(aptr, long int);
}
va_end(aptr);
int count = arg[6];
if (count < 0 || count > 6) {
errno = EINVAL;
return -1;
}
if (psx_tracker.has_forked) {
return __psx_immediate_syscall(syscall_nr, count, arg);
}
psx_new_state(_PSX_IDLE, _PSX_SETUP);
psx_confirm_sigaction();
long int ret;
ret = __psx_immediate_syscall(syscall_nr, count, arg);
if (ret == -1 || !psx_tracker.initialized) {
psx_new_state(_PSX_SETUP, _PSX_IDLE);
goto defer;
}
int restore_errno = errno;
psx_new_state(_PSX_SETUP, _PSX_SYSCALL);
psx_tracker.cmd.active = 1;
pthread_t self = pthread_self();
registered_thread_t *next = NULL, *ref;
psx_lock();
for (ref = psx_tracker.root; ref; ref = next) {
next = ref->next;
if (ref->thread == self) {
continue;
}
pthread_mutex_lock(&ref->mu);
ref->pending = 1;
int gone = ref->gone;
if (!gone) {
gone = pthread_kill(ref->thread, psx_tracker.psx_sig) != 0;
}
pthread_mutex_unlock(&ref->mu);
if (!gone) {
continue;
}
/*
* need to remove invalid thread id from linked list
*/
psx_do_unregister(ref);
}
psx_unlock();
int mismatch = 0;
for (;;) {
int waiting = 0;
psx_lock();
for (ref = psx_tracker.root; ref; ref = next) {
next = ref->next;
if (ref->thread == self) {
continue;
}
pthread_mutex_lock(&ref->mu);
int pending = ref->pending;
int gone = ref->gone;
if (!gone) {
if (pending) {
gone = (pthread_kill(ref->thread, 0) != 0);
} else {
mismatch |= (ref->retval != ret);
}
}
pthread_mutex_unlock(&ref->mu);
if (!gone) {
waiting += pending;
continue;
}
/*
* need to remove invalid thread id from linked list
*/
psx_do_unregister(ref);
}
psx_unlock();
if (!waiting) {
break;
}
sched_yield();
}
psx_tracker.cmd.active = 0;
if (mismatch) {
psx_lock();
switch (psx_tracker.sensitivity) {
case PSX_IGNORE:
break;
default:
fprintf(stderr, "psx_syscall result differs.\n");
if (psx_tracker.cmd.six) {
fprintf(stderr, "trap:%ld a123456=[%ld,%ld,%ld,%ld,%ld,%ld]\n",
psx_tracker.cmd.syscall_nr,
psx_tracker.cmd.arg1,
psx_tracker.cmd.arg2,
psx_tracker.cmd.arg3,
psx_tracker.cmd.arg4,
psx_tracker.cmd.arg5,
psx_tracker.cmd.arg6);
} else {
fprintf(stderr, "trap:%ld a123=[%ld,%ld,%ld]\n",
psx_tracker.cmd.syscall_nr,
psx_tracker.cmd.arg1,
psx_tracker.cmd.arg2,
psx_tracker.cmd.arg3);
}
fprintf(stderr, "results:");
for (ref = psx_tracker.root; ref; ref = next) {
next = ref->next;
if (ref->thread == self) {
continue;
}
if (ret != ref->retval) {
fprintf(stderr, " %d={%ld}", ref->tid, ref->retval);
}
}
fprintf(stderr, " wanted={%ld}\n", ret);
if (psx_tracker.sensitivity == PSX_WARNING) {
break;
}
pthread_kill(self, SIGSYS);
}
psx_unlock();
}
errno = restore_errno;
psx_new_state(_PSX_SYSCALL, _PSX_IDLE);
defer:
return ret;
}
/*
* _psx_cleanup its called when the program exits. It is used to free
* any memory used by the thread tracker.
*/
static void _psx_cleanup(void) {
registered_thread_t *ref, *next;
/*
* We enter the exiting state. Unlike exiting a single thread we
* never leave this state since this cleanup is only done at
* program exit.
*/
psx_lock();
while (psx_tracker.state != _PSX_IDLE && psx_tracker.state != _PSX_INFORK) {
pthread_cond_wait(&psx_tracker.cond, &psx_tracker.state_mu);
}
psx_tracker.state = _PSX_EXITING;
psx_unlock();
for (ref = psx_tracker.root; ref; ref = next) {
next = ref->next;
psx_do_unregister(ref);
}
}
/*
* Change the PSX sensitivity level. If the threads appear to have
* diverged in behavior, this can cause the library to notify the
* user.
*/
int psx_set_sensitivity(psx_sensitivity_t level) {
if (level < PSX_IGNORE || level > PSX_ERROR) {
errno = EINVAL;
return -1;
}
psx_lock();
psx_tracker.sensitivity = level;
psx_unlock();
return 0;
}

View File

@@ -0,0 +1,35 @@
// +build linux,!cgo
// +build go1.16
package psx // import "kernel.org/pub/linux/libs/security/libcap/psx"
import "syscall"
// Documentation for these functions are provided in the psx_cgo.go
// file.
//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) {
return syscall.AllThreadsSyscall(syscallnr, arg1, arg2, arg3)
}
//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) {
return syscall.AllThreadsSyscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6)
}

View File

@@ -0,0 +1,91 @@
// +build linux,cgo
package psx // import "kernel.org/pub/linux/libs/security/libcap/psx"
import (
"runtime"
"sync"
"syscall"
)
// #cgo LDFLAGS: -lpthread -Wl,-wrap,pthread_create
//
// #include <errno.h>
// #include "psx_syscall.h"
//
// long __errno_too(long set_errno) {
// long v = errno;
// if (set_errno >= 0) {
// errno = set_errno;
// }
// return v;
// }
import "C"
// setErrno returns the current C.errno value and, if v >= 0, sets the
// CGo errno for a random pthread to value v. If you want some
// consistency, this needs to be called from runtime.LockOSThread()
// code. This function is only defined for testing purposes. The psx.c
// code should properly handle the case that a non-zero errno is saved
// and restored independently of what these Syscall[36]() functions
// observe.
func setErrno(v int) int {
return int(C.__errno_too(C.long(v)))
}
var makeFatal sync.Once
// forceFatal configures the psx_syscall mechanism to PSX_ERROR.
func forceFatal() {
makeFatal.Do(func() {
C.psx_set_sensitivity(C.PSX_ERROR)
})
}
//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) {
forceFatal()
// We lock to the OSThread here because we may need errno to
// be the one for this thread.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
v := C.psx_syscall3(C.long(syscallnr), C.long(arg1), C.long(arg2), C.long(arg3))
var errno syscall.Errno
if v < 0 {
errno = syscall.Errno(C.__errno_too(-1))
}
return uintptr(v), uintptr(v), errno
}
//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) {
forceFatal()
// We lock to the OSThread here because we may need errno to
// be the one for this thread.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
v := C.psx_syscall6(C.long(syscallnr), C.long(arg1), C.long(arg2), C.long(arg3), C.long(arg4), C.long(arg5), C.long(arg6))
var errno syscall.Errno
if v < 0 {
errno = syscall.Errno(C.__errno_too(-1))
}
return uintptr(v), uintptr(v), errno
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2019 Andrew G. Morgan <morgan@kernel.org>
*
* This header, and the -lpsx library, provide a number of things to
* support POSIX semantics for syscalls associated with the pthread
* library. Linking this code is tricky and is done as follows:
*
* ld ... -lpsx -lpthread --wrap=pthread_create
* or, gcc ... -lpsx -lpthread -Wl,-wrap,pthread_create
*
* glibc provides a subset of this functionality natively through the
* nptl:setxid mechanism and could implement psx_syscall() directly
* using that style of functionality but, as of 2019-11-30, the setxid
* mechanism is limited to 9 specific set*() syscalls that do not
* support the syscall6 API (needed for prctl functions and the ambient
* capabilities set for example).
*/
#ifndef _SYS_PSX_SYSCALL_H
#define _SYS_PSX_SYSCALL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
/*
* psx_syscall performs the specified syscall on all psx registered
* threads. The mechanism by which this occurs is much less efficient
* than a standard system call on Linux, so it should only be used
* when POSIX semantics are required to change process relevant
* security state.
*
* Glibc has native support for POSIX semantics on setgroups() and the
* 8 set*[gu]id() functions. So, there is no need to use psx_syscall()
* for these calls. This call exists for all the other system calls
* that need to maintain parity on all pthreads of a program.
*
* Some macrology is used to allow the caller to provide only as many
* arguments as needed, thus psx_syscall() cannot be used as a
* function pointer. For those situations, we define psx_syscall3()
* and psx_syscall6().
*/
#define psx_syscall(syscall_nr, ...) \
__psx_syscall(syscall_nr, __VA_ARGS__, (long int) 6, (long int) 5, \
(long int) 4, (long int) 3, (long int) 2, \
(long int) 1, (long int) 0)
long int __psx_syscall(long int syscall_nr, ...);
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);
/*
* This function should be used by systems to obtain pointers to the
* two syscall functions provided by the PSX library. A linkage trick
* 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 usage.
*/
void psx_load_syscalls(long int (**syscall_fn)(long int,
long int, long int, long int),
long int (**syscall6_fn)(long int,
long int, long int, long int,
long int, long int, long int));
/*
* psx_sensitivity_t holds the level of paranoia for non-POSIX syscall
* behavior. The default is PSX_IGNORE: which is best effort - no
* enforcement; PSX_WARNING will dump to stderr a warning when a
* syscall's results differ; PSX_ERROR will dump info as per
* PSX_WARNING and generate a SIGSYS. The current mode can be set with
* psx_set_sensitivity().
*/
typedef enum {
PSX_IGNORE = 0,
PSX_WARNING = 1,
PSX_ERROR = 2,
} psx_sensitivity_t;
/*
* psx_set_sensitivity sets the current sensitivity of the PSX
* mechanism. The function returns 0 on success and -1 if the
* requested level is invalid.
*/
int psx_set_sensitivity(psx_sensitivity_t level);
#ifdef __cplusplus
}
#endif
#endif /* _SYS_PSX_SYSCALL_H */

6
vendor/modules.txt vendored
View File

@@ -988,3 +988,9 @@ google.golang.org/protobuf/types/pluginpb
# gopkg.in/yaml.v3 v3.0.1
## explicit
gopkg.in/yaml.v3
# kernel.org/pub/linux/libs/security/libcap/cap v1.2.67
## explicit; go 1.11
kernel.org/pub/linux/libs/security/libcap/cap
# kernel.org/pub/linux/libs/security/libcap/psx v1.2.67
## explicit; go 1.11
kernel.org/pub/linux/libs/security/libcap/psx