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  }