2022-02-08 15:10:40 +02:00
package fortify
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/piper-validation/fortify-client-go/models"
2022-02-23 10:30:19 +02:00
"github.com/SAP/jenkins-library/pkg/format"
2022-03-14 12:26:05 +02:00
2022-02-08 15:10:40 +02:00
"github.com/SAP/jenkins-library/pkg/log"
FileUtils "github.com/SAP/jenkins-library/pkg/piperutils"
)
2022-02-23 10:30:19 +02:00
// FVDL This struct encapsulates everyting in the FVDL document
2022-02-08 15:10:40 +02:00
type FVDL struct {
XMLName xml . Name ` xml:"FVDL" `
Xmlns string ` xml:"xmlns,attr" `
XmlnsXsi string ` xml:"xsi,attr" `
Version string ` xml:"version,attr" `
XsiType string ` xml:"type,attr" `
Created CreatedTS
Uuid UUID
Build Build
Vulnerabilities Vulnerabilities ` xml:"Vulnerabilities" `
ContextPool ContextPool ` xml:"ContextPool" `
UnifiedNodePool UnifiedNodePool ` xml:"UnifiedNodePool" `
Description [ ] Description ` xml:"Description" `
Snippets [ ] Snippet ` xml:"Snippets>Snippet" `
ProgramData ProgramData ` xml:"ProgramData" `
EngineData EngineData ` xml:"EngineData" `
}
2022-02-23 10:30:19 +02:00
// CreatedTS
2022-02-08 15:10:40 +02:00
type CreatedTS struct {
XMLName xml . Name ` xml:"CreatedTS" `
Date string ` xml:"date,attr" `
Time string ` xml:"time,attr" `
}
2022-03-14 12:26:05 +02:00
// UUIF
2022-02-08 15:10:40 +02:00
type UUID struct {
XMLName xml . Name ` xml:"UUID" `
Uuid string ` xml:",innerxml" `
}
2022-03-14 12:26:05 +02:00
// LOC
2022-02-08 15:10:40 +02:00
type LOC struct {
XMLName xml . Name ` xml:"LOC" `
LocType string ` xml:"type,attr" `
LocValue string ` xml:",innerxml" `
}
2022-03-14 12:26:05 +02:00
// These structures are relevant to the Build object
// The Build object transports all build and scan related information
2022-02-08 15:10:40 +02:00
type Build struct {
XMLName xml . Name ` xml:"Build" `
Project string ` xml:"Project" `
2022-03-14 12:26:05 +02:00
Version string ` xml:"Version" `
2022-02-08 15:10:40 +02:00
Label string ` xml:"Label" `
BuildID string ` xml:"BuildID" `
NumberFiles int ` xml:"NumberFiles" `
2022-03-14 12:26:05 +02:00
Locs [ ] LOC ` xml:"LOC" `
2022-02-08 15:10:40 +02:00
JavaClassPath string ` xml:"JavaClasspath" `
SourceBasePath string ` xml:"SourceBasePath" `
SourceFiles [ ] File ` xml:"SourceFiles>File" `
Scantime ScanTime ` xml:"ScanTime" `
}
2022-02-23 10:30:19 +02:00
// File
2022-02-08 15:10:40 +02:00
type File struct {
XMLName xml . Name ` xml:"File" `
FileSize int ` xml:"size,attr" `
FileTimestamp string ` xml:"timestamp,attr" `
FileLoc int ` xml:"loc,attr,omitempty" `
FileType string ` xml:"type,attr" `
Encoding string ` xml:"encoding,attr" `
Name string ` xml:"Name" `
Locs [ ] LOC ` xml:",any,omitempty" `
}
2022-02-23 10:30:19 +02:00
// ScanTime
2022-02-08 15:10:40 +02:00
type ScanTime struct {
XMLName xml . Name ` xml:"ScanTime" `
Value int ` xml:"value,attr" `
}
2022-02-23 10:30:19 +02:00
// Vulnerabilities These structures are relevant to the Vulnerabilities object
2022-02-08 15:10:40 +02:00
type Vulnerabilities struct {
XMLName xml . Name ` xml:"Vulnerabilities" `
Vulnerability [ ] Vulnerability ` xml:"Vulnerability" `
}
type Vulnerability struct {
XMLName xml . Name ` xml:"Vulnerability" `
ClassInfo ClassInfo ` xml:"ClassInfo" `
InstanceInfo InstanceInfo ` xml:"InstanceInfo" `
AnalysisInfo AnalysisInfo ` xml:"AnalysisInfo>Unified" `
}
2022-02-23 10:30:19 +02:00
// ClassInfo
2022-02-08 15:10:40 +02:00
type ClassInfo struct {
XMLName xml . Name ` xml:"ClassInfo" `
ClassID string ` xml:"ClassID" `
Kingdom string ` xml:"Kingdom,omitempty" `
Type string ` xml:"Type" `
Subtype string ` xml:"Subtype,omitempty" `
AnalyzerName string ` xml:"AnalyzerName" `
DefaultSeverity string ` xml:"DefaultSeverity" `
}
2022-02-23 10:30:19 +02:00
// InstanceInfo
2022-02-08 15:10:40 +02:00
type InstanceInfo struct {
XMLName xml . Name ` xml:"InstanceInfo" `
InstanceID string ` xml:"InstanceID" `
InstanceSeverity string ` xml:"InstanceSeverity" `
Confidence string ` xml:"Confidence" `
}
2022-02-23 10:30:19 +02:00
// AnalysisInfo
2022-02-08 15:10:40 +02:00
type AnalysisInfo struct { //Note that this is directly the "Unified" object
Context Context
ReplacementDefinitions ReplacementDefinitions ` xml:"ReplacementDefinitions" `
Trace [ ] Trace ` xml:"Trace" `
}
2022-02-23 10:30:19 +02:00
// Context
2022-02-08 15:10:40 +02:00
type Context struct {
XMLName xml . Name ` xml:"Context" `
ContextId string ` xml:"id,attr,omitempty" `
Function Function
FDSL FunctionDeclarationSourceLocation
}
2022-02-23 10:30:19 +02:00
// Function
2022-02-08 15:10:40 +02:00
type Function struct {
XMLName xml . Name ` xml:"Function" `
FunctionName string ` xml:"name,attr" `
FunctionNamespace string ` xml:"namespace,attr" `
FunctionEnclosingClass string ` xml:"enclosingClass,attr" `
}
2022-02-23 10:30:19 +02:00
// FunctionDeclarationSourceLocation
2022-02-08 15:10:40 +02:00
type FunctionDeclarationSourceLocation struct {
XMLName xml . Name ` xml:"FunctionDeclarationSourceLocation" `
FDSLPath string ` xml:"path,attr" `
FDSLLine string ` xml:"line,attr" `
FDSLLineEnd string ` xml:"lineEnd,attr" `
FDSLColStart string ` xml:"colStart,attr" `
FDSLColEnd string ` xml:"colEnd,attr" `
}
2022-02-23 10:30:19 +02:00
// ReplacementDefinitions
2022-02-08 15:10:40 +02:00
type ReplacementDefinitions struct {
XMLName xml . Name ` xml:"ReplacementDefinitions" `
Def [ ] Def ` xml:"Def" `
LocationDef [ ] LocationDef ` xml:"LocationDef" `
}
2022-02-23 10:30:19 +02:00
// Def
2022-02-08 15:10:40 +02:00
type Def struct {
XMLName xml . Name ` xml:"Def" `
DefKey string ` xml:"key,attr" `
DefValue string ` xml:"value,attr" `
}
2022-02-23 10:30:19 +02:00
// LocationDef
2022-02-08 15:10:40 +02:00
type LocationDef struct {
XMLName xml . Name ` xml:"LocationDef" `
Path string ` xml:"path,attr" `
Line int ` xml:"line,attr" `
LineEnd int ` xml:"lineEnd,attr" `
ColStart int ` xml:"colStart,attr" `
ColEnd int ` xml:"colEnd,attr" `
Key string ` xml:"key,attr" `
}
2022-02-23 10:30:19 +02:00
// Trace
2022-02-08 15:10:40 +02:00
type Trace struct {
XMLName xml . Name ` xml:"Trace" `
Primary Primary ` xml:"Primary" `
}
2022-02-23 10:30:19 +02:00
// Primary
2022-02-08 15:10:40 +02:00
type Primary struct {
XMLName xml . Name ` xml:"Primary" `
Entry [ ] Entry ` xml:"Entry" `
}
2022-02-23 10:30:19 +02:00
// Entry
2022-02-08 15:10:40 +02:00
type Entry struct {
XMLName xml . Name ` xml:"Entry" `
NodeRef NodeRef ` xml:"NodeRef,omitempty" `
Node Node ` xml:"Node,omitempty" `
}
2022-02-23 10:30:19 +02:00
// NodeRef
2022-02-08 15:10:40 +02:00
type NodeRef struct {
XMLName xml . Name ` xml:"NodeRef" `
RefId int ` xml:"id,attr" `
}
2022-02-23 10:30:19 +02:00
// Node
2022-02-08 15:10:40 +02:00
type Node struct {
XMLName xml . Name ` xml:"Node" `
IsDefault string ` xml:"isDefault,attr,omitempty" `
NodeLabel string ` xml:"label,attr,omitempty" `
SourceLocation SourceLocation ` xml:"SourceLocation" `
Action Action ` xml:"Action,omitempty" `
Reason Reason ` xml:"Reason,omitempty" `
Knowledge Knowledge ` xml:"Knowledge,omitempty" `
}
2022-02-23 10:30:19 +02:00
// SourceLocation
2022-02-08 15:10:40 +02:00
type SourceLocation struct {
XMLName xml . Name ` xml:"SourceLocation" `
Path string ` xml:"path,attr" `
Line int ` xml:"line,attr" `
LineEnd int ` xml:"lineEnd,attr" `
ColStart int ` xml:"colStart,attr" `
ColEnd int ` xml:"colEnd,attr" `
ContextId string ` xml:"contextId,attr" `
Snippet string ` xml:"snippet,attr" `
}
2022-02-23 10:30:19 +02:00
// Action
2022-02-08 15:10:40 +02:00
type Action struct {
XMLName xml . Name ` xml:"Action" `
Type string ` xml:"type,attr" `
ActionData string ` xml:",innerxml" `
}
2022-02-23 10:30:19 +02:00
// Reason
2022-02-08 15:10:40 +02:00
type Reason struct {
XMLName xml . Name ` xml:"Reason" `
Rule Rule ` xml:"Rule,omitempty" `
Trace Trace ` xml:"Trace,omitempty" `
}
2022-02-23 10:30:19 +02:00
// Rule
2022-02-08 15:10:40 +02:00
type Rule struct {
XMLName xml . Name ` xml:"Rule" `
RuleID string ` xml:"ruleID,attr" `
}
2022-02-23 10:30:19 +02:00
// Group
2022-02-08 15:10:40 +02:00
type Group struct {
XMLName xml . Name ` xml:"Group" `
Name string ` xml:"name,attr" `
Data string ` xml:",innerxml" `
}
2022-02-23 10:30:19 +02:00
// Knowledge
2022-02-08 15:10:40 +02:00
type Knowledge struct {
XMLName xml . Name ` xml:"Knowledge" `
Facts [ ] Fact ` xml:"Fact" `
}
2022-02-23 10:30:19 +02:00
// Fact
2022-02-08 15:10:40 +02:00
type Fact struct {
XMLName xml . Name ` xml:"Fact" `
Primary string ` xml:"primary,attr" `
Type string ` xml:"type,attr,omitempty" `
FactData string ` xml:",innerxml" `
}
2022-02-23 10:30:19 +02:00
// ContextPool These structures are relevant to the ContextPool object
2022-02-08 15:10:40 +02:00
type ContextPool struct {
XMLName xml . Name ` xml:"ContextPool" `
Context [ ] Context ` xml:"Context" `
}
2022-02-23 10:30:19 +02:00
// UnifiedNodePool These structures are relevant to the UnifiedNodePool object
2022-02-08 15:10:40 +02:00
type UnifiedNodePool struct {
XMLName xml . Name ` xml:"UnifiedNodePool" `
Node [ ] Node ` xml:"Node" `
}
2022-02-23 10:30:19 +02:00
// Description These structures are relevant to the Description object
2022-02-08 15:10:40 +02:00
type Description struct {
XMLName xml . Name ` xml:"Description" `
ContentType string ` xml:"contentType,attr" `
ClassID string ` xml:"classID,attr" `
Abstract Abstract ` xml:"Abstract" `
Explanation Explanation ` xml:"Explanation" `
Recommendations Recommendations ` xml:"Recommendations" `
Tips [ ] Tip ` xml:"Tips>Tip,omitempty" `
References [ ] Reference ` xml:"References>Reference" `
CustomDescription CustomDescription ` xml:"CustomDescription,omitempty" `
}
2022-02-23 10:30:19 +02:00
// Abstract
2022-02-08 15:10:40 +02:00
type Abstract struct {
XMLName xml . Name ` xml:"Abstract" `
Text string ` xml:",innerxml" `
}
2022-02-23 10:30:19 +02:00
// Explanation
2022-02-08 15:10:40 +02:00
type Explanation struct {
XMLName xml . Name ` xml:"Explanation" `
Text string ` xml:",innerxml" `
}
2022-02-23 10:30:19 +02:00
// Recommendations
2022-02-08 15:10:40 +02:00
type Recommendations struct {
XMLName xml . Name ` xml:"Recommendations" `
Text string ` xml:",innerxml" `
}
2022-02-23 10:30:19 +02:00
// Reference
2022-02-08 15:10:40 +02:00
type Reference struct {
XMLName xml . Name ` xml:"Reference" `
Title string ` xml:"Title" `
Author string ` xml:"Author" `
}
2022-02-23 10:30:19 +02:00
// Tip
2022-02-08 15:10:40 +02:00
type Tip struct {
XMLName xml . Name ` xml:"Tip" `
Tip string ` xml:",innerxml" `
}
2022-02-23 10:30:19 +02:00
// CustomDescription
2022-02-08 15:10:40 +02:00
type CustomDescription struct {
XMLName xml . Name ` xml:"CustomDescription" `
ContentType string ` xml:"contentType,attr" `
RuleID string ` xml:"ruleID,attr" `
Explanation Explanation ` xml:"Explanation" `
Recommendations Recommendations ` xml:"Recommendations" `
References [ ] Reference ` xml:"References>Reference" `
}
2022-02-23 10:30:19 +02:00
// Snippet These structures are relevant to the Snippets object
2022-02-08 15:10:40 +02:00
type Snippet struct {
XMLName xml . Name ` xml:"Snippet" `
SnippetId string ` xml:"id,attr" `
File string ` xml:"File" `
StartLine int ` xml:"StartLine" `
EndLine int ` xml:"EndLine" `
Text string ` xml:"Text" `
}
2022-02-23 10:30:19 +02:00
// ProgramData These structures are relevant to the ProgramData object
2022-02-08 15:10:40 +02:00
type ProgramData struct {
XMLName xml . Name ` xml:"ProgramData" `
Sources [ ] SourceInstance ` xml:"Sources>SourceInstance" `
Sinks [ ] SinkInstance ` xml:"Sinks>SinkInstance" `
CalledWithNoDef [ ] Function ` xml:"CalledWithNoDef>Function" `
}
2022-02-23 10:30:19 +02:00
// SourceInstance
2022-02-08 15:10:40 +02:00
type SourceInstance struct {
XMLName xml . Name ` xml:"SourceInstance" `
RuleID string ` xml:"ruleID,attr" `
FunctionCall FunctionCall ` xml:"FunctionCall,omitempty" `
FunctionEntry FunctionEntry ` xml:"FunctionEntry,omitempty" `
SourceLocation SourceLocation ` xml:"SourceLocation,omitempty" `
TaintFlags TaintFlags ` xml:"TaintFlags" `
}
2022-02-23 10:30:19 +02:00
// FunctionCall
2022-02-08 15:10:40 +02:00
type FunctionCall struct {
XMLName xml . Name ` xml:"FunctionCall" `
SourceLocation SourceLocation ` xml:"SourceLocation" `
Function Function ` xml:"Function" `
}
2022-02-23 10:30:19 +02:00
// FunctionEntry
2022-02-08 15:10:40 +02:00
type FunctionEntry struct {
XMLName xml . Name ` xml:"FunctionEntry" `
SourceLocation SourceLocation ` xml:"SourceLocation" `
Function Function ` xml:"Function" `
}
2022-02-23 10:30:19 +02:00
// TaintFlags
2022-02-08 15:10:40 +02:00
type TaintFlags struct {
XMLName xml . Name ` xml:"TaintFlags" `
TaintFlag [ ] TaintFlag ` xml:"TaintFlag" `
}
2022-02-23 10:30:19 +02:00
// TaintFlag
2022-02-08 15:10:40 +02:00
type TaintFlag struct {
XMLName xml . Name ` xml:"TaintFlag" `
TaintFlagName string ` xml:"name,attr" `
}
2022-02-23 10:30:19 +02:00
// SinkInstance
2022-02-08 15:10:40 +02:00
type SinkInstance struct {
XMLName xml . Name ` xml:"SinkInstance" `
RuleID string ` xml:"ruleID,attr" `
FunctionCall FunctionCall ` xml:"FunctionCall,omitempty" `
SourceLocation SourceLocation ` xml:"SourceLocation,omitempty" `
}
2022-02-23 10:30:19 +02:00
// EngineData These structures are relevant to the EngineData object
2022-02-08 15:10:40 +02:00
type EngineData struct {
XMLName xml . Name ` xml:"EngineData" `
EngineVersion string ` xml:"EngineVersion" `
RulePacks [ ] RulePack ` xml:"RulePacks>RulePack" `
Properties [ ] Properties ` xml:"Properties" `
CLArguments [ ] string ` xml:"CommandLine>Argument" `
Errors [ ] Error ` xml:"Errors>Error" `
MachineInfo MachineInfo ` xml:"MachineInfo" `
FilterResult FilterResult ` xml:"FilterResult" `
RuleInfo [ ] RuleInfo ` xml:"RuleInfo>Rule" `
LicenseInfo LicenseInfo ` xml:"LicenseInfo" `
}
2022-02-23 10:30:19 +02:00
// RulePack
2022-02-08 15:10:40 +02:00
type RulePack struct {
XMLName xml . Name ` xml:"RulePack" `
RulePackID string ` xml:"RulePackID" `
SKU string ` xml:"SKU" `
Name string ` xml:"Name" `
Version string ` xml:"Version" `
MAC string ` xml:"MAC" `
}
2022-02-23 10:30:19 +02:00
// Properties
2022-02-08 15:10:40 +02:00
type Properties struct {
XMLName xml . Name ` xml:"Properties" `
PropertiesType string ` xml:"type,attr" `
Property [ ] Property ` xml:"Property" `
}
2022-02-23 10:30:19 +02:00
// Property
2022-02-08 15:10:40 +02:00
type Property struct {
XMLName xml . Name ` xml:"Property" `
Name string ` xml:"name" `
Value string ` xml:"value" `
}
2022-02-23 10:30:19 +02:00
// Error
2022-02-08 15:10:40 +02:00
type Error struct {
XMLName xml . Name ` xml:"Error" `
ErrorCode string ` xml:"code,attr" `
ErrorMessage string ` xml:",innerxml" `
}
2022-02-23 10:30:19 +02:00
// MachineInfo
2022-02-08 15:10:40 +02:00
type MachineInfo struct {
XMLName xml . Name ` xml:"MachineInfo" `
Hostname string ` xml:"Hostname" `
Username string ` xml:"Username" `
Platform string ` xml:"Platform" `
}
2022-02-23 10:30:19 +02:00
// FilterResult
2022-02-08 15:10:40 +02:00
type FilterResult struct {
XMLName xml . Name ` xml:"FilterResult" `
//Todo? No data in sample audit file
}
2022-02-23 10:30:19 +02:00
// RuleInfo
2022-02-08 15:10:40 +02:00
type RuleInfo struct {
XMLName xml . Name ` xml:"Rule" `
RuleID string ` xml:"id,attr" `
MetaInfoGroup [ ] Group ` xml:"MetaInfo>Group,omitempty" `
}
2022-02-23 10:30:19 +02:00
// LicenseInfo
2022-02-08 15:10:40 +02:00
type LicenseInfo struct {
XMLName xml . Name ` xml:"LicenseInfo" `
Metadata [ ] Metadata ` xml:"Metadata" `
Capability [ ] Capability ` xml:"Capability" `
}
2022-02-23 10:30:19 +02:00
// Metadata
2022-02-08 15:10:40 +02:00
type Metadata struct {
XMLName xml . Name ` xml:"Metadata" `
Name string ` xml:"name" `
Value string ` xml:"value" `
}
2022-02-23 10:30:19 +02:00
// Capability
2022-02-08 15:10:40 +02:00
type Capability struct {
XMLName xml . Name ` xml:"Capability" `
Name string ` xml:"Name" `
Expiration string ` xml:"Expiration" `
Attribute Attribute ` xml:"Attribute" `
}
2022-02-23 10:30:19 +02:00
// Attribute
2022-02-08 15:10:40 +02:00
type Attribute struct {
XMLName xml . Name ` xml:"Attribute" `
Name string ` xml:"name" `
Value string ` xml:"value" `
}
2022-03-14 12:26:05 +02:00
// Utils
func ( n Node ) isEmpty ( ) bool {
return n . IsDefault == ""
}
func ( a Action ) isEmpty ( ) bool {
return a . ActionData == ""
}
2022-02-23 10:30:19 +02:00
// ConvertFprToSarif converts the FPR file contents into SARIF format
2022-03-17 14:09:15 +02:00
func ConvertFprToSarif ( sys System , project * models . Project , projectVersion * models . ProjectVersion , resultFilePath string , filterSet * models . FilterSet ) ( format . SARIF , error ) {
2022-02-08 15:10:40 +02:00
log . Entry ( ) . Debug ( "Extracting FPR." )
2022-02-23 10:30:19 +02:00
var sarif format . SARIF
2022-02-08 15:10:40 +02:00
tmpFolder , err := ioutil . TempDir ( "." , "temp-" )
defer os . RemoveAll ( tmpFolder )
if err != nil {
log . Entry ( ) . WithError ( err ) . WithField ( "path" , tmpFolder ) . Debug ( "Creating temp directory failed" )
return sarif , err
}
_ , err = FileUtils . Unzip ( resultFilePath , tmpFolder )
if err != nil {
return sarif , err
}
data , err := ioutil . ReadFile ( filepath . Join ( tmpFolder , "audit.fvdl" ) )
if err != nil {
return sarif , err
}
2022-03-14 12:26:05 +02:00
if len ( data ) == 0 {
log . Entry ( ) . Error ( "Error reading audit file at " + filepath . Join ( tmpFolder , "audit.fvdl" ) + ". This might be that the file is missing, corrupted, or too large. Aborting procedure." )
err := errors . New ( "cannot read audit file" )
return sarif , err
}
2022-02-08 15:10:40 +02:00
2022-03-14 12:26:05 +02:00
log . Entry ( ) . Debug ( "Calling Parse." )
2022-03-17 14:09:15 +02:00
return Parse ( sys , project , projectVersion , data , filterSet )
2022-02-08 15:10:40 +02:00
}
2022-02-23 10:30:19 +02:00
// Parse parses the FPR file
2022-03-17 14:09:15 +02:00
func Parse ( sys System , project * models . Project , projectVersion * models . ProjectVersion , data [ ] byte , filterSet * models . FilterSet ) ( format . SARIF , error ) {
2022-02-08 15:10:40 +02:00
//To read XML data, Unmarshal or Decode can be used, here we use Decode to work on the stream
reader := bytes . NewReader ( data )
decoder := xml . NewDecoder ( reader )
var fvdl FVDL
2022-03-14 12:26:05 +02:00
err := decoder . Decode ( & fvdl )
if err != nil {
return format . SARIF { } , err
}
2022-02-08 15:10:40 +02:00
2022-04-07 13:11:52 +02:00
//Create an object containing all audit data
log . Entry ( ) . Debug ( "Querying Fortify SSC for batch audit data" )
oneRequestPerIssueMode := false
var auditData [ ] * models . ProjectVersionIssue
if sys != nil {
auditData , err = sys . GetAllIssueDetails ( projectVersion . ID )
if err != nil {
log . Entry ( ) . WithError ( err ) . Error ( "failed to get all audit data, defaulting to one-request-per-issue basis" )
oneRequestPerIssueMode = true
} else {
log . Entry ( ) . Debug ( "Request successful, data frame size: " , len ( auditData ) , " audits" )
}
} else {
log . Entry ( ) . Error ( "no system instance found, lookup impossible" )
oneRequestPerIssueMode = true
}
2022-02-08 15:10:40 +02:00
//Now, we handle the sarif
2022-02-23 10:30:19 +02:00
var sarif format . SARIF
2022-03-22 15:47:19 +02:00
sarif . Schema = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json"
2022-02-08 15:10:40 +02:00
sarif . Version = "2.1.0"
2022-02-23 10:30:19 +02:00
var fortifyRun format . Runs
2022-03-14 12:26:05 +02:00
fortifyRun . ColumnKind = "utf16CodeUnits"
cweIdsForTaxonomies := make ( map [ string ] string ) //Defining this here and filling it in the course of the program helps filling the Taxonomies object easily. Map because easy to check for keys
2022-02-08 15:10:40 +02:00
sarif . Runs = append ( sarif . Runs , fortifyRun )
// Handle results/vulnerabilities
for i := 0 ; i < len ( fvdl . Vulnerabilities . Vulnerability ) ; i ++ {
2022-02-23 10:30:19 +02:00
result := * new ( format . Results )
2022-02-08 15:10:40 +02:00
result . RuleID = fvdl . Vulnerabilities . Vulnerability [ i ] . ClassInfo . ClassID
result . Level = "none" //TODO
//get message
for j := 0 ; j < len ( fvdl . Description ) ; j ++ {
if fvdl . Description [ j ] . ClassID == result . RuleID {
result . RuleIndex = j //Seems very abstract
rawMessage := fvdl . Description [ j ] . Abstract . Text
// Replacement defintions in message
for l := 0 ; l < len ( fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . ReplacementDefinitions . Def ) ; l ++ {
rawMessage = strings . ReplaceAll ( rawMessage , "Replace key=\"" + fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . ReplacementDefinitions . Def [ l ] . DefKey + "\"" , fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . ReplacementDefinitions . Def [ l ] . DefValue )
}
2022-03-17 14:09:15 +02:00
msg := new ( format . Message )
msg . Text = rawMessage
result . Message = msg
2022-02-08 15:10:40 +02:00
break
}
}
2022-03-14 12:26:05 +02:00
// Handle all locations items
location := * new ( format . Location )
var startingColumn int
//get location
for k := 0 ; k < len ( fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace ) ; k ++ { // k iterates on traces
//In each trace/primary, there can be one or more entries
//Each trace represents a codeflow, each entry represents a location in threadflow
codeFlow := * new ( format . CodeFlow )
threadFlow := * new ( format . ThreadFlow )
//We now iterate on Entries in the trace/primary
for l := 0 ; l < len ( fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry ) ; l ++ { // l iterates on entries
threadFlowLocation := * new ( format . Locations ) //One is created regardless
//the default node dictates the interesting threadflow (location, and so on)
//this will populate both threadFlowLocation AND the parent location object (result.Locations[0])
if ! fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . isEmpty ( ) && fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . IsDefault == "true" {
//initalize threadFlowLocation.Location
threadFlowLocation . Location = new ( format . Location )
//get artifact location
for j := 0 ; j < len ( fvdl . Build . SourceFiles ) ; j ++ { // j iterates on source files
if fvdl . Build . SourceFiles [ j ] . Name == fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . SourceLocation . Path {
threadFlowLocation . Location . PhysicalLocation . ArtifactLocation . Index = j
break
}
}
//get region & context region
threadFlowLocation . Location . PhysicalLocation . Region . StartLine = fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . SourceLocation . Line
//Snippet is handled last
//threadFlowLocation.Location.PhysicalLocation.Region.Snippet.Text = "foobar"
targetSnippetId := fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . SourceLocation . Snippet
for j := 0 ; j < len ( fvdl . Snippets ) ; j ++ {
if fvdl . Snippets [ j ] . SnippetId == targetSnippetId {
threadFlowLocation . Location . PhysicalLocation . ContextRegion . StartLine = fvdl . Snippets [ j ] . StartLine
threadFlowLocation . Location . PhysicalLocation . ContextRegion . EndLine = fvdl . Snippets [ j ] . EndLine
2022-03-22 15:47:19 +02:00
snippetSarif := new ( format . SnippetSarif )
snippetSarif . Text = fvdl . Snippets [ j ] . Text
threadFlowLocation . Location . PhysicalLocation . ContextRegion . Snippet = snippetSarif
2022-03-14 12:26:05 +02:00
break
}
}
//parse SourceLocation object for the startColumn value, store it appropriately
startingColumn = fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . SourceLocation . ColStart
//check for existance of action object, and if yes, save message
if ! fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . isEmpty ( ) {
threadFlowLocation . Location . Message = new ( format . Message )
threadFlowLocation . Location . Message . Text = fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData
// Handle snippet
snippetTarget := ""
switch fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . Type {
case "Assign" :
snippetWords := strings . Split ( fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData , " " )
if snippetWords [ 0 ] == "Assignment" {
snippetTarget = snippetWords [ 2 ]
} else {
snippetTarget = fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData
}
case "InCall" :
snippetTarget = strings . Split ( fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData , "(" ) [ 0 ]
case "OutCall" :
snippetTarget = strings . Split ( fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData , "(" ) [ 0 ]
case "InOutCall" :
snippetTarget = strings . Split ( fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData , "(" ) [ 0 ]
case "Return" :
snippetTarget = fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData
case "Read" :
snippetWords := strings . Split ( fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData , " " )
if len ( snippetWords ) > 1 {
snippetTarget = " " + snippetWords [ 1 ]
} else {
snippetTarget = snippetWords [ 0 ]
}
default :
snippetTarget = fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . Node . Action . ActionData
}
2022-03-31 12:13:17 +02:00
if threadFlowLocation . Location . PhysicalLocation . ContextRegion . Snippet != nil {
physLocationSnippetLines := strings . Split ( threadFlowLocation . Location . PhysicalLocation . ContextRegion . Snippet . Text , "\n" )
snippetText := ""
for j := 0 ; j < len ( physLocationSnippetLines ) ; j ++ {
if strings . Contains ( physLocationSnippetLines [ j ] , snippetTarget ) {
snippetText = physLocationSnippetLines [ j ]
break
}
2022-03-14 12:26:05 +02:00
}
2022-03-31 12:13:17 +02:00
snippetSarif := new ( format . SnippetSarif )
if snippetText != "" {
snippetSarif . Text = snippetText
} else {
snippetSarif . Text = threadFlowLocation . Location . PhysicalLocation . ContextRegion . Snippet . Text
}
threadFlowLocation . Location . PhysicalLocation . Region . Snippet = snippetSarif
2022-03-14 12:26:05 +02:00
}
2022-03-31 12:13:17 +02:00
} else {
if threadFlowLocation . Location . PhysicalLocation . ContextRegion . Snippet != nil {
snippetSarif := new ( format . SnippetSarif )
2022-03-22 15:47:19 +02:00
snippetSarif . Text = threadFlowLocation . Location . PhysicalLocation . ContextRegion . Snippet . Text
2022-03-31 12:13:17 +02:00
threadFlowLocation . Location . PhysicalLocation . Region . Snippet = snippetSarif
2022-03-14 12:26:05 +02:00
}
}
location = * threadFlowLocation . Location
//set Kinds
threadFlowLocation . Kinds = append ( threadFlowLocation . Kinds , "unknown" ) //TODO
} else { //is not a main threadflow: just register NodeRef index in threadFlowLocation
threadFlowLocation . Index = fvdl . Vulnerabilities . Vulnerability [ i ] . AnalysisInfo . Trace [ k ] . Primary . Entry [ l ] . NodeRef . RefId
}
//add the threadflowlocation to the list of locations
threadFlow . Locations = append ( threadFlow . Locations , threadFlowLocation )
}
codeFlow . ThreadFlows = append ( codeFlow . ThreadFlows , threadFlow )
result . CodeFlows = append ( result . CodeFlows , codeFlow )
}
//For some reason, the principal object only has 1 location: here we keep the last one
//Void message
location . Message = nil
result . Locations = append ( result . Locations , location )
//handle relatedLocation
relatedLocation := * new ( format . RelatedLocation )
relatedLocation . ID = 1
relatedLocation . PhysicalLocation = * new ( format . RelatedPhysicalLocation )
relatedLocation . PhysicalLocation . ArtifactLocation = location . PhysicalLocation . ArtifactLocation
relatedLocation . PhysicalLocation . Region = * new ( format . RelatedRegion )
relatedLocation . PhysicalLocation . Region . StartLine = location . PhysicalLocation . Region . StartLine
relatedLocation . PhysicalLocation . Region . StartColumn = startingColumn
result . RelatedLocations = append ( result . RelatedLocations , relatedLocation )
2022-02-08 15:10:40 +02:00
//handle properties
2022-03-22 15:47:19 +02:00
prop := new ( format . SarifProperties )
2022-02-08 15:10:40 +02:00
prop . InstanceSeverity = fvdl . Vulnerabilities . Vulnerability [ i ] . InstanceInfo . InstanceSeverity
prop . Confidence = fvdl . Vulnerabilities . Vulnerability [ i ] . InstanceInfo . Confidence
prop . InstanceID = fvdl . Vulnerabilities . Vulnerability [ i ] . InstanceInfo . InstanceID
//Use a query to get the audit data
// B5C0FEFD-CCB2-4F21-A9D7-87AE600A5885 is "custom rules": handle differently?
if result . RuleID == "B5C0FEFD-CCB2-4F21-A9D7-87AE600A5885" {
// Custom Rules has no audit value: it's notificaiton in the FVDL only.
prop . Audited = true
prop . ToolAuditMessage = "Custom Rules: not a vuln"
prop . ToolState = "Not an Issue"
prop . ToolStateIndex = 1
} else if sys != nil {
2022-04-07 13:11:52 +02:00
if err := integrateAuditData ( prop , fvdl . Vulnerabilities . Vulnerability [ i ] . InstanceInfo . InstanceID , sys , project , projectVersion , auditData , filterSet , oneRequestPerIssueMode ) ; err != nil {
2022-02-08 15:10:40 +02:00
log . Entry ( ) . Debug ( err )
prop . Audited = false
prop . ToolState = "Unknown"
prop . ToolAuditMessage = "Error fetching audit state"
}
} else {
prop . Audited = false
prop . ToolState = "Unknown"
prop . ToolAuditMessage = "Cannot fetch audit state"
}
result . Properties = prop
sarif . Runs [ 0 ] . Results = append ( sarif . Runs [ 0 ] . Results , result )
}
//handle the tool object
2022-02-23 10:30:19 +02:00
tool := * new ( format . Tool )
tool . Driver = * new ( format . Driver )
2022-02-08 15:10:40 +02:00
tool . Driver . Name = "MicroFocus Fortify SCA"
tool . Driver . Version = fvdl . EngineData . EngineVersion
tool . Driver . InformationUri = "https://www.microfocus.com/documentation/fortify-static-code-analyzer-and-tools/2020/SCA_Guide_20.2.0.pdf"
//handles rules
for i := 0 ; i < len ( fvdl . EngineData . RuleInfo ) ; i ++ { //i iterates on rules
2022-02-23 10:30:19 +02:00
sarifRule := * new ( format . SarifRule )
sarifRule . ID = fvdl . EngineData . RuleInfo [ i ] . RuleID
sarifRule . GUID = fvdl . EngineData . RuleInfo [ i ] . RuleID
2022-02-08 15:10:40 +02:00
for j := 0 ; j < len ( fvdl . Vulnerabilities . Vulnerability ) ; j ++ { //j iterates on vulns to find the name
if fvdl . Vulnerabilities . Vulnerability [ j ] . ClassInfo . ClassID == fvdl . EngineData . RuleInfo [ i ] . RuleID {
var nameArray [ ] string
if fvdl . Vulnerabilities . Vulnerability [ j ] . ClassInfo . Kingdom != "" {
nameArray = append ( nameArray , fvdl . Vulnerabilities . Vulnerability [ j ] . ClassInfo . Kingdom )
}
if fvdl . Vulnerabilities . Vulnerability [ j ] . ClassInfo . Type != "" {
nameArray = append ( nameArray , fvdl . Vulnerabilities . Vulnerability [ j ] . ClassInfo . Type )
}
if fvdl . Vulnerabilities . Vulnerability [ j ] . ClassInfo . Subtype != "" {
nameArray = append ( nameArray , fvdl . Vulnerabilities . Vulnerability [ j ] . ClassInfo . Subtype )
}
sarifRule . Name = strings . Join ( nameArray , "/" )
2022-03-17 14:09:15 +02:00
defaultConfig := new ( format . DefaultConfiguration )
defaultConfig . Properties . DefaultSeverity = fvdl . Vulnerabilities . Vulnerability [ j ] . ClassInfo . DefaultSeverity
sarifRule . DefaultConfiguration = defaultConfig
2022-02-08 15:10:40 +02:00
break
}
}
//Descriptions
for j := 0 ; j < len ( fvdl . Description ) ; j ++ {
2022-02-23 10:30:19 +02:00
if fvdl . Description [ j ] . ClassID == sarifRule . ID {
2022-02-08 15:10:40 +02:00
rawAbstract := fvdl . Description [ j ] . Abstract . Text
rawExplanation := fvdl . Description [ j ] . Explanation . Text
// Replacement defintions in abstract/explanation
for k := 0 ; k < len ( fvdl . Vulnerabilities . Vulnerability ) ; k ++ { // Iterate on vulns to find the correct one (where ReplacementDefinitions are)
if fvdl . Vulnerabilities . Vulnerability [ k ] . ClassInfo . ClassID == fvdl . Description [ j ] . ClassID {
for l := 0 ; l < len ( fvdl . Vulnerabilities . Vulnerability [ k ] . AnalysisInfo . ReplacementDefinitions . Def ) ; l ++ {
rawAbstract = strings . ReplaceAll ( rawAbstract , "Replace key=\"" + fvdl . Vulnerabilities . Vulnerability [ k ] . AnalysisInfo . ReplacementDefinitions . Def [ l ] . DefKey + "\"" , fvdl . Vulnerabilities . Vulnerability [ k ] . AnalysisInfo . ReplacementDefinitions . Def [ l ] . DefValue )
rawExplanation = strings . ReplaceAll ( rawExplanation , "Replace key=\"" + fvdl . Vulnerabilities . Vulnerability [ k ] . AnalysisInfo . ReplacementDefinitions . Def [ l ] . DefKey + "\"" , fvdl . Vulnerabilities . Vulnerability [ k ] . AnalysisInfo . ReplacementDefinitions . Def [ l ] . DefValue )
}
// Replacement locationdef in explanation
for l := 0 ; l < len ( fvdl . Vulnerabilities . Vulnerability [ k ] . AnalysisInfo . ReplacementDefinitions . LocationDef ) ; l ++ {
rawExplanation = strings . ReplaceAll ( rawExplanation , fvdl . Vulnerabilities . Vulnerability [ k ] . AnalysisInfo . ReplacementDefinitions . LocationDef [ l ] . Key , fvdl . Vulnerabilities . Vulnerability [ k ] . AnalysisInfo . ReplacementDefinitions . LocationDef [ l ] . Path )
}
// If Description has a CustomDescription, add it for good measure
if fvdl . Description [ j ] . CustomDescription . RuleID != "" {
rawExplanation = rawExplanation + "\n;" + fvdl . Description [ j ] . CustomDescription . Explanation . Text
}
2022-03-17 14:09:15 +02:00
sd := new ( format . Message )
sd . Text = rawAbstract
sarifRule . ShortDescription = sd
fd := new ( format . Message )
fd . Text = rawExplanation
sarifRule . FullDescription = fd
2022-02-08 15:10:40 +02:00
break
}
}
break
}
}
// Avoid empty descriptions to respect standard
2022-03-17 14:09:15 +02:00
//if sarifRule.ShortDescription.Text == "" {
// sarifRule.ShortDescription.Text = "None."
//}
//if sarifRule.FullDescription.Text == "" { // OR USE OMITEMPTY
// sarifRule.FullDescription.Text = "None."
//}
2022-02-08 15:10:40 +02:00
//properties
2022-03-14 12:26:05 +02:00
//Prepare a CWE id object as an in-case
cweIds := [ ] string { }
2022-02-08 15:10:40 +02:00
//scan for the properties we want:
var propArray [ ] [ ] string
for j := 0 ; j < len ( fvdl . EngineData . RuleInfo [ i ] . MetaInfoGroup ) ; j ++ {
if ( fvdl . EngineData . RuleInfo [ i ] . MetaInfoGroup [ j ] . Name == "Accuracy" ) || ( fvdl . EngineData . RuleInfo [ i ] . MetaInfoGroup [ j ] . Name == "Impact" ) || ( fvdl . EngineData . RuleInfo [ i ] . MetaInfoGroup [ j ] . Name == "Probability" ) {
propArray = append ( propArray , [ ] string { fvdl . EngineData . RuleInfo [ i ] . MetaInfoGroup [ j ] . Name , fvdl . EngineData . RuleInfo [ i ] . MetaInfoGroup [ j ] . Data } )
2022-03-14 12:26:05 +02:00
} else if fvdl . EngineData . RuleInfo [ i ] . MetaInfoGroup [ j ] . Name == "altcategoryCWE" {
//Get all CWE IDs. First, split on ", "
rawCweIds := strings . Split ( fvdl . EngineData . RuleInfo [ i ] . MetaInfoGroup [ j ] . Data , ", " )
//If not "None", split each string on " " and add its 2nd index
if rawCweIds [ 0 ] != "None" {
for k := 0 ; k < len ( rawCweIds ) ; k ++ {
cweId := strings . Split ( rawCweIds [ k ] , " " ) [ 2 ]
//Fill the cweIdsForTaxonomies map if not already in
if _ , isIn := cweIdsForTaxonomies [ cweId ] ; ! isIn {
cweIdsForTaxonomies [ cweId ] = cweId
}
cweIds = append ( cweIds , cweId )
}
} else {
cweIds = append ( cweIds , rawCweIds [ 0 ] )
}
2022-02-08 15:10:40 +02:00
}
}
2022-02-23 10:30:19 +02:00
var ruleProp * format . SarifRuleProperties
2022-02-08 15:10:40 +02:00
if len ( propArray ) != 0 {
2022-02-23 10:30:19 +02:00
ruleProp = new ( format . SarifRuleProperties )
2022-02-08 15:10:40 +02:00
for j := 0 ; j < len ( propArray ) ; j ++ {
if propArray [ j ] [ 0 ] == "Accuracy" {
ruleProp . Accuracy = propArray [ j ] [ 1 ]
} else if propArray [ j ] [ 0 ] == "Impact" {
ruleProp . Impact = propArray [ j ] [ 1 ]
} else if propArray [ j ] [ 0 ] == "Probability" {
ruleProp . Probability = propArray [ j ] [ 1 ]
}
}
}
sarifRule . Properties = ruleProp
2022-03-14 12:26:05 +02:00
//relationships: will most likely require some expansion
//One relationship per CWE id
for j := 0 ; j < len ( cweIds ) ; j ++ {
rls := * new ( format . Relationships )
rls . Target . Id = cweIds [ j ]
rls . Target . ToolComponent . Name = "CWE"
rls . Target . ToolComponent . Guid = "25F72D7E-8A92-459D-AD67-64853F788765"
rls . Kinds = append ( rls . Kinds , "relevant" )
sarifRule . Relationships = append ( sarifRule . Relationships , rls )
}
2022-02-08 15:10:40 +02:00
//Finalize: append the rule
tool . Driver . Rules = append ( tool . Driver . Rules , sarifRule )
}
2022-03-14 12:26:05 +02:00
//supportedTaxonomies
sTax := * new ( format . SupportedTaxonomies ) //This object seems fixed, but it will have to be checked
sTax . Name = "CWE"
sTax . Index = 0
sTax . Guid = "25F72D7E-8A92-459D-AD67-64853F788765"
tool . Driver . SupportedTaxonomies = append ( tool . Driver . SupportedTaxonomies , sTax )
2022-02-08 15:10:40 +02:00
//Finalize: tool
sarif . Runs [ 0 ] . Tool = tool
2022-03-14 12:26:05 +02:00
//handle invocations object
invocation := * new ( format . Invocations )
for i := 0 ; i < len ( fvdl . EngineData . Properties ) ; i ++ { //i selects the properties type
if fvdl . EngineData . Properties [ i ] . PropertiesType == "Fortify" { // This is the correct type, now iterate on props
for j := 0 ; j < len ( fvdl . EngineData . Properties [ i ] . Property ) ; j ++ {
if fvdl . EngineData . Properties [ i ] . Property [ j ] . Name == "com.fortify.SCAExecutablePath" {
splitPath := strings . Split ( fvdl . EngineData . Properties [ i ] . Property [ j ] . Value , "/" )
invocation . CommandLine = splitPath [ len ( splitPath ) - 1 ]
break
}
}
break
}
}
invocation . CommandLine = strings . Join ( append ( [ ] string { invocation . CommandLine } , fvdl . EngineData . CLArguments ... ) , " " )
invocation . StartTimeUtc = strings . Join ( [ ] string { fvdl . Created . Date , fvdl . Created . Time } , "T" ) + ".000Z"
for i := 0 ; i < len ( fvdl . EngineData . Errors ) ; i ++ {
ten := * new ( format . ToolExecutionNotifications )
ten . Message . Text = fvdl . EngineData . Errors [ i ] . ErrorMessage
ten . Descriptor . Id = fvdl . EngineData . Errors [ i ] . ErrorCode
invocation . ToolExecutionNotifications = append ( invocation . ToolExecutionNotifications , ten )
}
invocation . ExecutionSuccessful = true //fvdl doesn't seem to plan for this setting
invocation . Machine = fvdl . EngineData . MachineInfo . Hostname
invocation . Account = fvdl . EngineData . MachineInfo . Username
invocation . Properties . Platform = fvdl . EngineData . MachineInfo . Platform
sarif . Runs [ 0 ] . Invocations = append ( sarif . Runs [ 0 ] . Invocations , invocation )
//handle originalUriBaseIds
2022-03-17 14:09:15 +02:00
oubi := new ( format . OriginalUriBaseIds )
oubi . SrcRoot . Uri = "file:///" + fvdl . Build . SourceBasePath + "/"
sarif . Runs [ 0 ] . OriginalUriBaseIds = oubi
2022-03-14 12:26:05 +02:00
//handle artifacts
for i := 0 ; i < len ( fvdl . Build . SourceFiles ) ; i ++ { //i iterates on source files
artifact := * new ( format . Artifact )
artifact . Location . Uri = fvdl . Build . SourceFiles [ i ] . Name
artifact . Location . UriBaseId = "%SRCROOT%"
artifact . Length = fvdl . Build . SourceFiles [ i ] . FileSize
switch fvdl . Build . SourceFiles [ i ] . FileType {
case "java" :
artifact . MimeType = "text/x-java-source"
case "xml" :
artifact . MimeType = "text/xml"
default :
artifact . MimeType = "text"
}
artifact . Encoding = fvdl . Build . SourceFiles [ i ] . Encoding
sarif . Runs [ 0 ] . Artifacts = append ( sarif . Runs [ 0 ] . Artifacts , artifact )
}
//handle automationDetails
sarif . Runs [ 0 ] . AutomationDetails . Id = fvdl . Build . BuildID
//handle threadFlowLocations
threadFlowLocationsObject := [ ] format . Locations { }
//prepare a check object
for i := 0 ; i < len ( fvdl . UnifiedNodePool . Node ) ; i ++ {
unique := true
//Uniqueness Check
for check := 0 ; check < i ; check ++ {
if fvdl . UnifiedNodePool . Node [ i ] . SourceLocation . Snippet == fvdl . UnifiedNodePool . Node [ check ] . SourceLocation . Snippet &&
fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData == fvdl . UnifiedNodePool . Node [ check ] . Action . ActionData {
unique = false
}
}
if ! unique {
continue
}
locations := * new ( format . Locations )
loc := new ( format . Location )
//get artifact location
for j := 0 ; j < len ( fvdl . Build . SourceFiles ) ; j ++ { // j iterates on source files
if fvdl . Build . SourceFiles [ j ] . Name == fvdl . UnifiedNodePool . Node [ i ] . SourceLocation . Path {
loc . PhysicalLocation . ArtifactLocation . Index = j
break
}
}
//get region & context region
loc . PhysicalLocation . Region . StartLine = fvdl . UnifiedNodePool . Node [ i ] . SourceLocation . Line
//loc.PhysicalLocation.Region.Snippet.Text = "foobar" //TODO
targetSnippetId := fvdl . UnifiedNodePool . Node [ i ] . SourceLocation . Snippet
for j := 0 ; j < len ( fvdl . Snippets ) ; j ++ {
if fvdl . Snippets [ j ] . SnippetId == targetSnippetId {
loc . PhysicalLocation . ContextRegion . StartLine = fvdl . Snippets [ j ] . StartLine
loc . PhysicalLocation . ContextRegion . EndLine = fvdl . Snippets [ j ] . EndLine
2022-03-22 15:47:19 +02:00
snippetSarif := new ( format . SnippetSarif )
snippetSarif . Text = fvdl . Snippets [ j ] . Text
loc . PhysicalLocation . ContextRegion . Snippet = snippetSarif
2022-03-14 12:26:05 +02:00
break
}
}
loc . Message = new ( format . Message )
loc . Message . Text = fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData
// Handle snippet
snippetTarget := ""
switch fvdl . UnifiedNodePool . Node [ i ] . Action . Type {
case "Assign" :
snippetWords := strings . Split ( fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData , " " )
if snippetWords [ 0 ] == "Assignment" {
snippetTarget = snippetWords [ 2 ]
} else {
snippetTarget = fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData
}
case "InCall" :
snippetTarget = strings . Split ( fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData , "(" ) [ 0 ]
case "OutCall" :
snippetTarget = strings . Split ( fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData , "(" ) [ 0 ]
case "InOutCall" :
snippetTarget = strings . Split ( fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData , "(" ) [ 0 ]
case "Return" :
snippetTarget = fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData
case "Read" :
snippetWords := strings . Split ( fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData , " " )
if len ( snippetWords ) > 1 {
snippetTarget = " " + snippetWords [ 1 ]
} else {
snippetTarget = snippetWords [ 0 ]
}
default :
snippetTarget = fvdl . UnifiedNodePool . Node [ i ] . Action . ActionData
}
2022-03-22 15:47:19 +02:00
if loc . PhysicalLocation . ContextRegion . Snippet != nil {
physLocationSnippetLines := strings . Split ( loc . PhysicalLocation . ContextRegion . Snippet . Text , "\n" )
snippetText := ""
for j := 0 ; j < len ( physLocationSnippetLines ) ; j ++ {
if strings . Contains ( physLocationSnippetLines [ j ] , snippetTarget ) {
snippetText = physLocationSnippetLines [ j ]
break
}
2022-03-14 12:26:05 +02:00
}
2022-03-22 15:47:19 +02:00
snippetSarif := new ( format . SnippetSarif )
if snippetText != "" {
snippetSarif . Text = snippetText
} else {
snippetSarif . Text = loc . PhysicalLocation . ContextRegion . Snippet . Text
}
loc . PhysicalLocation . Region . Snippet = snippetSarif
2022-03-14 12:26:05 +02:00
}
locations . Location = loc
locations . Kinds = append ( locations . Kinds , "unknown" )
threadFlowLocationsObject = append ( threadFlowLocationsObject , locations )
}
sarif . Runs [ 0 ] . ThreadFlowLocations = threadFlowLocationsObject
//handle taxonomies
//Only one exists apparently: CWE. It is fixed
taxonomy := * new ( format . Taxonomies )
2022-04-04 16:12:35 +02:00
taxonomy . GUID = "25F72D7E-8A92-459D-AD67-64853F788765"
2022-03-14 12:26:05 +02:00
taxonomy . Name = "CWE"
taxonomy . Organization = "MITRE"
taxonomy . ShortDescription . Text = "The MITRE Common Weakness Enumeration"
2022-03-17 14:09:15 +02:00
for key := range cweIdsForTaxonomies {
2022-03-14 12:26:05 +02:00
taxa := * new ( format . Taxa )
taxa . Id = key
taxonomy . Taxa = append ( taxonomy . Taxa , taxa )
}
sarif . Runs [ 0 ] . Taxonomies = append ( sarif . Runs [ 0 ] . Taxonomies , taxonomy )
2022-02-08 15:10:40 +02:00
return sarif , nil
}
2022-04-07 13:11:52 +02:00
func integrateAuditData ( ruleProp * format . SarifProperties , issueInstanceID string , sys System , project * models . Project , projectVersion * models . ProjectVersion , auditData [ ] * models . ProjectVersionIssue , filterSet * models . FilterSet , oneRequestPerIssue bool ) error {
2022-03-14 12:26:05 +02:00
if sys == nil {
err := errors . New ( "no system instance, lookup impossible for " + issueInstanceID )
return err
}
if project == nil || projectVersion == nil {
err := errors . New ( "project or projectVersion is undefined: lookup aborted for " + issueInstanceID )
return err
}
2022-04-07 13:11:52 +02:00
var data [ ] * models . ProjectVersionIssue
var err error
if oneRequestPerIssue {
log . Entry ( ) . Debug ( "operating in one-request-per-issue mode: looking up audit state of " + issueInstanceID )
data , err = sys . GetIssueDetails ( projectVersion . ID , issueInstanceID )
if err != nil {
return err
}
} else {
for i := 0 ; i < len ( auditData ) ; i ++ {
if issueInstanceID == * auditData [ i ] . IssueInstanceID {
data = append ( data , auditData [ i ] )
break
}
}
2022-02-08 15:10:40 +02:00
}
if len ( data ) != 1 { //issueInstanceID is supposedly unique so len(data) = 1
2022-04-07 13:11:52 +02:00
//log.Entry().Error("not exactly 1 issue found, found " + fmt.Sprint(len(data)))
2022-02-08 15:10:40 +02:00
return errors . New ( "not exactly 1 issue found, found " + fmt . Sprint ( len ( data ) ) )
}
2022-02-23 10:30:19 +02:00
ruleProp . Audited = data [ 0 ] . Audited
ruleProp . ToolSeverity = * data [ 0 ] . Friority
switch ruleProp . ToolSeverity {
2022-02-08 15:10:40 +02:00
case "Critical" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolSeverityIndex = 5
2022-02-08 15:10:40 +02:00
case "Urgent" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolSeverityIndex = 4
2022-02-08 15:10:40 +02:00
case "High" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolSeverityIndex = 3
2022-02-08 15:10:40 +02:00
case "Medium" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolSeverityIndex = 2
2022-02-08 15:10:40 +02:00
case "Low" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolSeverityIndex = 1
2022-02-08 15:10:40 +02:00
}
2022-02-23 10:30:19 +02:00
if ruleProp . Audited {
ruleProp . ToolState = * data [ 0 ] . PrimaryTag
switch ruleProp . ToolState { //This is as easy as it can get, seeing that the index is not in the response.
2022-02-08 15:10:40 +02:00
case "Exploitable" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolStateIndex = 5
2022-02-08 15:10:40 +02:00
case "Suspicious" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolStateIndex = 4
2022-02-08 15:10:40 +02:00
case "Bad Practice" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolStateIndex = 3
2022-02-08 15:10:40 +02:00
case "Reliability Issue" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolStateIndex = 2
2022-02-08 15:10:40 +02:00
case "Not an Issue" :
2022-02-23 10:30:19 +02:00
ruleProp . ToolStateIndex = 1
2022-02-08 15:10:40 +02:00
}
} else {
2022-02-23 10:30:19 +02:00
ruleProp . ToolState = "Unreviewed"
2022-02-08 15:10:40 +02:00
}
if * data [ 0 ] . HasComments { //fetch latest message if comments exist
//Fetch the ID
parentID := data [ 0 ] . ID
commentData , err := sys . GetIssueComments ( parentID )
if err != nil {
return err
}
2022-02-23 10:30:19 +02:00
ruleProp . ToolAuditMessage = * commentData [ 0 ] . Comment
2022-02-08 15:10:40 +02:00
}
2022-03-17 14:09:15 +02:00
if filterSet != nil {
for i := 0 ; i < len ( filterSet . Folders ) ; i ++ {
if filterSet . Folders [ i ] . GUID == * data [ 0 ] . FolderGUID {
ruleProp . FortifyCategory = filterSet . Folders [ i ] . Name
break
}
}
} else {
err := errors . New ( "no filter set defined, category will be missing from " + issueInstanceID )
return err
}
2022-02-08 15:10:40 +02:00
return nil
}