|
5 | 5 | "fmt" |
6 | 6 | "io" |
7 | 7 | "os" |
| 8 | + "strconv" |
8 | 9 |
|
9 | 10 | "github.com/sirupsen/logrus" |
10 | 11 | "golang.org/x/sys/unix" |
|
19 | 20 | _ SealFunc = sealFile |
20 | 21 | ) |
21 | 22 |
|
| 23 | +func isExecutable(f *os.File) bool { |
| 24 | + if err := unix.Faccessat(int(f.Fd()), "", unix.X_OK, unix.AT_EACCESS|unix.AT_EMPTY_PATH); err == nil { |
| 25 | + return true |
| 26 | + } else if err == unix.EACCES { |
| 27 | + return false |
| 28 | + } |
| 29 | + path := "/proc/self/fd/" + strconv.Itoa(int(f.Fd())) |
| 30 | + if err := unix.Access(path, unix.X_OK); err == nil { |
| 31 | + return true |
| 32 | + } else if err == unix.EACCES { |
| 33 | + return false |
| 34 | + } |
| 35 | + // Cannot check -- assume it's executable (if not, exec will fail). |
| 36 | + logrus.Debugf("cannot do X_OK check on binary %s -- assuming it's executable", f.Name()) |
| 37 | + return true |
| 38 | +} |
| 39 | + |
22 | 40 | const baseMemfdSeals = unix.F_SEAL_SEAL | unix.F_SEAL_SHRINK | unix.F_SEAL_GROW | unix.F_SEAL_WRITE |
23 | 41 |
|
24 | 42 | func sealMemfd(f **os.File) error { |
@@ -106,15 +124,46 @@ func getSealableFile(comment, tmpDir string) (file *os.File, sealFn SealFunc, er |
106 | 124 | return |
107 | 125 | } |
108 | 126 | logrus.Debugf("memfd cloned binary failed, falling back to O_TMPFILE: %v", err) |
| 127 | + |
| 128 | + // The tmpDir here (c.root) might be mounted noexec, so we need a couple of |
| 129 | + // fallbacks to try. It's possible that none of these are writable and |
| 130 | + // executable, in which case there's nothing we can practically do (other |
| 131 | + // than mounting our own executable tmpfs, which would have its own |
| 132 | + // issues). |
| 133 | + tmpDirs := []string{ |
| 134 | + tmpDir, |
| 135 | + os.TempDir(), |
| 136 | + "/tmp", |
| 137 | + ".", |
| 138 | + "/bin", |
| 139 | + "/", |
| 140 | + } |
| 141 | + |
109 | 142 | // Try to fallback to O_TMPFILE (supported since Linux 3.11). |
110 | | - file, sealFn, err = otmpfile(tmpDir) |
111 | | - if err == nil { |
| 143 | + for _, dir := range tmpDirs { |
| 144 | + file, sealFn, err = otmpfile(dir) |
| 145 | + if err != nil { |
| 146 | + continue |
| 147 | + } |
| 148 | + if !isExecutable(file) { |
| 149 | + logrus.Debugf("tmpdir %s is noexec -- trying a different tmpdir", dir) |
| 150 | + file.Close() |
| 151 | + continue |
| 152 | + } |
112 | 153 | return |
113 | 154 | } |
114 | 155 | logrus.Debugf("O_TMPFILE cloned binary failed, falling back to mktemp(): %v", err) |
115 | 156 | // Finally, try a classic unlinked temporary file. |
116 | | - file, sealFn, err = mktemp(tmpDir) |
117 | | - if err == nil { |
| 157 | + for _, dir := range tmpDirs { |
| 158 | + file, sealFn, err = mktemp(dir) |
| 159 | + if err != nil { |
| 160 | + continue |
| 161 | + } |
| 162 | + if !isExecutable(file) { |
| 163 | + logrus.Debugf("tmpdir %s is noexec -- trying a different tmpdir", dir) |
| 164 | + file.Close() |
| 165 | + continue |
| 166 | + } |
118 | 167 | return |
119 | 168 | } |
120 | 169 | return nil, nil, fmt.Errorf("could not create sealable file for cloned binary: %w", err) |
|
0 commit comments