From cc88b91d75e8df16c1b2a10bb8632b535d4790ab Mon Sep 17 00:00:00 2001 From: Lucas TESSON Date: Mon, 9 Oct 2023 00:02:30 +0200 Subject: [PATCH] refactor: parsing simplification, slightly better performances --- 40/cvss40.go | 208 +++++++++++++++++++++++++++++--------------- README.md | 12 +-- benchmarks/go.mod | 2 +- differential/go.mod | 2 +- go.mod | 2 +- go.work | 2 +- go.work.sum | 7 ++ 7 files changed, 155 insertions(+), 80 deletions(-) diff --git a/40/cvss40.go b/40/cvss40.go index c7bd266..e598d52 100644 --- a/40/cvss40.go +++ b/40/cvss40.go @@ -1,15 +1,15 @@ package gocvss40 import ( + "log" "strings" - "sync" "unsafe" ) // This file is based on https://www.first.org/cvss/v4-0/cvss-v40-specification.pdf. const ( - header = "CVSS:4.0/" + header = "CVSS:4.0" ) var ( @@ -34,13 +34,6 @@ func ParseVector(vector string) (*CVSS40, error) { } vector = vector[len(header):] - // Split parts - partsPtr := splitPool.Get() - defer splitPool.Put(partsPtr) - pts := partsPtr.([]string) - ei := split(pts, vector) - pts = pts[:ei+1] - // Allocate CVSS v4.0 object cvss40 := &CVSS40{ u0: 0, @@ -54,38 +47,35 @@ func ParseVector(vector string) (*CVSS40, error) { u8: 0, // last 6 bits are not used } - slci := 0 - i := 0 - for _, pt := range pts { + cut := 0 + slci, orderi := 0, 0 + for i := 1; i <= len(vector); i++ { + if i != len(vector) && vector[i] != '/' { + continue + } + + // Remove leading / + pt := vector[cut:min(i, len(vector))] + cut = i + if !strings.HasPrefix(pt, "/") { + return nil, ErrInvalidMetricValue + } + pt = pt[1:] + // Cut on colon abv, v, _ := strings.Cut(pt, ":") - switch slci { - // Mandatory metrics - case 0: - if abv != order[slci][i] { + // Check (non)mandatory values + for { + if (slci == 0 && abv != order[0][orderi]) || slci == len(order) { return nil, ErrInvalidMetricOrder } - i++ - if i == len(order[slci]) { + out := abv == order[slci][orderi] + orderi++ + if orderi == len(order[slci]) { slci++ - i = 0 - } - // Non-mandatory metrics - default: - // Go to next element in slice, or next slice if fully consumed - for slci < len(order) && abv != order[slci][i] { - i++ - if i == len(order[slci]) { - slci++ - i = 0 - } + orderi = 0 } - if slci == len(order) { - return nil, ErrInvalidMetricOrder - } - i++ - if i == len(order[slci]) { - slci++ - i = 0 + if out { + break } } @@ -101,33 +91,6 @@ func ParseVector(vector string) (*CVSS40, error) { return cvss40, nil } -var splitPool = sync.Pool{ - New: func() any { - return make([]string, 32) - }, -} - -func split(dst []string, vector string) int { - start := 0 - curr := 0 - l := len(vector) - i := 0 - for ; i < l; i++ { - if vector[i] == '/' { - dst[curr] = vector[start:i] - - start = i + 1 - curr++ - - if curr == 31 { - break - } - } - } - dst[curr] = vector[start:] - return curr -} - // Vector returns the CVSS v4.0 vector string representation. func (cvss40 CVSS40) Vector() string { l := lenVec(&cvss40) @@ -135,7 +98,7 @@ func (cvss40 CVSS40) Vector() string { b = append(b, header...) // Base - mandatory(&b, "AV:", cvss40.get("AV")) + mandatory(&b, "/AV:", cvss40.get("AV")) mandatory(&b, "/AC:", cvss40.get("AC")) mandatory(&b, "/AT:", cvss40.get("AT")) mandatory(&b, "/PR:", cvss40.get("PR")) @@ -178,12 +141,12 @@ func (cvss40 CVSS40) Vector() string { } func lenVec(cvss40 *CVSS40) int { - // Header: constant, so fixed (9) + // Header: constant, so fixed (11) // Base: // - AV, AC, AT, PR, UI, VC, SC, VI, SI, VA, SA: 4 - // - separators: 10 - // Total: 11*4 + 10 = 54 - l := len(header) + 54 + // - separators: 11 + // Total: 11*4 + 11 = 55 + l := len(header) + 55 // Threat: // - E: 3 @@ -921,8 +884,113 @@ func (cvss40 CVSS40) get(abv string) string { // Score returns the CVSS v4.0's score. // Use Nomenclature for getting groups used by computation. func (cvss40 CVSS40) Score() float64 { - // TODO implement score computation when specification is fixed - return 0 + // Get metrics + // TODO fetch environmental instead when defined + // av := mod((cvss40.u0&0b11000000)>>6, (cvss40.u3&0b00001110)>>1) + // ac := mod((cvss40.u0&0b00100000)>>5, ((cvss40.u3&0b00000001)<<1)|((cvss40.u4&0b10000000)>>7)) + // at := mod((cvss40.u0&0b00010000)>>4, (cvss40.u4&0b01100000)>>5) + // pr := mod((cvss40.u0&0b00001100)>>2, (cvss40.u4&0b00011000)>>3) + // ui := mod(cvss40.u0&0b00000011, (cvss40.u4&0b00000110)>>1) + // vc := mod((cvss40.u1&0b11000000)>>6, ((cvss40.u4&0b00000001)<<1)|((cvss40.u5&0b10000000)>>7)) + // sc := mod((cvss40.u1&0b00110000)>>4, (cvss40.u5&0b00000110)>>1) + // vi := mod((cvss40.u1&0b00001100)>>2, (cvss40.u5&0b01100000)>>5) + // si := mod(cvss40.u1&0b00000011, ((cvss40.u5&0b00000001)<<2)|((cvss40.u6&0b11000000)>>6)) + // va := mod((cvss40.u2&0b11000000)>>6, (cvss40.u5&0b00011000)>>3) + // sa := mod((cvss40.u2&0b00110000)>>4, (cvss40.u6&0b00111000)>>3) + av := (cvss40.u0 & 0b11000000) >> 6 + ac := (cvss40.u0 & 0b00100000) >> 5 + at := (cvss40.u0 & 0b00010000) >> 4 + pr := (cvss40.u0 & 0b00001100) >> 2 + ui := cvss40.u0 & 0b00000011 + vc := (cvss40.u1 & 0b11000000) >> 6 + sc := (cvss40.u1 & 0b00110000) >> 4 + vi := (cvss40.u1 & 0b00001100) >> 2 + si := cvss40.u1 & 0b00000011 + va := (cvss40.u2 & 0b11000000) >> 6 + sa := (cvss40.u2 & 0b00110000) >> 4 + + // Compute EQs + // => EQ1 - Table 25 + eq1 := 0 + if av == av_n && pr == pr_n && ui == ui_n { + eq1 = 0 + } else if (av == av_n || pr == pr_n || ui == ui_n) && !(av == av_n && pr == pr_n && ui == ui_n) && !(av == av_p) { + eq1 = 1 + } else if av == av_p || !(av == av_n || pr == pr_n || ui == ui_n) { + eq1 = 2 + } else { + log.Fatalf("invalid CVSS configuration: AV:%s/PR:%s/UI:%s\n", cvss40.get("AV"), cvss40.get("PR"), cvss40.get("UI")) + } + // => EQ2 - Table 26 + eq2 := 0 + if ac == ac_l && at == at_n { + eq2 = 0 + } else if !(ac == ac_l && at == at_n) { + eq2 = 1 + } else { + log.Fatalf("invalid CVSS configuration: AC:%s/AT:%s\n", cvss40.get("AC"), cvss40.get("AT")) + } + // => EQ3 - Table 27 + eq3 := 0 + if vc == vscia_h && vi == vscia_h { + eq3 = 0 + } else if !(vc == vscia_h && vi == vscia_h) && (vc == vscia_h || vi == vscia_h || va == vscia_h) { + eq3 = 1 + } else if !(vc == vscia_h || vi == vscia_h || va == vscia_h) { + eq3 = 2 + } else { + log.Fatalf("invalid CVSS configuration: VC:%s/VI:%s/VA:%s\n", cvss40.get("VC"), cvss40.get("VI"), cvss40.get("VA")) + } + // => EQ4 - Table 28 + eq4 := 0 + if cvss40.get("MSI") == "S" || cvss40.get("MSA") == "S" { + eq4 = 0 + } else if !(cvss40.get("MSI") == "S" && cvss40.get("MSA") == "S") && (sc == vscia_h || si == vscia_h || sa == vscia_h) { + eq4 = 1 + } else if !(cvss40.get("MSI") == "S" && cvss40.get("MSA") == "S") && !(sc == vscia_h || si == vscia_h || sa == vscia_h) { + eq4 = 2 + } else { + log.Fatalf("invalid CVSS configuration: MSI:%s/MSA:%s/SC:%s/SI:%s/SA:%s\n", cvss40.get("MSI"), cvss40.get("MSA"), cvss40.get("SC"), cvss40.get("SI"), cvss40.get("SA")) + } + // => EQ5 - Table 29 + eq5 := 0 + if cvss40.get("E") == "A" { + eq5 = 0 + } else if cvss40.get("E") == "P" { + eq5 = 1 + } else if cvss40.get("E") == "U" { + eq5 = 2 + } else { + log.Fatalf("invalid CVSS configuration: E:%s\n", cvss40.get("E")) + } + // => EQ6 - Table 30 + eq6 := 0 + if av == av_n && pr == pr_n && ui == ui_n { + eq6 = 0 + } else if (cvss40.get("CR") == "H" && cvss40.get("VC") == "H") || (cvss40.get("IR") == "H" && vi == vscia_h) || (cvss40.get("AR") == "H" && va == vscia_h) { + eq6 = 1 + } else { + log.Fatalf("invalid CVSS configuration: AV:%s/PR:%s/UI:%s/CR:%s/VC:%s/IR:%s/VI:%s/AR:%s/VA:%s\n", cvss40.get("AV"), cvss40.get("PR"), cvss40.get("UI"), cvss40.get("CR"), cvss40.get("VC"), cvss40.get("IR"), cvss40.get("VI"), cvss40.get("AR"), cvss40.get("VA")) + } + // => EQ3+EQ6 - Table 31 + eq3eq6 := 0 + if vc == vscia_h && vi == vscia_h && (cvss40.get("CR") == "H" || cvss40.get("IR") == "H" || (cvss40.get("AR") == "H" && va == vscia_h)) { + eq3eq6 = 00 + } else if vc == vscia_h && vi == vscia_h && !(cvss40.get("CR") == "H" || cvss40.get("IR") == "H") && !(cvss40.get("AR") == "H" && va == vscia_h) { + eq3eq6 = 01 + } else if !(vc == vscia_h && vi == vscia_h) && (vc == vscia_h || vi == vscia_h || va == vscia_h) && (cvss40.get("CR") == "H" && cvss40.get("VC") == "H") || (cvss40.get("IR") == "H" && vi == vscia_h) || (cvss40.get("AR") == "H" && va == vscia_h) { + eq3eq6 = 10 + } else if !(vc == vscia_h && vi == vscia_h) && (vc == vscia_h || vi == vscia_h || va == vscia_h) && !(cvss40.get("CR") == "H" && cvss40.get("VC") == "H") && !(cvss40.get("IR") == "H" && vi == vscia_h) && !(cvss40.get("AR") == "H" && va == vscia_h) { + eq3eq6 = 11 + } else if !(vc == vscia_h || vi == vscia_h || va == vscia_h) && (cvss40.get("CR") == "H" && cvss40.get("VC") == "H") || (cvss40.get("IR") == "H" && vi == vscia_h) || (cvss40.get("AR") == "H" && va == vscia_h) { + eq3eq6 = 20 + } else if !(vc == vscia_h || vi == vscia_h || va == vscia_h) && !(cvss40.get("CR") == "H" && cvss40.get("VC") == "H") && !(cvss40.get("IR") == "H" && vi == vscia_h) && !(cvss40.get("AR") == "H" && va == vscia_h) { + eq3eq6 = 21 + } else { + log.Fatalf("invalid CVSS configuration: CR:%s/VC:%s/IR:%s/VI:%s/AR:%s/VA:%s\n", cvss40.get("CR"), cvss40.get("VC"), cvss40.get("IR"), cvss40.get("VI"), cvss40.get("AR"), cvss40.get("VA")) + } + + return float64(eq1) + float64(eq2) + float64(eq3) + float64(eq4) + float64(eq5) + float64(eq6) + float64(eq3eq6) } // Nomenclature returns the CVSS v4.0 configuration used when scoring. diff --git a/README.md b/README.md index 7fed1fe..4a06cb0 100644 --- a/README.md +++ b/README.md @@ -205,11 +205,11 @@ goos: linux goarch: amd64 pkg: github.com/pandatix/go-cvss/40 cpu: 11th Gen Intel(R) Core(TM) i5-11400H @ 2.70GHz -BenchmarkParseVector_B-12 4665024 259.9 ns/op 16 B/op 1 allocs/op -BenchmarkParseVector_BTES-12 1000000 1006 ns/op 16 B/op 1 allocs/op -BenchmarkCVSS40Vector-12 3513789 331.1 ns/op 192 B/op 1 allocs/op -BenchmarkCVSS40Get-12 309996543 3.984 ns/op 0 B/op 0 allocs/op -BenchmarkCVSS40Set-12 133516072 8.982 ns/op 0 B/op 0 allocs/op +BenchmarkParseVector_B-12 4363825 276.7 ns/op 16 B/op 1 allocs/op +BenchmarkParseVector_BTES-12 1304852 896.8 ns/op 16 B/op 1 allocs/op +BenchmarkCVSS40Vector-12 3962140 320.7 ns/op 192 B/op 1 allocs/op +BenchmarkCVSS40Get-12 300182163 3.900 ns/op 0 B/op 0 allocs/op +BenchmarkCVSS40Set-12 112736655 10.69 ns/op 0 B/op 0 allocs/op ``` ### How it works @@ -309,6 +309,6 @@ Vulnerability trophy list: - There is a lack of examples, as it's achieved by the CVSS v2.0 specification. -### CVSS v4.0 +### CVSS v4.0 There are no feedbacks from the implementation, as this work was used during the creation of CVSS v4.0 thus the concerns were adressed before publication. diff --git a/benchmarks/go.mod b/benchmarks/go.mod index 9cf2543..7be4709 100644 --- a/benchmarks/go.mod +++ b/benchmarks/go.mod @@ -1,6 +1,6 @@ module github.com/pandatix/go-cvss/benchmarks -go 1.20 +go 1.21 replace github.com/pandatix/go-cvss => ../ diff --git a/differential/go.mod b/differential/go.mod index 98b30dc..5c8149e 100644 --- a/differential/go.mod +++ b/differential/go.mod @@ -1,6 +1,6 @@ module github.com/pandatix/go-cvss/differential -go 1.20 +go 1.21 replace github.com/pandatix/go-cvss => ../ diff --git a/go.mod b/go.mod index 759e28a..64647b0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/pandatix/go-cvss -go 1.20 +go 1.21 require github.com/stretchr/testify v1.8.4 diff --git a/go.work b/go.work index 1c0ee41..9e134e5 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.20 +go 1.21 use ( . diff --git a/go.work.sum b/go.work.sum index 14f2bb0..c4fcc67 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,8 +1,15 @@ +github.com/dvyukov/go-fuzz v0.0.0-20220726122315-1d375ef9f9f6/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=