From bd0cb916df4376a06e56ef7f3cc0da6659d7c2c6 Mon Sep 17 00:00:00 2001 From: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Date: Wed, 7 Feb 2024 15:41:00 -0500 Subject: [PATCH] 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 --- syft/format/syftjson/to_syft_model.go | 22 +++++++++-- syft/format/syftjson/to_syft_model_test.go | 45 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/syft/format/syftjson/to_syft_model.go b/syft/format/syftjson/to_syft_model.go index f2c61d0a8..b2b1916e2 100644 --- a/syft/format/syftjson/to_syft_model.go +++ b/syft/format/syftjson/to_syft_model.go @@ -2,6 +2,8 @@ package syftjson import ( "fmt" + "io/fs" + "math" "os" "path" "strconv" @@ -76,14 +78,12 @@ func toSyftFiles(files []model.File) sbom.Artifacts { for _, f := range files { coord := f.Location if f.Metadata != nil { - mode, err := strconv.ParseInt(strconv.Itoa(f.Metadata.Mode), 8, 64) + fm, err := safeFileModeConvert(f.Metadata.Mode) if err != nil { 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{ FileInfo: stereoscopeFile.ManualInfo{ NameValue: path.Base(coord.RealPath), @@ -135,6 +135,20 @@ func toSyftFiles(files []model.File) sbom.Artifacts { 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) { for _, l := range m { p = append(p, pkg.License{ diff --git a/syft/format/syftjson/to_syft_model_test.go b/syft/format/syftjson/to_syft_model_test.go index a84c5aac8..8617bcb22 100644 --- a/syft/format/syftjson/to_syft_model_test.go +++ b/syft/format/syftjson/to_syft_model_test.go @@ -2,6 +2,9 @@ package syftjson import ( "errors" + "io/fs" + "math" + "os" "testing" "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) + }) + } +}