forked from speed47/spectre-meltdown-checker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
spectre-meltdown-checker.sh
executable file
·4536 lines (4254 loc) · 163 KB
/
spectre-meltdown-checker.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#! /bin/sh
# SPDX-License-Identifier: GPL-3.0-only
#
# Spectre & Meltdown checker
#
# Check for the latest version at:
# https://github.com/speed47/spectre-meltdown-checker
# git clone https://github.com/speed47/spectre-meltdown-checker.git
# or wget https://meltdown.ovh -O spectre-meltdown-checker.sh
# or curl -L https://meltdown.ovh -o spectre-meltdown-checker.sh
#
# Stephane Lesimple
#
VERSION='0.40'
trap 'exit_cleanup' EXIT
trap '_warn "interrupted, cleaning up..."; exit_cleanup; exit 1' INT
exit_cleanup()
{
# cleanup the temp decompressed config & kernel image
[ -n "$dumped_config" ] && [ -f "$dumped_config" ] && rm -f "$dumped_config"
[ -n "$kerneltmp" ] && [ -f "$kerneltmp" ] && rm -f "$kerneltmp"
[ -n "$kerneltmp2" ] && [ -f "$kerneltmp2" ] && rm -f "$kerneltmp2"
[ -n "$mcedb_tmp" ] && [ -f "$mcedb_tmp" ] && rm -f "$mcedb_tmp"
[ "$mounted_debugfs" = 1 ] && umount /sys/kernel/debug 2>/dev/null
[ "$mounted_procfs" = 1 ] && umount "$procfs" 2>/dev/null
[ "$insmod_cpuid" = 1 ] && rmmod cpuid 2>/dev/null
[ "$insmod_msr" = 1 ] && rmmod msr 2>/dev/null
[ "$kldload_cpuctl" = 1 ] && kldunload cpuctl 2>/dev/null
[ "$kldload_vmm" = 1 ] && kldunload vmm 2>/dev/null
}
# if we were git clone'd, adjust VERSION
if [ -d "$(dirname "$0")/.git" ] && command -v git >/dev/null 2>&1; then
describe=$(git -C "$(dirname "$0")" describe --tags --dirty 2>/dev/null)
[ -n "$describe" ] && VERSION=$(echo "$describe" | sed -e s/^v//)
fi
show_usage()
{
# shellcheck disable=SC2086
cat <<EOF
Usage:
Live mode: $(basename $0) [options] [--live]
Offline mode: $(basename $0) [options] [--kernel <kernel_file>] [--config <kernel_config>] [--map <kernel_map_file>]
Modes:
Two modes are available.
First mode is the "live" mode (default), it does its best to find information about the currently running kernel.
To run under this mode, just start the script without any option (you can also use --live explicitly)
Second mode is the "offline" mode, where you can inspect a non-running kernel.
You'll need to specify the location of the kernel file, config and System.map files:
--kernel kernel_file specify a (possibly compressed) Linux or BSD kernel file
--config kernel_config specify a kernel config file (Linux only)
--map kernel_map_file specify a kernel System.map file (Linux only)
Options:
--no-color don't use color codes
--verbose, -v increase verbosity level, possibly several times
--explain produce an additional human-readable explanation of actions to take to mitigate a vulnerability
--paranoid require IBPB to deem Variant 2 as mitigated
also require SMT disabled + unconditional L1D flush to deem Foreshadow-NG VMM as mitigated
--no-sysfs don't use the /sys interface even if present [Linux]
--sysfs-only only use the /sys interface, don't run our own checks [Linux]
--coreos special mode for CoreOS (use an ephemeral toolbox to inspect kernel) [Linux]
--arch-prefix PREFIX specify a prefix for cross-inspecting a kernel of a different arch, for example "aarch64-linux-gnu-",
so that invoked tools will be prefixed with this (i.e. aarch64-linux-gnu-objdump)
--batch text produce machine readable output, this is the default if --batch is specified alone
--batch short produce only one line with the vulnerabilities separated by spaces
--batch json produce JSON output formatted for Puppet, Ansible, Chef...
--batch nrpe produce machine readable output formatted for NRPE
--batch prometheus produce output for consumption by prometheus-node-exporter
--variant [1,2,3,3a,4,l1tf] specify which variant you'd like to check, by default all variants are checked
--cve [cve1,cve2,...] specify which CVE you'd like to check, by default all supported CVEs are checked
can be specified multiple times (e.g. --variant 2 --variant 3)
--hw-only only check for CPU information, don't check for any variant
--no-hw skip CPU information and checks, if you're inspecting a kernel not to be run on this host
--vmm [auto,yes,no] override the detection of the presence of a hypervisor (for CVE-2018-3646), default: auto
--update-mcedb update our local copy of the CPU microcodes versions database (from the awesome MCExtractor project)
--update-builtin-mcedb same as --update-mcedb but update builtin DB inside the script itself
Return codes:
0 (not vulnerable), 2 (vulnerable), 3 (unknown), 255 (error)
IMPORTANT:
A false sense of security is worse than no security at all.
Please use the --disclaimer option to understand exactly what this script does.
EOF
}
show_disclaimer()
{
cat <<EOF
Disclaimer:
This tool does its best to determine whether your system is immune (or has proper mitigations in place) for the
collectively named "speculative execution" vulnerabilities. It doesn't attempt to run any kind of exploit, and can't guarantee
that your system is secure, but rather helps you verifying whether your system has the known correct mitigations in place.
However, some mitigations could also exist in your kernel that this script doesn't know (yet) how to detect, or it might
falsely detect mitigations that in the end don't work as expected (for example, on backported or modified kernels).
Your system exposure also depends on your CPU. As of now, AMD and ARM processors are marked as immune to some or all of these
vulnerabilities (except some specific ARM models). All Intel processors manufactured since circa 1995 are thought to be vulnerable,
except some specific/old models, such as some early Atoms. Whatever processor one uses, one might seek more information
from the manufacturer of that processor and/or of the device in which it runs.
The nature of the discovered vulnerabilities being quite new, the landscape of vulnerable processors can be expected
to change over time, which is why this script makes the assumption that all CPUs are vulnerable, except if the manufacturer
explicitly stated otherwise in a verifiable public announcement.
Please also note that for Spectre vulnerabilities, all software can possibly be exploited, this tool only verifies that the
kernel (which is the core of the system) you're using has the proper protections in place. Verifying all the other software
is out of the scope of this tool. As a general measure, ensure you always have the most up to date stable versions of all
the software you use, especially for those who are exposed to the world, such as network daemons and browsers.
This tool has been released in the hope that it'll be useful, but don't use it to jump to conclusions about your security.
EOF
}
os=$(uname -s)
# parse options
opt_kernel=''
opt_config=''
opt_map=''
opt_live_explicit=0
opt_live=1
opt_no_color=0
opt_batch=0
opt_batch_format='text'
opt_verbose=1
opt_cve_list=''
opt_cve_all=1
opt_no_sysfs=0
opt_sysfs_only=0
opt_coreos=0
opt_arch_prefix=''
opt_hw_only=0
opt_no_hw=0
opt_vmm=-1
opt_explain=0
opt_paranoid=0
global_critical=0
global_unknown=0
nrpe_vuln=''
supported_cve_list='CVE-2017-5753 CVE-2017-5715 CVE-2017-5754 CVE-2018-3640 CVE-2018-3639 CVE-2018-3615 CVE-2018-3620 CVE-2018-3646'
# find a sane command to print colored messages, we prefer `printf` over `echo`
# because `printf` behavior is more standard across Linux/BSD
# we'll try to avoid using shell builtins that might not take options
echo_cmd_type='echo'
# ignore SC2230 here because `which` ignores builtins while `command -v` doesn't, and
# we don't want builtins here. Even if `which` is not installed, we'll fallback to the
# `echo` builtin anyway, so this is safe.
# shellcheck disable=SC2230
if command -v printf >/dev/null 2>&1; then
echo_cmd=$(command -v printf)
echo_cmd_type='printf'
elif which echo >/dev/null 2>&1; then
echo_cmd=$(which echo)
else
# maybe the `which` command is broken?
[ -x /bin/echo ] && echo_cmd=/bin/echo
# for Android
[ -x /system/bin/echo ] && echo_cmd=/system/bin/echo
fi
# still empty? fallback to builtin
[ -z "$echo_cmd" ] && echo_cmd='echo'
__echo()
{
opt="$1"
shift
_msg="$*"
if [ "$opt_no_color" = 1 ] ; then
# strip ANSI color codes
# some sed versions (i.e. toybox) can't seem to handle
# \033 aka \x1B correctly, so do it for them.
if [ "$echo_cmd_type" = printf ]; then
_interpret_chars=''
else
_interpret_chars='-e'
fi
_ctrlchar=$($echo_cmd $_interpret_chars "\033")
_msg=$($echo_cmd $_interpret_chars "$_msg" | sed -r "s/$_ctrlchar\[([0-9][0-9]?(;[0-9][0-9]?)?)?m//g")
fi
if [ "$echo_cmd_type" = printf ]; then
if [ "$opt" = "-n" ]; then
$echo_cmd "$_msg"
else
$echo_cmd "$_msg\n"
fi
else
# shellcheck disable=SC2086
$echo_cmd $opt -e "$_msg"
fi
}
_echo()
{
if [ "$opt_verbose" -ge "$1" ]; then
shift
__echo '' "$*"
fi
}
_echo_nol()
{
if [ "$opt_verbose" -ge "$1" ]; then
shift
__echo -n "$*"
fi
}
_warn()
{
_echo 0 "\033[31m$*\033[0m" >&2
}
_info()
{
_echo 1 "$*"
}
_info_nol()
{
_echo_nol 1 "$*"
}
_verbose()
{
_echo 2 "$*"
}
_verbose_nol()
{
_echo_nol 2 "$*"
}
_debug()
{
_echo 3 "\033[34m(debug) $*\033[0m"
}
explain()
{
if [ "$opt_explain" = 1 ] ; then
_info ''
_info "> \033[41m\033[30mHow to fix:\033[0m $*"
fi
}
cve2name()
{
case "$1" in
CVE-2017-5753) echo "Spectre Variant 1, bounds check bypass";;
CVE-2017-5715) echo "Spectre Variant 2, branch target injection";;
CVE-2017-5754) echo "Variant 3, Meltdown, rogue data cache load";;
CVE-2018-3640) echo "Variant 3a, rogue system register read";;
CVE-2018-3639) echo "Variant 4, speculative store bypass";;
CVE-2018-3615) echo "Foreshadow (SGX), L1 terminal fault";;
CVE-2018-3620) echo "Foreshadow-NG (OS), L1 terminal fault";;
CVE-2018-3646) echo "Foreshadow-NG (VMM), L1 terminal fault";;
*) echo "$0: error: invalid CVE '$1' passed to cve2name()" >&2; exit 255;;
esac
}
is_cpu_vulnerable_cached=0
_is_cpu_vulnerable_cached()
{
# shellcheck disable=SC2086
case "$1" in
CVE-2017-5753) return $variant1;;
CVE-2017-5715) return $variant2;;
CVE-2017-5754) return $variant3;;
CVE-2018-3640) return $variant3a;;
CVE-2018-3639) return $variant4;;
CVE-2018-3615) return $variantl1tf_sgx;;
CVE-2018-3620) return $variantl1tf;;
CVE-2018-3646) return $variantl1tf;;
*) echo "$0: error: invalid variant '$1' passed to is_cpu_vulnerable()" >&2; exit 255;;
esac
}
is_cpu_vulnerable()
{
# param: one of the $supported_cve_list items
# returns 0 if vulnerable, 1 if not vulnerable
# (note that in shell, a return of 0 is success)
# by default, everything is vulnerable, we work in a "whitelist" logic here.
# usage: is_cpu_vulnerable CVE-xxxx-yyyy && do something if vulnerable
if [ "$is_cpu_vulnerable_cached" = 1 ]; then
_is_cpu_vulnerable_cached "$1"
return $?
fi
variant1=''
variant2=''
variant3=''
variant3a=''
variant4=''
variantl1tf=''
if is_cpu_specex_free; then
variant1=immune
variant2=immune
variant3=immune
variant3a=immune
variant4=immune
variantl1tf=immune
elif is_intel; then
# Intel
# https://github.com/crozone/SpectrePoC/issues/1 ^F E5200 => spectre 2 not vulnerable
# https://github.com/paboldin/meltdown-exploit/issues/19 ^F E5200 => meltdown vulnerable
# model name : Pentium(R) Dual-Core CPU E5200 @ 2.50GHz
if grep -qE '^model name.+ Pentium\(R\) Dual-Core[[:space:]]+CPU[[:space:]]+E[0-9]{4}K? ' "$procfs/cpuinfo"; then
variant1=vuln
[ -z "$variant2" ] && variant2=immune
variant3=vuln
fi
if [ "$capabilities_rdcl_no" = 1 ]; then
# capability bit for future Intel processor that will explicitly state
# that they're not vulnerable to Meltdown
# this var is set in check_cpu()
[ -z "$variant3" ] && variant3=immune
[ -z "$variantl1tf" ] && variantl1tf=immune
_debug "is_cpu_vulnerable: RDCL_NO is set so not vuln to meltdown nor l1tf"
fi
if [ "$capabilities_ssb_no" = 1 ]; then
# capability bit for future Intel processor that will explicitly state
# that they're not vulnerable to Variant 4
# this var is set in check_cpu()
[ -z "$variant4" ] && variant4=immune
_debug "is_cpu_vulnerable: SSB_NO is set so not vuln to variant4"
fi
if is_cpu_ssb_free; then
[ -z "$variant4" ] && variant4=immune
_debug "is_cpu_vulnerable: cpu not affected by speculative store bypass so not vuln to variant4"
fi
# variant 4a for xeon phi
if [ "$cpu_family" = 6 ]; then
if [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then
_debug "is_cpu_vulnerable: xeon phi immune to variant 3a"
[ -z "$variant3a" ] && variant3a=immune
fi
fi
# L1TF (RDCL_NO already checked above)
if [ "$cpu_family" = 6 ]; then
if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_TABLET" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_MID" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL_MID" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_X" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT_MID" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_X" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_PLUS" ] || \
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || \
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then
_debug "is_cpu_vulnerable: intel family 6 but model known to be immune"
[ -z "$variantl1tf" ] && variantl1tf=immune
else
_debug "is_cpu_vulnerable: intel family 6 is vuln"
variantl1tf=vuln
fi
elif [ "$cpu_family" -lt 6 ]; then
_debug "is_cpu_vulnerable: intel family < 6 is immune"
[ -z "$variantl1tf" ] && variantl1tf=immune
fi
elif is_amd || is_hygon; then
# AMD revised their statement about variant2 => vulnerable
# https://www.amd.com/en/corporate/speculative-execution
variant1=vuln
variant2=vuln
[ -z "$variant3" ] && variant3=immune
# https://www.amd.com/en/corporate/security-updates
# "We have not identified any AMD x86 products susceptible to the Variant 3a vulnerability in our analysis to-date."
[ -z "$variant3a" ] && variant3a=immune
if is_cpu_ssb_free; then
[ -z "$variant4" ] && variant4=immune
_debug "is_cpu_vulnerable: cpu not affected by speculative store bypass so not vuln to variant4"
fi
variantl1tf=immune
elif [ "$cpu_vendor" = CAVIUM ]; then
variant3=immune
variant3a=immune
variantl1tf=immune
elif [ "$cpu_vendor" = ARM ]; then
# ARM
# reference: https://developer.arm.com/support/security-update
# some devices (phones or other) have several ARMs and as such different part numbers,
# an example is "bigLITTLE". we shouldn't rely on the first CPU only, so we check the whole list
i=0
for cpupart in $cpu_part_list
do
i=$(( i + 1 ))
# do NOT quote $cpu_arch_list below
# shellcheck disable=SC2086
cpuarch=$(echo $cpu_arch_list | awk '{ print $'$i' }')
_debug "checking cpu$i: <$cpupart> <$cpuarch>"
# some kernels report AArch64 instead of 8
[ "$cpuarch" = "AArch64" ] && cpuarch=8
if [ -n "$cpupart" ] && [ -n "$cpuarch" ]; then
# Cortex-R7 and Cortex-R8 are real-time and only used in medical devices or such
# I can't find their CPU part number, but it's probably not that useful anyway
# model R7 R8 A8 A9 A12 A15 A17 A57 A72 A73 A75 A76
# part ? ? c08 c09 c0d c0f c0e d07 d08 d09 d0a d0b?
# arch 7? 7? 7 7 7 7 7 8 8 8 8 8
#
# Whitelist identified non-vulnerable processors, use vulnerability information from
# https://developer.arm.com/support/arm-security-updates/speculative-processor-vulnerability
#
# Maintain cumulative check of vulnerabilities -
# if at least one of the cpu is vulnerable, then the system is vulnerable
if [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -q -w -e 0xc08 -e 0xc09 -e 0xc0d -e 0xc0e; then
variant1=vuln
variant2=vuln
[ -z "$variant3" ] && variant3=immune
[ -z "$variant3a" ] && variant3a=immune
[ -z "$variant4" ] && variant4=immune
_debug "checking cpu$i: armv7 A8/A9/A12/A17 non vulnerable to variants 3, 3a & 4"
elif [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -q -w -e 0xc0f; then
variant1=vuln
variant2=vuln
[ -z "$variant3" ] && variant3=immune
variant3a=vuln
[ -z "$variant4" ] && variant4=immune
_debug "checking cpu$i: armv7 A15 non vulnerable to variants 3 & 4"
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd07 -e 0xd08; then
variant1=vuln
variant2=vuln
[ -z "$variant3" ] && variant3=immune
variant3a=vuln
variant4=vuln
_debug "checking cpu$i: armv8 A57/A72 non vulnerable to variants 3"
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd09; then
variant1=vuln
variant2=vuln
[ -z "$variant3" ] && variant3=immune
[ -z "$variant3a" ] && variant3a=immune
variant4=vuln
_debug "checking cpu$i: armv8 A73 non vulnerable to variants 3 & 3a"
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd0a; then
variant1=vuln
variant2=vuln
variant3=vuln
[ -z "$variant3a" ] && variant3a=immune
variant4=vuln
_debug "checking cpu$i: armv8 A75 non vulnerable to variant 3a"
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd0b; then
variant1=vuln
[ -z "$variant2" ] && variant2=immune
[ -z "$variant3" ] && variant3=immune
[ -z "$variant3a" ] && variant3a=immune
variant4=vuln
_debug "checking cpu$i: armv8 A76 non vulnerable to variant 2, 3 & 3a"
elif [ "$cpuarch" -le 7 ] || { [ "$cpuarch" = 8 ] && [ $(( cpupart )) -lt $(( 0xd07 )) ]; } ; then
[ -z "$variant1" ] && variant1=immune
[ -z "$variant2" ] && variant2=immune
[ -z "$variant3" ] && variant3=immune
[ -z "$variant3a" ] && variant3a=immune
[ -z "$variant4" ] && variant4=immune
_debug "checking cpu$i: arm arch$cpuarch, all immune (v7 or v8 and model < 0xd07)"
else
variant1=vuln
variant2=vuln
variant3=vuln
variant3a=vuln
variant4=vuln
_debug "checking cpu$i: arm unknown arch$cpuarch part$cpupart, considering vuln"
fi
fi
_debug "is_cpu_vulnerable: for cpu$i and so far, we have <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4>"
done
variantl1tf=immune
fi
_debug "is_cpu_vulnerable: temp results are <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4> <$variantl1tf>"
[ "$variant1" = "immune" ] && variant1=1 || variant1=0
[ "$variant2" = "immune" ] && variant2=1 || variant2=0
[ "$variant3" = "immune" ] && variant3=1 || variant3=0
[ "$variant3a" = "immune" ] && variant3a=1 || variant3a=0
[ "$variant4" = "immune" ] && variant4=1 || variant4=0
[ "$variantl1tf" = "immune" ] && variantl1tf=1 || variantl1tf=0
variantl1tf_sgx="$variantl1tf"
# even if we are vulnerable to L1TF, if there's no SGX, we're safe for the original foreshadow
[ "$cpuid_sgx" = 0 ] && variantl1tf_sgx=1
_debug "is_cpu_vulnerable: final results are <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4> <$variantl1tf> <$variantl1tf_sgx>"
is_cpu_vulnerable_cached=1
_is_cpu_vulnerable_cached "$1"
return $?
}
is_cpu_specex_free()
{
# return true (0) if the CPU doesn't do speculative execution, false (1) if it does.
# if it's not in the list we know, return false (1).
# source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c#n882
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL, X86_FEATURE_ANY },
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL_TABLET, X86_FEATURE_ANY },
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_BONNELL_MID, X86_FEATURE_ANY },
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL_MID, X86_FEATURE_ANY },
# { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_BONNELL, X86_FEATURE_ANY },
# { X86_VENDOR_CENTAUR, 5 },
# { X86_VENDOR_INTEL, 5 },
# { X86_VENDOR_NSC, 5 },
# { X86_VENDOR_ANY, 4 },
parse_cpu_details
if is_intel; then
if [ "$cpu_family" = 6 ]; then
if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_TABLET" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL_MID" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_MID" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL" ]; then
return 0
fi
elif [ "$cpu_family" = 5 ]; then
return 0
fi
fi
[ "$cpu_family" = 4 ] && return 0
return 1
}
is_cpu_ssb_free()
{
# return true (0) if the CPU isn't affected by speculative store bypass, false (1) if it does.
# if it's not in the list we know, return false (1).
# source1: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c#n945
# source2: https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git/tree/arch/x86/kernel/cpu/common.c
# Only list CPUs that speculate but are immune, to avoid duplication of cpus listed in is_cpu_specex_free()
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT },
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT },
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT_X },
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT_MID },
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_CORE_YONAH },
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_XEON_PHI_KNL },
#{ X86_VENDOR_INTEL, 6, INTEL_FAM6_XEON_PHI_KNM },
#{ X86_VENDOR_AMD, 0x12, },
#{ X86_VENDOR_AMD, 0x11, },
#{ X86_VENDOR_AMD, 0x10, },
#{ X86_VENDOR_AMD, 0xf, },
parse_cpu_details
if is_intel; then
if [ "$cpu_family" = 6 ]; then
if [ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_X" ] || \
[ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ]; then
return 0
elif [ "$cpu_model" = "$INTEL_FAM6_CORE_YONAH" ] || \
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || \
[ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then
return 0
fi
fi
fi
if is_amd; then
if [ "$cpu_family" = "18" ] || \
[ "$cpu_family" = "17" ] || \
[ "$cpu_family" = "16" ] || \
[ "$cpu_family" = "15" ]; then
return 0
fi
fi
if is_hygon; then
return 1
fi
[ "$cpu_family" = 4 ] && return 0
return 1
}
show_header()
{
_info "Spectre and Meltdown mitigation detection tool v$VERSION"
_info
}
[ -z "$HOME" ] && HOME="$(getent passwd "$(whoami)" | cut -d: -f6)"
mcedb_cache="$HOME/.mcedb"
update_mcedb()
{
# We're using MCE.db from the excellent platomav's MCExtractor project
show_header
if [ -r "$mcedb_cache" ]; then
previous_mcedb_revision=$(awk '/^# %%% MCEDB / { print $4 }' "$mcedb_cache")
fi
# first download the database
mcedb_tmp="$(mktemp /tmp/mcedb-XXXXXX)"
mcedb_url='https://github.com/platomav/MCExtractor/raw/master/MCE.db'
_info_nol "Fetching MCE.db from the MCExtractor project... "
if command -v wget >/dev/null 2>&1; then
wget -q "$mcedb_url" -O "$mcedb_tmp"; ret=$?
elif command -v curl >/dev/null 2>&1; then
curl -sL "$mcedb_url" -o "$mcedb_tmp"; ret=$?
elif command -v fetch >/dev/null 2>&1; then
fetch -q "$mcedb_url" -o "$mcedb_tmp"; ret=$?
else
echo ERROR "please install one of \`wget\`, \`curl\` of \`fetch\` programs"
return 1
fi
if [ "$ret" != 0 ]; then
echo ERROR "error $ret while downloading MCE.db"
return $ret
fi
echo DONE
# now extract contents using sqlite
_info_nol "Extracting data... "
if ! command -v sqlite3 >/dev/null 2>&1; then
echo ERROR "please install the \`sqlite3\` program"
return 1
fi
mcedb_revision=$(sqlite3 "$mcedb_tmp" "select revision from MCE")
mcedb_date=$(sqlite3 "$mcedb_tmp" "select strftime('%Y/%m/%d', date, 'unixepoch') from MCE")
if [ -z "$mcedb_revision" ]; then
echo ERROR "downloaded file seems invalid"
return 1
fi
echo OK "MCExtractor database revision $mcedb_revision dated $mcedb_date"
if [ -n "$previous_mcedb_revision" ]; then
if [ "$previous_mcedb_revision" = "v$mcedb_revision" ]; then
echo "We already have this version locally, no update needed"
[ "$1" != builtin ] && return 0
fi
fi
echo "# Spectre & Meltdown Checker" > "$mcedb_cache"
echo "# %%% MCEDB v$mcedb_revision - $mcedb_date" >> "$mcedb_cache"
sqlite3 "$mcedb_tmp" "select '# I,0x'||cpuid||',0x'||version||','||max(yyyymmdd) from Intel group by cpuid order by cpuid asc; select '# A,0x'||cpuid||',0x'||version||','||max(yyyymmdd) from AMD group by cpuid order by cpuid asc" | grep -v '^# .,0x00000000,' >> "$mcedb_cache"
echo OK "local version updated"
if [ "$1" = builtin ]; then
newfile=$(mktemp /tmp/smc-XXXXXX)
awk '/^# %%% MCEDB / { exit }; { print }' "$0" > "$newfile"
awk '{ if (NR>1) { print } }' "$mcedb_cache" >> "$newfile"
cat "$newfile" > "$0"
rm -f "$newfile"
fi
}
parse_opt_file()
{
# parse_opt_file option_name option_value
option_name="$1"
option_value="$2"
if [ -z "$option_value" ]; then
show_header
show_usage
echo "$0: error: --$option_name expects one parameter (a file)" >&2
exit 1
elif [ ! -e "$option_value" ]; then
show_header
echo "$0: error: couldn't find file $option_value" >&2
exit 1
elif [ ! -f "$option_value" ]; then
show_header
echo "$0: error: $option_value is not a file" >&2
exit 1
elif [ ! -r "$option_value" ]; then
show_header
echo "$0: error: couldn't read $option_value (are you root?)" >&2
exit 1
fi
echo "$option_value"
exit 0
}
while [ -n "$1" ]; do
if [ "$1" = "--kernel" ]; then
opt_kernel=$(parse_opt_file kernel "$2"); ret=$?
[ $ret -ne 0 ] && exit 255
shift 2
opt_live=0
elif [ "$1" = "--config" ]; then
opt_config=$(parse_opt_file config "$2"); ret=$?
[ $ret -ne 0 ] && exit 255
shift 2
opt_live=0
elif [ "$1" = "--map" ]; then
opt_map=$(parse_opt_file map "$2"); ret=$?
[ $ret -ne 0 ] && exit 255
shift 2
opt_live=0
elif [ "$1" = "--arch-prefix" ]; then
opt_arch_prefix="$2"
shift 2
elif [ "$1" = "--live" ]; then
opt_live_explicit=1
shift
elif [ "$1" = "--no-color" ]; then
opt_no_color=1
shift
elif [ "$1" = "--no-sysfs" ]; then
opt_no_sysfs=1
shift
elif [ "$1" = "--sysfs-only" ]; then
opt_sysfs_only=1
shift
elif [ "$1" = "--coreos" ]; then
opt_coreos=1
shift
elif [ "$1" = "--coreos-within-toolbox" ]; then
# don't use directly: used internally by --coreos
opt_coreos=0
shift
elif [ "$1" = "--paranoid" ]; then
opt_paranoid=1
shift
elif [ "$1" = "--hw-only" ]; then
opt_hw_only=1
shift
elif [ "$1" = "--no-hw" ]; then
opt_no_hw=1
shift
elif [ "$1" = "--no-explain" ]; then
# deprecated, kept for compatibility
opt_explain=0
shift
elif [ "$1" = "--update-mcedb" ]; then
update_mcedb
exit $?
elif [ "$1" = "--update-builtin-mcedb" ]; then
update_mcedb builtin
exit $?
elif [ "$1" = "--explain" ]; then
opt_explain=1
shift
elif [ "$1" = "--batch" ]; then
opt_batch=1
opt_verbose=0
opt_no_color=1
shift
case "$1" in
text|short|nrpe|json|prometheus) opt_batch_format="$1"; shift;;
--*) ;; # allow subsequent flags
'') ;; # allow nothing at all
*)
echo "$0: error: unknown batch format '$1'" >&2
echo "$0: error: --batch expects a format from: text, nrpe, json" >&2
exit 255
;;
esac
elif [ "$1" = "-v" ] || [ "$1" = "--verbose" ]; then
opt_verbose=$(( opt_verbose + 1 ))
shift
elif [ "$1" = "--cve" ]; then
if [ -z "$2" ]; then
echo "$0: error: option --cve expects a parameter, supported CVEs are: $supported_cve_list" >&2
exit 255
fi
selected_cve=$(echo "$supported_cve_list" | grep -iwo "$2")
if [ -n "$selected_cve" ]; then
opt_cve_list="$opt_cve_list $selected_cve"
opt_cve_all=0
else
echo "$0: error: unsupported CVE specified ('$2'), supported CVEs are: $supported_cve_list" >&2
exit 255
fi
shift 2
elif [ "$1" = "--vmm" ]; then
if [ -z "$2" ]; then
echo "$0: error: option --vmm (auto, yes, no)" >&2
exit 255
fi
case "$2" in
auto) opt_vmm=-1;;
yes) opt_vmm=1;;
no) opt_vmm=0;;
*) echo "$0: error: expected one of (auto, yes, no) to option --vmm instead of '$2'" >&2; exit 255;;
esac
shift 2
elif [ "$1" = "--variant" ]; then
if [ -z "$2" ]; then
echo "$0: error: option --variant expects a parameter (1, 2, 3, 3a, 4 or l1tf)" >&2
exit 255
fi
case "$2" in
1) opt_cve_list="$opt_cve_list CVE-2017-5753"; opt_cve_all=0;;
2) opt_cve_list="$opt_cve_list CVE-2017-5715"; opt_cve_all=0;;
3) opt_cve_list="$opt_cve_list CVE-2017-5754"; opt_cve_all=0;;
3a) opt_cve_list="$opt_cve_list CVE-2018-3640"; opt_cve_all=0;;
4) opt_cve_list="$opt_cve_list CVE-2018-3639"; opt_cve_all=0;;
l1tf) opt_cve_list="$opt_cve_list CVE-2018-3615 CVE-2018-3620 CVE-2018-3646"; opt_cve_all=0;;
*)
echo "$0: error: invalid parameter '$2' for --variant, expected either 1, 2, 3, 3a, 4 or l1tf" >&2;
exit 255
;;
esac
shift 2
elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
show_header
show_usage
exit 0
elif [ "$1" = "--version" ]; then
opt_no_color=1
show_header
exit 0
elif [ "$1" = "--disclaimer" ]; then
show_header
show_disclaimer
exit 0
else
show_header
show_usage
echo "$0: error: unknown option '$1'"
exit 255
fi
done
show_header
if [ "$opt_no_sysfs" = 1 ] && [ "$opt_sysfs_only" = 1 ]; then
_warn "Incompatible options specified (--no-sysfs and --sysfs-only), aborting"
exit 255
fi
if [ "$opt_no_hw" = 1 ] && [ "$opt_hw_only" = 1 ]; then
_warn "Incompatible options specified (--no-hw and --hw-only), aborting"
exit 255
fi
# print status function
pstatus()
{
if [ "$opt_no_color" = 1 ]; then
_info_nol "$2"
else
case "$1" in
red) col="\033[41m\033[30m";;
green) col="\033[42m\033[30m";;
yellow) col="\033[43m\033[30m";;
blue) col="\033[44m\033[30m";;
*) col="";;
esac
_info_nol "$col $2 \033[0m"
fi
[ -n "$3" ] && _info_nol " ($3)"
_info
unset col
}
# Print the final status of a vulnerability (incl. batch mode)
# Arguments are: CVE UNK/OK/VULN description
pvulnstatus()
{
pvulnstatus_last_cve="$1"
if [ "$opt_batch" = 1 ]; then
case "$1" in
CVE-2017-5753) aka="SPECTRE VARIANT 1";;
CVE-2017-5715) aka="SPECTRE VARIANT 2";;
CVE-2017-5754) aka="MELTDOWN";;
CVE-2018-3640) aka="VARIANT 3A";;
CVE-2018-3639) aka="VARIANT 4";;
CVE-2018-3615) aka="L1TF SGX";;
CVE-2018-3620) aka="L1TF OS";;
CVE-2018-3646) aka="L1TF VMM";;
*) echo "$0: error: invalid CVE '$1' passed to pvulnstatus()" >&2; exit 255;;
esac
case "$opt_batch_format" in
text) _echo 0 "$1: $2 ($3)";;
short) short_output="${short_output}$1 ";;
json)
case "$2" in
UNK) is_vuln="null";;
VULN) is_vuln="true";;
OK) is_vuln="false";;
*) echo "$0: error: unknown status '$2' passed to pvulnstatus()" >&2; exit 255;;
esac
json_output="${json_output:-[}{\"NAME\":\"$aka\",\"CVE\":\"$1\",\"VULNERABLE\":$is_vuln,\"INFOS\":\"$3\"},"
;;
nrpe) [ "$2" = VULN ] && nrpe_vuln="$nrpe_vuln $1";;
prometheus)
prometheus_output="${prometheus_output:+$prometheus_output\n}specex_vuln_status{name=\"$aka\",cve=\"$1\",status=\"$2\",info=\"$3\"} 1"
;;
*) echo "$0: error: invalid batch format '$opt_batch_format' specified" >&2; exit 255;;
esac
fi
# always fill global_* vars because we use that do decide the program exit code
case "$2" in
UNK) global_unknown="1";;
VULN) global_critical="1";;
OK) ;;
*) echo "$0: error: unknown status '$2' passed to pvulnstatus()" >&2; exit 255;;
esac
# display info if we're not in quiet/batch mode
vulnstatus="$2"
shift 2
_info_nol "> \033[46m\033[30mSTATUS:\033[0m "
case "$vulnstatus" in
UNK) pstatus yellow 'UNKNOWN' "$@"; final_summary="$final_summary \033[43m\033[30m$pvulnstatus_last_cve:??\033[0m";;
VULN) pstatus red 'VULNERABLE' "$@"; final_summary="$final_summary \033[41m\033[30m$pvulnstatus_last_cve:KO\033[0m";;
OK) pstatus green 'NOT VULNERABLE' "$@"; final_summary="$final_summary \033[42m\033[30m$pvulnstatus_last_cve:OK\033[0m";;
*) echo "$0: error: unknown status '$vulnstatus' passed to pvulnstatus()" >&2; exit 255;;
esac
}
# The 3 below functions are taken from the extract-linux script, available here:
# https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux
# The functions have been modified for better integration to this script
# The original header of the file has been retained below
# ----------------------------------------------------------------------
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image
#
# Inspired from extract-ikconfig
# (c) 2009,2010 Dick Streefland <dick@streefland.net>
#
# (c) 2011 Corentin Chary <corentin.chary@gmail.com>
#
# Licensed under the GNU General Public License, version 2 (GPLv2).
# ----------------------------------------------------------------------
kernel=''
kernel_err=''
check_kernel()
{
_file="$1"
_desperate_mode="$2"
# checking the return code of readelf -h is not enough, we could get
# a damaged ELF file and validate it, check for stderr warnings too
_readelf_warnings=$("${opt_arch_prefix}readelf" -S "$_file" 2>&1 >/dev/null | tr "\n" "/"); ret=$?
_readelf_sections=$("${opt_arch_prefix}readelf" -S "$_file" 2>/dev/null | grep -c -e data -e text -e init)
_kernel_size=$(stat -c %s "$_file" 2>/dev/null || stat -f %z "$_file" 2>/dev/null || echo 10000)
_debug "check_kernel: ret=$? size=$_kernel_size sections=$_readelf_sections warnings=$_readelf_warnings"
if [ -n "$_desperate_mode" ]; then
if "${opt_arch_prefix}strings" "$_file" | grep -Eq '^Linux version '; then
_debug "check_kernel (desperate): ... matched!"
return 0
else
_debug "check_kernel (desperate): ... invalid"
fi
else
if [ $ret -eq 0 ] && [ -z "$_readelf_warnings" ] && [ "$_readelf_sections" -gt 0 ]; then
if [ "$_kernel_size" -ge 100000 ]; then
_debug "check_kernel: ... file is valid"
return 0
else
_debug "check_kernel: ... file seems valid but is too small, ignoring"
fi
else
_debug "check_kernel: ... file is invalid"
fi
fi
return 1
}
try_decompress()
{
# The obscure use of the "tr" filter is to work around older versions of
# "grep" that report the byte offset of the line instead of the pattern.
# Try to find the header ($1) and decompress from here
_debug "try_decompress: looking for $3 magic in $6"
for pos in $(tr "$1\n$2" "\n$2=" < "$6" | grep -abo "^$2")
do
_debug "try_decompress: magic for $3 found at offset $pos"
if ! command -v "$3" >/dev/null 2>&1; then
kernel_err="missing '$3' tool, please install it, usually it's in the '$5' package"
return 0
fi
pos=${pos%%:*}
# shellcheck disable=SC2086
tail -c+$pos "$6" 2>/dev/null | $3 $4 > "$kerneltmp" 2>/dev/null; ret=$?
if [ ! -s "$kerneltmp" ]; then
# don't rely on $ret, sometimes it's != 0 but worked
# (e.g. gunzip ret=2 just means there was trailing garbage)
_debug "try_decompress: decompression with $3 failed (err=$ret)"
elif check_kernel "$kerneltmp" "$7"; then
kernel="$kerneltmp"
_debug "try_decompress: decompressed with $3 successfully!"
return 0
elif [ "$3" != "cat" ]; then
_debug "try_decompress: decompression with $3 worked but result is not a kernel, trying with an offset"
[ -z "$kerneltmp2" ] && kerneltmp2=$(mktemp /tmp/kernel-XXXXXX)
cat "$kerneltmp" > "$kerneltmp2"
try_decompress '\177ELF' xxy 'cat' '' cat "$kerneltmp2" && return 0
else