github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/embeddedfs/vdi/vdi_test.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package vdi_test
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"testing"
    25  
    26  	cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto"
    27  	"github.com/google/osv-scalibr/extractor/filesystem"
    28  	"github.com/google/osv-scalibr/extractor/filesystem/embeddedfs/vdi"
    29  	"github.com/google/osv-scalibr/extractor/filesystem/simplefileapi"
    30  	"github.com/google/osv-scalibr/testing/fakefs"
    31  )
    32  
    33  func TestFileRequired(t *testing.T) {
    34  	tests := []struct {
    35  		desc                  string
    36  		path                  string
    37  		fileSize              int64
    38  		maxFileSize           int64
    39  		pluginSpecificMaxSize int64
    40  		want                  bool
    41  	}{
    42  		{
    43  			desc: "vdi_lowercase",
    44  			path: "testdata/disk.vdi",
    45  			want: true,
    46  		},
    47  		{
    48  			desc: "vdi_uppercase",
    49  			path: "testdata/DISK.VDI",
    50  			want: true,
    51  		},
    52  		{
    53  			desc: "not_vdi",
    54  			path: "testdata/document.txt",
    55  			want: false,
    56  		},
    57  		{
    58  			desc: "no_extension",
    59  			path: "testdata/noextension",
    60  			want: false,
    61  		},
    62  		{
    63  			desc:                  "override_global_size_below_limit",
    64  			path:                  "disk.vdi",
    65  			fileSize:              1001,
    66  			maxFileSize:           1000,
    67  			pluginSpecificMaxSize: 1001,
    68  			want:                  true,
    69  		},
    70  		{
    71  			desc:                  "override_global_size_above_limit",
    72  			path:                  "disk.vdi",
    73  			fileSize:              1001,
    74  			maxFileSize:           1001,
    75  			pluginSpecificMaxSize: 1000,
    76  			want:                  false,
    77  		},
    78  	}
    79  
    80  	for _, tt := range tests {
    81  		t.Run(tt.desc, func(t *testing.T) {
    82  			extractor := vdi.New(&cpb.PluginConfig{
    83  				MaxFileSizeBytes: tt.maxFileSize,
    84  				PluginSpecific: []*cpb.PluginSpecificConfig{
    85  					{Config: &cpb.PluginSpecificConfig_Vdi{Vdi: &cpb.VDIConfig{MaxFileSizeBytes: tt.pluginSpecificMaxSize}}},
    86  				},
    87  			})
    88  			if got := extractor.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{
    89  				FileSize: tt.fileSize,
    90  			})); got != tt.want {
    91  				t.Errorf("FileRequired(%q) = %v, want %v", tt.path, got, tt.want)
    92  			}
    93  		})
    94  	}
    95  }
    96  
    97  func TestExtractValidVDI(t *testing.T) {
    98  	extractor := vdi.New(&cpb.PluginConfig{})
    99  
   100  	tests := []struct {
   101  		name string
   102  		path string
   103  	}{
   104  		{
   105  			name: "StaticVDI",
   106  			path: filepath.FromSlash("testdata/valid-ext-exfat-fat32-ntfs-static.vdi"),
   107  		},
   108  		{
   109  			name: "DynamicVDI",
   110  			path: filepath.FromSlash("testdata/valid-ext-exfat-fat32-ntfs-dynamic.vdi"),
   111  		},
   112  	}
   113  
   114  	for _, tt := range tests {
   115  		t.Run(tt.name, func(t *testing.T) {
   116  			info, err := os.Stat(tt.path)
   117  			if err != nil {
   118  				t.Fatalf("os.Stat(%q) failed: %v", tt.path, err)
   119  			}
   120  
   121  			f, err := os.Open(tt.path)
   122  			if err != nil {
   123  				t.Fatalf("os.Open(%q) failed: %v", tt.path, err)
   124  			}
   125  			defer f.Close()
   126  
   127  			input := &filesystem.ScanInput{
   128  				Path:   tt.path,
   129  				Root:   ".",
   130  				Info:   info,
   131  				Reader: f,
   132  				FS:     nil,
   133  			}
   134  
   135  			ctx := t.Context()
   136  			inv, err := extractor.Extract(ctx, input)
   137  			if err != nil {
   138  				t.Fatalf("Extract(%q) failed: %v", tt.path, err)
   139  			}
   140  
   141  			if len(inv.EmbeddedFSs) == 0 {
   142  				t.Fatal("Extract returned no DiskImages")
   143  			}
   144  
   145  			for i, embeddedFS := range inv.EmbeddedFSs {
   146  				t.Run(fmt.Sprintf("DiskImage_%d", i), func(t *testing.T) {
   147  					if !strings.HasPrefix(embeddedFS.Path, tt.path) {
   148  						t.Errorf("EmbeddedFS.Path = %q, want prefix %q", embeddedFS.Path, tt.path)
   149  					}
   150  
   151  					fs, err := embeddedFS.GetEmbeddedFS(ctx)
   152  					if err != nil {
   153  						t.Errorf("GetEmbeddedFS() failed: %v", err)
   154  					}
   155  
   156  					entries, err := fs.ReadDir("/")
   157  					if err != nil {
   158  						t.Fatalf("fs.ReadDir(/) failed: %v", err)
   159  					}
   160  					t.Logf("ReadDir(/) returned %d entries", len(entries))
   161  
   162  					info, err := fs.Stat("/")
   163  					if err != nil {
   164  						t.Fatalf("fs.Stat(/) failed: %v", err)
   165  					}
   166  					if !info.IsDir() {
   167  						t.Errorf("fs.Stat(/) IsDir() = %v, want true", info.IsDir())
   168  					}
   169  
   170  					found := false
   171  					for _, entry := range entries {
   172  						name := entry.Name()
   173  						if strings.HasSuffix(name, ".pem") {
   174  							found = true
   175  							filePath := name
   176  							f, err := fs.Open(filePath)
   177  							if err != nil {
   178  								t.Fatalf("fs.Open(%q) failed: %v", filePath, err)
   179  							}
   180  							defer f.Close()
   181  
   182  							buf := make([]byte, 4096)
   183  							n, err := f.Read(buf)
   184  							if err != nil && !errors.Is(err, io.EOF) {
   185  								t.Errorf("f.Read(%q) failed: %v", filePath, err)
   186  							}
   187  							t.Logf("Read %d bytes from %s\n", n, name)
   188  
   189  							// The buffer must start with "-----BEGIN"
   190  							if string(buf[:10]) != "-----BEGIN" {
   191  								t.Errorf("%s contains unexpected data!", filePath)
   192  							}
   193  
   194  							info, err := f.Stat()
   195  							if err != nil {
   196  								t.Errorf("f.Stat(%q) failed: %v", filePath, err)
   197  							} else if info.IsDir() {
   198  								t.Errorf("f.Stat(%q) IsDir() = %v, want false", filePath, info.IsDir())
   199  							}
   200  							break
   201  						}
   202  					}
   203  					if !found {
   204  						t.Errorf("private keys not found")
   205  					}
   206  				})
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  func TestExtractInvalidVDI(t *testing.T) {
   213  	extractor := vdi.New(&cpb.PluginConfig{})
   214  	path := filepath.FromSlash("testdata/invalid.vdi")
   215  	info, err := os.Stat(path)
   216  	if err != nil {
   217  		t.Fatalf("os.Stat(%q) failed: %v", path, err)
   218  	}
   219  
   220  	f, err := os.Open(path)
   221  	if err != nil {
   222  		t.Fatalf("os.Open(%q) failed: %v", path, err)
   223  	}
   224  	defer f.Close()
   225  
   226  	input := &filesystem.ScanInput{
   227  		Path:   path,
   228  		Root:   ".",
   229  		Info:   info,
   230  		Reader: f,
   231  		FS:     nil,
   232  	}
   233  
   234  	ctx := t.Context()
   235  	_, err = extractor.Extract(ctx, input)
   236  	if err == nil {
   237  		t.Errorf("Extract(%q) succeeded, want error", path)
   238  	}
   239  }
   240  
   241  func TestExtractNonExistentVDI(t *testing.T) {
   242  	extractor := vdi.New(&cpb.PluginConfig{})
   243  	path := filepath.FromSlash("testdata/nonexistent.vdi")
   244  	input := &filesystem.ScanInput{
   245  		Path:   path,
   246  		Root:   "testdata",
   247  		Info:   nil,
   248  		Reader: nil,
   249  		FS:     nil,
   250  	}
   251  
   252  	ctx := t.Context()
   253  	_, err := extractor.Extract(ctx, input)
   254  	if err == nil {
   255  		t.Errorf("Extract(%q) succeeded, want error", path)
   256  	}
   257  }