Skip to content

Commit

Permalink
merge #34 into openSUSE/libpathrs:main
Browse files Browse the repository at this point in the history
Aleksa Sarai (9):
  Root::create: use mknod for regular files as well
  capi: switch to fd-based API
  Root::create_file: take open flags to configure returned fd
  go bindings: completely rewrite Go bindings to new API
  examples: go: switch to modules
  python bindings: update bindings to use new API
  examples: python/static_web: handle non-directories
  capi: remove backtraces from FFI API
  capi: correctly mark Leakable methods as unsafe

LGTMs: cyphar
  • Loading branch information
cyphar committed Jul 18, 2024
2 parents fc8e99a + 45ca5c1 commit e1b3176
Show file tree
Hide file tree
Showing 30 changed files with 1,498 additions and 2,485 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ backtrace = "^0.3"
bitflags = "^2"
lazy_static = "^1"
libc = "^0.2"
rand = "^0.8"
snafu = { version = "^0.6", features = ["backtraces-impl-backtrace-crate"] }
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,37 @@ int get_my_fd(void)
const char *root_path = "/path/to/root";
const char *unsafe_path = "/etc/passwd";

int fd = -1;
pathrs_root_t *root = NULL;
pathrs_handle_t *handle = NULL;
pathrs_error_t *error = NULL;

root = pathrs_open(root_path);
error = pathrs_error(PATHRS_ROOT, root);
if (error)
int liberr = 0;
int root = -EBADF,
handle = -EBADF,
fd = -EBADF;

root = pathrs_root_open(root_path);
if (root < 0) {
liberr = root;
goto err;
}

handle = pathrs_resolve(root, unsafe_path);
error = pathrs_error(PATHRS_ROOT, root);
if (error) /* or (!handle) */
if (handle < 0) {
liberr = handle;
goto err;
}

fd = pathrs_reopen(handle, O_RDONLY);
error = pathrs_error(PATHRS_HANDLE, handle);
if (error) /* or (fd < 0) */
if (fd < 0) {
liberr = fd;
goto err;
}

