profile
viewpoint
Aleksa Sarai [see §317C(6)] cyphar @SUSE Linux GmbH Oceania (circa 1984) https://www.cyphar.com Backdoors are courtesy of the Australian Government. "designated communications provider" under §317C(6) of the Telecommunications Act 1997.

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {

I hope I did not misread you, you mean to use pathrs_duplicate to get a new descriptor and return it as a *os.File?

Yeah, that's what I suggested. Another option would be to set Root.root to nil (so any further operations are a no-op or produce an error), but that's definitely not thread-safe.

zhiburt

comment created time in 11 hours

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (r *Root) Clone() (*Root, error) {+	newRoot := (*C.pathrs_root_t)(C.pathrs_duplicate(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(newRoot))+	if err != nil {+		return nil, err+	}++	return &Root{root: newRoot}, nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		C.pathrs_free(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}+}++func newHandle(handlePtr *C.pathrs_handle_t) *Handle {+	return &Handle{+		handle:  handlePtr,+		nameGen: rand.New(rand.NewSource(time.Now().UTC().UnixNano())),+	}+}++// Reopen upgrade the handle to a file representation+// which holds a usable fd, suitable for reading and writing+func (h *Handle) Reopen(flags int) (*os.File, error) {

There should probably be a short-hand for pathrs_resolve+pathrs_reopen, but I don't like the idea of having an interface which is basically open(root, path) for a few reasons:

  • It's not type-safe, so if you get the argument order wrong you've just opened an attacker-controlled path. Yeah, Open has the same problem but ideally you'll be creating one Root object for all operations within a given root.

  • It looks too much like filepath.Join which makes me worried since the entire point of libpathrs is that using string paths is the wrong interface for paths in certain applications. Encouraging people to use string paths is a recipe for disaster IMHO (it'll be a few days before someone starts doing filepath.Join again out of habit).

  • I don't like that File contains all three objects (the root, handle, and re-opened file). A "file handle" shouldn't care about what root it came from.

If someone really wants to do something like that, they can just write an implementation in their own packages.

zhiburt

comment created time in 11 hours

pull request commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

/ping @opencontainers/runc-maintainers

cyphar

comment created time in 11 hours

push eventopenSUSE/libpathrs

Aleksa Sarai

commit sha 69fefcec864251af24507f4bc41a2e00e6ab05ab

utils: inline needless PATH_SEPARATOR global Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 12 hours

pull request commentNebulosus/shamir

ensure that we require enough shares

LGTM.

ChrisMacNaughton

comment created time in 20 hours

push eventcyphar/ncss-2020-syd-group1

Aleksa Sarai

commit sha f419d6baee3e904910af38541aadfa19d9068ff6

*: import location-api Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in a day

push eventcyphar/ncss-2020-syd-group1

Aleksa Sarai

commit sha ae6590fa5f07809a58851637fe317dbd87217372

*: add bots.json Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in a day

issue openedNebulosus/shamir

SECURITY: Threshold value is ignored (all shares are n=3).

There is a pretty serious bug in SecretData::with_secret -- all shares (no matter the threshold setting) will require three shares to reconstruct the secret. Here's an example:

extern crate shamir;

use shamir::SecretData;

fn try_recover(n: u8, shares: &Vec<Vec<u8>>) -> Option<String> {
    let shares = shares.iter().take(n as usize).cloned().collect::<Vec<_>>();
    SecretData::recover_secret(n, shares)
}

fn main() {
    let secret_data = SecretData::with_secret("Hello World!", 5);

    let shares = vec![
        secret_data.get_share(1),
        secret_data.get_share(2),
        secret_data.get_share(3),
        secret_data.get_share(4),
        secret_data.get_share(5),
    ];

    let recovered = try_recover(5, &shares).unwrap();
    println!("Good recover: {}", recovered);

    let recovered = try_recover(3, &shares).unwrap();
    println!("Bad recover: {}", recovered);
}

The reason for this bug is the following part of with_secret:

        let mut rand_container = [0u8, threshold - 1];

I presume this was meant to be [0u8; threshold - 1] (an array of zeroes of threshold - 1 size) but this won't work in Rust -- instead you should use something like vec![0; threshold - 1]. I'm a bit worried by the very minimal test-cases (failure to reconstruct isn't checked, and the only threshold tests are for 3 shares -- this bug would've been obvious if there was a test for 2 shares).

As it stands, I would humbly suggest that you yank all releases of shamir.

created time in a day

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (r *Root) Clone() (*Root, error) {+	newRoot := (*C.pathrs_root_t)(C.pathrs_duplicate(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(newRoot))+	if err != nil {+		return nil, err+	}++	return &Root{root: newRoot}, nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		C.pathrs_free(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}+}++func newHandle(handlePtr *C.pathrs_handle_t) *Handle {+	return &Handle{+		handle:  handlePtr,+		nameGen: rand.New(rand.NewSource(time.Now().UTC().UnixNano())),+	}+}++// Reopen upgrade the handle to a file representation+// which holds a usable fd, suitable for reading and writing+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := C.pathrs_reopen(h.handle, C.int(flags))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+	if err != nil {+		return nil, err+	}++	name, err := randName(h.nameGen, 32)+	if err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "pathrs-handle:"+name)+	return file, nil+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (h *Handle) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (h *Handle) Clone() (*Handle, error) {+	newHandler := (*C.pathrs_handle_t)(C.pathrs_duplicate(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(newHandler))+	if err != nil {+		return nil, err+	}++	return newHandle(newHandler), nil+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	C.pathrs_free(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	errno       uint64+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++func (e *Error) Unwrap() error {+	if e.errno != 0 {+		return syscall.Errno(e.errno)+	}++	return nil+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, line.ip-line.sAddress))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(e *C.pathrs_error_t) error {+	if e == nil {+		return nil+	}++	err := &Error{+		errno:       uint64(e.saved_errno),+		description: C.GoString(e.description),+		backtrace:   nil,+	}++	if e.backtrace != nil {+		head := uintptr(unsafe.Pointer(e.backtrace.head))+		length := uintptr(e.backtrace.length)+		sizeof := unsafe.Sizeof(C.__pathrs_backtrace_entry_t{})+		for ptr := head; ptr < head+length*sizeof; ptr += sizeof {+			entry := (*C.__pathrs_backtrace_entry_t)(unsafe.Pointer(ptr))+			line := backtraceLine{+				ip:       uintptr(entry.ip),+				sAddress: uintptr(entry.symbol_address),+				sLineno:  uint32(entry.symbol_lineno),+				sFile:    C.GoString(entry.symbol_file),

Oh wait, we'd need to make it a *string... If only Go had Option types... Oh well, for now (since symbols and files can't be empty normally) we could just leave it as-is.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand

"crypto/rand".Read doesn't need a seed.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {

The other alternative would be to set the inner pointer to NULL inside IntoFd, that way you avoid the double-frees.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (r *Root) Clone() (*Root, error) {+	newRoot := (*C.pathrs_root_t)(C.pathrs_duplicate(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(newRoot))+	if err != nil {+		return nil, err+	}++	return &Root{root: newRoot}, nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		C.pathrs_free(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}+}++func newHandle(handlePtr *C.pathrs_handle_t) *Handle {+	return &Handle{+		handle:  handlePtr,+		nameGen: rand.New(rand.NewSource(time.Now().UTC().UnixNano())),+	}+}++// Reopen upgrade the handle to a file representation+// which holds a usable fd, suitable for reading and writing+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := C.pathrs_reopen(h.handle, C.int(flags))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+	if err != nil {+		return nil, err+	}++	name, err := randName(h.nameGen, 32)+	if err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "pathrs-handle:"+name)+	return file, nil+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (h *Handle) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (h *Handle) Clone() (*Handle, error) {+	newHandler := (*C.pathrs_handle_t)(C.pathrs_duplicate(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(newHandler))+	if err != nil {+		return nil, err+	}++	return newHandle(newHandler), nil+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {

This should match Root.Free for consistency.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {

I'm not a huge fan of this function name, but I can clean that up later.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {

Should be moved along with the struct Handle definition. Honestly I actually missed this the first time I read through the change.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (r *Root) Clone() (*Root, error) {+	newRoot := (*C.pathrs_root_t)(C.pathrs_duplicate(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(newRoot))+	if err != nil {+		return nil, err+	}++	return &Root{root: newRoot}, nil+}++// Free frees underling caught resources+func (r *Root) Free() {

This (and handle.Free) should be called Close to match most other Go libraries.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {

Since Go doesn't have a lifetime model in the same way as Rust does, really this wrapper should internally clone the Root so that it gives you a new file descriptor rather than possibly triggering use-after-frees (especially if the user did defer root.Free()). Also this should probably return an *os.File.

Same goes for Handle.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (r *Root) Clone() (*Root, error) {+	newRoot := (*C.pathrs_root_t)(C.pathrs_duplicate(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(newRoot))+	if err != nil {+		return nil, err+	}++	return &Root{root: newRoot}, nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		C.pathrs_free(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}+}++func newHandle(handlePtr *C.pathrs_handle_t) *Handle {+	return &Handle{+		handle:  handlePtr,+		nameGen: rand.New(rand.NewSource(time.Now().UTC().UnixNano())),+	}+}++// Reopen upgrade the handle to a file representation+// which holds a usable fd, suitable for reading and writing+func (h *Handle) Reopen(flags int) (*os.File, error) {

I don't like that callers would have to pass unix.O_RDONLY (it's unlike most Go libraries)-- ideally there would be separate methods for each (to match os.Open and os.CreateFile). But I can fix this later...

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (r *Root) Clone() (*Root, error) {+	newRoot := (*C.pathrs_root_t)(C.pathrs_duplicate(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(newRoot))+	if err != nil {+		return nil, err+	}++	return &Root{root: newRoot}, nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		C.pathrs_free(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}+}++func newHandle(handlePtr *C.pathrs_handle_t) *Handle {

This won't be necessary once you drop nameGen.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand

Also (sorry, I should've been clearer about this in my last review) this Handle declaration should be right above where you define the methods for *Handle -- as is common with most Go codebases.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand

There's no real need for this -- the top-level "math/rand".Read is safe for concurrent access (and in fact, by storing a rand.Rand this randomness source is no longer safe for concurrency). Another alternative is "crypto/rand".Read.

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (r *Root) Clone() (*Root, error) {+	newRoot := (*C.pathrs_root_t)(C.pathrs_duplicate(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(newRoot))+	if err != nil {+		return nil, err+	}++	return &Root{root: newRoot}, nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		C.pathrs_free(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}+}++func newHandle(handlePtr *C.pathrs_handle_t) *Handle {+	return &Handle{+		handle:  handlePtr,+		nameGen: rand.New(rand.NewSource(time.Now().UTC().UnixNano())),+	}+}++// Reopen upgrade the handle to a file representation+// which holds a usable fd, suitable for reading and writing+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := C.pathrs_reopen(h.handle, C.int(flags))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+	if err != nil {+		return nil, err+	}++	name, err := randName(h.nameGen, 32)+	if err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "pathrs-handle:"+name)+	return file, nil+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (h *Handle) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (h *Handle) Clone() (*Handle, error) {+	newHandler := (*C.pathrs_handle_t)(C.pathrs_duplicate(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(newHandler))+	if err != nil {+		return nil, err+	}++	return newHandle(newHandler), nil+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	C.pathrs_free(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	errno       uint64+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++func (e *Error) Unwrap() error {+	if e.errno != 0 {+		return syscall.Errno(e.errno)+	}++	return nil+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, line.ip-line.sAddress))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(e *C.pathrs_error_t) error {+	if e == nil {+		return nil+	}++	err := &Error{+		errno:       uint64(e.saved_errno),+		description: C.GoString(e.description),+		backtrace:   nil,+	}++	if e.backtrace != nil {+		head := uintptr(unsafe.Pointer(e.backtrace.head))+		length := uintptr(e.backtrace.length)+		sizeof := unsafe.Sizeof(C.__pathrs_backtrace_entry_t{})+		for ptr := head; ptr < head+length*sizeof; ptr += sizeof {+			entry := (*C.__pathrs_backtrace_entry_t)(unsafe.Pointer(ptr))+			line := backtraceLine{+				ip:       uintptr(entry.ip),+				sAddress: uintptr(entry.symbol_address),+				sLineno:  uint32(entry.symbol_lineno),+				sFile:    C.GoString(entry.symbol_file),

C.GoString returns "" if given a NULL pointer (but we'd want nil to indicate that symbol information isn't available).

zhiburt

comment created time in 2 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2020 Maxim Zhiburt <zhiburt@gmail.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++// #cgo LDFLAGS: -lpathrs+// #include <pathrs.h>+import "C"+import (+	"fmt"+	"math/rand"+	"os"+	"strings"+	"syscall"+	"time"+	"unsafe"+)++// Root holds the responsibility to provide safe api to functions of pathrs root api+type Root struct {+	root *C.pathrs_root_t+}++// Handle represents an handle pathrs api interface+type Handle struct {+	handle  *C.pathrs_handle_t+	nameGen *rand.Rand+}++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := C.pathrs_open(C.CString(path))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// RootFromFd constructs a new file-based libpathrs object of root from a file descriptor+// Uses often in combination with Root.IntoFd methood+func RootFromFd(fd int) (*Root, error) {+	root := (*C.pathrs_root_t)(C.pathrs_from_fd(C.PATHRS_ROOT, C.int(fd)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(root))+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// HandleFromFd constructs a new file-based libpathrs object of handle from a file descriptor+// Uses often in combination with Handle.IntoFd methood+func HandleFromFd(fd int) (*Handle, error) {+	handler := (*C.pathrs_handle_t)(C.pathrs_from_fd(C.PATHRS_HANDLE, C.int(fd)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(handler))+	if err != nil {+		return nil, err+	}++	return newHandle(handler), nil+}++// Resolve resolves the given path within the given root's tree+// and return a handle to that path. The path must+// already exist, otherwise an error will occur.+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := C.pathrs_resolve(r.root, C.CString(path))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Creat creates a file with a such mode by path according with the root path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := C.pathrs_creat(r.root, C.CString(path), C.uint(mode))+	if handler == nil {+		return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return newHandle(handler), nil+}++// Rename Within the given root's tree, perform the rename of src to dst,+// or change flags on this file if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	C.pathrs_rename(r.root, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	C.pathrs_mkdir(r.root, C.CString(path), C.uint(mode))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	C.pathrs_mknod(r.root, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	C.pathrs_hardlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	C.pathrs_symlink(r.root, C.CString(path), C.CString(target))+	return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (r *Root) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (r *Root) Clone() (*Root, error) {+	newRoot := (*C.pathrs_root_t)(C.pathrs_duplicate(C.PATHRS_ROOT, unsafe.Pointer(r.root)))+	err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(newRoot))+	if err != nil {+		return nil, err+	}++	return &Root{root: newRoot}, nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		C.pathrs_free(C.PATHRS_ROOT, unsafe.Pointer(r.root))+	}+}++func newHandle(handlePtr *C.pathrs_handle_t) *Handle {+	return &Handle{+		handle:  handlePtr,+		nameGen: rand.New(rand.NewSource(time.Now().UTC().UnixNano())),+	}+}++// Reopen upgrade the handle to a file representation+// which holds a usable fd, suitable for reading and writing+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := C.pathrs_reopen(h.handle, C.int(flags))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+	if err != nil {+		return nil, err+	}++	name, err := randName(h.nameGen, 32)+	if err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "pathrs-handle:"+name)+	return file, nil+}++// IntoFd unwraps a file-based libpathrs object to obtain its underlying file+// descriptor.+//+// It is critical that you do not operate on this file descriptor yourself,+// because the security properties of libpathrs depend on users doing all+// relevant filesystem operations through libpathrs.+func (h *Handle) IntoFd() (int, error) {+	fd := int(C.pathrs_into_fd(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)))+	if fd < 0 {+		return 0, handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+	}++	return fd, nil+}++// Clone creates a copy of root handler the new object will have a separate lifetime+// from the original, but will refer to the same underlying file+func (h *Handle) Clone() (*Handle, error) {+	newHandler := (*C.pathrs_handle_t)(C.pathrs_duplicate(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)))+	err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(newHandler))+	if err != nil {+		return nil, err+	}++	return newHandle(newHandler), nil+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	C.pathrs_free(C.PATHRS_HANDLE, unsafe.Pointer(h.handle))+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	errno       uint64+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++func (e *Error) Unwrap() error {+	if e.errno != 0 {+		return syscall.Errno(e.errno)+	}++	return nil+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, line.ip-line.sAddress))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(e *C.pathrs_error_t) error {+	if e == nil {+		return nil+	}++	err := &Error{+		errno:       uint64(e.saved_errno),+		description: C.GoString(e.description),+		backtrace:   nil,+	}++	if e.backtrace != nil {+		head := uintptr(unsafe.Pointer(e.backtrace.head))+		length := uintptr(e.backtrace.length)+		sizeof := unsafe.Sizeof(C.__pathrs_backtrace_entry_t{})

It turns out you can do this with C.sizeof___pathrs_backtrace_entry_t. Then again, maybe we shouldn't...

zhiburt

comment created time in 2 days

pull request commentopenSUSE/libpathrs

Golang binding

@zhiburt You're right that technically the current error handling is not thread-safe. Maybe we should use thread-local storage to fix that (then again, @brauner complained very loudly when I suggested this a while ago). But the underlying libpathrs operations are thread-safe so I'll need to think about how to do deal with this...

zhiburt

comment created time in 2 days

push eventopenSUSE/libpathrs

Aleksa Sarai

commit sha 246973ef0c515e86cd215837a4b11344dcf58ee6

utils: update PROCFS_HANDLE issues Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 2 days

push eventcyphar/umoci

Aleksa Sarai

commit sha 891b77e3de9981acf489d5cf5df80e53f18ee5d0

oci: tar_extract: make debugging tar type-flags easier It's a bit confusing to figure out what a type-flag is when you're just given the ASCII value, so instead provide a textual value for debugging if the type-flag is known. Signed-off-by: Aleksa Sarai <asarai@suse.de>

view details

push time in 3 days

create barnchcyphar/umoci

branch : ubuntu-link-eperm

created branch time in 3 days

push eventopenSUSE/libpathrs

Aleksa Sarai

commit sha e1519925ed553ff9341e0fe9db5a3822bbecdc91

*: drop errno crate requirement std::io::Error gives us everything we need in order to handle errno values, thus using a separate crate is pointless. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 SUSE LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");

Examples should be licensed under the LGPL-3.0-or-later (only the binding code is Apache-2.0) See the other examples for the license text to copy.

Also, given that you wrote this (and the binding) feel free to add your own // Copyright (C) 2020 ... line with your name for this file and the other binding file.

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"fmt"+	"os"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := unsafe.Pointer(C.pathrs_open(C.CString(path)))+	err := handleErr(ptrTypeError, root)+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_resolve(rootPtr, C.CString(path)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_creat(rootPtr, C.CString(path), C.uint(mode)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_rename(rootPtr, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(ptrTypeRoot, r.root)+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mkdir(rootPtr, C.CString(path), C.uint(mode))+	return handleErr(ptrTypeRoot, r.root)+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mknod(rootPtr, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(ptrTypeRoot, r.root)+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_hardlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_symlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		free(ptrTypeRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle      cRootPtr+	fileCounter int+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(ptrTypeHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	handlePtr := (*C.pathrs_handle_t)(h.handle)+	fd := fd(C.pathrs_reopen(handlePtr, C.int(flags)))++	if err := handleErr(ptrTypeHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), h.uniqFileName())+	return file, nil+}++func (h *Handle) uniqFileName() string {+	name := fmt.Sprintf("pathrs:%d", h.fileCounter)+	h.fileCounter+++	return name+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	errno       uint64+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++func (e *Error) Unwrap() error {+	if e.errno != 0 {+		return syscall.Errno(e.errno)+	}++	return nil+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}+	buf.Reset()++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, line.ip-line.sAddress))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(e cErrorPtr) *Error {+	realError := (*errorType)(e)++	err := &Error{}+	err.errno = uint64(realError.savedErrno)+	if realError.description != nil {+		err.description = C.GoString(realError.description)+	}++	if realError.backtrace != nil {+		length := uintptr(realError.backtrace.length)+		size := unsafe.Sizeof(backtraceEntry{})+		for i := uintptr(0); i < length; i++ {+			head := (*backtraceEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(realError.backtrace.head)) + size*i))+			line := backtraceLine{}+			line.sAddress = head.symbolAddress+			line.ip = head.ip+			if head.symbolName != nil {+				line.sName = C.GoString(head.symbolName)+			}+			if head.symbolFile != nil {+				line.sFile = C.GoString(head.symbolFile)+				line.sLineno = uint32(head.symbolLineno)+			}++			err.backtrace = append(err.backtrace, line)+		}+	}++	return err+}++func handleErr(t pointerType, ptr unsafe.Pointer) (err error) {+	ptrType := C.pathrs_type_t(t)+	errPtr := cErrorPtr(C.pathrs_error(ptrType, ptr))+	if errPtr != nil {+		err = newError(errPtr)+	}+	free(ptrTypeError, errPtr)++	return+}++func free(ptrType pointerType, ptr unsafe.Pointer) {+	typeVal := C.pathrs_type_t(ptrType)+	C.pathrs_free(typeVal, ptr)+}++type pointerType = int++const (+	ptrTypeNone   pointerType = C.PATHRS_NONE+	ptrTypeError  pointerType = C.PATHRS_ERROR+	ptrTypeRoot   pointerType = C.PATHRS_ROOT+	ptrTypeHandle pointerType = C.PATHRS_HANDLE+)++type resolverType = uint64++const (+	kernelResolver   resolverType = C.PATHRS_KERNEL_RESOLVER+	emulatedResolver resolverType = C.PATHRS_EMULATED_RESOLVER+)++type cRootPtr = unsafe.Pointer+type cErrorPtr = unsafe.Pointer+type cHandlePtr = unsafe.Pointer

All of these type and const definitions should go at the start of the file.

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"fmt"+	"os"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := unsafe.Pointer(C.pathrs_open(C.CString(path)))+	err := handleErr(ptrTypeError, root)+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_resolve(rootPtr, C.CString(path)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_creat(rootPtr, C.CString(path), C.uint(mode)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_rename(rootPtr, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(ptrTypeRoot, r.root)+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mkdir(rootPtr, C.CString(path), C.uint(mode))+	return handleErr(ptrTypeRoot, r.root)+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mknod(rootPtr, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(ptrTypeRoot, r.root)+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_hardlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_symlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		free(ptrTypeRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle      cRootPtr+	fileCounter int+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(ptrTypeHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	handlePtr := (*C.pathrs_handle_t)(h.handle)+	fd := fd(C.pathrs_reopen(handlePtr, C.int(flags)))++	if err := handleErr(ptrTypeHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), h.uniqFileName())+	return file, nil+}++func (h *Handle) uniqFileName() string {+	name := fmt.Sprintf("pathrs:%d", h.fileCounter)+	h.fileCounter+++	return name+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	errno       uint64+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++func (e *Error) Unwrap() error {+	if e.errno != 0 {+		return syscall.Errno(e.errno)+	}++	return nil+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}+	buf.Reset()++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, line.ip-line.sAddress))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(e cErrorPtr) *Error {+	realError := (*errorType)(e)++	err := &Error{}+	err.errno = uint64(realError.savedErrno)+	if realError.description != nil {+		err.description = C.GoString(realError.description)+	}++	if realError.backtrace != nil {+		length := uintptr(realError.backtrace.length)+		size := unsafe.Sizeof(backtraceEntry{})+		for i := uintptr(0); i < length; i++ {+			head := (*backtraceEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(realError.backtrace.head)) + size*i))+			line := backtraceLine{}+			line.sAddress = head.symbolAddress+			line.ip = head.ip+			if head.symbolName != nil {+				line.sName = C.GoString(head.symbolName)+			}+			if head.symbolFile != nil {+				line.sFile = C.GoString(head.symbolFile)+				line.sLineno = uint32(head.symbolLineno)+			}++			err.backtrace = append(err.backtrace, line)+		}+	}++	return err+}++func handleErr(t pointerType, ptr unsafe.Pointer) (err error) {+	ptrType := C.pathrs_type_t(t)+	errPtr := cErrorPtr(C.pathrs_error(ptrType, ptr))+	if errPtr != nil {+		err = newError(errPtr)+	}+	free(ptrTypeError, errPtr)++	return+}++func free(ptrType pointerType, ptr unsafe.Pointer) {+	typeVal := C.pathrs_type_t(ptrType)+	C.pathrs_free(typeVal, ptr)+}++type pointerType = int++const (+	ptrTypeNone   pointerType = C.PATHRS_NONE+	ptrTypeError  pointerType = C.PATHRS_ERROR+	ptrTypeRoot   pointerType = C.PATHRS_ROOT+	ptrTypeHandle pointerType = C.PATHRS_HANDLE+)++type resolverType = uint64++const (+	kernelResolver   resolverType = C.PATHRS_KERNEL_RESOLVER+	emulatedResolver resolverType = C.PATHRS_EMULATED_RESOLVER+)++type cRootPtr = unsafe.Pointer+type cErrorPtr = unsafe.Pointer+type cHandlePtr = unsafe.Pointer++type fd = int

I don't think this type alias adds much.

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"fmt"+	"os"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := unsafe.Pointer(C.pathrs_open(C.CString(path)))+	err := handleErr(ptrTypeError, root)+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_resolve(rootPtr, C.CString(path)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_creat(rootPtr, C.CString(path), C.uint(mode)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_rename(rootPtr, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(ptrTypeRoot, r.root)+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mkdir(rootPtr, C.CString(path), C.uint(mode))+	return handleErr(ptrTypeRoot, r.root)+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mknod(rootPtr, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(ptrTypeRoot, r.root)+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_hardlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_symlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		free(ptrTypeRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle      cRootPtr+	fileCounter int+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(ptrTypeHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	handlePtr := (*C.pathrs_handle_t)(h.handle)+	fd := fd(C.pathrs_reopen(handlePtr, C.int(flags)))++	if err := handleErr(ptrTypeHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), h.uniqFileName())+	return file, nil+}++func (h *Handle) uniqFileName() string {+	name := fmt.Sprintf("pathrs:%d", h.fileCounter)+	h.fileCounter+++	return name+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	errno       uint64+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++func (e *Error) Unwrap() error {+	if e.errno != 0 {+		return syscall.Errno(e.errno)+	}++	return nil+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}+	buf.Reset()++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, line.ip-line.sAddress))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(e cErrorPtr) *Error {+	realError := (*errorType)(e)++	err := &Error{}+	err.errno = uint64(realError.savedErrno)+	if realError.description != nil {+		err.description = C.GoString(realError.description)+	}++	if realError.backtrace != nil {+		length := uintptr(realError.backtrace.length)+		size := unsafe.Sizeof(backtraceEntry{})+		for i := uintptr(0); i < length; i++ {+			head := (*backtraceEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(realError.backtrace.head)) + size*i))+			line := backtraceLine{}+			line.sAddress = head.symbolAddress+			line.ip = head.ip+			if head.symbolName != nil {+				line.sName = C.GoString(head.symbolName)+			}+			if head.symbolFile != nil {+				line.sFile = C.GoString(head.symbolFile)+				line.sLineno = uint32(head.symbolLineno)+			}++			err.backtrace = append(err.backtrace, line)+		}+	}++	return err+}++func handleErr(t pointerType, ptr unsafe.Pointer) (err error) {+	ptrType := C.pathrs_type_t(t)+	errPtr := cErrorPtr(C.pathrs_error(ptrType, ptr))+	if errPtr != nil {+		err = newError(errPtr)+	}+	free(ptrTypeError, errPtr)++	return

I don't like Go's implicit named returns. And since pathrs_free handles NULL pointers perfectly fine, we can just do:

func handleErr(ptrType C.pathrs_type_t, ptr unsafe.Pointer) error {
	err := C.pathrs_error(ptrType, ptr)
	defer free(C.PATHRS_ERROR, err)
	return newError(err)
}

And newError returns nil if errPtr is nil.

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"fmt"+	"os"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := unsafe.Pointer(C.pathrs_open(C.CString(path)))+	err := handleErr(ptrTypeError, root)+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_resolve(rootPtr, C.CString(path)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_creat(rootPtr, C.CString(path), C.uint(mode)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_rename(rootPtr, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(ptrTypeRoot, r.root)+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mkdir(rootPtr, C.CString(path), C.uint(mode))+	return handleErr(ptrTypeRoot, r.root)+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mknod(rootPtr, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(ptrTypeRoot, r.root)+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_hardlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_symlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		free(ptrTypeRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle      cRootPtr+	fileCounter int+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(ptrTypeHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	handlePtr := (*C.pathrs_handle_t)(h.handle)+	fd := fd(C.pathrs_reopen(handlePtr, C.int(flags)))++	if err := handleErr(ptrTypeHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), h.uniqFileName())+	return file, nil+}++func (h *Handle) uniqFileName() string {+	name := fmt.Sprintf("pathrs:%d", h.fileCounter)+	h.fileCounter+++	return name+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	errno       uint64+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++func (e *Error) Unwrap() error {+	if e.errno != 0 {+		return syscall.Errno(e.errno)+	}++	return nil+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}+	buf.Reset()++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, line.ip-line.sAddress))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(e cErrorPtr) *Error {+	realError := (*errorType)(e)++	err := &Error{}+	err.errno = uint64(realError.savedErrno)+	if realError.description != nil {+		err.description = C.GoString(realError.description)+	}++	if realError.backtrace != nil {+		length := uintptr(realError.backtrace.length)+		size := unsafe.Sizeof(backtraceEntry{})+		for i := uintptr(0); i < length; i++ {+			head := (*backtraceEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(realError.backtrace.head)) + size*i))+			line := backtraceLine{}+			line.sAddress = head.symbolAddress+			line.ip = head.ip+			if head.symbolName != nil {+				line.sName = C.GoString(head.symbolName)+			}+			if head.symbolFile != nil {+				line.sFile = C.GoString(head.symbolFile)+				line.sLineno = uint32(head.symbolLineno)+			}++			err.backtrace = append(err.backtrace, line)+		}+	}++	return err+}++func handleErr(t pointerType, ptr unsafe.Pointer) (err error) {+	ptrType := C.pathrs_type_t(t)+	errPtr := cErrorPtr(C.pathrs_error(ptrType, ptr))+	if errPtr != nil {+		err = newError(errPtr)+	}+	free(ptrTypeError, errPtr)++	return+}++func free(ptrType pointerType, ptr unsafe.Pointer) {

Why not just make this take a C.pathrs_type_t? I find it harder to follow as-is, and there's no real benefit to having the Go versions of these constants (since they're only used inside the binding).

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.+package pathrs++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"fmt"+	"os"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	root := unsafe.Pointer(C.pathrs_open(C.CString(path)))+	err := handleErr(ptrTypeError, root)+	if err != nil {+		return nil, err+	}++	return &Root{root: root}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_resolve(rootPtr, C.CString(path)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	rootPtr := (*C.pathrs_root_t)(r.root)+	handler := cHandlePtr(C.pathrs_creat(rootPtr, C.CString(path), C.uint(mode)))+	if handler == nil {+		return nil, handleErr(ptrTypeRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_rename(rootPtr, C.CString(src), C.CString(dst), C.int(flags))+	return handleErr(ptrTypeRoot, r.root)+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mkdir(rootPtr, C.CString(path), C.uint(mode))+	return handleErr(ptrTypeRoot, r.root)+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_mknod(rootPtr, C.CString(path), C.uint(mode), C.dev_t(dev))+	return handleErr(ptrTypeRoot, r.root)+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_hardlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	rootPtr := (*C.pathrs_root_t)(r.root)+	C.pathrs_symlink(rootPtr, C.CString(path), C.CString(target))+	return handleErr(ptrTypeRoot, r.root)+}++// Free frees underling caught resources+func (r *Root) Free() {+	if r != nil {+		free(ptrTypeRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle      cRootPtr+	fileCounter int+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(ptrTypeHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	handlePtr := (*C.pathrs_handle_t)(h.handle)+	fd := fd(C.pathrs_reopen(handlePtr, C.int(flags)))++	if err := handleErr(ptrTypeHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), h.uniqFileName())+	return file, nil+}++func (h *Handle) uniqFileName() string {+	name := fmt.Sprintf("pathrs:%d", h.fileCounter)+	h.fileCounter+++	return name+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	errno       uint64+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++func (e *Error) Unwrap() error {+	if e.errno != 0 {+		return syscall.Errno(e.errno)+	}++	return nil+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}+	buf.Reset()++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, line.ip-line.sAddress))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(e cErrorPtr) *Error {+	realError := (*errorType)(e)++	err := &Error{}+	err.errno = uint64(realError.savedErrno)+	if realError.description != nil {+		err.description = C.GoString(realError.description)+	}++	if realError.backtrace != nil {+		length := uintptr(realError.backtrace.length)+		size := unsafe.Sizeof(backtraceEntry{})+		for i := uintptr(0); i < length; i++ {+			head := (*backtraceEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(realError.backtrace.head)) + size*i))+			line := backtraceLine{}+			line.sAddress = head.symbolAddress+			line.ip = head.ip+			if head.symbolName != nil {+				line.sName = C.GoString(head.symbolName)+			}+			if head.symbolFile != nil {+				line.sFile = C.GoString(head.symbolFile)+				line.sLineno = uint32(head.symbolLineno)+			}++			err.backtrace = append(err.backtrace, line)+		}+	}++	return err+}++func handleErr(t pointerType, ptr unsafe.Pointer) (err error) {+	ptrType := C.pathrs_type_t(t)+	errPtr := cErrorPtr(C.pathrs_error(ptrType, ptr))+	if errPtr != nil {+		err = newError(errPtr)+	}+	free(ptrTypeError, errPtr)++	return+}++func free(ptrType pointerType, ptr unsafe.Pointer) {+	typeVal := C.pathrs_type_t(ptrType)+	C.pathrs_free(typeVal, ptr)+}++type pointerType = int++const (+	ptrTypeNone   pointerType = C.PATHRS_NONE+	ptrTypeError  pointerType = C.PATHRS_ERROR+	ptrTypeRoot   pointerType = C.PATHRS_ROOT+	ptrTypeHandle pointerType = C.PATHRS_HANDLE+)++type resolverType = uint64++const (+	kernelResolver   resolverType = C.PATHRS_KERNEL_RESOLVER+	emulatedResolver resolverType = C.PATHRS_EMULATED_RESOLVER+)++type cRootPtr = unsafe.Pointer+type cErrorPtr = unsafe.Pointer+type cHandlePtr = unsafe.Pointer++type fd = int++type errorType struct {

This question applies to all of these structs -- why not use the C.pathrs_error_t and similar cgo-generated types (in case you didn't know, cgo generates Go structs based on the C structs defined in import "C" source). I have a feeling quite a few of the unsafe.Pointer usages are actually incorrect because Go doesn't guarantee that their structs have identical layouts to C -- only the cgo ones are guaranteed to work.

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory

Don't sweat it, I can clean it up myself.

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "")+	return file, nil+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {

Oh I forgot we don't have this https://github.com/pkg/errors/issues/216...

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "")+	return file, nil+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}+	buf.Reset()++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, uintptr(line.ip)-uintptr(line.sAddress)))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(err cErrorPtr) *Error {+	var description string+	var backtrace []backtraceLine++	realError := (*errorType)(err)++	if realError.savedErrno != 0 {+		description = fmt.Sprint(syscall.Errno(realError.savedErrno))+	} else if realError.description != nil {+		description = toGoString(realError.description)+	}++	if realError.backtrace != nil {+		length := uintptr(realError.backtrace.length)+		size := unsafe.Sizeof(backtraceEntry{})+		for i := uintptr(0); i < length; i++ {+			head := (*backtraceEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(realError.backtrace.head)) + size*i))

Here's a nicer way of doing it (see this playground for an example):

	head := uintptr(unsafe.Pointer(realError.backtrace.head))
	length := uintptr(realError.backtrace.length)
	sizeof := unsafe.Sizeof(C.backtrace_entry_t{})

	for ptr := head; ptr < head + length*sizeof; ptr += sizeof {
		entry := (*C.backtrace_entry_t)(unsafe.Pointer(ptr))
		// and now put it inside a backtraceEntry
	}

The trick is to avoid going to unsafe.Pointer until the last possible second, and doing all the arithmetic with uintptr. The above loop is effectively the Go equivalent to a similar loop in C.

It's not quite right to cast to a Go struct from a C struct pointer, hence why I've used C.backtrace_entry_t above (though I haven't checked if it will compile using those).

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"

Yeah, you can use the CGO_* flags -- but in the long term I would prefer we had:

// #cgo pkg-config: pathrs
// #include <pathrs.h>

For now, how about you just have:

// #cgo LDFLAGS: -lpathrs
// #include <pathrs.h>

And the other LDFLAGS can be set in the environment. You also will need to set CGO_CFLAGS=-I../../../../include/ or similar.

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>

Right, it's because you're using cgo in the bindings and Go has weird rules about what files can be in the same directory.

Okay, keep it in a separate directory for now and I'll deal with it later.

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/

Sorry, I misread the check -- I thought the check was

if r.root != nil {
   free(...)
}

Yeah, this is fine -- though can you please fix up the comment so it matches the rest of the coding style?

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "")

Sort of (thought that code is not race-safe because different threads could attempt to re-open the file at the same time leading to the same counter being used). It'd be simpler to just generate a random hex string each time. Something like

import "math/rand"
func randName(len int) (string, error) {
  var nameBuf strings.Builder
  randBuf := make([]byte, len / 2)

  n, err := rand.Read(randBuf)
  if n != len || err != nil {
    // shouldn't happen
    return "", fmt.Errorf("rand.Read didn't return %d bytes: %v", len, err)
  }

  for _, b := range randBuf {
    nameBuf.WriteString(fmt.Sprintf("%.2x", b))
  }
  return nameBuf.String()
}

And then you can just do

name, err := randName(32)
if err != nil {
  return nil, err
}
file := os.NewFile(uintptr(fd), "pathrs-handle:" + name)
zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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 main++import (+	"fmt"+	"io/ioutil"+	"os"++	"../../contrib/bindings/go/pathrs"

LDFLAGS are separate to Go import paths. If you clone this repo (libpathrs) into $GOPATH/src/github.com/openSUSE/libpathrs then the import github.com/openSUSE/libpathrs/contrib/bindings/go will work.

However, you're right that this is a pain to import. I will think about this some more, so you can just leave it as-is...

zhiburt

comment created time in 3 days

pull request commentopenSUSE/libpathrs

Golang binding

@zhiburt

The latest commits were provided with it but the initial ones are untouched since it seems I will have to make a force push.

Please just force-push to your branch. I would also prefer if you squashed the commits down into a few small pieces. If you've never used git rebase -i before, here's a video that might help.

Personally I find it helpful to also have commit messages that have a body, but you don't need to bother with that too much.

And according the fact that the commit list have slightly grown may I just reopen it after the review process with the foregoing line?

You don't need to open a new PR, just force push to your current PR's branch with fixed up commits.

What is more I have recently found out that commit style of the repo is different to mine, apparently that is my fault.

Don't worry about it too much. If it helps, the commit style of this repo is loosely based on the Linux kernel commit style or the style of several other container runtime projects like Docker and runc (and LXC, and LXD, and ...).

zhiburt

comment created time in 3 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"

Yeah, you can use the CGO_* flags -- but in the long term I would prefer we had:

// #cgo pkg-config: pathrs
// #include <pathrs.h>

For now, how about you just have:

// #cgo LDFLAGS: -lpathrs
// #include <pathrs.h>

And the other LDFLAGS can be set in the environment. You also will need to set CGO_CFLAGS=-I../../../../include/ or similar.

zhiburt

comment created time in 3 days

push eventopenSUSE/libpathrs

Aleksa Sarai

commit sha 074f03e077f5a39ec48a2c4e9648c6012bdefc59

syscalls: update to newest openat2 ABI The __padding field is going to go away[1]. [1]: https://lore.kernel.org/lkml/20200118120800.16358-1-cyphar@cyphar.com/ Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 4 days

push eventcyphar/linux

Aleksa Sarai

commit sha 2b98149c2377bff12be5dd3ce02ae0506e2dd613

namei: only return -ECHILD from follow_dotdot_rcu() It's over-zealous to return hard errors under RCU-walk here, given that a REF-walk will be triggered for all other cases handling ".." under RCU. The original purpose of this check was to ensure that if a rename occurs such that a directory is moved outside of the bind-mount which the resolution started in, it would be detected and blocked to avoid being able to mess with paths outside of the bind-mount. However, triggering a new REF-walk is just as effective a solution. Cc: "Eric W. Biederman" <ebiederm@xmission.com> Fixes: 397d425dc26d ("vfs: Test for and handle paths that are unreachable from their mnt_root") Suggested-by: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha ce623f89872df4253719be71531116751eeab85f

nsfs: clean-up ns_get_path() signature to return int ns_get_path() and ns_get_path_cb() only ever return either NULL or an ERR_PTR. It is far more idiomatic to simply return an integer, and it makes all of the callers of ns_get_path() more straightforward to read. Fixes: e149ed2b805f ("take the targets of /proc/*/ns/* symlinks to separate fs") Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 1bc82070fa2763bdca626fa8bde72b35f11e8960

namei: allow nd_jump_link() to produce errors In preparation for LOOKUP_NO_MAGICLINKS, it's necessary to add the ability for nd_jump_link() to return an error which the corresponding get_link() caller must propogate back up to the VFS. Suggested-by: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 740a16782750a5b6c7d1609a9c09641ce6753ea6

namei: allow set_root() to produce errors For LOOKUP_BENEATH and LOOKUP_IN_ROOT it is necessary to ensure that set_root() is never called, and thus (for hardening purposes) it should return an error rather than permit a breakout from the root. In addition, move all of the repetitive set_root() calls to nd_jump_root(). Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 278121417a72d87fb29dd8c48801f80821e8f75a

namei: LOOKUP_NO_SYMLINKS: block symlink resolution /* Background. */ Userspace cannot easily resolve a path without resolving symlinks, and would have to manually resolve each path component with O_PATH and O_NOFOLLOW. This is clearly inefficient, and can be fairly easy to screw up (resulting in possible security bugs). Linus has mentioned that Git has a particular need for this kind of flag[1]. It also resolves a fairly long-standing perceived deficiency in O_NOFOLLOw -- that it only blocks the opening of trailing symlinks. This is part of a refresh of Al's AT_NO_JUMPS patchset[2] (which was a variation on David Drysdale's O_BENEATH patchset[3], which in turn was based on the Capsicum project[4]). /* Userspace API. */ LOOKUP_NO_SYMLINKS will be exposed to userspace through openat2(2). /* Semantics. */ Unlike most other LOOKUP flags (most notably LOOKUP_FOLLOW), LOOKUP_NO_SYMLINKS applies to all components of the path. With LOOKUP_NO_SYMLINKS, any symlink path component encountered during path resolution will yield -ELOOP. If the trailing component is a symlink (and no other components were symlinks), then O_PATH|O_NOFOLLOW will not error out and will instead provide a handle to the trailing symlink -- without resolving it. /* Testing. */ LOOKUP_NO_SYMLINKS is tested as part of the openat2(2) selftests. [1]: https://lore.kernel.org/lkml/CA+55aFyOKM7DW7+0sdDFKdZFXgptb5r1id9=Wvhd8AgSP7qjwQ@mail.gmail.com/ [2]: https://lore.kernel.org/lkml/20170429220414.GT29622@ZenIV.linux.org.uk/ [3]: https://lore.kernel.org/lkml/1415094884-18349-1-git-send-email-drysdale@google.com/ [4]: https://lore.kernel.org/lkml/1404124096-21445-1-git-send-email-drysdale@google.com/ Cc: Christian Brauner <christian.brauner@ubuntu.com> Suggested-by: Al Viro <viro@zeniv.linux.org.uk> Suggested-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 4b99d4996979d582859c5a49072e92de124bf691

namei: LOOKUP_NO_MAGICLINKS: block magic-link resolution /* Background. */ There has always been a special class of symlink-like objects in procfs (and a few other pseudo-filesystems) which allow for non-lexical resolution of paths using nd_jump_link(). These "magic-links" do not follow traditional mount namespace boundaries, and have been used consistently in container escape attacks because they can be used to trick unsuspecting privileged processes into resolving unexpected paths. It is also non-trivial for userspace to unambiguously avoid resolving magic-links, because they do not have a reliable indication that they are a magic-link (in order to verify them you'd have to manually open the path given by readlink(2) and then verify that the two file descriptors reference the same underlying file, which is plagued with possible race conditions or supplementary attack scenarios). It would therefore be very helpful for userspace to be able to avoid these symlinks easily, thus hopefully removing a tool from attackers' toolboxes. This is part of a refresh of Al's AT_NO_JUMPS patchset[1] (which was a variation on David Drysdale's O_BENEATH patchset[2], which in turn was based on the Capsicum project[3]). /* Userspace API. */ LOOKUP_NO_MAGICLINKS will be exposed to userspace through openat2(2). /* Semantics. */ Unlike most other LOOKUP flags (most notably LOOKUP_FOLLOW), LOOKUP_NO_MAGICLINKS applies to all components of the path. With LOOKUP_NO_MAGICLINKS, any magic-link path component encountered during path resolution will yield -ELOOP. The handling of ~LOOKUP_FOLLOW for a trailing magic-link is identical to LOOKUP_NO_SYMLINKS. LOOKUP_NO_SYMLINKS implies LOOKUP_NO_MAGICLINKS. /* Testing. */ LOOKUP_NO_MAGICLINKS is tested as part of the openat2(2) selftests. [1]: https://lore.kernel.org/lkml/20170429220414.GT29622@ZenIV.linux.org.uk/ [2]: https://lore.kernel.org/lkml/1415094884-18349-1-git-send-email-drysdale@google.com/ [3]: https://lore.kernel.org/lkml/1404124096-21445-1-git-send-email-drysdale@google.com/ Cc: Christian Brauner <christian.brauner@ubuntu.com> Suggested-by: David Drysdale <drysdale@google.com> Suggested-by: Al Viro <viro@zeniv.linux.org.uk> Suggested-by: Andy Lutomirski <luto@kernel.org> Suggested-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 72ba29297e1439efaa54d9125b866ae9d15df339

namei: LOOKUP_NO_XDEV: block mountpoint crossing /* Background. */ The need to contain path operations within a mountpoint has been a long-standing usecase that userspace has historically implemented manually with liberal usage of stat(). find, rsync, tar and many other programs implement these semantics -- but it'd be much simpler to have a fool-proof way of refusing to open a path if it crosses a mountpoint. This is part of a refresh of Al's AT_NO_JUMPS patchset[1] (which was a variation on David Drysdale's O_BENEATH patchset[2], which in turn was based on the Capsicum project[3]). /* Userspace API. */ LOOKUP_NO_XDEV will be exposed to userspace through openat2(2). /* Semantics. */ Unlike most other LOOKUP flags (most notably LOOKUP_FOLLOW), LOOKUP_NO_XDEV applies to all components of the path. With LOOKUP_NO_XDEV, any path component which crosses a mount-point during path resolution (including "..") will yield an -EXDEV. Absolute paths, absolute symlinks, and magic-links will only yield an -EXDEV if the jump involved changing mount-points. /* Testing. */ LOOKUP_NO_XDEV is tested as part of the openat2(2) selftests. [1]: https://lore.kernel.org/lkml/20170429220414.GT29622@ZenIV.linux.org.uk/ [2]: https://lore.kernel.org/lkml/1415094884-18349-1-git-send-email-drysdale@google.com/ [3]: https://lore.kernel.org/lkml/1404124096-21445-1-git-send-email-drysdale@google.com/ Cc: Christian Brauner <christian.brauner@ubuntu.com> Suggested-by: David Drysdale <drysdale@google.com> Suggested-by: Al Viro <viro@zeniv.linux.org.uk> Suggested-by: Andy Lutomirski <luto@kernel.org> Suggested-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha adb21d2b526f7f196b2f3fdca97d80ba05dd14a0

namei: LOOKUP_BENEATH: O_BENEATH-like scoped resolution /* Background. */ There are many circumstances when userspace wants to resolve a path and ensure that it doesn't go outside of a particular root directory during resolution. Obvious examples include archive extraction tools, as well as other security-conscious userspace programs. FreeBSD spun out O_BENEATH from their Capsicum project[1,2], so it also seems reasonable to implement similar functionality for Linux. This is part of a refresh of Al's AT_NO_JUMPS patchset[3] (which was a variation on David Drysdale's O_BENEATH patchset[4], which in turn was based on the Capsicum project[5]). /* Userspace API. */ LOOKUP_BENEATH will be exposed to userspace through openat2(2). /* Semantics. */ Unlike most other LOOKUP flags (most notably LOOKUP_FOLLOW), LOOKUP_BENEATH applies to all components of the path. With LOOKUP_BENEATH, any path component which attempts to "escape" the starting point of the filesystem lookup (the dirfd passed to openat) will yield -EXDEV. Thus, all absolute paths and symlinks are disallowed. Due to a security concern brought up by Jann[6], any ".." path components are also blocked. This restriction will be lifted in a future patch, but requires more work to ensure that permitting ".." is done safely. Magic-link jumps are also blocked, because they can beam the path lookup across the starting point. It would be possible to detect and block only the "bad" crossings with path_is_under() checks, but it's unclear whether it makes sense to permit magic-links at all. However, userspace is recommended to pass LOOKUP_NO_MAGICLINKS if they want to ensure that magic-link crossing is entirely disabled. /* Testing. */ LOOKUP_BENEATH is tested as part of the openat2(2) selftests. [1]: https://reviews.freebsd.org/D2808 [2]: https://reviews.freebsd.org/D17547 [3]: https://lore.kernel.org/lkml/20170429220414.GT29622@ZenIV.linux.org.uk/ [4]: https://lore.kernel.org/lkml/1415094884-18349-1-git-send-email-drysdale@google.com/ [5]: https://lore.kernel.org/lkml/1404124096-21445-1-git-send-email-drysdale@google.com/ [6]: https://lore.kernel.org/lkml/CAG48ez1jzNvxB+bfOBnERFGp=oMM0vHWuLD6EULmne3R6xa53w@mail.gmail.com/ Cc: Christian Brauner <christian.brauner@ubuntu.com> Suggested-by: David Drysdale <drysdale@google.com> Suggested-by: Al Viro <viro@zeniv.linux.org.uk> Suggested-by: Andy Lutomirski <luto@kernel.org> Suggested-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 8db52c7e7ee1bd861b6096fcafc0fe7d0f24a994

namei: LOOKUP_IN_ROOT: chroot-like scoped resolution /* Background. */ Container runtimes or other administrative management processes will often interact with root filesystems while in the host mount namespace, because the cost of doing a chroot(2) on every operation is too prohibitive (especially in Go, which cannot safely use vfork). However, a malicious program can trick the management process into doing operations on files outside of the root filesystem through careful crafting of symlinks. Most programs that need this feature have attempted to make this process safe, by doing all of the path resolution in userspace (with symlinks being scoped to the root of the malicious root filesystem). Unfortunately, this method is prone to foot-guns and usually such implementations have subtle security bugs. Thus, what userspace needs is a way to resolve a path as though it were in a chroot(2) -- with all absolute symlinks being resolved relative to the dirfd root (and ".." components being stuck under the dirfd root). It is much simpler and more straight-forward to provide this functionality in-kernel (because it can be done far more cheaply and correctly). More classical applications that also have this problem (which have their own potentially buggy userspace path sanitisation code) include web servers, archive extraction tools, network file servers, and so on. /* Userspace API. */ LOOKUP_IN_ROOT will be exposed to userspace through openat2(2). /* Semantics. */ Unlike most other LOOKUP flags (most notably LOOKUP_FOLLOW), LOOKUP_IN_ROOT applies to all components of the path. With LOOKUP_IN_ROOT, any path component which attempts to cross the starting point of the pathname lookup (the dirfd passed to openat) will remain at the starting point. Thus, all absolute paths and symlinks will be scoped within the starting point. There is a slight change in behaviour regarding pathnames -- if the pathname is absolute then the dirfd is still used as the root of resolution of LOOKUP_IN_ROOT is specified (this is to avoid obvious foot-guns, at the cost of a minor API inconsistency). As with LOOKUP_BENEATH, Jann's security concern about ".."[1] applies to LOOKUP_IN_ROOT -- therefore ".." resolution is blocked. This restriction will be lifted in a future patch, but requires more work to ensure that permitting ".." is done safely. Magic-link jumps are also blocked, because they can beam the path lookup across the starting point. It would be possible to detect and block only the "bad" crossings with path_is_under() checks, but it's unclear whether it makes sense to permit magic-links at all. However, userspace is recommended to pass LOOKUP_NO_MAGICLINKS if they want to ensure that magic-link crossing is entirely disabled. /* Testing. */ LOOKUP_IN_ROOT is tested as part of the openat2(2) selftests. [1]: https://lore.kernel.org/lkml/CAG48ez1jzNvxB+bfOBnERFGp=oMM0vHWuLD6EULmne3R6xa53w@mail.gmail.com/ Cc: Christian Brauner <christian.brauner@ubuntu.com> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha ab87f9a56c8ee9fa6856cb13d8f2905db913baae

namei: LOOKUP_{IN_ROOT,BENEATH}: permit limited ".." resolution Allow LOOKUP_BENEATH and LOOKUP_IN_ROOT to safely permit ".." resolution (in the case of LOOKUP_BENEATH the resolution will still fail if ".." resolution would resolve a path outside of the root -- while LOOKUP_IN_ROOT will chroot(2)-style scope it). Magic-link jumps are still disallowed entirely[*]. As Jann explains[1,2], the need for this patch (and the original no-".." restriction) is explained by observing there is a fairly easy-to-exploit race condition with chroot(2) (and thus by extension LOOKUP_IN_ROOT and LOOKUP_BENEATH if ".." is allowed) where a rename(2) of a path can be used to "skip over" nd->root and thus escape to the filesystem above nd->root. thread1 [attacker]: for (;;) renameat2(AT_FDCWD, "/a/b/c", AT_FDCWD, "/a/d", RENAME_EXCHANGE); thread2 [victim]: for (;;) openat2(dirb, "b/c/../../etc/shadow", { .flags = O_PATH, .resolve = RESOLVE_IN_ROOT } ); With fairly significant regularity, thread2 will resolve to "/etc/shadow" rather than "/a/b/etc/shadow". There is also a similar (though somewhat more privileged) attack using MS_MOVE. With this patch, such cases will be detected *during* ".." resolution and will return -EAGAIN for userspace to decide to either retry or abort the lookup. It should be noted that ".." is the weak point of chroot(2) -- walking *into* a subdirectory tautologically cannot result in you walking *outside* nd->root (except through a bind-mount or magic-link). There is also no other way for a directory's parent to change (which is the primary worry with ".." resolution here) other than a rename or MS_MOVE. The primary reason for deferring to userspace with -EAGAIN is that an in-kernel retry loop (or doing a path_is_under() check after re-taking the relevant seqlocks) can become unreasonably expensive on machines with lots of VFS activity (nfsd can cause lots of rename_lock updates). Thus it should be up to userspace how many times they wish to retry the lookup -- the selftests for this attack indicate that there is a ~35% chance of the lookup succeeding on the first try even with an attacker thrashing rename_lock. A variant of the above attack is included in the selftests for openat2(2) later in this patch series. I've run this test on several machines for several days and no instances of a breakout were detected. While this is not concrete proof that this is safe, when combined with the above argument it should lend some trustworthiness to this construction. [*] It may be acceptable in the future to do a path_is_under() check for magic-links after they are resolved. However this seems unlikely to be a feature that people *really* need -- it can be added later if it turns out a lot of people want it. [1]: https://lore.kernel.org/lkml/CAG48ez1jzNvxB+bfOBnERFGp=oMM0vHWuLD6EULmne3R6xa53w@mail.gmail.com/ [2]: https://lore.kernel.org/lkml/CAG48ez30WJhbsro2HOc_DR7V91M+hNFzBP5ogRMZaxbAORvqzg@mail.gmail.com/ Cc: Christian Brauner <christian.brauner@ubuntu.com> Suggested-by: Jann Horn <jannh@google.com> Suggested-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 7297f65b1ad355cc36721e46f9a0b28141c56bcd

open: introduce openat2(2) syscall /* Background. */ For a very long time, extending openat(2) with new features has been incredibly frustrating. This stems from the fact that openat(2) is possibly the most famous counter-example to the mantra "don't silently accept garbage from userspace" -- it doesn't check whether unknown flags are present[1]. This means that (generally) the addition of new flags to openat(2) has been fraught with backwards-compatibility issues (O_TMPFILE has to be defined as __O_TMPFILE|O_DIRECTORY|[O_RDWR or O_WRONLY] to ensure old kernels gave errors, since it's insecure to silently ignore the flag[2]). All new security-related flags therefore have a tough road to being added to openat(2). Userspace also has a hard time figuring out whether a particular flag is supported on a particular kernel. While it is now possible with contemporary kernels (thanks to [3]), older kernels will expose unknown flag bits through fcntl(F_GETFL). Giving a clear -EINVAL during openat(2) time matches modern syscall designs and is far more fool-proof. In addition, the newly-added path resolution restriction LOOKUP flags (which we would like to expose to user-space) don't feel related to the pre-existing O_* flag set -- they affect all components of path lookup. We'd therefore like to add a new flag argument. Adding a new syscall allows us to finally fix the flag-ignoring problem, and we can make it extensible enough so that we will hopefully never need an openat3(2). /* Syscall Prototype. */ /* * open_how is an extensible structure (similar in interface to * clone3(2) or sched_setattr(2)). The size parameter must be set to * sizeof(struct open_how), to allow for future extensions. All future * extensions will be appended to open_how, with their zero value * acting as a no-op default. */ struct open_how { /* ... */ }; int openat2(int dfd, const char *pathname, struct open_how *how, size_t size); /* Description. */ The initial version of 'struct open_how' contains the following fields: flags Used to specify openat(2)-style flags. However, any unknown flag bits or otherwise incorrect flag combinations (like O_PATH|O_RDWR) will result in -EINVAL. In addition, this field is 64-bits wide to allow for more O_ flags than currently permitted with openat(2). mode The file mode for O_CREAT or O_TMPFILE. Must be set to zero if flags does not contain O_CREAT or O_TMPFILE. resolve Restrict path resolution (in contrast to O_* flags they affect all path components). The current set of flags are as follows (at the moment, all of the RESOLVE_ flags are implemented as just passing the corresponding LOOKUP_ flag). RESOLVE_NO_XDEV => LOOKUP_NO_XDEV RESOLVE_NO_SYMLINKS => LOOKUP_NO_SYMLINKS RESOLVE_NO_MAGICLINKS => LOOKUP_NO_MAGICLINKS RESOLVE_BENEATH => LOOKUP_BENEATH RESOLVE_IN_ROOT => LOOKUP_IN_ROOT open_how does not contain an embedded size field, because it is of little benefit (userspace can figure out the kernel open_how size at runtime fairly easily without it). It also only contains u64s (even though ->mode arguably should be a u16) to avoid having padding fields which are never used in the future. Note that as a result of the new how->flags handling, O_PATH|O_TMPFILE is no longer permitted for openat(2). As far as I can tell, this has always been a bug and appears to not be used by userspace (and I've not seen any problems on my machines by disallowing it). If it turns out this breaks something, we can special-case it and only permit it for openat(2) but not openat2(2). After input from Florian Weimer, the new open_how and flag definitions are inside a separate header from uapi/linux/fcntl.h, to avoid problems that glibc has with importing that header. /* Testing. */ In a follow-up patch there are over 200 selftests which ensure that this syscall has the correct semantics and will correctly handle several attack scenarios. In addition, I've written a userspace library[4] which provides convenient wrappers around openat2(RESOLVE_IN_ROOT) (this is necessary because no other syscalls support RESOLVE_IN_ROOT, and thus lots of care must be taken when using RESOLVE_IN_ROOT'd file descriptors with other syscalls). During the development of this patch, I've run numerous verification tests using libpathrs (showing that the API is reasonably usable by userspace). /* Future Work. */ Additional RESOLVE_ flags have been suggested during the review period. These can be easily implemented separately (such as blocking auto-mount during resolution). Furthermore, there are some other proposed changes to the openat(2) interface (the most obvious example is magic-link hardening[5]) which would be a good opportunity to add a way for userspace to restrict how O_PATH file descriptors can be re-opened. Another possible avenue of future work would be some kind of CHECK_FIELDS[6] flag which causes the kernel to indicate to userspace which openat2(2) flags and fields are supported by the current kernel (to avoid userspace having to go through several guesses to figure it out). [1]: https://lwn.net/Articles/588444/ [2]: https://lore.kernel.org/lkml/CA+55aFyyxJL1LyXZeBsf2ypriraj5ut1XkNDsunRBqgVjZU_6Q@mail.gmail.com [3]: commit 629e014bb834 ("fs: completely ignore unknown open flags") [4]: https://sourceware.org/bugzilla/show_bug.cgi?id=17523 [5]: https://lore.kernel.org/lkml/20190930183316.10190-2-cyphar@cyphar.com/ [6]: https://youtu.be/ggD-eb3yPVs Suggested-by: Christian Brauner <christian.brauner@ubuntu.com> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 2c50cdafb2184043796354a8b9162469f5d116f8

selftests: add openat2(2) selftests Test all of the various openat2(2) flags. A small stress-test of a symlink-rename attack is included to show that the protections against ".."-based attacks are sufficient. The main things these self-tests are enforcing are: * The struct+usize ABI for openat2(2) and copy_struct_from_user() to ensure that upgrades will be handled gracefully (in addition, ensuring that misaligned structures are also handled correctly). * The -EINVAL checks for openat2(2) are all correctly handled to avoid userspace passing unknown or conflicting flag sets (most importantly, ensuring that invalid flag combinations are checked). * All of the RESOLVE_* semantics (including errno values) are correctly handled with various combinations of paths and flags. * RESOLVE_IN_ROOT correctly protects against the symlink rename(2) attack that has been responsible for several CVEs (and likely will be responsible for several more). Cc: Shuah Khan <shuah@kernel.org> Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Aleksa Sarai

commit sha 81fb78656e26eb5881cee024733342d0313dc609

Documentation: path-lookup: include new LOOKUP flags Now that we have new LOOKUP flags, we should document them in the relevant path-walking documentation. And now that we've settled on a common name for nd_jump_link() style symlinks ("magic links"), use that term where magic-link semantics are described. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

push time in 4 days

push eventcyphar/linux

Jan Kara

commit sha e0ff126ee7ad405c1ef531f9f3db92929de4f20f

pipe: Fix bogus dereference in iov_iter_alignment() We cannot look at 'i->pipe' unless we know the iter is a pipe. Move the ring_size load to a branch in iov_iter_alignment() where we've already checked the iter is a pipe to avoid bogus dereference. Reported-by: syzbot+bea68382bae9490e7dd6@syzkaller.appspotmail.com Fixes: 8cefc107ca54 ("pipe: Use head and tail pointers for the ring, not cursor and length") Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Eric Sandeen

commit sha 04646aebd30b99f2cfa0182435a2ec252fcb16d0

fs: avoid softlockups in s_inodes iterators Anything that walks all inodes on sb->s_inodes list without rescheduling risks softlockups. Previous efforts were made in 2 functions, see: c27d82f fs/drop_caches.c: avoid softlockups in drop_pagecache_sb() ac05fbb inode: don't softlockup when evicting inodes but there hasn't been an audit of all walkers, so do that now. This also consistently moves the cond_resched() calls to the bottom of each loop in cases where it already exists. One loop remains: remove_dquot_ref(), because I'm not quite sure how to deal with that one w/o taking the i_lock. Signed-off-by: Eric Sandeen <sandeen@redhat.com> Reviewed-by: Jan Kara <jack@suse.cz> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Eric Sandeen

commit sha 1edc8eb2e93130e36ac74ac9c80913815a57d413

fs: call fsnotify_sb_delete after evict_inodes When a filesystem is unmounted, we currently call fsnotify_sb_delete() before evict_inodes(), which means that fsnotify_unmount_inodes() must iterate over all inodes on the superblock looking for any inodes with watches. This is inefficient and can lead to livelocks as it iterates over many unwatched inodes. At this point, SB_ACTIVE is gone and dropping refcount to zero kicks the inode out out immediately, so anything processed by fsnotify_sb_delete / fsnotify_unmount_inodes gets evicted in that loop. After that, the call to evict_inodes will evict everything else with a zero refcount. This should speed things up overall, and avoid livelocks in fsnotify_unmount_inodes(). Signed-off-by: Eric Sandeen <sandeen@redhat.com> Reviewed-by: Jan Kara <jack@suse.cz> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 7a955b7363b8f62cd494fe74701523ed3c3d2bfd

reimplement path_mountpoint() with less magic ... and get rid of a bunch of bugs in it. Background: the reason for path_mountpoint() is that umount() really doesn't want attempts to revalidate the root of what it's trying to umount. The thing we want to avoid actually happen from complete_walk(); solution was to do something parallel to normal path_lookupat() and it both went overboard and got the boilerplate subtly (and not so subtly) wrong. A better solution is to do pretty much what the normal path_lookupat() does, but instead of complete_walk() do unlazy_walk(). All it takes to avoid that ->d_weak_revalidate() call... mountpoint_last() goes away, along with everything it got wrong, and so does the magic around LOOKUP_NO_REVAL. Another source of bugs is that when we traverse mounts at the final location (and we need to do that - umount . expects to get whatever's overmounting ., if any, out of the lookup) we really ought to take care of ->d_manage() - as it is, manual umount of autofs automount in progress can lead to unpleasant surprises for the daemon. Easily solved by using handle_lookup_down() instead of follow_mount(). Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 8f5afa60a9ff64e81d9aec6171ffcfb515779a51

do_add_mount(): lift lock_mount/unlock_mount into callers preparation to finish_automount() fix (next commit) Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 725a9225591635e379232d971f25f4177bd6c5af

fix automount/automount race properly Protection against automount/automount races (two threads hitting the same referral point at the same time) is based upon do_add_mount() prevention of identical overmounts - trying to overmount the root of mounted tree with the same tree fails with -EBUSY. It's unreliable (the other thread might've mounted something on top of the automount it has triggered) *and* causes no end of headache for follow_automount() and its caller, since finish_automount() behaves like do_new_mount() - if the mountpoint to be is overmounted, it mounts on top what's overmounting it. It's not only wrong (we want to go into what's overmounting the automount point and quietly discard what we planned to mount there), it introduces the possibility of original parent mount getting dropped. That's what 8aef18845266 (VFS: Fix vfsmount overput on simultaneous automount) deals with, but it can't do anything about the reliability of conflict detection - if something had been overmounted the other thread's automount (e.g. that other thread having stepped into automount in mount(2)), we don't get that -EBUSY and the result is referral point under automounted NFS under explicit overmount under another copy of automounted NFS What we need is finish_automount() *NOT* digging into overmounts - if it finds one, it should just quietly discard the thing it was asked to mount. And don't bother with actually crossing into the results of finish_automount() - the same loop that calls follow_automount() will do that just fine on the next iteration. IOW, instead of calling lock_mount() have finish_automount() do it manually, _without_ the "move into overmount and retry" part. And leave crossing into the results to the caller of follow_automount(), which simplifies it a lot. Moral: if you end up with a lot of glue working around the calling conventions of something, perhaps these calling conventions are simply wrong... Fixes: 8aef18845266 (VFS: Fix vfsmount overput on simultaneous automount) Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 740020eaa34c12591d409281b9a3bd41f1fd5e27

follow_automount(): get rid of dead^Wstillborn code 1) no instances of ->d_automount() have ever made use of the "return ERR_PTR(-EISDIR) if you don't feel like mounting anything" - that's a rudiment of plans that got superseded before the thing went into the tree. Despite the comment in follow_automount(), autofs has never done that. 2) if there's no ->d_automount() in dentry_operations, filesystems should not set DCACHE_NEED_AUTOMOUNT in the first place. None have ever done so... Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha ea6c77f3b4ead4f38d167efc7e1f35dcddb26d21

make build_open_flags() treat O_CREAT | O_EXCL as implying O_NOFOLLOW O_CREAT | O_EXCL means "-EEXIST if we run into a trailing symlink". As it is, we might or might not have LOOKUP_FOLLOW in op->intent in that case - that depends upon having O_NOFOLLOW in open flags. It doesn't matter, since we won't be checking it in that case - do_last() bails out earlier. However, making sure it's not set (i.e. acting as if we had an explicit O_NOFOLLOW) makes the behaviour more explicit and allows to reorder the check for O_CREAT | O_EXCL in do_last() with the call of step_into() immediately following it. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha c8c7f924f6f5bc0e82148eb9ebf4caad9abe4fc8

follow_managed(): fold setting inode/seq into follow_managed() success case All callers of follow_managed() follow it on success with the same steps - d_backing_inode(path->dentry) is calculated and stored into some struct inode * variable and, in all but one case, an unsigned variable (nd->seq to be) is zeroed. The single exception is lookup_fast() and there zeroing is correct thing to do - not doing it is a pointless microoptimization. Pass struct inode ** and unsigned int * to follow_managed(), do assignments in success case right there. That makes the arguments identical to those of __follow_mount_rcu(), whose non-RCU counterpart follow_managed() is. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 867f67ed5248a7db913d654b644a9a10ed8a6480

atomic_open(): saner calling conventions (return dentry on success) Currently it either returns -E... or puts (nd->path.mnt,dentry) into *path and returns 0. Make it return ERR_PTR(-E...) or dentry; adjust the caller. Fewer arguments and it's easier to keep track of *path contents that way. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha be75356c980248758b122768a42d4fe7599105ac

lookup_open(): saner calling conventions (return dentry on success) same story as for atomic_open() in the previous commit. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha c1af9200fcfed52473ac9a67f72a7d454d6fc70f

do_last(): collapse the call of path_to_nameidata() ... and shift filling struct path to just before the call of follow_managed(). All callers of follow_managed() are immediately preceded by path->mnt = nd->path.mnt now. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha a34f3debb17f47b025851655b6de0432c439301b

follow_managed(): pass dentry in, turn path into a pure out argument All callers are equivalent to path->dentry = dentry; path->mnt = nd->path.mnt; err = follow_managed(path, ...) Pass dentry as an explicit argument, fill *path in follow_managed() itself. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 321a603d300963455c8f7c79a5fe864cc3eaaf7e

teach follow_managed() to handle RCU mode ... and make the callers of __follow_mount_rcu() use follow_managed(). Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 4b875a348025cdb252b046a846d6cfd2a5d4265b

lookup_fast(): take mount traversal into callers Current calling conventions: -E... on error, 0 on cache miss, result of follow_managed(nd, dentry, path, inode, seqp) on success. Turn that into returning ERR_PTR(-E...), NULL and dentry resp.; deal with follow_managed() in the callers. The thing is, they already do that in cache miss handling case, so we just need to supply dentry to them and unify the mount traversal in those cases. Fewer arguments that way, and we get closer to merging follow_managed() and step_into(). Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 6f1f7cbaeef2494037aee81875310293106dd35b

handle_lookup_down(): switch to use of step_into() ... one more step closer to pairing follow_managed()/step_into(). Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha f78c4daf30c3d670ff45a061944ff6e9a346e3f9

LOOKUP_MOUNTPOINT: fold path_mountpointat() into path_lookupat() New LOOKUP flag, telling path_lookupat() to act as path_mountpointat(). IOW, traverse mounts at the final point and skip revalidation of the location where it ends up. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha ccc76f62e5f8e57e9d5bdbcad2a800a668a108c4

fold follow_managed() into step_into() The following is true: * calls of follow_managed() and step_into() are always paired in sequences like err = follow_managed(nd, dentry, &path, &inode, &seq); if (unlikely(err < 0)) return err; err = step_into(nd, &path, flags, inode, seq); * in all such sequences path is uninitialized before and unused after this pair of calls * in all such sequences inode and seq are unused afterwards. So the call of follow_managed() can be shifted inside step_into(), turning 'path' into a local variable in the combined function. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

Al Viro

commit sha 4fe219c29950561630e74a31523036c269119698

expand the only remaining call of path_lookup_conditional() Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

view details

push time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>

I'd prefer an alternative example to cat but I can do that myself later. But can you please move this to examples/cat.go. My plan is to not have separate directories for each language binding (though I might change that in the future).

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"unsafe"+)++type pointerType = int++const (+	pointerOnNone   pointerType = C.PATHRS_NONE

I don't really like these names ("pointer on ..." isn't proper English), but I can't think of anything nicer than pointerToNone or ptrTypeNone.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "")+	return file, nil+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}+	buf.Reset()++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, uintptr(line.ip)-uintptr(line.sAddress)))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(err cErrorPtr) *Error {+	var description string+	var backtrace []backtraceLine++	realError := (*errorType)(err)++	if realError.savedErrno != 0 {+		description = fmt.Sprint(syscall.Errno(realError.savedErrno))+	} else if realError.description != nil {+		description = toGoString(realError.description)+	}++	if realError.backtrace != nil {+		length := uintptr(realError.backtrace.length)+		size := unsafe.Sizeof(backtraceEntry{})+		for i := uintptr(0); i < length; i++ {+			head := (*backtraceEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(realError.backtrace.head)) + size*i))

This is a bit ugly but I'm not sure how to make it nicer... Oh well, we can fix that later.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "")

It wouldn't hurt to give a descriptive name to the file (like pathrs:<some unique ID for the file>).

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"unsafe"+)++type pointerType = int++const (+	pointerOnNone   pointerType = C.PATHRS_NONE+	pointerOnError  pointerType = C.PATHRS_ERROR+	pointerOnRoot   pointerType = C.PATHRS_ROOT+	pointerOnHandle pointerType = C.PATHRS_HANDLE+)++type resolverType = uint64++const (+	kernelResolver   resolverType = C.PATHRS_KERNEL_RESOLVER+	emulatedResolver resolverType = C.PATHRS_EMULATED_RESOLVER+)++type cRootPtr = unsafe.Pointer++type cErrorPtr = unsafe.Pointer++type cHandlePtr = unsafe.Pointer++type fd = int++type errorType struct {+	savedErrno  C.uint64_t+	description *C.char+	backtrace   *errorBacktrace+}++type errorBacktrace struct {+	head   *backtraceEntry+	length C.uintptr_t+}++type backtraceEntry struct {+	ip            uintptr+	symbolAddress uintptr+	symbolName    *C.char+	symbolFile    *C.char+	symbolLineno  C.uint32_t+	// TODO: This field I found it to be generated by `go tool cgo`+	// But after it be deleted nothing is changed but it has to be+	// It should be investigate more dipply+	// _             [4]byte+}++type configGlobal struct {+	ErrorBacktraces C.bool+}++type configRoot struct {+	resolver resolverType+	flags    C.uint64_t+}++func open(rootPath string) cRootPtr {+	croot := unsafe.Pointer(C.pathrs_open(C.CString(rootPath)))+	return croot+}++func free(ptrType pointerType, ptr unsafe.Pointer) {+	typeVal := C.pathrs_type_t(ptrType)+	C.pathrs_free(typeVal, ptr)+}++func errorCheck(ptrType pointerType, ptr unsafe.Pointer) cErrorPtr {+	typeVal := C.pathrs_type_t(ptrType)+	cErrorPtr := cErrorPtr(C.pathrs_error(typeVal, ptr))+	return cErrorPtr+}++func resolve(root cRootPtr, path string) cHandlePtr {+	rootStructPtr := (*C.pathrs_root_t)(root)+	return cHandlePtr(C.pathrs_resolve(rootStructPtr, C.CString(path)))+}++func reopen(handler cHandlePtr, flags int) fd {+	handlePtr := (*C.pathrs_handle_t)(handler)+	cFlags := C.int(flags)+	return fd(C.pathrs_reopen(handlePtr, cFlags))+}++func creat(root cRootPtr, path string, mode uint) cHandlePtr {+	cRoot := (*C.pathrs_root_t)(root)+	cPath := C.CString(path)+	cMode := C.uint(mode)+	handler := C.pathrs_creat(cRoot, cPath, cMode)+	return cHandlePtr(handler)+}++func duplicate(ptrType pointerType, ptr unsafe.Pointer) unsafe.Pointer {+	typeVal := C.pathrs_type_t(ptrType)+	return unsafe.Pointer(C.pathrs_duplicate(typeVal, ptr))+}++func fromfd(ptrType pointerType, fd int) unsafe.Pointer {+	typeVal := C.pathrs_type_t(ptrType)+	cFd := C.int(fd)+	return unsafe.Pointer(C.pathrs_from_fd(typeVal, cFd))+}++func intofd(ptrType pointerType, ptr unsafe.Pointer) fd {+	typeVal := C.pathrs_type_t(ptrType)+	return fd(C.pathrs_into_fd(typeVal, ptr))+}++func hardlink(root cRootPtr, path, target string) int {+	cRoot := (*C.pathrs_root_t)(root)+	cPath := C.CString(path)+	cTarget := C.CString(target)+	return int(C.pathrs_hardlink(cRoot, cPath, cTarget))+}++func mkdir(root cRootPtr, path string, mode uint) int {+	cRoot := (*C.pathrs_root_t)(root)+	cPath := C.CString(path)+	cMode := C.uint(mode)+	return int(C.pathrs_mkdir(cRoot, cPath, cMode))+}++func mknod(root cRootPtr, path string, mode uint, dev int) int {+	cRoot := (*C.pathrs_root_t)(root)+	cPath := C.CString(path)+	cMode := C.uint(mode)+	cDev := C.dev_t(dev)+	return int(C.pathrs_mknod(cRoot, cPath, cMode, cDev))+}++func rename(root cRootPtr, src, dst string, flags int) int {+	cRoot := (*C.pathrs_root_t)(root)+	cSrc := C.CString(src)+	cDst := C.CString(src)+	cFlags := C.int(flags)+	return int(C.pathrs_rename(cRoot, cSrc, cDst, cFlags))+}++func symlink(root cRootPtr, path, target string) int {+	cRoot := (*C.pathrs_root_t)(root)+	cPath := C.CString(path)+	cTarget := C.CString(target)+	return int(C.pathrs_symlink(cRoot, cPath, cTarget))+}++func configure(ptrType pointerType, ptr, oldCfgPtr, newCfgPtr unsafe.Pointer, cfgSize uintptr) cErrorPtr {+	typeVal := C.pathrs_type_t(ptrType)+	cSize := C.uintptr_t(cfgSize)+	return cErrorPtr(C.pathrs_configure(typeVal, ptr, oldCfgPtr, newCfgPtr, cSize))+}++func toGoString(str *C.char) string {

This wrapper makes the code harder to read. Just use C.GoString.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "")+	return file, nil+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {

I will need to think some more on whether this backtrace interface is ideal -- I'd really want to be as compatible as possible with github.com/pkg/errors.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "")+	return file, nil+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {

This type should implement the new Unwrap() interface -- if there is an errno then we return an syscall.Errno or similar.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {+		return nil, err+	}++	file := os.NewFile(uintptr(fd), "")+	return file, nil+}++// Error representation of rust error+// particularly useful to not frighten to lost controll of pointer which can be rewritten.+type Error struct {+	description string+	backtrace   []backtraceLine+}++type backtraceLine struct {+	ip       uintptr+	sAddress uintptr+	sName    string+	sFile    string+	sLineno  uint32+}++func (err *Error) Error() string {+	return err.description+}++// Backtrace flush backtrace of underlying error to string.+//+// Its not passed to realization of Error interface on purpose since+// the main error should remain clear and simple+func (err *Error) Backtrace() string {+	buf := strings.Builder{}+	buf.Reset()++	for _, line := range err.backtrace {+		if line.sName != "" {+			buf.WriteString(fmt.Sprintf("'%s'@", line.sName))+		}+		buf.WriteString(fmt.Sprintf("<0x%x>+0x%x\n", line.sAddress, uintptr(line.ip)-uintptr(line.sAddress)))+		if line.sFile != "" {+			buf.WriteString(fmt.Sprintf("  in file '%s':%d\n", line.sFile, line.sLineno))+		}+	}++	return buf.String()+}++func newError(err cErrorPtr) *Error {+	var description string+	var backtrace []backtraceLine++	realError := (*errorType)(err)++	if realError.savedErrno != 0 {+		description = fmt.Sprint(syscall.Errno(realError.savedErrno))+	} else if realError.description != nil {+		description = toGoString(realError.description)+	}

The description includes more information than just the errno. Really the way this should be done is always storing description, and if the saved_errno is non-zero then you add a wrapped syscall.Errno which is returned on Unwrap.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory

All of these doc comments are ... not really that great. But I can rewrite them after merging so you don't need to worry about that for now.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)

For all of these wrappers, return handleErr(...) would probably be cleaner.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/

Yeah, Root should never contain a null pointer.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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"+	"strings"+	"syscall"+	"unsafe"+)++// Open opens directory as a root directory+func Open(path string) (*Root, error) {+	r := open(path)+	err := handleErr(pointerOnRoot, r)+	if err != nil {+		return nil, err+	}++	return &Root{root: r}, nil+}++// Root is a wrapper on pointer to root api,+// to use it (safe)+type Root struct {+	root cRootPtr+}++// Resolve resolves name with regard to root path+func (r *Root) Resolve(path string) (*Handle, error) {+	handler := resolve(r.root, path)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}+	return &Handle{handle: handler}, nil+}++// Creat creates a file with a such mode by path+func (r *Root) Creat(path string, mode uint) (*Handle, error) {+	handler := creat(r.root, path, mode)+	if handler == nil {+		return nil, handleErr(pointerOnRoot, r.root)+	}++	return &Handle{handle: handler}, nil+}++// Rename renames src to dst, or change flags on this file+// if the names are the same it's only change the flags+func (r *Root) Rename(src, dst string, flags int) error {+	status := rename(r.root, src, dst, flags)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mkdir creates a directory with a such mode by path+func (r *Root) Mkdir(path string, mode uint) error {+	status := mkdir(r.root, path, mode)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Mknod creates a filesystem node named path+// with attributes mode and dev+func (r *Root) Mknod(path string, mode uint, dev int) error {+	status := mknod(r.root, path, mode, dev)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Hardlink creates a hardlink of file named target and place it to path+func (r *Root) Hardlink(path, target string) error {+	status := hardlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Symlink creates a symlink of file named target and place it to path+func (r *Root) Symlink(path, target string) error {+	status := symlink(r.root, path, target)+	if status < 0 {+		return handleErr(pointerOnRoot, r.root)+	}++	return nil+}++// Free frees underling caught resources+func (r *Root) Free() {+	/*+		Should we handle it in this way or always return not nil pointer on root?+	*/+	if r != nil {+		free(pointerOnRoot, r.root)+	}+}++// Handle represents an handle api of rspath+type Handle struct {+	handle cRootPtr+}++// Free frees underling caught resources+func (h *Handle) Free() {+	if h == nil {+		return+	}++	free(pointerOnHandle, h.handle)+}++// Reopen upgrade the handle to a file representation+func (h *Handle) Reopen(flags int) (*os.File, error) {+	fd := reopen(h.handle, flags)+	// TODO: verify what is correct path to handle this type of errors+	// must it always be called error handling as in cat.c file or we can check fd first+	if err := handleErr(pointerOnHandle, h.handle); err != nil {

This is fine, as is fd < 0. The C API guarantees that errors stored in each structure are cleared if there was no error during execution.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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 main++import (+	"fmt"+	"io/ioutil"+	"os"++	"../../contrib/bindings/go/pathrs"

I would really prefer github.com/openSUSE/libpathrs/contrib/bindings/go or whatever. To be honest I wasn't even aware you could do this...

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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 main

As everywhere else, add a newline to avoid putting the license in the docs.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Package pathrs is safe path resolution on Linux.+// A binding for rust library with a consequent name.+// It provides primitives to handle path resolving in a great manner.+//+// The configure, intofd, fromfd and duplicate api is not realized yet+//+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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.

As in the other file, the license shouldn't be in the documentation. Also some of the comments in the docs shouldn't really be included. This should read more like:

// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2019, 2020 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 a bindings for libpathrs, a library for safe path resolution on Linux.
package pathrs
zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"unsafe"+)++type pointerType = int++const (+	pointerOnNone   pointerType = C.PATHRS_NONE+	pointerOnError  pointerType = C.PATHRS_ERROR+	pointerOnRoot   pointerType = C.PATHRS_ROOT+	pointerOnHandle pointerType = C.PATHRS_HANDLE+)++type resolverType = uint64++const (+	kernelResolver   resolverType = C.PATHRS_KERNEL_RESOLVER+	emulatedResolver resolverType = C.PATHRS_EMULATED_RESOLVER+)++type cRootPtr = unsafe.Pointer++type cErrorPtr = unsafe.Pointer++type cHandlePtr = unsafe.Pointer++type fd = int++type errorType struct {+	savedErrno  C.uint64_t+	description *C.char+	backtrace   *errorBacktrace+}++type errorBacktrace struct {+	head   *backtraceEntry+	length C.uintptr_t+}++type backtraceEntry struct {+	ip            uintptr+	symbolAddress uintptr+	symbolName    *C.char+	symbolFile    *C.char+	symbolLineno  C.uint32_t+	// TODO: This field I found it to be generated by `go tool cgo`+	// But after it be deleted nothing is changed but it has to be+	// It should be investigate more dipply+	// _             [4]byte

This is padding added to the structure. Technically it is necessary to include, but since it's at the end it doesn't make too much difference. I'm probably going to redesign how structure passing works with libpathrs because I really don't like pathrs_configure right now -- we might switch to a string-based configuration system.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>

My comment for this entire file is that I'm not sure it's worth having separate code for the C bindings. Personally I think it'd read more reasonably if everything was in one file (rather than adding additional wrapping). But I could be wrong...

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"

This won't work outside of the libpathrs repo. To be fair, we need to work around this somehow but the Python bindings do handle LD_LIBRARY_PATH properly. I would suggest we should fix the pkg-config issue (#13) first so that we can just do pkg-config magic for the source tree and make these bindings work for system installs of libpathrs.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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

There should be a space here -- the license shouldn't be part of the doc comments.

zhiburt

comment created time in 4 days

Pull request review commentopenSUSE/libpathrs

Golang binding

+// Copyright (C) 2019, 2020 Aleksa Sarai <cyphar@cyphar.com>+// Copyright (C) 2019, 2020 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++/*+#cgo LDFLAGS:-L ../../../../target/debug/ -l pathrs+#include "../../../../include/pathrs.h"+*/+import "C"+import (+	"unsafe"+)++type pointerType = int++const (+	pointerOnNone   pointerType = C.PATHRS_NONE+	pointerOnError  pointerType = C.PATHRS_ERROR+	pointerOnRoot   pointerType = C.PATHRS_ROOT+	pointerOnHandle pointerType = C.PATHRS_HANDLE+)++type resolverType = uint64++const (+	kernelResolver   resolverType = C.PATHRS_KERNEL_RESOLVER+	emulatedResolver resolverType = C.PATHRS_EMULATED_RESOLVER+)++type cRootPtr = unsafe.Pointer++type cErrorPtr = unsafe.Pointer++type cHandlePtr = unsafe.Pointer++type fd = int++type errorType struct {+	savedErrno  C.uint64_t+	description *C.char+	backtrace   *errorBacktrace+}++type errorBacktrace struct {+	head   *backtraceEntry+	length C.uintptr_t+}++type backtraceEntry struct {+	ip            uintptr+	symbolAddress uintptr+	symbolName    *C.char+	symbolFile    *C.char+	symbolLineno  C.uint32_t+	// TODO: This field I found it to be generated by `go tool cgo`+	// But after it be deleted nothing is changed but it has to be+	// It should be investigate more dipply+	// _             [4]byte

This is padding added to the structure. Technically it is necessary to include, but since it's at the end it doesn't make too much difference. I'm probably going to redesign how structure passing works with libpathrs because I really don't like pathrs_configure right now -- we might switch to a string-based configuration system.

zhiburt

comment created time in 4 days

pull request commentopenSUSE/libpathrs

Golang binding

As for tests, I have plans for binding-agnostic tests (see #6) so we can wait for a while before adding tests for individual bindings (though I would suggest that you should add an example program in examples/ -- as a form of documentation for the bindings).

zhiburt

comment created time in 4 days

pull request commentopenSUSE/libpathrs

Golang binding

Wow, awesome! I wasn't expecting this at all (I have draft Go bindings on my laptop I've been meaning to polish and push). I'll review this when I get home -- but one quick thing is that I need to you add Signed-off-by: [Your Name] <your email> to all of your commits. This is to certify that you agree with the Developer Certificate of Origin -- this is not a CLA, it's just a way for you to formally say that the PR is your own work (or is not your own work and you have the right to publish it under the relevant license). This is the same process Linux and many other large projects use.

zhiburt

comment created time in 4 days

pull request commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

Then again, all of our masked-path logic is very much tied to the actual path (not the filesystem) so there is an argument for making procfs only mountable on /proc. My main disagreement is that I don't think that change helps protect against the threat model it's trying to protect against -- because if you can provide a way to mount procfs to a custom path there are plenty of other ways of causing trouble.

cyphar

comment created time in 4 days

pull request commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

I see your point, but IMHO the threat model for OCI runtimes is not an attacker being able to control the configuration (because in that case, it's game over no matter what you do). The fact that higher-level runtimes allow for this (such as Docker's VOLUME) is something we have to deal with anyway, but I'd prefer to avoid giving anyone the impression that we can solve the general (or even semi-general) case here.

And while it isn't possible (AFAICS) to trick Docker into creating a /foo/proc mount for procfs (in such a way that it isn't also possible to mount anything), it is very possible to get Docker to make / a volume -- which as I mentioned above (and in the other issue) allows you to bypass all of these checks entirely (including the one you described).

cyphar

comment created time in 4 days

push eventcyphar/linux

Aleksa Sarai

commit sha f8e65087afaedf008c77cd937ec40ee0f5eb25ae

[wip] selftests: openat2: add CHECK_FIELDS tests Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 5 days

push eventcyphar/linux

Aleksa Sarai

commit sha 99eb1624d5ea3f6bb42263678dd50d8997af29e2

uaccess: make copy_struct_from_user return custom errno Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

Aleksa Sarai

commit sha 84637afbc4ff7de8a09b4a32c8bf6559b94af0a4

uaccess: add copy_struct_to_user helper Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

Aleksa Sarai

commit sha 0bb131096f640c00cae1abb22c23bc419c52d0f1

sched_getattr: port to copy_struct_to_user Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

Aleksa Sarai

commit sha ea236ca424e85a9bba2771d854c813666a6856d7

openat2: add CHECK_FLAGS field Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 5 days

create barnchcyphar/linux

branch : openat2/check_fields

created branch time in 5 days

pull request commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

Even given how crazy VOLUME is, I don't think you can trick Docker (or other such tools) into mounting procfs on /foo/proc. Also these are temporary restrictions, there's really no need to over-engineer them -- I want to drop this the second runc is ported to libpathrs.

Not to mention that if someone decides to create a container with CAP_SYS_ADMIN then they are running an insecure setup and it isn't our job to help protect them at that point -- there is literally nothing we can do to protect someone in that case (because the container can do many more malicious things than just mount(MS_MOVE)). If you have full-on CAP_SYS_ADMIN you can just re-mount anything as read-write.

cyphar

comment created time in 5 days

pull request commentbats-core/bats-core

Expose new env variable to tests: BATS_JOB_SLOT

I'm not one of the maintainers. @sublimino and the others are the ones who can actually get it merged.

2opremio

comment created time in 5 days

issue commentopencontainers/runc

chroot() function appears to ignore its parameter

Feel free to send a patch which either uses the path or removes the parameter.

setharnold

comment created time in 5 days

pull request commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

/cc @opencontainers/runc-maintainers

cyphar

comment created time in 5 days

Pull request review commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

 func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b  	switch m.Device { 	case "proc", "sysfs":+		// If the destination already exists and is not a directory, we remove+		// it. This is to avoid mounting through a symlink or similar -- which+		// has been a "fun" attack scenario in the past.+		// TODO: This won't be necessary once we switch to libpathrs and we can+		//       stop all of these symlink-exchange attacks.+		if fi, err := os.Lstat(dest); err != nil {+			if !os.IsNotExist(err) {+				return err+			}+		} else if fi.Mode()&os.ModeDir == 0 {+			if err := os.Remove(dest); err != nil {

Fixed.

cyphar

comment created time in 5 days

push eventcyphar/runc

Aleksa Sarai

commit sha 3291d66b98445bd7f7d02eac7f2bca2ac2c56942

rootfs: do not permit /proc mounts to non-directories mount(2) will blindly follow symlinks, which is a problem because it allows a malicious container to trick runc into mounting /proc to an entirely different location (and thus within the attacker's control for a rename-exchange attack). This is just a hotfix (to "stop the bleeding"), and the more complete fix would be finish libpathrs and port runc to it (to avoid these types of attacks entirely, and defend against a variety of other /proc-related attacks). It can be bypased by someone having "/" be a volume controlled by another container. Fixes: CVE-2019-19921 Signed-off-by: Aleksa Sarai <asarai@suse.de>

view details

push time in 5 days

Pull request review commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

 func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b  	switch m.Device { 	case "proc", "sysfs":+		// If the destination already exists and is not a directory, we remove+		// it. This is to avoid mounting through a symlink or similar -- which+		// has been a "fun" attack scenario in the past.+		// TODO: This won't be necessary once we switch to libpathrs and we can+		//       stop all of these symlink-exchange attacks.+		if fi, err := os.Lstat(dest); err != nil {+			if !os.IsNotExist(err) {+				return err+			}+		} else if fi.Mode()&os.ModeDir == 0 {+			if err := os.Remove(dest); err != nil {

Yeah that might be a better idea.

cyphar

comment created time in 5 days

push eventopencontainers/runc

Julia Nedialkova

commit sha e63b797f3827676303c639837957a06c1bbbbac8

Handle ENODEV when accessing the freezer.state file ...when checking if a container is paused Signed-off-by: Julia Nedialkova <julianedialkova@hotmail.com>

view details

Aleksa Sarai

commit sha f6fb7a0338c3ea8488bd9bd7cc7667b113aff8d8

merge branch 'pr-2133' Julia Nedialkova (1): Handle ENODEV when accessing the freezer.state file LGTMs: @crosbymichael @cyphar Closes #2133

view details

push time in 6 days

PR closed opencontainers/runc

Handle ENODEV when accessing the freezer.state file

...when checking if a container is paused

We saw the following failure multiple times when trying to destroy a container:

container_linux.go:1807: checking if container is paused caused \"read /tmp/cgroups-2/freezer/7b63e111-f0cc-4ce2-67fb-bcded8f7fb63/garden-3/134bbc40-0713-4456-6960-3ca2586b4e03/freezer.state: no such device\"

We asynchronously delete two containers that share the same freezer cgroup and PID namespace and something like this is happening:

  1. We start destroying both the containers in the same time
  2. Since the process of one of them is PID 1 in the PID namespace, killing container-1 also kills the processes of container-2. That results in no processes in the freezer cgroup so the deletion of container-1 starts destroying it.
  3. In the meantime, the deletion of container-2 reaches the point where it checks if the container is paused by looking at the freezer.state file. Sometimes the file is still there, but the cgroup is dead (it does not have the CSS_ONLINE flag) because it is currently being deleted by the deletion of container-1 which results in ENODEV.

Signed-off-by: Julia Nedialkova julianedialkova@hotmail.com

+1 -1

5 comments

1 changed file

yulianedyalkova

pr closed time in 6 days

pull request commentopencontainers/runc

Handle ENODEV when accessing the freezer.state file

LGTM.

yulianedyalkova

comment created time in 6 days

issue commentopencontainers/runc

chroot() function appears to ignore its parameter

This is an oversight, but it works okay because we Chdir(rootfs) earlier during setup.

setharnold

comment created time in 6 days

Pull request review commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

 func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b  	switch m.Device { 	case "proc", "sysfs":+		// If the destination already exists and is not a directory, we remove+		// it. This is to avoid mounting through a symlink or similar -- which+		// has been a "fun" attack scenario in the past.+		// TODO: This won't be necessary once we switch to libpathrs and we can+		//       stop all of these symlink-exchange attacks.+		if fi, err := os.Lstat(dest); err != nil {

Well, runc allows it. :wink:

cyphar

comment created time in 7 days

push eventcyphar/talks

Aleksa Sarai

commit sha dc8136ee03fda0bb54da07476e7899d5e9560d8a

2020: 01-lca: add 'Securing Container Runtimes' talk Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 7 days

push eventcyphar/talks

Aleksa Sarai

commit sha 7611fdfbdb7b566218de39a82d8c7329dd76a078

2020: 01-lca: add 'Securing Container Runtimes' talk Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 7 days

push eventcyphar/talks

Aleksa Sarai

commit sha 5e5d3361d44a275118ac3c98cced2c30319fb22f

2020: 01-lca: add 'Securing Container Runtimes' talk Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 7 days

Pull request review commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

 func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b  	switch m.Device { 	case "proc", "sysfs":+		// If the destination already exists and is not a directory, we remove+		// it. This is to avoid mounting through a symlink or similar -- which+		// has been a "fun" attack scenario in the past.+		// TODO: This won't be necessary once we switch to libpathrs and we can+		//       stop all of these symlink-exchange attacks.+		if fi, err := os.Lstat(dest); err != nil {

Yes -- but exploiting it requires / to be mounted in another container (because the parent directory of /proc and /sys is /). I'm not happy with this compromise, but the alternative patch I posted (which also didn't work) had similar limitations.

cyphar

comment created time in 7 days

push eventcyphar/talks

Aleksa Sarai

commit sha 40258eab67cb4272b10fae1b17cf1fd1124036b0

2020: 01-lca: update OCIv2 talk slides Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 7 days

push eventcyphar/talks

Aleksa Sarai

commit sha 1555d0cc755af633a94cac50463bca841e635e44

2020: 01-lca: complete kernel syscalls talk Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>

view details

push time in 8 days

pull request commentopencontainers/runc

temporarily disable CRIU tests

LGTM, but please open a tracking bug to re-enable the CRIU tests.

AkihiroSuda

comment created time in 8 days

PR closed opencontainers/runc

fix merging #2177 and #2169

A new method was added to the cgroup interface when #2177 was merged.

After #2177 got merged, #2169 was merged without rebase (sorry!) and compilation was failing:

libcontainer/cgroups/fs2/fs2.go:208:22: container.Cgroup undefined (type *configs.Config has no field or method Cgroup)

+5 -0

4 comments

1 changed file

AkihiroSuda

pr closed time in 8 days

pull request commentopencontainers/runc

fix merging #2177 and #2169

Let's merge #2198 instead.

AkihiroSuda

comment created time in 8 days

pull request commentopencontainers/runc

temporarily disable CRIU tests

Ah, I'll close #2206 since it's really hard to track which PRs contain each other...

AkihiroSuda

comment created time in 8 days

pull request commentopencontainers/runc

fix merging #2177 and #2169

@mrunalp PullApprove missed your LGTM.

AkihiroSuda

comment created time in 8 days

pull request commentopencontainers/runc

fix merging #2177 and #2169

LGTM

AkihiroSuda

comment created time in 8 days

issue commentopencontainers/runc

[CVE-2019-19921]: Volume mount race condition with shared mounts

#2207 contains a very simplified version of the above patch (the patch I posted above doesn't work because rootfs_linux.go has a very fun relationship with pathnames that I don't have time to debug right now).

leoluk

comment created time in 8 days

pull request commentopencontainers/runc

rootfs: do not permit /proc mounts to non-directories

/cc @leoluk @opencontainers/runc-maintainers

cyphar

comment created time in 8 days

PR opened opencontainers/runc

rootfs: do not permit /proc mounts to non-directories

mount(2) will blindly follow symlinks, which is a problem because it allows a malicious container to trick runc into mounting /proc to an entirely different location (and thus within the attacker's control for a rename-exchange attack).

This is just a hotfix (to "stop the bleeding"), and the more complete fix would be finish libpathrs and port runc to it (to avoid these types of attacks entirely, and defend against a variety of other /proc-related attacks). It can be bypased by someone having "/" be a volume controlled by another container.

Fixes: CVE-2019-19921 Fixes #2197 Signed-off-by: Aleksa Sarai asarai@suse.de

+14 -0

0 comment

1 changed file

pr created time in 8 days

more