From 4bf1736a83d8e73eb917fdc7302c04622e80c361 Mon Sep 17 00:00:00 2001 From: Vladimir Stoilov Date: Fri, 28 Jun 2024 13:20:18 +0300 Subject: [PATCH] [service] Add check for kext command size --- .../interception/windowskext2/handler.go | 9 + windows_kext/kextinterface/info.go | 206 +++++++++++++----- windows_kext/kextinterface/protocol_test.go | 9 + .../kextinterface/testdata/rust_info_test.bin | Bin 57546 -> 58213 bytes windows_kext/protocol/src/info.rs | 17 ++ .../protocol/testdata/go_command_test.bin | Bin 43436 -> 42792 bytes 6 files changed, 186 insertions(+), 55 deletions(-) diff --git a/service/firewall/interception/windowskext2/handler.go b/service/firewall/interception/windowskext2/handler.go index bb6348dd..166ebbc2 100644 --- a/service/firewall/interception/windowskext2/handler.go +++ b/service/firewall/interception/windowskext2/handler.go @@ -5,11 +5,13 @@ package windowskext import ( "context" + "errors" "fmt" "net" "time" "github.com/safing/portmaster/service/process" + "github.com/safing/portmaster/windows_kext/kextinterface" "github.com/tevino/abool" @@ -32,8 +34,15 @@ func (v *VersionInfo) String() string { func Handler(ctx context.Context, packets chan packet.Packet, bandwidthUpdate chan *packet.BandwidthUpdate) { for { packetInfo, err := RecvVerdictRequest() + + if errors.Is(err, kextinterface.ErrUnexpectedInfoSize) || errors.Is(err, kextinterface.ErrUnexpectedReadError) { + log.Criticalf("unexpected kext info data: %s", err) + continue // Depending on the info type this may not affect the functionality. Try to continue reading the next commands. + } + if err != nil { log.Warningf("failed to get packet from windows kext: %s", err) + // Probably IO error, nothing else we can do. return } diff --git a/windows_kext/kextinterface/info.go b/windows_kext/kextinterface/info.go index 763c3e8e..a2f5cd91 100644 --- a/windows_kext/kextinterface/info.go +++ b/windows_kext/kextinterface/info.go @@ -3,6 +3,7 @@ package kextinterface import ( "encoding/binary" "errors" + "fmt" "io" ) @@ -18,6 +19,7 @@ const ( var ( ErrUnknownInfoType = errors.New("unknown info type") + ErrUnexpectedInfoSize = errors.New("unexpected info size") ErrUnexpectedReadError = errors.New("unexpected read error") ) @@ -135,117 +137,215 @@ type Info struct { BandwidthStats *BandwidthStatsArray } -func RecvInfo(reader io.Reader) (*Info, error) { - var infoType byte - err := binary.Read(reader, binary.LittleEndian, &infoType) +type readHelper struct { + infoType byte + commandSize uint32 + + readSize int + + reader io.Reader +} + +func newReadHelper(reader io.Reader) (*readHelper, error) { + helper := &readHelper{reader: reader} + + err := binary.Read(reader, binary.LittleEndian, &helper.infoType) if err != nil { return nil, err } - // Read size of data - var size uint32 - err = binary.Read(reader, binary.LittleEndian, &size) + err = binary.Read(reader, binary.LittleEndian, &helper.commandSize) if err != nil { return nil, err } + return helper, nil +} + +func (r *readHelper) ReadData(data any) error { + err := binary.Read(r, binary.LittleEndian, data) + if err != nil { + return errors.Join(ErrUnexpectedReadError, err) + } + + if err := r.checkOverRead(); err != nil { + return err + } + + return nil +} + +// Passing size = 0 will read the rest of the command. +func (r *readHelper) ReadBytes(size uint32) ([]byte, error) { + if uint32(r.readSize) >= r.commandSize { + return nil, errors.Join(fmt.Errorf("cannot read more bytes than the command size: %d >= %d", r.readSize, r.commandSize), ErrUnexpectedReadError) + } + + if size == 0 { + size = r.commandSize - uint32(r.readSize) + } + + if r.commandSize < uint32(r.readSize)+size { + return nil, ErrUnexpectedInfoSize + } + + bytes := make([]byte, size) + err := binary.Read(r, binary.LittleEndian, bytes) + if err != nil { + return nil, errors.Join(ErrUnexpectedReadError, err) + } + + return bytes, nil +} + +func (r *readHelper) ReadUntilTheEnd() { + _, _ = r.ReadBytes(0) +} + +func (r *readHelper) checkOverRead() error { + if uint32(r.readSize) > r.commandSize { + return ErrUnexpectedInfoSize + } + + return nil +} + +func (r *readHelper) Read(p []byte) (n int, err error) { + n, err = r.reader.Read(p) + r.readSize += n + return +} + +func RecvInfo(reader io.Reader) (*Info, error) { + helper, err := newReadHelper(reader) + if err != nil { + return nil, err + } + + // Make sure the whole command is read before return. + defer helper.ReadUntilTheEnd() + // Read data - switch infoType { + switch helper.infoType { case InfoConnectionIpv4: { + parseError := fmt.Errorf("failed to parse InfoConnectionIpv4") + newInfo := ConnectionV4{} var fixedSizeValues connectionV4Internal - err = binary.Read(reader, binary.LittleEndian, &fixedSizeValues) + // Read fixed size values. + err = helper.ReadData(&fixedSizeValues) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err, fmt.Errorf("fixed")) } - // Read size of payload - var size uint32 - err = binary.Read(reader, binary.LittleEndian, &size) + newInfo.connectionV4Internal = fixedSizeValues + // Read size of payload. + var payloadSize uint32 + err = helper.ReadData(&payloadSize) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err, fmt.Errorf("payloadsize")) } - newInfo := ConnectionV4{connectionV4Internal: fixedSizeValues, Payload: make([]byte, size)} - err = binary.Read(reader, binary.LittleEndian, &newInfo.Payload) - if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + + // Check if there is payload. + if payloadSize > 0 { + // Read payload. + newInfo.Payload, err = helper.ReadBytes(payloadSize) + if err != nil { + return nil, errors.Join(parseError, err, fmt.Errorf("payload")) + } } return &Info{ConnectionV4: &newInfo}, nil } case InfoConnectionIpv6: { - var fixedSizeValues connectionV6Internal - err = binary.Read(reader, binary.LittleEndian, &fixedSizeValues) + parseError := fmt.Errorf("failed to parse InfoConnectionIpv6") + newInfo := ConnectionV6{} + + // Read fixed size values. + err = helper.ReadData(&newInfo.connectionV6Internal) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } - // Read size of payload - var size uint32 - err = binary.Read(reader, binary.LittleEndian, &size) + + // Read size of payload. + var payloadSize uint32 + err = helper.ReadData(&payloadSize) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } - newInfo := ConnectionV6{connectionV6Internal: fixedSizeValues, Payload: make([]byte, size)} - err = binary.Read(reader, binary.LittleEndian, &newInfo.Payload) - if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + + // Check if there is payload. + if payloadSize > 0 { + // Read payload. + newInfo.Payload, err = helper.ReadBytes(payloadSize) + if err != nil { + return nil, errors.Join(parseError, err) + } } + return &Info{ConnectionV6: &newInfo}, nil } case InfoConnectionEndEventV4: { + parseError := fmt.Errorf("failed to parse InfoConnectionEndEventV4") var connectionEnd ConnectionEndV4 - err = binary.Read(reader, binary.LittleEndian, &connectionEnd) + + // Read fixed size values. + err = helper.ReadData(&connectionEnd) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } return &Info{ConnectionEndV4: &connectionEnd}, nil } case InfoConnectionEndEventV6: { + parseError := fmt.Errorf("failed to parse InfoConnectionEndEventV6") var connectionEnd ConnectionEndV6 - err = binary.Read(reader, binary.LittleEndian, &connectionEnd) + + // Read fixed size values. + err = helper.ReadData(&connectionEnd) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } return &Info{ConnectionEndV6: &connectionEnd}, nil } case InfoLogLine: { + parseError := fmt.Errorf("failed to parse InfoLogLine") logLine := LogLine{} // Read severity - err = binary.Read(reader, binary.LittleEndian, &logLine.Severity) + err = helper.ReadData(&logLine.Severity) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } // Read string - line := make([]byte, size-1) // -1 for the severity enum. - err = binary.Read(reader, binary.LittleEndian, &line) + bytes, err := helper.ReadBytes(0) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } - logLine.Line = string(line) + logLine.Line = string(bytes) return &Info{LogLine: &logLine}, nil } case InfoBandwidthStatsV4: { + parseError := fmt.Errorf("failed to parse InfoBandwidthStatsV4") // Read Protocol var protocol uint8 - err = binary.Read(reader, binary.LittleEndian, &protocol) + err = helper.ReadData(&protocol) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } // Read size of array var size uint32 - err = binary.Read(reader, binary.LittleEndian, &size) + err = helper.ReadData(&size) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } // Read array statsArray := make([]BandwidthValueV4, size) for i := range int(size) { - err = binary.Read(reader, binary.LittleEndian, &statsArray[i]) + err = helper.ReadData(&statsArray[i]) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } } @@ -253,24 +353,25 @@ func RecvInfo(reader io.Reader) (*Info, error) { } case InfoBandwidthStatsV6: { + parseError := fmt.Errorf("failed to parse InfoBandwidthStatsV6") // Read Protocol var protocol uint8 - err = binary.Read(reader, binary.LittleEndian, &protocol) + err = helper.ReadData(&protocol) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } // Read size of array var size uint32 - err = binary.Read(reader, binary.LittleEndian, &size) + err = helper.ReadData(&size) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } // Read array statsArray := make([]BandwidthValueV6, size) for i := range int(size) { - err = binary.Read(reader, binary.LittleEndian, &statsArray[i]) + err = helper.ReadData(&statsArray[i]) if err != nil { - return nil, errors.Join(ErrUnexpectedReadError, err) + return nil, errors.Join(parseError, err) } } @@ -278,10 +379,5 @@ func RecvInfo(reader io.Reader) (*Info, error) { } } - // Command not recognized, read until the end of command and return. - // During normal operation this should not happen. - unknownData := make([]byte, size) - _, _ = reader.Read(unknownData) - return nil, ErrUnknownInfoType } diff --git a/windows_kext/kextinterface/protocol_test.go b/windows_kext/kextinterface/protocol_test.go index cf047442..c09cf286 100644 --- a/windows_kext/kextinterface/protocol_test.go +++ b/windows_kext/kextinterface/protocol_test.go @@ -18,8 +18,17 @@ func TestRustInfoFile(t *testing.T) { defer func() { _ = file.Close() }() + first := true for { info, err := RecvInfo(file) + // First info should be with invalid size. + if first { + if !errors.Is(err, ErrUnexpectedInfoSize) { + t.Errorf("unexpected error: %s\n", err) + } + first = false + continue + } if err != nil { if errors.Is(err, ErrUnexpectedReadError) { t.Errorf("unexpected error: %s\n", err) diff --git a/windows_kext/kextinterface/testdata/rust_info_test.bin b/windows_kext/kextinterface/testdata/rust_info_test.bin index 3f9049a9d776e2eedec281a37386a26f05a396e1..3b8588c7f89039b308ff0da53345ebd70d20134a 100644 GIT binary patch literal 58213 zcmeHJ+l~|k5bfES0mLgWKJx?m0VY2BDQHYI80EpwcgEdFfMX@+GN-#)h-9T_tLogU zdJaD=%W}B)$`TeDmtBH!pvC{mbFI>(k5M>z{Wuxe@`~kKbN?^S_GY70&fHPs@41 zBl=yE(T&numeTQ=Fg@w-E)uCJ8nE?#H6nuqXhEU?i4}a0%`SuMKopwf-?Yat5&Mgu3db`S_P!azxAB-h z{6Y?>`VwIscv5}j{fUEdt1gy*%W(?u(?JD3HhjgZN(;mLG0k0UKUjibLdg(GpF&Q~ zT&I4hd>sW}`RjmBmjCpT2!7K=;@#}4o?Fper@jjPi8;0SlnI9^2iL!=__ztAa3dce z?+9NZ(B+5p*8*G8$FZfn3s(CX#zy#k`4A4T=$uQ#5UfN$k{AaKoHw9Ru|}|jz2c?F zMO1)0go9@?G|Q13h6wz5l70^Hn)c5{f8yX>E+AX%G!P#?;=_rfl~ZH@>+c0mcYq_@ zf}z_^6dV}v;_?;4_*)0Ff6M&5V@-tM=F+)G8GR!J$+34nls#LS!@k8%!*Pq{XPDtf ztH0a)8zg7EuNRO0ruN~B(>GNJ`<7fCJlchpeUSZ$gGc;Q=E5f=zpouW6FCtEZv=gK zJNb$ncmJJ|!!r2L51rtAP6=T+A0oU~ejqB~b6WSKm9pwq1`d0;XW93R-QUU_dp^GX z6|3AJVTkWzU(4Yf2nq-#zG(PT_NDAk5=wmUhXX3$;dL73^UVar(fK^$ zRab8hd*mmaH-)dj-!=B2Z!Um+w!`k`Xklyj8xen@ge$wLZ1ln8(%8q&Cq2h@Sa7_S z_-S7>i~~O%NDdzfONhq{jXEU*`CP(so&39s{mCKBb>eTrU}k?idH8*dIkWs=@_Oaa z^RxL1ym-Qu^QF!S=VR%Iez+1bkA@k(WJB{*Ec>qXCk`^^79`gRXEWDdxI}!~R)y1*n#PH*gBfR%u4KeLg^Nr*EnoSXQBAOJ)o3`4#3Xh79t}e7(1laGty1 zuP+x5Gk3i{-R|YE-2b^d9`25(%YWz7a$a~-`cevM6-tKVlFOmLMfm9OHV$+J@%Z_| zkbd%V#tU?LO>=Ploc7J)^}@U4a~I6cS%*pG`U~XtkcB=*Y?L4`AN;M5&84!>Xn2A4 z!SE4?rLRwRP=5GmQ#g6QguBpZj6;Q=@)B%?508A6uN3~!+5z=dB~_-Q`}4X!t}JxhQ; zCD_PZ82gd9H6vbQz9O3ntO$OZojlx?iYS=lRx-J@!?G1!wt^$O3%Q#*Y!wbX^E3w& IPwkug4@6CXZ2$lO literal 57546 zcmeHN$!=X$5PY8E5QfAX*7*VW03?=tNl=@iCYW+kR5<<`(3|!}!mC z`LNo3i;K%ofqh!_IQZfE#qHJqp9r|let59?ZCTKSOfoMfAU@pOBL3^|H^2V=?@zDa zz4_p$N9);9shAq;d<{qYo6Ki~Q4-dyiv^gwjp$K|t#>_rbeUykzroFso+4@+A6 zj=|uCJPB8D@0DFrS;aYmHg%R@h<>d70L>-$d-i$4QS~Pw4#X|Vg-y}qLWk#CA}xgc zz9oIR{R)H-@~F=h_MAUIuA9q>kP3gOzC81H2qwOd9OJ;JIa}l^k(3JFo9pPib>5TA z?8E6>aMN)F6Z9DnKoZi{39s)~LPM5R(r6xF0h&)0wgs6h{-__Uxis?|f|!Ka6*v+m z7OvzzBlI~X0+UAqNu-*;G3HtDd*UrMMm=ZMGk=Q#UZV&wRAKF|CHNv3brIUGy` z0h#lZKQ0E3Pjk41mv>5++z^-~e7cv=`iAifnu~~Y*ymupD#jP~GO3Ww-Cn_* z3UF~D|8=nPS`cVV<=-dXuaJ{&kCplxcJ2+bVzoy(Ay%NzkHB0qyhnvif(!L&Cr0!6 z#Y5uL{^+^1_)Pf~`uxRQPdtddB6MDn;Q4y$n_!MHCaK_pSnn0>gopPh@hb>QlAV#; z>y;+`%x-0b{v<@8Cl01Re!nA8fBstP!=Gam_N2d@3Cn)*tya;dpf@ev_hIgRiPg;2 z&sCadkBB}mCIq?f3o@}Ee1QSs3N|DeY%V2kAz+=i&c(sk)2}d}Um#hyf(6cTPu$qg zyugoer9W3iu1XVPmr8<2|E-fUpU>Y)egz>PIU`|7@M*5=%K7eNCyDBV@%@g?C8R*O z;-jDMn7&4sVSL(0C&-4O3x!UhKZ&mo=Oi3pIp)bA%tVWkK95QPtczfgIs1(p{Z)kt z-PBH`c>EC_&yNJIoBQ}kh!D))x7F(Sb4t$F#u5VK0N~RnMFDYsKZgB|bFKRrw_5)~ zaD6C#gmFDj}Jewe|aD`B=J?by>oOER3ToXS*eg(lO z!WADU5yUU;%W)ko4_XFldH6%Kb_TgOufmv9AVq3=twi*rQx z$I4;HLiu6Db%`AFJ-IUND@@zxH@0Zz^yU(^Rd#X+`@)jk<3-_{lRJ>JuV(O}H{Q*ZhV6%hyt9TG{vlF1)_xIuLu$ zi}eG6X~3rf%Q^Kiq+IQ|xk~&?!tu zmJfO4i%W{nX%M)M_Maop<%|rnGnFbc3sw-W^j~4VmKFMam@86?-vBwnPq|-mQ>^HX zO}DGCcnF@HP>&~qTL&|%pdXN&2QYKQe)|XSBlIf}0{f}UxuuVN{z~-4>ra{8FG4lp zO3qazEWVh+WCYLLjzWKb=)Qw}%zg!;o1j9UL;Nnz1f%7GL74D0UuQpTO2ZSgRL-2f1BGn$)#`VgzjAPteSvUYe7XWpeT-yv^}+at z5E7ePIM{cDVSE9f=3o*)^JLF&_#*oia%9fue>%N*xfW(UAtdpyRk>b<7 z*krr(!3fU7Jnm{L3{jYMfvIA%=JH2a!)X1_Z=_{ERP(nKre$77uy{`PIow=tqJO1{ zK>VC8p-FCdY|(zW`W*LigB;PhN^{=+1DT4+>i_@% diff --git a/windows_kext/protocol/src/info.rs b/windows_kext/protocol/src/info.rs index b8eb0c79..fc50d589 100644 --- a/windows_kext/protocol/src/info.rs +++ b/windows_kext/protocol/src/info.rs @@ -441,6 +441,22 @@ fn generate_test_info_file() -> Result<(), std::io::Error> { for _ in 0..selected.capacity() { selected.push(enums.choose(&mut rng).unwrap().clone()); } + // Write wrong size data. + let mut info = connection_info_v6( + 1, + 2, + 3, + 4, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + 5, + 6, + 7, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ); + info.assert_size(); + info.0[0] = InfoType::ConnectionIpv4 as u8; + file.write_all(&info.0)?; for value in selected { file.write_all(&match value { @@ -548,5 +564,6 @@ fn generate_test_info_file() -> Result<(), std::io::Error> { } })?; } + return Ok(()); } diff --git a/windows_kext/protocol/testdata/go_command_test.bin b/windows_kext/protocol/testdata/go_command_test.bin index 586c70adf31fba822a9baddee108d6f8a36b6763..d518dbf12a3ac354dd6381a8f4a530e549449206 100644 GIT binary patch literal 42792 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 Tp79?V4C>}vZI{q>d*A;9LH~IY literal 43436 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 D1$2qa