github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/embeddedfs/vmdk/vmdk_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 vmdk_test
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  
    27  	cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto"
    28  	"github.com/google/osv-scalibr/extractor/filesystem"
    29  	"github.com/google/osv-scalibr/extractor/filesystem/embeddedfs/vmdk"
    30  	"github.com/google/osv-scalibr/extractor/filesystem/simplefileapi"
    31  	"github.com/google/osv-scalibr/testing/fakefs"
    32  )
    33  
    34  func TestFileRequired(t *testing.T) {
    35  	tests := []struct {
    36  		desc                  string
    37  		path                  string
    38  		fileSize              int64
    39  		maxFileSize           int64
    40  		pluginSpecificMaxSize int64
    41  		want                  bool
    42  	}{
    43  		{
    44  			desc: "vmdk_lowercase",
    45  			path: "testdata/disk.vmdk",
    46  			want: true,
    47  		},
    48  		{
    49  			desc: "vmdk_uppercase",
    50  			path: "testdata/DISK.VMDK",
    51  			want: true,
    52  		},
    53  		{
    54  			desc: "not_vmdk",
    55  			path: "testdata/document.txt",
    56  			want: false,
    57  		},
    58  		{
    59  			desc: "no_extension",
    60  			path: "testdata/noextension",
    61  			want: false,
    62  		},
    63  		{
    64  			desc:        "file_size_below_limit",
    65  			path:        "disk.vmdk",
    66  			fileSize:    1000,
    67  			maxFileSize: 1000,
    68  			want:        true,
    69  		},
    70  		{
    71  			desc:        "file_size_above_limit",
    72  			path:        "disk.vmdk",
    73  			fileSize:    1001,
    74  			maxFileSize: 1000,
    75  			want:        false,
    76  		},
    77  		{
    78  			desc:                  "override_global_size_below_limit",
    79  			path:                  "disk.vmdk",
    80  			fileSize:              1001,
    81  			maxFileSize:           1000,
    82  			pluginSpecificMaxSize: 1001,
    83  			want:                  true,
    84  		},
    85  		{
    86  			desc:                  "override_global_size_above_limit",
    87  			path:                  "disk.vmdk",
    88  			fileSize:              1001,
    89  			maxFileSize:           1001,
    90  			pluginSpecificMaxSize: 1000,
    91  			want:                  false,
    92  		},
    93  	}
    94  
    95  	for _, tt := range tests {
    96  		t.Run(tt.path, func(t *testing.T) {
    97  			extractor := vmdk.New(&cpb.PluginConfig{
    98  				MaxFileSizeBytes: tt.maxFileSize,
    99  				PluginSpecific: []*cpb.PluginSpecificConfig{
   100  					{Config: &cpb.PluginSpecificConfig_Vmdk{Vmdk: &cpb.VMDKConfig{MaxFileSizeBytes: tt.pluginSpecificMaxSize}}},
   101  				},
   102  			})
   103  			if got := extractor.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{
   104  				FileSize: tt.fileSize,
   105  			})); got != tt.want {
   106  				t.Errorf("FileRequired(%q) = %v, want %v", tt.path, got, tt.want)
   107  			}
   108  		})
   109  	}
   110  }
   111  
   112  func TestExtractValidVMDK(t *testing.T) {
   113  	extractor := vmdk.New(&cpb.PluginConfig{})
   114  	path := filepath.FromSlash("testdata/valid-ext-exfat-fat32-ntfs.vmdk")
   115  	info, err := os.Stat(path)
   116  	if err != nil {
   117  		t.Fatalf("os.Stat(%q) failed: %v", path, err)
   118  	}
   119  	input := &filesystem.ScanInput{
   120  		Path:   path,
   121  		Root:   ".",
   122  		Info:   info,
   123  		Reader: nil,
   124  		FS:     nil,
   125  	}
   126  
   127  	ctx := context.Background()
   128  	inv, err := extractor.Extract(ctx, input)
   129  	if err != nil {
   130  		t.Fatalf("Extract(%q) failed: %v", path, err)
   131  	}
   132  
   133  	if len(inv.EmbeddedFSs) == 0 {
   134  		t.Fatal("Extract returned no DiskImages")
   135  	}
   136  
   137  	for i, embeddedFS := range inv.EmbeddedFSs {
   138  		t.Run(fmt.Sprintf("DiskImage_%d", i), func(t *testing.T) {
   139  			if !strings.HasPrefix(embeddedFS.Path, path) {
   140  				t.Errorf("EmbeddedFS.Path = %q, want prefix %q", embeddedFS.Path, path)
   141  			}
   142  
   143  			fs, err := embeddedFS.GetEmbeddedFS(ctx)
   144  			if err != nil {
   145  				t.Errorf("GetEmbeddedFS() failed: %v", err)
   146  			}
   147  
   148  			entries, err := fs.ReadDir("/")
   149  			if err != nil {
   150  				t.Fatalf("fs.ReadDir(/) failed: %v", err)
   151  			}
   152  			t.Logf("ReadDir(/) returned %d entries", len(entries))
   153  
   154  			info, err := fs.Stat("/")
   155  			if err != nil {
   156  				t.Fatalf("fs.Stat(/) failed: %v", err)
   157  			}
   158  			if !info.IsDir() {
   159  				t.Errorf("fs.Stat(/) IsDir() = %v, want true", info.IsDir())
   160  			}
   161  
   162  			found := false
   163  			for _, entry := range entries {
   164  				name := entry.Name()
   165  				if strings.HasSuffix(name, ".pem") {
   166  					found = true
   167  					filePath := name
   168  					f, err := fs.Open(filePath)
   169  					if err != nil {
   170  						t.Fatalf("fs.Open(%q) failed: %v", filePath, err)
   171  					}
   172  					defer f.Close()
   173  
   174  					buf := make([]byte, 4096)
   175  					n, err := f.Read(buf)
   176  					if err != nil && !errors.Is(err, io.EOF) {
   177  						t.Errorf("f.Read(%q) failed: %v", filePath, err)
   178  					}
   179  					t.Logf("Read %d bytes from %s\n", n, name)
   180  
   181  					// The buffer must start with "-----BEGIN"
   182  					if string(buf[:10]) != "-----BEGIN" {
   183  						t.Errorf("%s contains unexpected data!", filePath)
   184  					}
   185  
   186  					info, err := f.Stat()
   187  					if err != nil {
   188  						t.Errorf("f.Stat(%q) failed: %v", filePath, err)
   189  					} else if info.IsDir() {
   190  						t.Errorf("f.Stat(%q) IsDir() = %v, want false", filePath, info.IsDir())
   191  					}
   192  					break
   193  				}
   194  			}
   195  			if !found {
   196  				t.Errorf("private keys not found")
   197  			}
   198  		})
   199  	}
   200  }
   201  
   202  func TestExtractInvalidVMDK(t *testing.T) {
   203  	extractor := vmdk.New(&cpb.PluginConfig{})
   204  	path := "testdata/invalid.vmdk"
   205  	info, err := os.Stat(path)
   206  	if err != nil {
   207  		t.Fatalf("os.Stat(%q) failed: %v", path, err)
   208  	}
   209  	input := &filesystem.ScanInput{
   210  		Path:   path,
   211  		Root:   ".",
   212  		Info:   info,
   213  		Reader: nil,
   214  		FS:     nil,
   215  	}
   216  
   217  	ctx := context.Background()
   218  	_, err = extractor.Extract(ctx, input)
   219  	if err == nil {
   220  		t.Errorf("Extract(%q) succeeded, want error", path)
   221  	}
   222  }
   223  
   224  func TestExtractNonExistentVMDK(t *testing.T) {
   225  	extractor := vmdk.New(&cpb.PluginConfig{})
   226  	path := "testdata/nonexistent.vmdk"
   227  	input := &filesystem.ScanInput{
   228  		Path:   path,
   229  		Root:   "testdata",
   230  		Info:   nil,
   231  		Reader: nil,
   232  		FS:     nil,
   233  	}
   234  
   235  	ctx := context.Background()
   236  	_, err := extractor.Extract(ctx, input)
   237  	if err == nil {
   238  		t.Errorf("Extract(%q) succeeded, want error", path)
   239  	}
   240  }