github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-update-ns/secure_bindmount.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "fmt" 24 "syscall" 25 ) 26 27 // BindMount performs a bind mount between two absolute paths containing no 28 // symlinks. 29 func BindMount(sourceDir, targetDir string, flags uint) error { 30 // This function only attempts to handle bind mounts. Expanding to other 31 // mounts will require examining do_mount() from fs/namespace.c of the 32 // kernel that called functions (eventually) verify `DCACHE_CANT_MOUNT` is 33 // not set (eg, by calling lock_mount()). 34 if flags&syscall.MS_BIND == 0 { 35 return fmt.Errorf("cannot perform non-bind mount operation") 36 } 37 38 // The kernel doesn't support recursively switching a tree of bind mounts 39 // to read only, and we haven't written a work around. 40 if flags&syscall.MS_RDONLY != 0 && flags&syscall.MS_REC != 0 { 41 return fmt.Errorf("cannot use MS_RDONLY and MS_REC together") 42 } 43 44 // Step 1: acquire file descriptors representing the source and destination 45 // directories, ensuring no symlinks are followed. 46 sourceFd, err := OpenPath(sourceDir) 47 if err != nil { 48 return err 49 } 50 defer sysClose(sourceFd) 51 targetFd, err := OpenPath(targetDir) 52 if err != nil { 53 return err 54 } 55 defer sysClose(targetFd) 56 57 // Step 2: perform a bind mount between the paths identified by the two 58 // file descriptors. We primarily care about privilege escalation here and 59 // trying to race the sysMount() by removing any part of the dir (sourceDir 60 // or targetDir) after we have an open file descriptor to it (sourceFd or 61 // targetFd) to then replace an element of the dir's path with a symlink 62 // will cause the fd path (ie, sourceFdPath or targetFdPath) to be marked 63 // as unmountable within the kernel (this path is also changed to show as 64 // '(deleted)'). Alternatively, simply renaming the dir (sourceDir or 65 // targetDir) after we have an open file descriptor to it (sourceFd or 66 // targetFd) causes the mount to happen with the newly renamed path, but 67 // this rename is controlled by DAC so while the user could race the mount 68 // source or target, this rename can't be used to gain privileged access to 69 // files. For systems with AppArmor enabled, this raced rename would be 70 // denied by the per-snap snap-update-ns AppArmor profle. 71 sourceFdPath := fmt.Sprintf("/proc/self/fd/%d", sourceFd) 72 targetFdPath := fmt.Sprintf("/proc/self/fd/%d", targetFd) 73 bindFlags := syscall.MS_BIND | (flags & syscall.MS_REC) 74 if err := sysMount(sourceFdPath, targetFdPath, "", uintptr(bindFlags), ""); err != nil { 75 return err 76 } 77 78 // Step 3: optionally change to readonly 79 if flags&syscall.MS_RDONLY != 0 { 80 // We need to look up the target directory a second time, because 81 // targetFd refers to the path shadowed by the mount point. 82 mountFd, err := OpenPath(targetDir) 83 if err != nil { 84 // FIXME: the mount occurred, but the user moved the target 85 // somewhere 86 return err 87 } 88 defer sysClose(mountFd) 89 mountFdPath := fmt.Sprintf("/proc/self/fd/%d", mountFd) 90 remountFlags := syscall.MS_REMOUNT | syscall.MS_BIND | syscall.MS_RDONLY 91 if err := sysMount("none", mountFdPath, "", uintptr(remountFlags), ""); err != nil { 92 sysUnmount(mountFdPath, syscall.MNT_DETACH|umountNoFollow) 93 return err 94 } 95 } 96 return nil 97 }