github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/exp/disk_unlock/disk_unlock.go (about) 1 // Copyright 2022 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // The disk_unlock command is used to unlock a disk drive with a 6 // HSS-derived password, and rescan the drive to enumerate the 7 // unlocked partitions. 8 package main 9 10 import ( 11 "flag" 12 "fmt" 13 "io" 14 "log" 15 "os" 16 "path/filepath" 17 18 "github.com/mvdan/u-root-coreutils/pkg/hsskey" 19 "github.com/mvdan/u-root-coreutils/pkg/mount/block" 20 "github.com/mvdan/u-root-coreutils/pkg/mount/scuzz" 21 ) 22 23 const ( 24 // Master Password ID for SKM-based unlock. 25 skmMPI = 0x0601 26 ) 27 28 var ( 29 disk = flag.String("disk", "/dev/sda", "The disk to be unlocked") 30 verbose = flag.Bool("d", false, "print debug output") 31 verboseNoSanitize = flag.Bool("dangerously-disable-sanitize", false, "Print sensitive information - this should only be used for testing!") 32 noRereadPartitions = flag.Bool("no-reread-partitions", false, "Only attempt to unlock the disk, don't re-read the partition table.") 33 retries = flag.Int("num_retries", 1, "Number of times to retry password if unlocking fails for any reason other than the password being wrong.") 34 salt = flag.String("salt", hsskey.DefaultPasswordSalt, "Salt for password generation") 35 ) 36 37 func verboseLog(msg string) { 38 if *verbose { 39 log.Print(msg) 40 } 41 } 42 43 // writeFile is ioutil.WriteFile but disallows creating new file 44 func writeFile(filename string, contents string) error { 45 file, err := os.OpenFile(filename, os.O_WRONLY|os.O_SYNC, 0) 46 if err != nil { 47 return err 48 } 49 wlen, err := file.WriteString(contents) 50 if err != nil && wlen < len(contents) { 51 err = io.ErrShortWrite 52 } 53 // If Close() fails this likely indicates a write failure. 54 if errClose := file.Close(); err == nil { 55 err = errClose 56 } 57 return err 58 } 59 60 func main() { 61 flag.Parse() 62 63 // Open the disk. Read its identity, and use it to unlock the disk. 64 sgdisk, err := scuzz.NewSGDisk(*disk) 65 if err != nil { 66 log.Fatalf("failed to open disk %v: %v", *disk, err) 67 } 68 69 info, err := sgdisk.Identify() 70 if err != nil { 71 log.Fatalf("failed to read disk %v identity: %v", *disk, err) 72 } 73 74 verboseLog(fmt.Sprintf("Disk info for %s: %s", *disk, info.String())) 75 76 // Obtain 32 byte Host Secret Seed (HSS) from IPMI. 77 hssList, err := hsskey.GetAllHss(*verbose, *verboseNoSanitize) 78 if err != nil { 79 log.Fatalf("error getting HSS: %v", err) 80 } 81 82 if len(hssList) == 0 { 83 log.Fatalf("no HSS found - can't unlock disk.") 84 } 85 86 verboseLog(fmt.Sprintf("Found %d Host Secret Seeds.", len(hssList))) 87 88 switch { 89 case !info.SecurityStatus.SecurityEnabled(): 90 log.Printf("Disk security is not enabled on %v.", *disk) 91 return 92 case info.SecurityStatus.SecurityFrozen(): 93 // If the disk is frozen, its security state cannot be changed until the next 94 // power on or hardware reset. Disk unlock will fail anyways, so return. 95 // This is unlikely to occur, since someone would need to freeze the drive's 96 // security state between the last AC cycle and this code being run. 97 log.Print("Disk security is frozen. Power cycle the machine to unfreeze the disk.") 98 return 99 case !info.SecurityStatus.SecurityLocked(): 100 log.Print("Disk is already unlocked.") 101 return 102 case info.SecurityStatus.SecurityCountExpired(): 103 // If the security count is expired, this means too many attempts have been 104 // made to unlock the disk. Reset this with an AC cycle or hardware reset 105 // on the disk. 106 log.Fatalf("Security count expired on disk. Reset the password counter by power cycling the disk.") 107 case info.MasterRevision != skmMPI: 108 log.Fatalf("Disk is locked with unknown master password ID: %X (Do you have skm tools installed?)", info.MasterRevision) 109 } 110 111 // Try using each HSS to unlock the disk - only 1 should work. 112 unlocked := false 113 114 TryAllHSS: 115 for i, hss := range hssList { 116 key, err := hsskey.GenPassword(hss, *salt, info.Serial, info.Model) 117 if err != nil { 118 log.Printf("Couldn't generate password with HSS %d: %v", i, err) 119 continue 120 } 121 122 for r := 0; r < *retries; r++ { 123 if err := sgdisk.Unlock(string(key), false); err != nil { 124 log.Printf("Couldn't unlock disk with HSS %d: %v", i, err) 125 } else { 126 unlocked = true 127 break TryAllHSS 128 } 129 } 130 } 131 132 if unlocked { 133 log.Printf("Successfully unlocked disk %s.", *disk) 134 } else { 135 log.Fatalf("Failed to unlock disk %s with any HSS.", *disk) 136 } 137 138 if *noRereadPartitions { 139 return 140 } 141 142 // Rescans all LUNs/Channels/Targets on the scsi_host. This ensures the kernel 143 // updates the ATA driver to see the newly unlocked disk. 144 verboseLog("Rescanning scsi...") 145 if err := writeFile("/sys/class/scsi_host/host0/scan", "- - -"); err != nil { 146 log.Fatalf("couldn't rescan SCSI to reload newly unlocked disk: %v", err) 147 } 148 149 // Update partitions on the on the disk. 150 verboseLog("Reloading disk partitions...") 151 diskdev, err := block.Device(*disk) 152 if err != nil { 153 log.Fatalf("Could not find %s: %v", *disk, err) 154 } 155 156 if err := diskdev.ReadPartitionTable(); err != nil { 157 log.Fatalf("Could not re-read partition table: %v", err) 158 } 159 160 glob := filepath.Join("/sys/class/block", diskdev.Name+"*") 161 parts, err := filepath.Glob(glob) 162 if err != nil { 163 log.Fatalf("Could not find disk partitions: %v", err) 164 } 165 166 verboseLog(fmt.Sprintf("Found these %s unlocked partitions: %v", *disk, parts)) 167 }