|
|
@@ -74,8 +74,8 @@ func listen(path string, acc Access) (net.Listener, error) {
|
|
|
// is; only on a directory this call created.
|
|
|
switch err := os.Mkdir(dir, 0755); {
|
|
|
case err == nil:
|
|
|
- if err := os.Chmod(dir, 0755); err != nil {
|
|
|
- return nil, fmt.Errorf("could not set the mode of %s: %w", dir, err)
|
|
|
+ if err := pinDirMode(dir, 0755); err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
case !errors.Is(err, os.ErrExist):
|
|
|
return nil, fmt.Errorf("could not create %s: %w", dir, err)
|
|
|
@@ -159,6 +159,33 @@ func describeNode(fi os.FileInfo) string {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// pinDirMode sets the mode of the directory listen has just made, through a descriptor
|
|
|
+// rather than by path. Whoever can write the parent could rename the new directory
|
|
|
+// away and leave a link in its place between the Mkdir and a chmod by path, which a
|
|
|
+// root daemon would then follow to set the mode on the link's target. The open does
|
|
|
+// not follow a link, and the directory it finds must be one this process owns, or it
|
|
|
+// is not the one just made and is refused.
|
|
|
+func pinDirMode(dir string, mode os.FileMode) error {
|
|
|
+ f, err := os.OpenFile(dir, os.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("could not open %s to set its mode: %w", dir, err)
|
|
|
+ }
|
|
|
+ defer f.Close()
|
|
|
+
|
|
|
+ fi, err := f.Stat()
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("could not set the mode of %s: %w", dir, err)
|
|
|
+ }
|
|
|
+ if uid, ok := owner(fi); !ok || uid != uint32(os.Geteuid()) {
|
|
|
+ return fmt.Errorf("%s was replaced after it was created, refusing to change it", dir)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := f.Chmod(mode); err != nil {
|
|
|
+ return fmt.Errorf("could not set the mode of %s: %w", dir, err)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
// checkSocketDir refuses to serve from a directory that somebody other than the user
|
|
|
// nebula runs as, or root, could change. The socket's mode and group say who may connect
|
|
|
// to it; what makes the path they connect to the daemon's is the directory, since
|