mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
The `RemoteAddr` of a request can now store more than one interface: `...;iface=foo&bar&baz;` includes the interfaces "foo", "bar", and "baz". Importantly, `ucrednetAttachInterface` is now idempotent. No matter how many times it is called with the same interface, once the interface is in the list, it is not added again. Also, the corresponding `ucrednetGetWithInterface` now returns a slice of interfaces instead of a single one. This slice is obtained by splitting the value of the `iface=` field on `'&'` characters. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> daemon: refactor ucrednetAttachInterface to use raddrRegexp Signed-off-by: Oliver Calder <oliver.calder@canonical.com> daemon: add comments about ucrednet remote address regexp groups Signed-off-by: Oliver Calder <oliver.calder@canonical.com> daemon: deduplicate types in notices filter and test types for multiple interfaces Signed-off-by: Oliver Calder <oliver.calder@canonical.com> daemon: add duplicate type to notices types filter test Signed-off-by: Oliver Calder <oliver.calder@canonical.com> daemon: rename ucrednetGetWithInterface to ucrednetGetWithInterfaces Signed-off-by: Oliver Calder <oliver.calder@canonical.com>
189 lines
4.6 KiB
Go
189 lines
4.6 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2015-2024 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
sys "syscall"
|
|
|
|
"github.com/snapcore/snapd/strutil"
|
|
)
|
|
|
|
var errNoID = errors.New("no pid/uid found")
|
|
|
|
const (
|
|
ucrednetNoProcess = int32(0)
|
|
ucrednetNobody = uint32((1 << 32) - 1)
|
|
)
|
|
|
|
var raddrRegexp = regexp.MustCompile(`^pid=(\d+);uid=(\d+);socket=([^;]*);(iface=([^;]*);)?$`)
|
|
|
|
var ucrednetGet = ucrednetGetImpl
|
|
var ucrednetGetWithInterfaces = ucrednetGetWithInterfacesImpl
|
|
|
|
func ucrednetGetImpl(remoteAddr string) (*ucrednet, error) {
|
|
uc, _, err := ucrednetGetWithInterfaces(remoteAddr)
|
|
return uc, err
|
|
}
|
|
|
|
func ucrednetGetWithInterfacesImpl(remoteAddr string) (ucred *ucrednet, ifaces []string, err error) {
|
|
// NOTE treat remoteAddr at one point included a user-controlled
|
|
// string. In case that happens again by accident, treat it as tainted,
|
|
// and be very suspicious of it.
|
|
u := &ucrednet{
|
|
Pid: ucrednetNoProcess,
|
|
Uid: ucrednetNobody,
|
|
}
|
|
subs := raddrRegexp.FindStringSubmatch(remoteAddr)
|
|
if subs != nil {
|
|
if v, err := strconv.ParseInt(subs[1], 10, 32); err == nil {
|
|
u.Pid = int32(v)
|
|
}
|
|
if v, err := strconv.ParseUint(subs[2], 10, 32); err == nil {
|
|
u.Uid = uint32(v)
|
|
}
|
|
// group: ([^;]*) - socket path following socket=
|
|
u.Socket = subs[3]
|
|
// group: (iface=([^;]*);)
|
|
if len(subs[4]) > 0 {
|
|
// group: ([^;]*) - actual interfaces joined together with & separator
|
|
ifaces = strings.Split(subs[5], "&")
|
|
}
|
|
}
|
|
if u.Pid == ucrednetNoProcess || u.Uid == ucrednetNobody {
|
|
return nil, nil, errNoID
|
|
}
|
|
|
|
return u, ifaces, nil
|
|
}
|
|
|
|
func ucrednetAttachInterface(remoteAddr, iface string) string {
|
|
inds := raddrRegexp.FindStringSubmatchIndex(remoteAddr)
|
|
if inds == nil {
|
|
// This should only occur if remoteAddr is invalid.
|
|
return fmt.Sprintf("%siface=%s;", remoteAddr, iface)
|
|
}
|
|
// start of string matching group "(iface=([^;]*);)"
|
|
ifaceSubStart := inds[8]
|
|
ifaceSubEnd := inds[9]
|
|
if ifaceSubStart == ifaceSubEnd {
|
|
// "(iface=([^;]*);)" not present.
|
|
return fmt.Sprintf("%siface=%s;", remoteAddr, iface)
|
|
}
|
|
// string matching group "([^;]*)" within "(iface=([^;]*);)"
|
|
ifacesStr := remoteAddr[inds[10]:inds[11]]
|
|
ifaces := strings.Split(ifacesStr, "&")
|
|
if strutil.ListContains(ifaces, iface) {
|
|
return remoteAddr
|
|
}
|
|
ifaces = append(ifaces, iface)
|
|
return fmt.Sprintf("%siface=%s;", remoteAddr[:ifaceSubStart], strings.Join(ifaces, "&"))
|
|
}
|
|
|
|
type ucrednet struct {
|
|
Pid int32
|
|
Uid uint32
|
|
Socket string
|
|
}
|
|
|
|
func (un *ucrednet) String() string {
|
|
if un == nil {
|
|
return "pid=;uid=;socket=;"
|
|
}
|
|
return fmt.Sprintf("pid=%d;uid=%d;socket=%s;", un.Pid, un.Uid, un.Socket)
|
|
}
|
|
|
|
type ucrednetAddr struct {
|
|
net.Addr
|
|
*ucrednet
|
|
}
|
|
|
|
func (wa *ucrednetAddr) String() string {
|
|
// NOTE we drop the original (user-supplied) net.Addr from the
|
|
// serialization entirely. We carry it this far so it helps debugging
|
|
// (via %#v logging), but from here on in it's not helpful.
|
|
return wa.ucrednet.String()
|
|
}
|
|
|
|
type ucrednetConn struct {
|
|
net.Conn
|
|
*ucrednet
|
|
}
|
|
|
|
func (wc *ucrednetConn) RemoteAddr() net.Addr {
|
|
return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.ucrednet}
|
|
}
|
|
|
|
type ucrednetListener struct {
|
|
net.Listener
|
|
|
|
idempotClose sync.Once
|
|
closeErr error
|
|
}
|
|
|
|
var getUcred = sys.GetsockoptUcred
|
|
|
|
func (wl *ucrednetListener) Accept() (net.Conn, error) {
|
|
con, err := wl.Listener.Accept()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var unet *ucrednet
|
|
if ucon, ok := con.(*net.UnixConn); ok {
|
|
syscallConn, err := ucon.SyscallConn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ucred *sys.Ucred
|
|
scErr := syscallConn.Control(func(fd uintptr) {
|
|
ucred, err = getUcred(int(fd), sys.SOL_SOCKET, sys.SO_PEERCRED)
|
|
})
|
|
if scErr != nil {
|
|
return nil, scErr
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
unet = &ucrednet{
|
|
Pid: ucred.Pid,
|
|
Uid: ucred.Uid,
|
|
Socket: ucon.LocalAddr().String(),
|
|
}
|
|
}
|
|
|
|
return &ucrednetConn{con, unet}, nil
|
|
}
|
|
|
|
func (wl *ucrednetListener) Close() error {
|
|
wl.idempotClose.Do(func() {
|
|
wl.closeErr = wl.Listener.Close()
|
|
})
|
|
return wl.closeErr
|
|
}
|