Skip to content

Commit 8346800

Browse files
authored
Create admin socket synchronously before privdrop (#1201)
Creating UNIX sockets the listen() goroutine that races against the main one dropping to an unprivileged user may cause startup failure when privdrop happens before privileged filesystem access. Setup or fail in New() and only do listen(2) in listen() to avoid this. ``` # yggdrasil -autoconf -user nobody 2024/11/03 21:15:27 Build name: yggdrasil-go 2024/11/03 21:15:27 Build version: 0.5.9 ... 2024/11/03 21:15:27 Admin socket failed to listen: listen unix /var/run/yggdrasil.sock: bind: permission denied ``` Rerun, now the order is flipped: ``` # yggdrasil -autoconf -user nobody 2024/11/03 21:15:34 Build name: yggdrasil-go 2024/11/03 21:15:34 Build version: 0.5.9 [...] 2024/11/03 21:15:34 UNIX admin socket listening on /var/run/yggdrasil.sock [...] ``` Fixes #927.
1 parent eef6139 commit 8346800

File tree

1 file changed

+46
-44
lines changed

1 file changed

+46
-44
lines changed

src/admin/admin.go

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,52 @@ func New(c *core.Core, log core.Logger, opts ...SetupOption) (*AdminSocket, erro
8383
if a.config.listenaddr == "none" || a.config.listenaddr == "" {
8484
return nil, nil
8585
}
86+
87+
listenaddr := string(a.config.listenaddr)
88+
u, err := url.Parse(listenaddr)
89+
if err == nil {
90+
switch strings.ToLower(u.Scheme) {
91+
case "unix":
92+
if _, err := os.Stat(u.Path); err == nil {
93+
a.log.Debugln("Admin socket", u.Path, "already exists, trying to clean up")
94+
if _, err := net.DialTimeout("unix", u.Path, time.Second*2); err == nil || err.(net.Error).Timeout() {
95+
a.log.Errorln("Admin socket", u.Path, "already exists and is in use by another process")
96+
os.Exit(1)
97+
} else {
98+
if err := os.Remove(u.Path); err == nil {
99+
a.log.Debugln(u.Path, "was cleaned up")
100+
} else {
101+
a.log.Errorln(u.Path, "already exists and was not cleaned up:", err)
102+
os.Exit(1)
103+
}
104+
}
105+
}
106+
a.listener, err = net.Listen("unix", u.Path)
107+
if err == nil {
108+
switch u.Path[:1] {
109+
case "@": // maybe abstract namespace
110+
default:
111+
if err := os.Chmod(u.Path, 0660); err != nil {
112+
a.log.Warnln("WARNING:", u.Path, "may have unsafe permissions!")
113+
}
114+
}
115+
}
116+
case "tcp":
117+
a.listener, err = net.Listen("tcp", u.Host)
118+
default:
119+
a.listener, err = net.Listen("tcp", listenaddr)
120+
}
121+
} else {
122+
a.listener, err = net.Listen("tcp", listenaddr)
123+
}
124+
if err != nil {
125+
a.log.Errorf("Admin socket failed to listen: %v", err)
126+
os.Exit(1)
127+
}
128+
a.log.Infof("%s admin socket listening on %s",
129+
strings.ToUpper(a.listener.Addr().Network()),
130+
a.listener.Addr().String())
131+
86132
_ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) {
87133
res := &ListResponse{}
88134
for name, handler := range a.handlers {
@@ -233,50 +279,6 @@ func (a *AdminSocket) Stop() error {
233279

234280
// listen is run by start and manages API connections.
235281
func (a *AdminSocket) listen() {
236-
listenaddr := string(a.config.listenaddr)
237-
u, err := url.Parse(listenaddr)
238-
if err == nil {
239-
switch strings.ToLower(u.Scheme) {
240-
case "unix":
241-
if _, err := os.Stat(u.Path); err == nil {
242-
a.log.Debugln("Admin socket", u.Path, "already exists, trying to clean up")
243-
if _, err := net.DialTimeout("unix", u.Path, time.Second*2); err == nil || err.(net.Error).Timeout() {
244-
a.log.Errorln("Admin socket", u.Path, "already exists and is in use by another process")
245-
os.Exit(1)
246-
} else {
247-
if err := os.Remove(u.Path); err == nil {
248-
a.log.Debugln(u.Path, "was cleaned up")
249-
} else {
250-
a.log.Errorln(u.Path, "already exists and was not cleaned up:", err)
251-
os.Exit(1)
252-
}
253-
}
254-
}
255-
a.listener, err = net.Listen("unix", u.Path)
256-
if err == nil {
257-
switch u.Path[:1] {
258-
case "@": // maybe abstract namespace
259-
default:
260-
if err := os.Chmod(u.Path, 0660); err != nil {
261-
a.log.Warnln("WARNING:", u.Path, "may have unsafe permissions!")
262-
}
263-
}
264-
}
265-
case "tcp":
266-
a.listener, err = net.Listen("tcp", u.Host)
267-
default:
268-
a.listener, err = net.Listen("tcp", listenaddr)
269-
}
270-
} else {
271-
a.listener, err = net.Listen("tcp", listenaddr)
272-
}
273-
if err != nil {
274-
a.log.Errorf("Admin socket failed to listen: %v", err)
275-
os.Exit(1)
276-
}
277-
a.log.Infof("%s admin socket listening on %s",
278-
strings.ToUpper(a.listener.Addr().Network()),
279-
a.listener.Addr().String())
280282
defer a.listener.Close()
281283
for {
282284
conn, err := a.listener.Accept()

0 commit comments

Comments
 (0)