-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathloaders-update
589 lines (537 loc) · 15.8 KB
/
loaders-update
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
#!/bin/sh
# Copyright (c) 2024, Emrion
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#### Functions & data ####
readonly Version="loaders-update v1.2.0"
# Available modes
readonly WMN="shoot-me"
readonly SMN="show-me"
# Loaders file names
readonly EFILOADER="loader.efi"
readonly PMBR="pmbr"
readonly GPTBOOT="gptboot"
readonly GPTZFSBOOT="gptzfsboot"
# Mount point for efi partitions
EMD="/mnt"
# Loaders source dir
SLD="/boot"
# If the root file system must be checked or not (BIOS loaders update)
USE_RFS=true
# If the content of the freebsd-boot partition(s) must be checked or not
USE_FBP=true
# We prefer to use sysctl kern.disk to list the disks
USE_KERNDISK=true
Usage()
{
local exn
exn=$(basename "$0")
echo "Usage: $exn mode [-fgr] [-m efi_mount_dir] [-s loaders_source_dir]"
echo "mode can be one of:"
echo " $SMN: just show the commands to type, change nothing."
echo " $WMN: may update the loader(s), but ask for confirmation before each one."
echo "Options:"
echo " -f: won't check the freebsd-boot content for BIOS loaders update."
echo " -g: force to use 'gpart show' for disk detection."
echo " -r: won't check the root file system for BIOS loaders update."
exit 1
}
TestRoot()
{
if [ "$(id -u)" -ne 0 ]; then
echo "Consider to run this utility as root."
fi
}
# TestCmd cmd
# Warning: the command is executed, not only tested
TestCmd()
{
local r
r="$(eval "$1" 2>&1)"
if [ $? -ne 0 ]; then
echo "$r"
fi
}
# PossiblyRun CmdToExecute VarUpdatable VarUpdated
PossiblyRun()
{
local cmd rep
local VarUpdatable # Counts what it can be updated
local VarUpdated # Counts what it has been updated
cmd="$1"
VarUpdatable="$2"
VarUpdated="$3"
if ($SM); then # SM is true if Shoot-me Mode is selected
unset rep
echo "About to execute: $cmd"
read -p "Are you sure (y/N)? " rep
case "$rep" in
[yY]*) ;;
*) echo "Nothing has been writted."
eval "$VarUpdatable=\$(($VarUpdatable+1))"
return;;
esac
eval "$cmd"
if [ $? -eq 0 ]; then
eval "$VarUpdated=\$(($VarUpdated+1))"
else
eval "$VarUpdatable=\$(($VarUpdatable+1))"
Err=$((Err+1))
fi
else
echo "Would run: $cmd"
eval "$VarUpdatable=\$(($VarUpdatable+1))"
fi
}
# CheckSourceFile SourceFile
CheckSourceFile()
{
if ! [ -e "$1" ]; then
echo "Cannot find $1 on your system! Exiting."
exit 1
fi
}
# CopyEfiLoader SourceFile TargetFile
CopyEfiLoader()
{
local SourceFile TargetFile TFmd5
# global FBSDL: is a loader found in TargetPartition?
SourceFile="$1"
TargetFile="$2"
# Checks if it's an executable efi file
if [ -z "$(file -b "$TargetFile" | grep PE32+)" ]; then
return
fi
# Checks if the file is a FreeBSD loader
if [ "$(grep -c FreeBSD "$TargetFile")" -gt 0 ]; then
FBSDL=true
TFmd5="$(md5 -q "$TargetFile")"
if [ "$TFmd5" = "$Efimd5" ]; then
echo "EFI loader $TargetFile is up-to-date."
else
PossiblyRun "cp $SourceFile $TargetFile" UpEFI mEFI
fi
fi
}
# EfiUpdate SourceFile TargetPartition MountedDest
EfiUpdate()
{
local SourceFile TargetPartition MountedDest
local mt cmd e lf f
# global FBSDL: is a loader found in TargetPartition?
SourceFile="$1"
TargetPartition="$2"
MountedDest="$3"
mt=false
# If $MountedDest is empty, the target partition isn't mounted, we must mount it
if [ -z "$MountedDest" ]; then
MountedDest="$EMD"
cmd="mount -t msdosfs /dev/$TargetPartition $MountedDest"
echo "$cmd"
e="$(TestCmd "$cmd")"
if [ -n "$e" ]; then
echo "$e"
echo "Cannot mount $TargetPartition, so cannot looking for its loader(s)."
case "$e" in
*Invalid*) echo "Is this partition formatted?"
Ferr=$((Ferr+1));;
*) Err=$((Err+1));;
esac
return
fi
mt=true # Don't forget to umount before returning
fi
FBSDL=false
lf="$(find "$MountedDest" -type f)"
for f in $lf; do
CopyEfiLoader "$SourceFile" "$f"
done
if ! ("$FBSDL"); then
echo "There is no FreeBSD loader in $TargetPartition"
fi
if ($mt); then
cmd="umount $MountedDest"
echo "$cmd"
eval "$cmd"
fi
}
# SelectBiosLoader (ufs or zfs)
SelectBiosLoader()
{
# global BiosLoader BCmd5 LBC # Path/name, MD5 and size of the selected source loader
if [ "$1" = "zfs" ]; then
BiosLoader="$Szfs"
BCmd5="$ZfsMD5"
LBC="$Lzfs"
else
BiosLoader="$Sgpt"
BCmd5="$GptMD5"
LBC="$Lgpt"
fi
}
BiosUpdate()
{
local Spmbr PmbrMD5 # Path/name & md5 of source pmbr
# global Szfs ZfsMD5 Lzfs # Path/name, md5 and size of source gptzfsboot
# global Sgpt GptMD5 Lgpt # Path/name, md5 and size of source gptboot
# global BiosLoader BCmd5 LBC # Path/name, MD5 and size of the selected source loader
local d index p # Disk, index and partition currenlty examined
local rBTX rZFS rzfs # Results / offset of strings search
local eof # End of file offset in the freebsd-boot partition
local pfs # File system managed by the loader inside the partition
# global rfs # Root file system
local CpmbrMD5 CfbbMD5 # MD5 of the current disk pmbr & partition content
local bcode pcode i
# Check the source pmbr
Spmbr="$SLD/$PMBR" # Path/name of the source loader
CheckSourceFile "$Spmbr"
PmbrMD5="$(head -c 446 "$Spmbr" | md5 -q)" # MD5 computation
# Check the sources BIOS loaders gptzfsboot & gptboot
Szfs="$SLD/$GPTZFSBOOT"
CheckSourceFile "$Szfs"
ZfsMD5="$(md5 -q "$Szfs")"
Lzfs="$(stat -f "%z" "$Szfs")" # Size in bytes of the file
Sgpt="$SLD/$GPTBOOT"
CheckSourceFile "$Sgpt"
GptMD5="$(md5 -q "$Sgpt")"
Lgpt="$(stat -f "%z" "$Sgpt")"
echo
i=1
for d in $BIOSD; do
echo "Examining $d..."
# Retrieving the corresponding partition index in BIOSI
index=$(eval "echo \$BIOSI | awk '{ print \$$i }'")
i=$((i+1))
p="/dev/${d}p$index"
# Is there a BTX loader in this partition?
rBTX="$(grep -c 'BTX halted' "$p")"
# Check first if the partition is readable
if [ $? -gt 1 ]; then
echo "Error during access to $p. Won't update these loaders."
Err=$((Err+1))
continue
fi
if ("$USE_FBP"); then
if [ "$rBTX" -eq 0 ]; then
echo "There is currently no loader in $p."
echo "Cannot update without option -f."
Ferr=$((Ferr+1))
continue
else
# Try to determine whether the partition content is gptboot or gptzfsboot
# $rZFS is set at the offset where the first string 'ZFS' has been detected
rZFS="$(grep -abo ZFS "$p" | head -n1 | cut -f1 -d ':')"
rzfs="$(grep -c zfs "$p")" # Detection of the string 'zfs'
# $eof is near the end of the last file copied into this partition (sequence of 24 NUL bytes)
eof=$(cat "$p" | hexdump -v -e '/1 "%02x"' | grep -bo -E '0{48}' | head -n1 | cut -f1 -d ':')
if [ -z "$eof" ]; then
echo "Cannot find eof in $p. It won't be analyzed."
echo "Use option -f if you want to update it."
Ferr=$((Ferr+1))
continue
fi
eof=$((eof/2)) # Each byte is 2 characters in hexadecimal
# The comparison of $rZFS versus $eof allows to detect a special case
# where a partition has been filled with gptzfsboot, then filled with gptboot
# in a second time. As the size of gptboot is << to the one of gptzfsboot,
# the strings 'ZFS' and 'zfs' remain in the partition and would be falsely detected
# as gptzfsboot otherwise.
if [ -n "$rZFS" ] && [ "$rzfs" -gt 0 ] && [ "$rZFS" -lt "$eof" ]; then
pfs="zfs" # gptzfsboot
else
pfs="ufs" # gptboot
fi
SelectBiosLoader "$pfs"
fi
fi
if ($USE_RFS) && ($USE_FBP) && ! [ "$rfs" = "$pfs" ]; then
echo "There is a mismatch between the root fs and the current loader."
echo "Root fs: $rfs / Partition has: $pfs."
echo "--> No loader update on $p."
Ferr=$((Ferr+1))
continue
fi
if ! ($USE_FBP) && ($USE_RFS); then
SelectBiosLoader "$rfs"
fi
CpmbrMD5="$(head -c 446 /dev/"$d" | md5 -q)" # MD5 of current disk pmbr
bcode="-b $Spmbr "
if [ "$CpmbrMD5" = "$PmbrMD5" ]; then
echo "The pmbr on this disk is up-to-date."
unset bcode
fi
# MD5 of current freebsd-boot partition content
CfbbMD5="$(head -c "$LBC" /dev/"${d}"p"$index" | md5 -q)"
pcode="-p $BiosLoader -i $index "
if ! ("$USE_RFS"); then
echo "Loader in ${d}p$index is $(basename "$BiosLoader")."
fi
if [ "$CfbbMD5" = "$BCmd5" ]; then
echo "The freebsd-boot partition ${d}p$index is up-to-date."
unset pcode
fi
if [ -n "$bcode" ] || [ -n "$pcode" ]; then
PossiblyRun "gpart bootcode $bcode$pcode$d" UpBIOS mBIOS
fi
done
}
#### Main ####
# To avoid some errors related to grep
unset GREP_OPTIONS
echo "$Version"
case "$1" in
"$WMN") SM=true
if [ "$(id -u)" -ne 0 ]; then
echo "You need to be root to operate in $WMN mode."
return 1
fi;;
"$SMN") SM=false;;
*) Usage;;
esac
shift
while getopts "m:s:rgf" opt; do
case ${opt} in
m) EMD="${OPTARG}";;
s) SLD="${OPTARG}";;
r) USE_RFS=false;;
g) USE_KERNDISK=false;;
f) USE_FBP=false;;
*) Usage;;
esac
done
if ! ($USE_RFS) && ! ($USE_FBP); then
echo "Options -r and -f are mutually exclusive. Exiting."
return 1
fi
if ! [ -d "$EMD" ]; then
echo "EFI mount point doesn't exist: $EMD"
return 1
fi
if ! [ -d "$SLD" ]; then
echo "Loaders source dir doesn't exist: $SLD"
return 1
fi
a="$(sysctl -n hw.machine)"
if [ $? -ne 0 ]; then
echo "Cannot get the architecture. Exiting."
echo
return 1
fi
if ! [ "$a" = "amd64" ]; then
echo "This utility works only with amd64 architecture. Exiting."
echo
return 1
fi
# Check some cmd to detect a problem with hardened systems
em=$(TestCmd "mount -p")
egs=$(TestCmd "gpart show")
# Get the list of disks
if ($USE_KERNDISK); then
LD="$(sysctl -n kern.disks)"
if [ $? -ne 0 ]; then
echo "Cannot get the list of disks."
echo "'sysctl kern.disks' doesn't work. Exiting."
echo
return 1
fi
else
if [ -z "$egs" ]; then
LD="$(gpart show | grep GPT | cut -wf4)"
else
echo "Cannot get the list of disks."
echo "'gpart show' doesn't work. Exiting."
echo "$egs"
echo
return 1
fi
fi
if [ -z "$LD" ]; then
echo "No disk has been detected. Exiting."
echo
return 1
fi
echo
# Search for efi & freebsd-boot partitions
GPT=false
for d in $LD; do
# To avoid an error if the disk is amovible and absent (cdrom)
gpart show "$d" > /dev/null 2>&1
if [ $? -eq 0 ]; then
gp="$(gpart show "$d" | head -n1 | awk '{ print $5 }')"
if [ "$gp" = "GPT" ]; then
GPT=true
# Looking for a efi type partition
p="$(gpart show -p "$d" | grep efi | awk '{ print $3 }')"
if [ -n "$p" ]; then
EFIP="$EFIP $p"
fi
# Looking for a freebsd-boot type partition
pi="$(gpart show "$d" | grep freebsd-boot | awk '{ print $3 }')"
if [ -n "$pi" ]; then
# BIOSD lists the disks and BIOSI the corresponding indexes
BIOSD="$BIOSD $d"
BIOSI="$BIOSI $pi"
fi
fi
fi
done
if ! ($GPT); then
echo "This machine seems to have no disk with GPT scheme. Exiting."
echo
return 1
fi
Err=0 # Errors that may be resolved by running this script as root
Ferr=0 # Other errors
UpEFI=0 # Updatable EFI loaders
UpBIOS=0 # Updatable BIOS loaders
mEFI=0 # Updated EFI loaders
mBIOS=0 # Updated BIOS loaders
if [ -n "$EFIP" ]; then
echo "One or more efi partition(s) have been found."
CheckSourceFile "$SLD/$EFILOADER"
Efimd5="$(md5 -q "$SLD/$EFILOADER")"
echo
if [ -n "$em" ]; then
Err=$((Err+1))
echo "Cannot work on EFI loaders because 'mount -p' doesn't work."
echo "-> $em"
else
for p in $EFIP; do
echo "Examining $p..."
# Try to see if the efi partition is already mounted
# Search first by the geom name (e.g. ada0p1)
m="$(mount -p | grep "$p" | awk '{ print $2 }')"
# If not found, try by the label name (e.g. efiboot0)
if [ -z "$m" ]; then
if [ -n "$egs" ]; then
echo "$egs"
Err=$((Err+1))
continue
fi
lp="$(gpart show -pl | grep "$p" | awk '{ print $4 }')"
m="$(mount -p | grep "$lp" | awk '{ print $2 }')"
fi
if [ -n "$m" ]; then
echo "Efi partition $p is already mounted in $m."
fi
EfiUpdate "$SL" "$p" "$m"
done
fi
echo
fi
if [ -n "$BIOSD" ]; then
echo "One or more freebsd-boot partition(s) have been found."
if ! ($USE_FBP); then
echo "The content of the freebsd-boot partition(s) won't be analyzed."
fi
# Check the root file system
if ($USE_RFS); then
if [ -z "$em" ]; then
rfs="$(mount -p | grep "\s/\s" | awk '{ print $3 }')"
if [ -n "$rfs" ]; then
echo "The root file system is $rfs."
BiosUpdate
else
echo "Cannot determine the root file system."
echo "Won't work on the freebsd-boot partition(s)."
Err=$((Err+1))
fi
else
echo "The root fs must be checked, but 'mount -p' doesn't work."
echo "-> $em"
Err=$((Err+1))
fi
else
echo "The root file system won't be checked."
BiosUpdate
fi
echo
fi
echo "-------------------------------"
BM=$(sysctl -n machdep.bootmethod)
if [ $? -ne 0 ]; then
echo "Cannot get the boot method."
else
echo "Your current boot method is $BM."
# Try to figure out partition & efi file on which the system is currently booting
if [ "$BM" = "UEFI" ] && [ "$(id -u)" -eq 0 ]; then
eb="$(TestCmd efibootmgr)"
if [ -z "$eb" ]; then
cb="$(efibootmgr -v | grep +)"
echo -n "Boot device: "
# It may be impossible for efibootmgr to convert to a unix path
efibootmgr -E > /dev/null 2>&1
if [ $? -gt 0 ]; then
# Just print the line reported by efibootmgr
echo "$cb" | cut -f3- -d ' '
else
# There, we can identify the boot partition
cbp="$(efibootmgr -E)"
# Case where the boot partition is a label, convert it to a geom partition
if [ -n "$(echo "$cbp" | grep gpt/)" ]; then
lp="$(echo "$cbp" | cut -f2 -d /)"
if [ -n "$lp" ] && [ -z "$egs" ]; then
cbp="$(gpart show -lp | grep "$lp" | awk '{ print $3 }')"
fi
fi
echo "$cbp $(echo "$cb" |cut -f2 -d /)"
fi
else
echo "Cannot use efibootmgr."
echo "-> $eb"
fi
fi
fi
if [ -z "$EFIP" ] && [ -z "$BIOSD" ]; then
echo "Found no efi partition and no freebsd-boot partition."
echo "Nothing seems updatable."
else
if [ "$mEFI" -eq 0 ] && [ "$mBIOS" -eq 0 ] && [ "$UpEFI" -eq 0 ] && [ "$UpBIOS" -eq 0 ]; then
echo "One or more target partition(s) have been found..."
if [ "$Err" -gt 0 ]; then
echo "But no loader seems to be updatable."
echo "$Err error(s) occured during the scan."
TestRoot
else
if [ "$Ferr" -eq 0 ]; then
echo "All loaders are up-to-date."
fi
fi
else
if [ "$UpEFI" -gt 0 ]; then
echo "Updatable EFI loader: $UpEFI"
fi
if [ "$UpBIOS" -gt 0 ]; then
echo "Updatable BIOS loader: $UpBIOS"
fi
if [ "$mEFI" -gt 0 ]; then
echo "Updated EFI loader: $mEFI"
fi
if [ "$mBIOS" -gt 0 ]; then
echo "Updated BIOS loader: $mBIOS"
fi
if [ "$Err" -gt 0 ]; then
echo "One or more loaders may be updatable, but encountered $Err error(s)."
TestRoot
fi
fi
if [ "$Ferr" -gt 0 ]; then
echo "$Ferr serious error(s) occured. See texts above."
fi
fi
echo "-------------------------------"