diff --git a/cmd/sharefs/fuse-operations.h b/cmd/sharefs/fuse-operations.h index 34f388b..9abc47c 100644 --- a/cmd/sharefs/fuse-operations.h +++ b/cmd/sharefs/fuse-operations.h @@ -13,7 +13,11 @@ /* sharefs_private is populated by sharefs_init and contains process-wide context */ struct sharefs_private { - int dirfd; /* source dirfd opened during sharefs_init */ + int dirfd; /* source dirfd opened during sharefs_init */ + bool init_failed; /* whether sharefs_init failed */ + uintptr_t source_handle; /* cgo handle of pathname to open for dirfd, freed during sharefs_init */ + uintptr_t setuid; /* uid to set by sharefs_init when running as root */ + uintptr_t setgid; /* gid to set by sharefs_init when running as root */ }; int sharefs_getattr(const char *pathname, struct stat *statbuf, struct fuse_file_info *fi); diff --git a/cmd/sharefs/fuse.go b/cmd/sharefs/fuse.go index 1a17f80..945650b 100644 --- a/cmd/sharefs/fuse.go +++ b/cmd/sharefs/fuse.go @@ -20,6 +20,8 @@ import ( "log" "os" "path/filepath" + "runtime" + "runtime/cgo" "strconv" "syscall" "unsafe" @@ -33,27 +35,19 @@ type ( fuseArgs = C.struct_fuse_args ) -var ( - // initFailed is set to true by sharefs_init if initialisation was unsuccessful. - initFailed bool - - // initSource is pathname to the writable source directory used by sharefs_init to populate sharefs_private. - initSource string - // initSetUidGid is the uid and gid to set by sharefs_init when running as root. - initSetUidGid [2]int -) - //export sharefs_init func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe.Pointer { - private_data := C.malloc(C.size_t(unsafe.Sizeof(C.struct_sharefs_private{}))) - priv := (*C.struct_sharefs_private)(private_data) + ctx := C.fuse_get_context() + priv := (*C.struct_sharefs_private)(ctx.private_data) + source := cgo.Handle(priv.source_handle).Value().(string) + setuid, setgid := int(priv.setuid), int(priv.setgid) if os.Geteuid() == 0 { - if initSetUidGid[0] <= 0 || initSetUidGid[1] <= 0 { + if setuid <= 0 || setgid <= 0 { log.Println("setuid and setgid must not be 0") goto fail } - if err := syscall.Setresgid(initSetUidGid[1], initSetUidGid[1], initSetUidGid[1]); err != nil { + if err := syscall.Setresgid(setgid, setgid, setgid); err != nil { log.Printf("cannot set gid: %v", err) goto fail } @@ -61,7 +55,7 @@ func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe. log.Printf("cannot set supplementary groups: %v", err) goto fail } - if err := syscall.Setresuid(initSetUidGid[0], initSetUidGid[0], initSetUidGid[0]); err != nil { + if err := syscall.Setresuid(setuid, setuid, setuid); err != nil { log.Printf("cannot set uid: %v", err) goto fail } @@ -75,30 +69,37 @@ func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe. cfg.negative_timeout = 0 // all future filesystem operations happen through this dirfd - if fd, err := syscall.Open(initSource, syscall.O_DIRECTORY|syscall.O_RDONLY, 0); err != nil { - log.Printf("cannot open %q: %v", initSource, err) + if fd, err := syscall.Open(source, syscall.O_DIRECTORY|syscall.O_RDONLY, 0); err != nil { + log.Printf("cannot open %q: %v", source, err) goto fail } else if err = syscall.Fchdir(fd); err != nil { - log.Printf("cannot enter %q: %s", initSource, err) + log.Printf("cannot enter %q: %s", source, err) goto fail } else { priv.dirfd = C.int(fd) } - return private_data + return ctx.private_data fail: - C.free(private_data) - C.fuse_exit(C.fuse_get_context().fuse) - initFailed = true + sourceHandle := cgo.Handle(priv.source_handle) + priv.source_handle = 0 + sourceHandle.Delete() + priv.init_failed = true + + C.fuse_exit(ctx.fuse) return nil } //export sharefs_destroy func sharefs_destroy(private_data unsafe.Pointer) { if private_data != nil { - defer C.free(private_data) priv := (*C.struct_sharefs_private)(private_data) + if priv.source_handle != 0 { + sourceHandle := cgo.Handle(priv.source_handle) + priv.source_handle = 0 + sourceHandle.Delete() + } if err := syscall.Close(int(priv.dirfd)); err != nil { log.Printf("cannot close source directory: %v", err) @@ -153,7 +154,7 @@ func parseOpts(args *C.struct_fuse_args) ( defer C.free(unsafe.Pointer(unsafeOpts.setgid)) } - if unsafeOpts.source == nil { + if unsafeOpts.source == nil || *unsafeOpts.source == 0 { showHelp() ret = 1 return @@ -205,12 +206,18 @@ func _main(argc int, argv **C.char) int { // don't mask creation mode, kernel already did that syscall.Umask(0) + var pinner runtime.Pinner + defer pinner.Unpin() + args := C.struct_fuse_args{argc: C.int(argc), argv: argv, allocated: 1} // this causes the kernel to enforce access control based on // struct stat populated by sharefs_getattr unsafeAddArgument(&args, "-odefault_permissions\x00") + var priv C.struct_sharefs_private + pinner.Pin(&priv) + { source, setuid, setgid, ret := parseOpts(&args) if ret != 0 { @@ -221,7 +228,7 @@ func _main(argc int, argv **C.char) int { log.Println(err) return 1 } else { - initSource = a + priv.source_handle = C.uintptr_t(cgo.NewHandle(a)) } if os.Geteuid() == 0 { @@ -233,7 +240,7 @@ func _main(argc int, argv **C.char) int { log.Println("setuid and setgid has no effect when not starting as root") return 1 } - initSetUidGid[0], initSetUidGid[1] = setuid, setgid + priv.setuid, priv.setgid = C.uintptr_t(setuid), C.uintptr_t(setgid) } // TODO(ophestra): spawn container here, set PR_SET_NO_NEW_PRIVS and enforce landlock @@ -257,9 +264,9 @@ func _main(argc int, argv **C.char) int { statfs: closure(C.sharefs_statfs), release: closure(C.sharefs_release), fsync: closure(C.sharefs_fsync), - }, nil) + }, unsafe.Pointer(&priv)) - if initFailed { + if priv.init_failed { return 1 } else { return int(fuse_main_return)