|
1 | 1 | package imageinspect |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "strings" |
4 | 5 | "testing" |
5 | 6 | ) |
6 | 7 |
|
@@ -975,3 +976,201 @@ func TestCompareImages_EFISigningStatusChanges(t *testing.T) { |
975 | 976 | t.Fatalf("expected signed false->true, got %v->%v", res.Diff.EFIBinaries.Modified[0].From.Signed, res.Diff.EFIBinaries.Modified[0].To.Signed) |
976 | 977 | } |
977 | 978 | } |
| 979 | +func TestCompareUUIDReferences_Branches(t *testing.T) { |
| 980 | + from := []UUIDReference{ |
| 981 | + {UUID: "00000000-0000-0000-0000-000000000001", Context: "kernel_cmdline", Mismatch: false}, // removed |
| 982 | + {UUID: "00000000-0000-0000-0000-000000000002", Context: "kernel_cmdline", Mismatch: false}, // mismatch changed |
| 983 | + {UUID: "00000000-0000-0000-0000-000000000003", Context: "grub_search", Mismatch: false}, // context changed only |
| 984 | + {UUID: "00000000-0000-0000-0000-000000000004", Context: "root_device", Mismatch: true}, // unchanged |
| 985 | + } |
| 986 | + to := []UUIDReference{ |
| 987 | + {UUID: "00000000-0000-0000-0000-000000000002", Context: "root_device", Mismatch: true}, |
| 988 | + {UUID: "00000000-0000-0000-0000-000000000003", Context: "kernel_cmdline", Mismatch: false}, |
| 989 | + {UUID: "00000000-0000-0000-0000-000000000004", Context: "root_device", Mismatch: true}, |
| 990 | + {UUID: "00000000-0000-0000-0000-000000000005", Context: "kernel_cmdline", Mismatch: false}, // added |
| 991 | + } |
| 992 | + |
| 993 | + changes := compareUUIDReferences(from, to) |
| 994 | + if len(changes) != 4 { |
| 995 | + t.Fatalf("expected 4 UUID reference changes, got %d: %+v", len(changes), changes) |
| 996 | + } |
| 997 | + |
| 998 | + byUUID := map[string]UUIDRefChange{} |
| 999 | + for _, ch := range changes { |
| 1000 | + byUUID[ch.UUID] = ch |
| 1001 | + } |
| 1002 | + |
| 1003 | + removed, ok := byUUID["00000000-0000-0000-0000-000000000001"] |
| 1004 | + if !ok || removed.Status != "removed" || removed.ContextFrom != "kernel_cmdline" || removed.MismatchFrom { |
| 1005 | + t.Fatalf("unexpected removed UUID change: %+v", removed) |
| 1006 | + } |
| 1007 | + |
| 1008 | + added, ok := byUUID["00000000-0000-0000-0000-000000000005"] |
| 1009 | + if !ok || added.Status != "added" || added.ContextTo != "kernel_cmdline" || added.MismatchTo { |
| 1010 | + t.Fatalf("unexpected added UUID change: %+v", added) |
| 1011 | + } |
| 1012 | + |
| 1013 | + mismatchChanged, ok := byUUID["00000000-0000-0000-0000-000000000002"] |
| 1014 | + if !ok || mismatchChanged.Status != "modified" || mismatchChanged.MismatchFrom != false || mismatchChanged.MismatchTo != true { |
| 1015 | + t.Fatalf("unexpected mismatch-changed UUID entry: %+v", mismatchChanged) |
| 1016 | + } |
| 1017 | + |
| 1018 | + contextChanged, ok := byUUID["00000000-0000-0000-0000-000000000003"] |
| 1019 | + if !ok || contextChanged.Status != "modified" || contextChanged.ContextFrom != "grub_search" || contextChanged.ContextTo != "kernel_cmdline" { |
| 1020 | + t.Fatalf("unexpected context-changed UUID entry: %+v", contextChanged) |
| 1021 | + } |
| 1022 | +} |
| 1023 | + |
| 1024 | +func TestUkiOnlyVolatile_Branches(t *testing.T) { |
| 1025 | + tests := []struct { |
| 1026 | + name string |
| 1027 | + mod ModifiedEFIBinaryEvidence |
| 1028 | + want bool |
| 1029 | + }{ |
| 1030 | + { |
| 1031 | + name: "kernel hash changed is meaningful", |
| 1032 | + mod: ModifiedEFIBinaryEvidence{ |
| 1033 | + UKI: &UKIDiff{KernelSHA256: &ValueDiff[string]{From: "k1", To: "k2"}, Changed: true}, |
| 1034 | + }, |
| 1035 | + want: false, |
| 1036 | + }, |
| 1037 | + { |
| 1038 | + name: "uname hash changed is meaningful", |
| 1039 | + mod: ModifiedEFIBinaryEvidence{ |
| 1040 | + UKI: &UKIDiff{UnameSHA256: &ValueDiff[string]{From: "u1", To: "u2"}, Changed: true}, |
| 1041 | + }, |
| 1042 | + want: false, |
| 1043 | + }, |
| 1044 | + { |
| 1045 | + name: "cmdline changed only by UUID is volatile", |
| 1046 | + mod: ModifiedEFIBinaryEvidence{ |
| 1047 | + From: EFIBinaryEvidence{Cmdline: "root=UUID=11111111-1111-1111-1111-111111111111 ro quiet"}, |
| 1048 | + To: EFIBinaryEvidence{Cmdline: "root=UUID=22222222-2222-2222-2222-222222222222 ro quiet"}, |
| 1049 | + UKI: &UKIDiff{CmdlineSHA256: &ValueDiff[string]{From: "c1", To: "c2"}, Changed: true}, |
| 1050 | + }, |
| 1051 | + want: true, |
| 1052 | + }, |
| 1053 | + { |
| 1054 | + name: "cmdline semantic change is meaningful", |
| 1055 | + mod: ModifiedEFIBinaryEvidence{ |
| 1056 | + From: EFIBinaryEvidence{Cmdline: "root=UUID=11111111-1111-1111-1111-111111111111 ro quiet"}, |
| 1057 | + To: EFIBinaryEvidence{Cmdline: "root=UUID=22222222-2222-2222-2222-222222222222 rw quiet"}, |
| 1058 | + UKI: &UKIDiff{CmdlineSHA256: &ValueDiff[string]{From: "c1", To: "c2"}, Changed: true}, |
| 1059 | + }, |
| 1060 | + want: false, |
| 1061 | + }, |
| 1062 | + { |
| 1063 | + name: "no uki details defaults volatile", |
| 1064 | + mod: ModifiedEFIBinaryEvidence{}, |
| 1065 | + want: true, |
| 1066 | + }, |
| 1067 | + } |
| 1068 | + |
| 1069 | + for _, tc := range tests { |
| 1070 | + t.Run(tc.name, func(t *testing.T) { |
| 1071 | + got := ukiOnlyVolatile(tc.mod) |
| 1072 | + if got != tc.want { |
| 1073 | + t.Fatalf("expected %v, got %v", tc.want, got) |
| 1074 | + } |
| 1075 | + }) |
| 1076 | + } |
| 1077 | +} |
| 1078 | +func TestCompareVerity_NilCasesAndFieldChanges(t *testing.T) { |
| 1079 | + if got := compareVerity(nil, nil); got != nil { |
| 1080 | + t.Fatalf("expected nil diff when both verity values are nil") |
| 1081 | + } |
| 1082 | + |
| 1083 | + added := compareVerity(nil, &VerityInfo{Enabled: true, Method: "systemd-verity", RootDevice: "/dev/vda2", HashPartition: 3}) |
| 1084 | + if added == nil || !added.Changed || added.Added == nil { |
| 1085 | + t.Fatalf("expected added verity diff") |
| 1086 | + } |
| 1087 | + |
| 1088 | + removed := compareVerity(&VerityInfo{Enabled: true, Method: "systemd-verity"}, nil) |
| 1089 | + if removed == nil || !removed.Changed || removed.Removed == nil { |
| 1090 | + t.Fatalf("expected removed verity diff") |
| 1091 | + } |
| 1092 | + |
| 1093 | + from := &VerityInfo{ |
| 1094 | + Enabled: true, |
| 1095 | + Method: "systemd-verity", |
| 1096 | + RootDevice: "/dev/vda2", |
| 1097 | + HashPartition: 3, |
| 1098 | + } |
| 1099 | + to := &VerityInfo{ |
| 1100 | + Enabled: false, |
| 1101 | + Method: "custom-initramfs", |
| 1102 | + RootDevice: "/dev/vda3", |
| 1103 | + HashPartition: 4, |
| 1104 | + } |
| 1105 | + |
| 1106 | + changed := compareVerity(from, to) |
| 1107 | + if changed == nil || !changed.Changed { |
| 1108 | + t.Fatalf("expected changed verity diff") |
| 1109 | + } |
| 1110 | + if changed.Enabled == nil || changed.Enabled.From != true || changed.Enabled.To != false { |
| 1111 | + t.Fatalf("expected enabled diff to be set") |
| 1112 | + } |
| 1113 | + if changed.Method == nil || changed.Method.From != "systemd-verity" || changed.Method.To != "custom-initramfs" { |
| 1114 | + t.Fatalf("expected method diff to be set") |
| 1115 | + } |
| 1116 | + if changed.RootDevice == nil || changed.RootDevice.From != "/dev/vda2" || changed.RootDevice.To != "/dev/vda3" { |
| 1117 | + t.Fatalf("expected root device diff to be set") |
| 1118 | + } |
| 1119 | + if changed.HashPartition == nil || changed.HashPartition.From != 3 || changed.HashPartition.To != 4 { |
| 1120 | + t.Fatalf("expected hash partition diff to be set") |
| 1121 | + } |
| 1122 | +} |
| 1123 | + |
| 1124 | +func TestTallyBootloaderConfigDiff_ClassificationCoverage(t *testing.T) { |
| 1125 | + tally := &diffTally{} |
| 1126 | + d := &BootloaderConfigDiff{ |
| 1127 | + ConfigFileChanges: []ConfigFileChange{ |
| 1128 | + {Path: "/boot/grub/grub.cfg", Status: "added"}, |
| 1129 | + {Path: "/loader/entries/old.conf", Status: "removed"}, |
| 1130 | + {Path: "/loader/entries/default.conf", Status: "modified"}, |
| 1131 | + }, |
| 1132 | + BootEntryChanges: []BootEntryChange{ |
| 1133 | + {Name: "entry-added", Status: "added"}, |
| 1134 | + {Name: "entry-removed", Status: "removed"}, |
| 1135 | + {Name: "entry-kernel", Status: "modified", KernelFrom: "/vmlinuz-a", KernelTo: "/vmlinuz-b"}, |
| 1136 | + {Name: "entry-initrd", Status: "modified", KernelFrom: "/vmlinuz", KernelTo: "/vmlinuz", InitrdFrom: "/initrd-a", InitrdTo: "/initrd-b"}, |
| 1137 | + {Name: "entry-cmdline", Status: "modified", KernelFrom: "/vmlinuz", KernelTo: "/vmlinuz", InitrdFrom: "/initrd", InitrdTo: "/initrd", CmdlineFrom: "root=UUID=11111111-1111-1111-1111-111111111111 ro", CmdlineTo: "root=UUID=22222222-2222-2222-2222-222222222222 rw"}, |
| 1138 | + {Name: "entry-meta", Status: "modified", KernelFrom: "/vmlinuz", KernelTo: "/vmlinuz", InitrdFrom: "/initrd", InitrdTo: "/initrd", CmdlineFrom: " root=UUID=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa ro ", CmdlineTo: "root=UUID=bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb ro"}, |
| 1139 | + }, |
| 1140 | + KernelRefChanges: []KernelRefChange{ |
| 1141 | + {Path: "EFI/Linux/new.efi", Status: "added"}, |
| 1142 | + {Path: "EFI/Linux/old.efi", Status: "removed"}, |
| 1143 | + {Path: "EFI/Linux/uuid.efi", Status: "modified", UUIDFrom: "u1", UUIDTo: "u2"}, |
| 1144 | + {Path: "EFI/Linux/meta.efi", Status: "modified", UUIDFrom: "same", UUIDTo: "same"}, |
| 1145 | + }, |
| 1146 | + UUIDReferenceChanges: []UUIDRefChange{ |
| 1147 | + {UUID: "a", Status: "added", MismatchTo: true}, |
| 1148 | + {UUID: "b", Status: "added", MismatchTo: false}, |
| 1149 | + {UUID: "c", Status: "removed", MismatchFrom: true}, |
| 1150 | + {UUID: "d", Status: "removed", MismatchFrom: false}, |
| 1151 | + {UUID: "e", Status: "modified", MismatchFrom: false, MismatchTo: true}, |
| 1152 | + {UUID: "f", Status: "modified", MismatchFrom: false, MismatchTo: false}, |
| 1153 | + }, |
| 1154 | + NotesAdded: []string{"note-a", "note-b"}, |
| 1155 | + NotesRemoved: []string{"note-c"}, |
| 1156 | + } |
| 1157 | + |
| 1158 | + tallyBootloaderConfigDiff(tally, d, "EFI/BOOT/BOOTX64.EFI") |
| 1159 | + |
| 1160 | + if tally.meaningful != 14 { |
| 1161 | + t.Fatalf("expected meaningful=14, got %d (reasons=%v)", tally.meaningful, tally.mReasons) |
| 1162 | + } |
| 1163 | + if tally.volatile != 8 { |
| 1164 | + t.Fatalf("expected volatile=8, got %d (reasons=%v)", tally.volatile, tally.vReasons) |
| 1165 | + } |
| 1166 | + |
| 1167 | + meaningfulJoined := strings.Join(tally.mReasons, "\n") |
| 1168 | + volatileJoined := strings.Join(tally.vReasons, "\n") |
| 1169 | + |
| 1170 | + if !strings.Contains(meaningfulJoined, "boot entry initrd changed: entry-initrd") { |
| 1171 | + t.Fatalf("expected initrd change to be classified meaningful, reasons=%v", tally.mReasons) |
| 1172 | + } |
| 1173 | + if !strings.Contains(volatileJoined, "boot entry metadata changed: entry-meta") { |
| 1174 | + t.Fatalf("expected metadata-only boot entry change to be classified volatile, reasons=%v", tally.vReasons) |
| 1175 | + } |
| 1176 | +} |
0 commit comments