Skip to content

Commit

Permalink
refactor: parsing simplification, slightly better performances
Browse files Browse the repository at this point in the history
  • Loading branch information
pandatix committed Oct 8, 2023
1 parent cf68f0a commit cc88b91
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 80 deletions.
208 changes: 138 additions & 70 deletions 40/cvss40.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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,
Expand All @@ -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
}
}

Expand All @@ -101,41 +91,14 @@ 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)
b := make([]byte, 0, l)
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"))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
2 changes: 1 addition & 1 deletion benchmarks/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/pandatix/go-cvss/benchmarks

go 1.20
go 1.21

replace github.com/pandatix/go-cvss => ../

Expand Down
2 changes: 1 addition & 1 deletion differential/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/pandatix/go-cvss/differential

go 1.20
go 1.21

replace github.com/pandatix/go-cvss => ../

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/pandatix/go-cvss

go 1.20
go 1.21

require github.com/stretchr/testify v1.8.4

Expand Down
2 changes: 1 addition & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
go 1.20
go 1.21

use (
.
Expand Down
7 changes: 7 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -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=

0 comments on commit cc88b91

Please sign in to comment.