platform_unix.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. //go:build !windows
  2. package localapi
  3. import (
  4. "errors"
  5. "fmt"
  6. "log/slog"
  7. "net"
  8. "os"
  9. "os/user"
  10. "path/filepath"
  11. "strconv"
  12. "sync"
  13. "syscall"
  14. "time"
  15. )
  16. const network = "unix"
  17. // withPeerFD runs f on the descriptor behind conn, which must be a unix socket, and
  18. // returns whichever error came first: not a unix socket, no raw access to it, or f's
  19. // own. It is the scaffolding every platform's peerUID shares; only the getsockopt inside
  20. // differs.
  21. func withPeerFD(conn net.Conn, f func(fd int) error) error {
  22. uc, ok := conn.(*net.UnixConn)
  23. if !ok {
  24. return fmt.Errorf("not a unix connection")
  25. }
  26. raw, err := uc.SyscallConn()
  27. if err != nil {
  28. return err
  29. }
  30. var inner error
  31. if err := raw.Control(func(fd uintptr) { inner = f(int(fd)) }); err != nil {
  32. return err
  33. }
  34. return inner
  35. }
  36. // currentUID is the user the owner checks are made for. A variable so a test can stand
  37. // in for root, or for a stranger, without owning a second account on the host.
  38. var currentUID = func() uint32 { return uint32(os.Geteuid()) }
  39. // listen binds the socket at path with the given access.
  40. //
  41. // Access control is the socket's own mode and group, which the node is created no
  42. // looser than and which are set after bind: connect(2) needs write permission on the
  43. // socket file on every unix we build for. The control socket is 0600 and adds the peer
  44. // check the server applies on accept, unless a group shares it, in which case it is
  45. // 0660 with that group and the kernel's check is the whole of it; the info socket is
  46. // whatever the operator configured, since opening it to others is its purpose. The directory is not part of that, and is never
  47. // chmoded: a directory a service manager made for us is served from at whatever mode it
  48. // came with (systemd's RuntimeDirectoryMode= is 0755 by default), and one an operator
  49. // pointed us into keeps whatever else it holds readable. The one we make ourselves is
  50. // 0755, searchable by everyone, so that a nebula ctl run as another user reaches the
  51. // socket and gets the kernel's answer, which for a member of the configured group is
  52. // a connection and for anyone else is permission denied.
  53. func listen(path string, acc Access) (net.Listener, error) {
  54. // The kernel silently truncates an over long sun_path and bind then fails with
  55. // "invalid argument", which tells an operator nothing. Say what is actually wrong.
  56. if pathTooLong(path) {
  57. return nil, fmt.Errorf("socket path is %d bytes, over the %d byte limit for a unix socket: %s", len(path), maxSocketPath, path)
  58. }
  59. dir := filepath.Dir(path)
  60. // One level only. Every directory defaultPathFor offers sits directly under one that
  61. // exists, and an operator who names a deeper path creates it; a missing parent is a
  62. // mistake to report, not a tree to build. A directory made here is remembered, so
  63. // that removeSocket can take it away again once nothing is left in it.
  64. //
  65. // 0755 gives nothing away: the socket's own mode is the access control. What it
  66. // gives is a directory others can look into, which is what a client that is not the
  67. // daemon's user needs to reach the socket. Mkdir applies the umask, so the mode is
  68. // pinned afterwards, as the socket's is; only on a directory this call created.
  69. switch err := os.Mkdir(dir, 0755); {
  70. case err == nil:
  71. if err := os.Chmod(dir, 0755); err != nil {
  72. return nil, fmt.Errorf("could not set the mode of %s: %w", dir, err)
  73. }
  74. madeDirs.add(dir)
  75. case !errors.Is(err, os.ErrExist):
  76. return nil, fmt.Errorf("could not create %s: %w", dir, err)
  77. }
  78. // Whether it was made here or came with the environment, the directory has to be
  79. // one nobody else could change; see checkSocketDir.
  80. if err := checkSocketDir(dir); err != nil {
  81. return nil, err
  82. }
  83. if err := clearStale(path); err != nil {
  84. return nil, err
  85. }
  86. // bind creates the socket node at the process umask, and a connect(2) between that
  87. // and a later chmod would be queued for accept. Tighten the umask around the bind so
  88. // the node is never looser than 0600, however briefly, whatever mode it ends up at;
  89. // see umaskMu.
  90. umaskMu.Lock()
  91. old := syscall.Umask(0077)
  92. ln, err := net.Listen(network, path)
  93. syscall.Umask(old)
  94. umaskMu.Unlock()
  95. if err != nil {
  96. return nil, err
  97. }
  98. // What was just bound, so that a failure below gives up that node and not whatever
  99. // has been put at the path since.
  100. id, _ := identifySocket(path)
  101. // The node at path is given up by release, which unlinks it before the listener
  102. // closes; see there for why the order matters. Go's listener would unlink on Close
  103. // as well, and an unlink after the close could only be of whatever bound the path
  104. // next, so that is turned off and release is the one place the node is removed.
  105. if ul, ok := ln.(*net.UnixListener); ok {
  106. ul.SetUnlinkOnClose(false)
  107. }
  108. // The umask only promises no looser than 0600; this pins the mode and the group
  109. // exactly, and is the one place a failure to secure the socket is reported.
  110. if err := applyAccess(path, acc); err != nil {
  111. release(ln, path, id)
  112. return nil, fmt.Errorf("could not secure %s: %w", path, err)
  113. }
  114. return ln, nil
  115. }
  116. // checkOwnSocket refuses what fi found at path unless it is a socket owned by the user
  117. // this process runs as. fi comes from a look that does not follow links, so a symlink is
  118. // seen as one, and the mode is only ever applied to a socket of ours. checkSocketDir
  119. // vouched for the directory at bind, but that was then: a directory loosened since, or a
  120. // reload that moves the socket, is a chance for whoever can write it to replace the node
  121. // with a link, or a hard link to a file of their choosing, and have a root daemon set the
  122. // configured mode, 0660 or 0666 for an info socket, on that file. A link, another type
  123. // of file, or somebody else's socket is reported and left as it is. applyAccess, per
  124. // platform, is what makes the look and the change land on the same node.
  125. func checkOwnSocket(path string, fi os.FileInfo) error {
  126. if !isSocket(fi) {
  127. return fmt.Errorf("%s is not a socket but %s, refusing to change its mode", path, describeNode(fi))
  128. }
  129. if uid, ok := owner(fi); ok && uid != currentUID() {
  130. return fmt.Errorf("%s is owned by %s, not by us, refusing to change its mode", path, describeUID(uid))
  131. }
  132. return nil
  133. }
  134. // describeNode names what kind of file fi describes, for a message about something found
  135. // where a socket was expected.
  136. func describeNode(fi os.FileInfo) string {
  137. switch m := fi.Mode(); {
  138. case m&os.ModeSymlink != 0:
  139. return "a symlink"
  140. case m.IsDir():
  141. return "a directory"
  142. case m.IsRegular():
  143. return "a regular file"
  144. default:
  145. return "a " + m.Type().String() + " file"
  146. }
  147. }
  148. // checkSocketDir refuses to serve from a directory that somebody other than the user
  149. // nebula runs as, or root, could change. The socket's mode and group say who may connect
  150. // to it; what makes the path they connect to the daemon's is the directory, since
  151. // whoever can write it can put a socket of their own at the path and answer the clients
  152. // in the daemon's place. So the directory must be owned by the daemon's user or by root
  153. // and writable by its owner alone; a sticky bit is not an exception. A runtime directory
  154. // a service manager made for another user, or /var/run/nebula created ahead of a root
  155. // daemon by a member of group daemon, where darwin lets that group write /var/run, are
  156. // both refused with the owner named.
  157. //
  158. // A link where the directory should be is judged twice: whoever owns the link decides
  159. // where the path leads, so the link must be ours or root's too, and the directory it
  160. // resolves to is then held to the rule above. /var/run is such a link on linux, root's,
  161. // to /run, and a socket named directly under it is served; a link somebody else planted
  162. // is refused as theirs.
  163. //
  164. // Only this directory is checked, not the ones above it. /var/run itself is group
  165. // writable on darwin by design, and the parents are the operator's to trust, as they are
  166. // for every file nebula reads. The check is made at bind, so a directory loosened while
  167. // the daemon runs is noticed at the next reload.
  168. func checkSocketDir(dir string) error {
  169. fi, err := os.Lstat(dir)
  170. if err != nil {
  171. return err
  172. }
  173. if fi.Mode()&os.ModeSymlink != 0 {
  174. if uid, ok := owner(fi); ok && uid != currentUID() && uid != 0 {
  175. return fmt.Errorf("%s is a symlink owned by %s, not by the user nebula runs as or by root, and its owner decides where the path leads; name a socket path that does not go through it", dir, describeUID(uid))
  176. }
  177. if fi, err = os.Stat(dir); err != nil {
  178. return err
  179. }
  180. }
  181. if !fi.IsDir() {
  182. return fmt.Errorf("%s is not a directory", dir)
  183. }
  184. if uid, ok := owner(fi); ok && uid != currentUID() && uid != 0 {
  185. return fmt.Errorf("%s is owned by %s, not by the user nebula runs as or by root, and its owner could replace the socket; make it ours or root's, or name a socket path in a directory that is", dir, describeUID(uid))
  186. }
  187. if perm := fi.Mode().Perm(); perm&0022 != 0 {
  188. who := "its group"
  189. if perm&0002 != 0 {
  190. who = "everyone"
  191. }
  192. return fmt.Errorf("%s is writable by %s (mode %04o), who could replace the socket; make it writable by its owner alone, or name a socket path in a directory that is", dir, who, perm)
  193. }
  194. return nil
  195. }
  196. // owner returns the uid that owns the file fi describes.
  197. func owner(fi os.FileInfo) (uint32, bool) {
  198. st, ok := fi.Sys().(*syscall.Stat_t)
  199. if !ok {
  200. return 0, false
  201. }
  202. return uint32(st.Uid), true
  203. }
  204. // describeUID renders a uid with its account name when the host can say what it is, so
  205. // a message about somebody else's socket tells the operator who that is.
  206. func describeUID(uid uint32) string {
  207. id := strconv.FormatUint(uint64(uid), 10)
  208. if u, err := user.LookupId(id); err == nil && u.Username != "" {
  209. return "uid " + id + " (" + u.Username + ")"
  210. }
  211. return "uid " + id
  212. }
  213. // clearStale removes a leftover socket file, but only after proving nothing is
  214. // listening on it. An unclean restart leaves one behind every time, so this is a
  215. // routine path; blindly unlinking would let a second daemon steal a live endpoint.
  216. //
  217. // Liveness is a plain connect. Only a refused connect proves there is nothing behind
  218. // the file. Anything else, a timeout or a permission error, is a socket we cannot vouch
  219. // for, and it is left alone with the reason in the error.
  220. //
  221. // The look is an Lstat: a symlink at path is refused as not a socket, whether or not it
  222. // resolves. Stat would report a dangling one as nothing there, and bind(2) on darwin
  223. // and the BSDs follows a dangling link and creates the socket node at its target.
  224. func clearStale(path string) error {
  225. fi, err := os.Lstat(path)
  226. if err != nil {
  227. if os.IsNotExist(err) {
  228. return nil
  229. }
  230. return err
  231. }
  232. if !isSocket(fi) {
  233. return fmt.Errorf("%s exists and is not a socket", path)
  234. }
  235. conn, err := dial(path, 250*time.Millisecond)
  236. if err == nil {
  237. _ = conn.Close()
  238. return fmt.Errorf("%s is already in use by another nebula instance", path)
  239. }
  240. if errors.Is(err, os.ErrNotExist) {
  241. // Gone between the stat and the connect; nothing to clear.
  242. return nil
  243. }
  244. if !errors.Is(err, syscall.ECONNREFUSED) {
  245. return fmt.Errorf("%s exists and could not be shown to be stale, refusing to remove it: %w", path, err)
  246. }
  247. return os.Remove(path)
  248. }
  249. // removeSocket unlinks the socket node and, when listen made the directory it is in,
  250. // removes that too, so a socket that moves off an override or a daemon that stops
  251. // leaves nothing behind. A directory that came with the environment, a service
  252. // manager's runtime directory or one the operator pointed us into, is not ours to
  253. // remove and is left as it was.
  254. //
  255. // Only the node id names is removed. One that replaced it is another daemon's, bound
  256. // after ours was taken from under us, and is left where it is. A zero id, a node that
  257. // could not be identified when it was bound, is removed as it always was.
  258. func removeSocket(path string, id socketID) {
  259. if cur, ok := identifySocket(path); ok && id != (socketID{}) && cur != id {
  260. return
  261. }
  262. _ = os.Remove(path)
  263. madeDirs.remove(filepath.Dir(path))
  264. }
  265. // identifySocket returns the identity of the node at path, looked at without following
  266. // links, and false when there is nothing there or it cannot be read.
  267. func identifySocket(path string) (socketID, bool) {
  268. fi, err := os.Lstat(path)
  269. if err != nil {
  270. return socketID{}, false
  271. }
  272. st, ok := fi.Sys().(*syscall.Stat_t)
  273. if !ok {
  274. return socketID{}, false
  275. }
  276. return socketID{dev: uint64(st.Dev), ino: uint64(st.Ino)}, true
  277. }
  278. // madeDirs is the set of directories listen created and has not yet removed. It is
  279. // package state because the socket's path is all release and listen share.
  280. var madeDirs = dirSet{made: map[string]bool{}}
  281. type dirSet struct {
  282. mu sync.Mutex
  283. made map[string]bool
  284. }
  285. func (d *dirSet) add(dir string) {
  286. d.mu.Lock()
  287. defer d.mu.Unlock()
  288. d.made[dir] = true
  289. }
  290. // remove takes dir away if listen made it and it is empty. One that still holds
  291. // something, another socket of ours or whatever else arrived, is kept and stays
  292. // remembered, so the release of the last socket in it is the one that removes it. The
  293. // same goes for any other failure: a directory that is still there is still ours to
  294. // remove, and the next release tries again. Only one that is gone is forgotten.
  295. func (d *dirSet) remove(dir string) {
  296. d.mu.Lock()
  297. defer d.mu.Unlock()
  298. if !d.made[dir] {
  299. return
  300. }
  301. if err := os.Remove(dir); err != nil && !errors.Is(err, os.ErrNotExist) {
  302. return
  303. }
  304. delete(d.made, dir)
  305. }
  306. func dial(path string, timeout time.Duration) (net.Conn, error) {
  307. return net.DialTimeout(network, path, timeout)
  308. }
  309. // sunPathMax is the smallest sun_path any platform we build for offers (104 on darwin,
  310. // 108 on linux). Overrunning it truncates the path rather than failing cleanly, so we
  311. // check against the tighter bound everywhere.
  312. const sunPathMax = 104
  313. // maxSocketPath is the longest path that actually binds. sun_path holds the trailing
  314. // NUL as well, and Go's syscall package refuses a path that fills the whole buffer
  315. // with a bare EINVAL, so the usable limit is one byte short of the buffer.
  316. const maxSocketPath = sunPathMax - 1
  317. // umaskMu serialises setting the process umask around one syscall: the umask is process
  318. // wide, and two binds at once, a reload landing during startup, would otherwise
  319. // interleave their save and restore and leave the daemon's umask at one of ours. It is
  320. // held for the syscall alone. Another goroutine creating a file in that moment gets the
  321. // umask of the window rather than the process's own; nothing creates files on a hot
  322. // path.
  323. var umaskMu sync.Mutex
  324. func pathTooLong(path string) bool { return len(path) > maxSocketPath }
  325. // defaultSocketPath is the control socket when the config names none. It is root's to
  326. // create, as the tun device is root's to open; a daemon run as anyone else names a path
  327. // of its own with CtlSocketKey.
  328. const defaultSocketPath = "/var/run/" + DirName + "/ctl.sock"
  329. // checkSocketPath refuses a configured socket path that is relative, or too long to
  330. // bind. A relative path would mean a different socket to the daemon and to nebula ctl,
  331. // each resolving it against the directory it happened to be started from.
  332. func checkSocketPath(key, value string) error {
  333. if !filepath.IsAbs(value) {
  334. return fmt.Errorf("%s must be an absolute path, got %q", key, value)
  335. }
  336. if pathTooLong(value) {
  337. return fmt.Errorf("%s is %d bytes, too long for this platform: %s", key, len(value), value)
  338. }
  339. return nil
  340. }
  341. // logListening is the line written when the socket is up. It says what the access
  342. // control is: the socket's mode plus the peer check on each connection, or, when a group
  343. // shares the socket, the mode and group alone.
  344. func logListening(l *slog.Logger, path string, acc Access) {
  345. if acc.Group != nil {
  346. l.Info("Local control socket listening, access decided by the socket's group", "path", path, "access", acc.String())
  347. return
  348. }
  349. l.Info("Local control socket listening", "path", path, "access", acc.String())
  350. }
  351. func isSocket(fi os.FileInfo) bool { return fi.Mode()&os.ModeSocket != 0 }