From 80d793c26c79d84a478e5d3ec9d11f18482d20f4 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Mon, 20 Jan 2020 03:03:12 +0300 Subject: [PATCH 1/2] contrib: bindings: add go binding This binding is mainly inspired by python building. The public interface is built on the shoulders of `Root` and `Handle` structures, in a similar way as the original library does. These ones are straightforward wrappers of the C api to provide a safe and idiomatic interface. As for error handling it upholds the idea to copy all the data which is provided by ffi interface including backtrace. It copies it to an internal structure to not being bound by handling errors on place. Since golang does not has a standart way to manage stackraces it can be get by casting error to `pathrs.Error` type and call `Backtrace` method. This binding does not provide configuration interface of pathrs. Signed-off-by: Maxim Zhiburt --- contrib/bindings/go/pathrs/pathrs.go | 328 +++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 contrib/bindings/go/pathrs/pathrs.go diff --git a/contrib/bindings/go/pathrs/pathrs.go b/contrib/bindings/go/pathrs/pathrs.go new file mode 100644 index 0000000..45bfd7f --- /dev/null +++ b/contrib/bindings/go/pathrs/pathrs.go @@ -0,0 +1,328 @@ +// Copyright (C) 2019, 2020 Aleksa Sarai +// Copyright (C) 2020 Maxim Zhiburt +// 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 +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), + sName: C.GoString(entry.symbol_name), + } + err.backtrace = append(err.backtrace, line) + } + } + + return err +} + +func handleErr(ptrType C.pathrs_type_t, ptr unsafe.Pointer) error { + err := C.pathrs_error(ptrType, ptr) + defer C.pathrs_free(C.PATHRS_ERROR, unsafe.Pointer(err)) + return newError(err) +} + +func randName(rand *rand.Rand, len int) (string, error) { + var nameBuf strings.Builder + lenBuf := len / 2 + randBuf := make([]byte, lenBuf) + + n, err := rand.Read(randBuf) + if n != lenBuf || err != nil { + 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(), nil +} From e6737f8369bf365fecf684835cdd8afd150e1342 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Mon, 20 Jan 2020 03:29:18 +0300 Subject: [PATCH 2/2] examples: create a golang 'cat' binding example This is a simple program to print the whole file to stdout using safe path resolution. And demonstrates safe handling errors and key functions such as `*Root`.Resolve, `*Handle`.Open. To start this program was set a pile of env variables: `CGO_LDFLAGS`, `CGO_CFLAGS` and `LD_LIBRARY_PATH`. Signed-off-by: Maxim Zhiburt --- contrib/bindings/go/pathrs/pathrs.go | 112 ++++++++++++++------------- examples/go/cat.go | 78 +++++++++++++++++++ 2 files changed, 137 insertions(+), 53 deletions(-) create mode 100644 examples/go/cat.go diff --git a/contrib/bindings/go/pathrs/pathrs.go b/contrib/bindings/go/pathrs/pathrs.go index 45bfd7f..2fb983a 100644 --- a/contrib/bindings/go/pathrs/pathrs.go +++ b/contrib/bindings/go/pathrs/pathrs.go @@ -21,12 +21,11 @@ package pathrs // #include import "C" import ( + "crypto/rand" "fmt" - "math/rand" "os" "strings" "syscall" - "time" "unsafe" ) @@ -35,12 +34,6 @@ 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)) @@ -52,9 +45,10 @@ func Open(path string) (*Root, error) { 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) { +// RootFromFile constructs a new file-based libpathrs object of root from a file descriptor +// Uses often in combination with Root.IntoFile methood +func RootFromFile(file *os.File) (*Root, error) { + fd := file.Fd() 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 { @@ -64,18 +58,6 @@ func RootFromFd(fd int) (*Root, error) { 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. @@ -85,17 +67,17 @@ func (r *Root) Resolve(path string) (*Handle, error) { return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root)) } - return newHandle(handler), nil + return &Handle{handle: 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) { +// Create creates a file with a such mode by path according with the root path +func (r *Root) Create(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 + return &Handle{handle: handler}, nil } // Rename Within the given root's tree, perform the rename of src to dst, @@ -130,26 +112,36 @@ func (r *Root) Symlink(path, target string) error { return handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root)) } -// IntoFd unwraps a file-based libpathrs object to obtain its underlying file +// IntoFile 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))) +func (r *Root) IntoFile() (*os.File, error) { + cloned, err := r.Clone() + if err != nil { + return nil, err + } + + fd := int(C.pathrs_into_fd(C.PATHRS_ROOT, unsafe.Pointer(cloned.root))) if fd < 0 { - return 0, handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root)) + return nil, handleErr(C.PATHRS_ROOT, unsafe.Pointer(cloned.root)) } - return fd, nil + name, err := randName(32) + if err != nil { + return nil, err + } + + return os.NewFile(uintptr(fd), "pathrs-root:"+name), 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)) + err := handleErr(C.PATHRS_ROOT, unsafe.Pointer(r.root)) if err != nil { return nil, err } @@ -157,30 +149,46 @@ func (r *Root) Clone() (*Root, error) { return &Root{root: newRoot}, nil } -// Free frees underling caught resources -func (r *Root) Free() { +// Close frees underling caught resources +func (r *Root) Close() { 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())), +// Handle represents an handle pathrs api interface +type Handle struct { + handle *C.pathrs_handle_t +} + +// 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 &Handle{handle: handler}, nil +} + +// Open upgrade the handle to a file representation +// which holds a usable fd, suitable for reading +func (h *Handle) Open() (*os.File, error) { + return h.OpenFile(os.O_RDONLY) } -// 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) { +// OpenFile upgrade the handle to a file representation +// which holds a usable fd, with a specific settings by provided flags +func (h *Handle) OpenFile(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) + name, err := randName(32) if err != nil { return nil, err } @@ -208,21 +216,19 @@ func (h *Handle) IntoFd() (int, error) { // 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)) + err := handleErr(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)) if err != nil { return nil, err } - return newHandle(newHandler), nil + return &Handle{handle: newHandler}, nil } -// Free frees underling caught resources -func (h *Handle) Free() { - if h == nil { - return +// Close frees underling caught resources +func (h *Handle) Close() { + if h != nil { + C.pathrs_free(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)) } - - C.pathrs_free(C.PATHRS_HANDLE, unsafe.Pointer(h.handle)) } // Error representation of rust error @@ -287,7 +293,7 @@ func newError(e *C.pathrs_error_t) error { if e.backtrace != nil { head := uintptr(unsafe.Pointer(e.backtrace.head)) length := uintptr(e.backtrace.length) - sizeof := unsafe.Sizeof(C.__pathrs_backtrace_entry_t{}) + sizeof := uintptr(C.sizeof___pathrs_backtrace_entry_t) for ptr := head; ptr < head+length*sizeof; ptr += sizeof { entry := (*C.__pathrs_backtrace_entry_t)(unsafe.Pointer(ptr)) line := backtraceLine{ @@ -310,7 +316,7 @@ func handleErr(ptrType C.pathrs_type_t, ptr unsafe.Pointer) error { return newError(err) } -func randName(rand *rand.Rand, len int) (string, error) { +func randName(len int) (string, error) { var nameBuf strings.Builder lenBuf := len / 2 randBuf := make([]byte, lenBuf) diff --git a/examples/go/cat.go b/examples/go/cat.go new file mode 100644 index 0000000..ead8083 --- /dev/null +++ b/examples/go/cat.go @@ -0,0 +1,78 @@ +// libpathrs: safe path resolution on Linux +// Copyright (C) 2019, 2020 Aleksa Sarai +// Copyright (C) 2020 Maxim Zhiburt +// Copyright (C) 2019, 2020 SUSE LLC +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program. If not, see . + +// Package main implements a program which print file content to stdout +// safely resolving paths with libpathrs. +package main + +import ( + "errors" + "fmt" + "io" + "os" + + "../../contrib/bindings/go/pathrs" +) + +func usage() { + fmt.Println("usage: cat ") + os.Exit(1) +} + +func main() { + if len(os.Args) < 3 { + usage() + } + + rootPath := os.Args[1] + path := os.Args[2] + + root, err := pathrs.Open(rootPath) + if err != nil { + printPathError(err) + } + defer root.Close() + + handle, err := root.Resolve(path) + if err != nil { + printPathError(err) + } + defer handle.Close() + + file, err := handle.Open() + if err != nil { + printPathError(err) + } + defer file.Close() + + _, err = io.Copy(os.Stdout, file) + if err != nil { + fmt.Printf("Cannot write content of file to stdout, %v\n", err) + os.Exit(1) + } +} + +func printPathError(err error) { + fmt.Println("Error", err) + fmt.Println("Unwrapped error", errors.Unwrap(err)) + fmt.Println("Backtrace:") + if pathrsErr, ok := err.(*pathrs.Error); ok { + fmt.Println(pathrsErr.Backtrace()) + } + + os.Exit(1) +}