fix: incorrect conversion between integer types (#2605)

* chore: match strconv.ParseInt to file mode type

if a string is parsed into an int using strconv.Atoi,
and subsequently that int is converted into another integer type of a smaller size,
the result can produce unexpected values.
---------
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
Christopher Angelo Phillips 2024-02-07 15:41:00 -05:00 committed by GitHub
parent da31eed637
commit bd0cb916df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 63 additions and 4 deletions

View File

@ -2,6 +2,8 @@ package syftjson
import ( import (
"fmt" "fmt"
"io/fs"
"math"
"os" "os"
"path" "path"
"strconv" "strconv"
@ -76,14 +78,12 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
for _, f := range files { for _, f := range files {
coord := f.Location coord := f.Location
if f.Metadata != nil { if f.Metadata != nil {
mode, err := strconv.ParseInt(strconv.Itoa(f.Metadata.Mode), 8, 64) fm, err := safeFileModeConvert(f.Metadata.Mode)
if err != nil { if err != nil {
log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coord, f.Metadata.Mode, err) log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coord, f.Metadata.Mode, err)
mode = 0 fm = 0
} }
fm := os.FileMode(mode)
ret.FileMetadata[coord] = file.Metadata{ ret.FileMetadata[coord] = file.Metadata{
FileInfo: stereoscopeFile.ManualInfo{ FileInfo: stereoscopeFile.ManualInfo{
NameValue: path.Base(coord.RealPath), NameValue: path.Base(coord.RealPath),
@ -135,6 +135,20 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
return ret return ret
} }
func safeFileModeConvert(val int) (fs.FileMode, error) {
if val < math.MinInt32 || val > math.MaxInt32 {
// Value is out of the range that int32 can represent
return 0, fmt.Errorf("value %d is out of the range that int32 can represent", val)
}
// Safe to convert to os.FileMode
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
if err != nil {
return 0, err
}
return os.FileMode(mode), nil
}
func toSyftLicenses(m []model.License) (p []pkg.License) { func toSyftLicenses(m []model.License) (p []pkg.License) {
for _, l := range m { for _, l := range m {
p = append(p, pkg.License{ p = append(p, pkg.License{

View File

@ -2,6 +2,9 @@ package syftjson
import ( import (
"errors" "errors"
"io/fs"
"math"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -471,3 +474,45 @@ func Test_deduplicateErrors(t *testing.T) {
}) })
} }
} }
func Test_safeFileModeConvert(t *testing.T) {
tests := []struct {
name string
val int
want fs.FileMode
wantErr bool
}{
{
// fs.go ModePerm 511 = FileMode = 0777 // Unix permission bits :192
name: "valid perm",
val: 777,
want: os.FileMode(511), // 777 in octal equals 511 in decimal
wantErr: false,
},
{
name: "outside int32 high",
val: int(math.MaxInt32) + 1,
want: 0,
wantErr: true,
},
{
name: "outside int32 low",
val: int(math.MinInt32) - 1,
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safeFileModeConvert(tt.val)
if tt.wantErr {
assert.Error(t, err)
assert.Equal(t, tt.want, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}