err:
if (error)
if (liberr < 0) {
pathrs_error_t *error = pathrs_errorinfo(liberr);
fprintf(stderr, "Uh-oh: %s (errno=%d)\n", error->description, error->saved_errno);
pathrs_free(PATHRS_ROOT, root);
pathrs_free(PATHRS_HANDLE, handle);
pathrs_free(PATHRS_ERROR, error);
pathrs_errorinfo_free(error);
}
close(root);
close(handle);
return fd;
}
```
Expand Down
21 changes: 21 additions & 0 deletions contrib/bindings/go-pathrs/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build linux

// libpathrs: safe path resolution on Linux
// Copyright (C) 2019-2024 Aleksa Sarai <[email protected]>
// Copyright (C) 2019-2024 SUSE LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package pathrs provides bindings for libpathrs, a library for safe path
// resolution on Linux.
package pathrs
43 changes: 43 additions & 0 deletions contrib/bindings/go-pathrs/error_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//go:build linux

// libpathrs: safe path resolution on Linux
// Copyright (C) 2019-2024 Aleksa Sarai <[email protected]>
// Copyright (C) 2019-2024 SUSE LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pathrs

import (
"syscall"
)

// Error represents an underlying libpathrs error.
type Error struct {
description string
errno syscall.Errno
}

// Error returns a textual description of the error.
func (err *Error) Error() string {
return err.description
}

// Unwrap returns the underlying error which was wrapped by this error (if
// applicable).
func (err *Error) Unwrap() error {
if err.errno != 0 {
return err.errno
}
return nil
}
5 changes: 5 additions & 0 deletions contrib/bindings/go-pathrs/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/openSUSE/libpathrs/go-pathrs

go 1.18

require golang.org/x/sys v0.22.0
2 changes: 2 additions & 0 deletions contrib/bindings/go-pathrs/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
108 changes: 108 additions & 0 deletions contrib/bindings/go-pathrs/handle_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//go:build linux

// libpathrs: safe path resolution on Linux
// Copyright (C) 2019-2024 Aleksa Sarai <[email protected]>
// Copyright (C) 2019-2024 SUSE LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pathrs

import (
"fmt"
"os"
)

// Handle is a handle for a path within a given Root. This handle references an
// already-resolved path which can be used for only one purpose -- to "re-open"
// the handle and get an actual *os.File which can be used for ordinary
// operations.
//
// It is critical that perform all relevant operations through this Handle
// (rather than fetching the file descriptor yourself with IntoRaw), because
// the security properties of libpathrs depend on users doing all relevant
// filesystem operations through libpathrs.
type Handle struct {
inner *os.File
}

// HandleFromFile creates a new Handle from an exisitng file handle. The handle
// will be copied by this method, so the original handle should still be freed
// by the caller.
//
// This is effectively the inverse operation of Handle.IntoRaw, and is used for
// "deserialising" pathrs root handles.
func HandleFromFile(file *os.File) (*Handle, error) {
newFile, err := dupFile(file)
if err != nil {
return nil, fmt.Errorf("duplicate handle fd: %w", err)
}
return &Handle{inner: newFile}, nil
}

// Open creates an "upgraded" file handle to the file referenced by the Handle.
// Note that the original Handle is not consumed by this operation, and can be
// opened multiple times.
//
// The handle returned is only usable for reading, and this is method is
// shorthand for handle.OpenFile(os.O_RDONLY).
//
// TODO: Rename these to "Reopen" or something.
func (h *Handle) Open() (*os.File, error) {
return h.OpenFile(os.O_RDONLY)
}

// OpenFile creates an "upgraded" file handle to the file referenced by the
// Handle. Note that the original Handle is not consumed by this operation, and
// can be opened multiple times.
//
// The provided flags indicate which open(2) flags are used to create the new
// handle.
//
// TODO: Rename these to "Reopen" or something.
func (h *Handle) OpenFile(flags int) (*os.File, error) {
return withFileFd(h.inner, func(fd uintptr) (*os.File, error) {
newFd, err := pathrsReopen(fd, flags)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(newFd), h.inner.Name()), nil
})
}

// IntoFile unwraps the Handle into its underlying *os.File.
//
// You almost certainly want to use OpenFile() to get a non-O_PATH version of
// this Handle.
//
// This operation returns the internal *os.File of the Handle directly, so
// Close()ing the Handle will also close any copies of the returned *os.File.
// If you want to get an independent copy, use Clone().IntoFile().
func (h *Handle) IntoFile() *os.File {
// TODO: Figure out if we really don't want to make a copy.
// TODO: We almost certainly want to clear r.inner here, but we can't do
// that easily atomically (we could use atomic.Value but that'll make
// things quite a bit uglier).
return h.inner
}

// Clone creates a copy of a Handle, such that it has a separate lifetime to
// the original (while refering to the same underlying file).
func (h *Handle) Clone() (*Handle, error) {
return HandleFromFile(h.inner)
}

// Close frees all of the resources used by the Handle.
func (h *Handle) Close() error {
return h.inner.Close()
}
126 changes: 126 additions & 0 deletions contrib/bindings/go-pathrs/libpathrs_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//go:build linux

// libpathrs: safe path resolution on Linux
// Copyright (C) 2019-2024 Aleksa Sarai <[email protected]>
// Copyright (C) 2019-2024 SUSE LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pathrs

import (
"syscall"
"unsafe"
)

// TODO: Switch to pkg-config.

// #cgo CFLAGS: -I${SRCDIR}/../../../include
// #cgo LDFLAGS: -L${SRCDIR}/../../../target/release -L${SRCDIR}/../../../target/debug -lpathrs
// #include <pathrs.h>
import "C"

func fetchError(errId C.int) error {
if errId >= 0 {
return nil
}
cErr := C.pathrs_errorinfo(errId)
defer C.pathrs_errorinfo_free(cErr)

var err error
if cErr != nil {
err = &Error{
errno: syscall.Errno(cErr.saved_errno),
description: C.GoString(cErr.description),
}
}
return err
}

func pathrsOpen(path string) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

fd := C.pathrs_root_open(cPath)
return uintptr(fd), fetchError(fd)
}

func pathrsReopen(fd uintptr, flags int) (uintptr, error) {
newFd := C.pathrs_reopen(C.int(fd), C.int(flags))
return uintptr(newFd), fetchError(newFd)
}

func pathrsResolve(rootFd uintptr, path string) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

fd := C.pathrs_resolve(C.int(rootFd), cPath)
return uintptr(fd), fetchError(fd)
}

func pathrsCreat(rootFd uintptr, path string, flags int, mode uint32) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

fd := C.pathrs_creat(C.int(rootFd), cPath, C.int(flags), C.uint(mode))
return uintptr(fd), fetchError(fd)
}

func pathrsRename(rootFd uintptr, src, dst string, flags uint) error {
cSrc := C.CString(src)
defer C.free(unsafe.Pointer(cSrc))

cDst := C.CString(dst)
defer C.free(unsafe.Pointer(cDst))

err := C.pathrs_rename(C.int(rootFd), cSrc, cDst, C.uint(flags))
return fetchError(err)
}

func pathrsMkdir(rootFd uintptr, path string, mode uint32) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

err := C.pathrs_mkdir(C.int(rootFd), cPath, C.uint(mode))
return fetchError(err)
}

func pathrsMknod(rootFd uintptr, path string, mode uint32, dev uint64) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

err := C.pathrs_mknod(C.int(rootFd), cPath, C.uint(mode), C.dev_t(dev))
return fetchError(err)
}

func pathrsSymlink(rootFd uintptr, path, target string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

cTarget := C.CString(target)
defer C.free(unsafe.Pointer(cTarget))

err := C.pathrs_symlink(C.int(rootFd), cPath, cTarget)
return fetchError(err)
}

func pathrsHardlink(rootFd uintptr, path, target string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

cTarget := C.CString(target)
defer C.free(unsafe.Pointer(cTarget))

err := C.pathrs_hardlink(C.int(rootFd), cPath, cTarget)
return fetchError(err)
}
Loading

0 comments on commit e1b3176

Please sign in to comment.