diff --git a/management/internals/shared/grpc/peer_meta_test.go b/management/internals/shared/grpc/peer_meta_test.go new file mode 100644 index 000000000..c6336ff1b --- /dev/null +++ b/management/internals/shared/grpc/peer_meta_test.go @@ -0,0 +1,45 @@ +package grpc + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/shared/management/proto" +) + +func TestExtractPeerMeta_RemovesNulAndInvalidUTF8(t *testing.T) { + meta := extractPeerMeta(context.Background(), &proto.PeerSystemMeta{ + Hostname: "host\x00name", + OS: "\x00\x00\x00\x00\x00\x00s", + OSVersion: "11\xff", + SysSerialNumber: "SN\x00\x00", + Environment: &proto.Environment{Cloud: "\x00aws"}, + NetworkAddresses: []*proto.NetworkAddress{ + {NetIP: "192.168.0.1/24", Mac: "e0:d5\x00"}, + }, + Files: []*proto.File{{Path: "C:\\x\x00.exe"}}, + }) + + assert.Equal(t, "hostname", meta.Hostname) + assert.Equal(t, "s", meta.OS) + assert.Equal(t, "11", meta.OSVersion) + assert.Equal(t, "SN", meta.SystemSerialNumber) + assert.Equal(t, "aws", meta.Environment.Cloud) + assert.Equal(t, "e0:d5", meta.NetworkAddresses[0].Mac) + assert.Equal(t, "C:\\x.exe", meta.Files[0].Path) +} + +func TestCleanClientString(t *testing.T) { + tests := map[string]string{ + "": "", + "\x00\x00": "", + "Wïndows": "Wïndows", + "ssh-ed25519 AAA\x00": "ssh-ed25519 AAA", + "bad\xc3\x28utf8": "bad(utf8", + } + for in, want := range tests { + assert.Equal(t, want, cleanClientString(in), "input %q", in) + } +} diff --git a/management/internals/shared/grpc/server.go b/management/internals/shared/grpc/server.go index 51023c34d..b48a86d55 100644 --- a/management/internals/shared/grpc/server.go +++ b/management/internals/shared/grpc/server.go @@ -553,9 +553,9 @@ func extractPeerMeta(ctx context.Context, meta *proto.PeerSystemMeta) nbpeer.Pee return nbpeer.PeerSystemMeta{} } - osVersion := meta.GetOSVersion() + osVersion := cleanClientString(meta.GetOSVersion()) if osVersion == "" { - osVersion = meta.GetCore() + osVersion = cleanClientString(meta.GetCore()) } networkAddresses := make([]nbpeer.NetworkAddress, 0, len(meta.GetNetworkAddresses())) @@ -567,36 +567,36 @@ func extractPeerMeta(ctx context.Context, meta *proto.PeerSystemMeta) nbpeer.Pee } networkAddresses = append(networkAddresses, nbpeer.NetworkAddress{ NetIP: netAddr, - Mac: addr.GetMac(), + Mac: cleanClientString(addr.GetMac()), }) } files := make([]nbpeer.File, 0, len(meta.GetFiles())) for _, file := range meta.GetFiles() { files = append(files, nbpeer.File{ - Path: file.GetPath(), + Path: cleanClientString(file.GetPath()), Exist: file.GetExist(), ProcessIsRunning: file.GetProcessIsRunning(), }) } return nbpeer.PeerSystemMeta{ - Hostname: meta.GetHostname(), - GoOS: meta.GetGoOS(), - Kernel: meta.GetKernel(), - Platform: meta.GetPlatform(), - OS: meta.GetOS(), + Hostname: cleanClientString(meta.GetHostname()), + GoOS: cleanClientString(meta.GetGoOS()), + Kernel: cleanClientString(meta.GetKernel()), + Platform: cleanClientString(meta.GetPlatform()), + OS: cleanClientString(meta.GetOS()), OSVersion: osVersion, - WtVersion: meta.GetNetbirdVersion(), - UIVersion: meta.GetUiVersion(), - KernelVersion: meta.GetKernelVersion(), + WtVersion: cleanClientString(meta.GetNetbirdVersion()), + UIVersion: cleanClientString(meta.GetUiVersion()), + KernelVersion: cleanClientString(meta.GetKernelVersion()), NetworkAddresses: networkAddresses, - SystemSerialNumber: meta.GetSysSerialNumber(), - SystemProductName: meta.GetSysProductName(), - SystemManufacturer: meta.GetSysManufacturer(), + SystemSerialNumber: cleanClientString(meta.GetSysSerialNumber()), + SystemProductName: cleanClientString(meta.GetSysProductName()), + SystemManufacturer: cleanClientString(meta.GetSysManufacturer()), Environment: nbpeer.Environment{ - Cloud: meta.GetEnvironment().GetCloud(), - Platform: meta.GetEnvironment().GetPlatform(), + Cloud: cleanClientString(meta.GetEnvironment().GetCloud()), + Platform: cleanClientString(meta.GetEnvironment().GetPlatform()), }, Flags: nbpeer.Flags{ RosenpassEnabled: meta.GetFlags().GetRosenpassEnabled(), @@ -618,6 +618,11 @@ func extractPeerMeta(ctx context.Context, meta *proto.PeerSystemMeta) nbpeer.Pee } } +// cleanClientString removes NUL bytes and invalid UTF-8 from a client-reported string, which Postgres rejects in text columns. +func cleanClientString(s string) string { + return strings.ToValidUTF8(strings.ReplaceAll(s, "\x00", ""), "") +} + func capabilitiesToInt32(caps []proto.PeerCapability) []int32 { result := make([]int32, len(caps)) for i, c := range caps { @@ -711,7 +716,7 @@ func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto peer, network, postureChecks, enableSSH, err := s.accountManager.LoginPeer(ctx, types.PeerLogin{ WireGuardPubKey: peerKey.String(), - SSHKey: string(sshKey), + SSHKey: cleanClientString(string(sshKey)), Meta: peerMeta, UserID: userID, SetupKey: loginReq.GetSetupKey(),