when I said some things about Secure Boot in June of 2014. I …">

The Uncoöperative Organization

Programming and other human stuff.

Secure Boot — Fedora, RHEL, and Shim Upstream Maintenance: Government Involvement or Lack Thereof

You probably remember when I said some things about Secure Boot in June of 2014. I said there’d be more along those lines, and there is.

So there’s another statement about that here.

I’m going to try to remember to post a message like this once per month or so. If I miss one, keep an eye out, but maybe don’t get terribly suspicious unless I miss several in a row.

Note that there are parts of this chain I’m not a part of, and obviously linux distributions I’m not involved in that support Secure Boot. I encourage other maintainers to offer similar statements for their respective involvement.

The UEFI Security Databases

A little background

When we talk about UEFI Secure Boot, a lot of times we talk about what we call the whitelist and the blacklist. These databases go by many names—formally EFI_IMAGE_SECURITY_DATABASE and EFI_IMAGE_SECURITY_DATABASE1, sometimes d719b2cb-3d3a-4596-a3bc-dad00e67656f:db and d719b2cb-3d3a-4596-a3bc-dad00e67656f:dbx, but most often just db and dbx. These two1 databases, stored in UEFI Authenticated Variables2, constitute lists of which binaries can and cannot be executed within a UEFI environment when Secure Boot is enabled.

When a UEFI binary is loaded, the system first checks to see if any revocations in dbx are applicable to that binary. A dbx entry may apply in three ways—it may contain the hash of a specific binary, an X.509 certificate, or the hash of a certificate3. If any of these matches the binary in question, UEFI raises the error EFI_SECURITY_VIOLATION, and your binary will not go to space today.

If a binary successfully passes that hurdle, then the same verification method is processed with db, the whitelist, but with the opposite policy: if any entry describes the binary in question, verification has succeeded. If no entry does, EFI_SECURITY_VIOLATION is raised.

The need for updates

When a UEFI binary is discovered to have a serious security flaw which would allow a malicious user to circumvent Secure Boot, it becomes necessary to prevent it from running on machines. When this happens, the UEFI CA issues a dbx update. The mechanism for the update is a file that’s structured as a UEFI Authenticated Variable append. This file gets distributed as part of an OS update, and when the update is applied, the UEFI variable is updated.

One mechanism for doing this in Linux is via dbxtool. In Fedora, the dbxtool package includes the current dbx updates in /usr/share/dbxtool/DBXUpdate-2014-04-13-22-14-00.bin, as well as a systemd service to apply them during boot4. In the special case of Fedora’s shim being added to a blacklist, we would include a dependency on the fixed version of shim in the dbxtool package so that systems would remain bootable.

The structure of a `dbx` update

The dbx variable is composed of an array of EFI_SIGNATURE_LIST structures, each themselves containing an array of EFI_SIGNATURE_DATA entries. A dbx update is that same structure, but wrapped in a EFI_VARIABLE_AUTHENTICATION_2 structure to authenticate it. The definitions look like this5:

wincert.h
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
typedef struct {
        efi_guid_t      SignatureOwner;         // who owns this entry
        uint8_t         SignatureData[0];       // the data we want to
                                                // fish out of this thing
} EFI_SIGNATURE_DATA;

typedef struct {
        efi_guid_t      SignatureType;       // type of structure in
                                             // EFI_SIGNATURE_DATA.SignatureData
        uint32_t        SignatureListSize;   // Total size of the signature
                                             // list, including this header.
        uint32_t        SignatureHeaderSize; // Size of type-specific header
        uint32_t        SignatureSize;       // The size of each individual
                                             // EFI_SIGNATURE_DATA.SignatureData
                                             // in this list.
        // uint8_t      SignatureHeader[SignatureHeaderSize]
                                             // this is a header defined by
                                             // and for each specific
                                             // signature type.  Of course
                                             // none of them actually define
                                             // a header.
        // EFI_SIGNATURE_DATA[...][SignatureSize] // actual signature data
} EFI_SIGNATURE_LIST;

typedef struct {
        efi_guid_t        HashType;
        uint8_t           PublicKey[256];
        uint8_t           Signature[256];
} EFI_CERT_BLOCK_RSA_2048_SHA256;

typedef struct {
        uint32_t        dwLength;         // Length of this structure
        uint16_t        wRevision;        // Revision of this structure (2)
        uint16_t        wCertificateType; // The kind of signature this is
        //uint16_t      bCertificate[0];  // The signature data itself. This
                                          // is actually, and not the least
                                          // bit confusingly, the rest of
                                          // the WIN_CERTIFICATE_EFI_GUID
                                          // structure wrapping this one.
} WIN_CERTIFICATE;

#define WIN_CERT_TYPE_PKCS_SIGNED_DATA  0x0002
#define WIN_CERT_TYPE_EFI_PKCS115       0x0ef0
#define WIN_CERT_TYPE_EFI_GUID          0x0ef1

typedef struct {
        WIN_CERTIFICATE   Hdr;         // Info about which structure this is
        efi_guid_t        CertType;    // Type of certificate in CertData
        uint8_t           CertData[0]; // A certificate of some kind
} WIN_CERTIFICATE_EFI_GUID;

typedef struct {
        EFI_TIME                 TimeStamp; // monotonically increasing
                                            // timestamp to prevent replay
                                            // attacks.
        WIN_CERTIFICATE_EFI_GUID AuthInfo;  // Information about how to
                                            // authenticate this variable
                                            // against some KEK entry
} EFI_VARIABLE_AUTHENTICATION_2;

Conceptually, this means the structure we’ve got is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[ dbx update file:
  [ Authentication structure:
    [ monotonic number | timestamp ]
    [ WIN_CERTIFICATE Header ]
    [ Cert Type ]
    [ Certificate Data ] ]
  [ EFI_SIGNATURE_LIST:
    [ EFI_SIGNATURE_DATA ]
    [ EFI_SIGNATURE_DATA ]
    [ EFI_SIGNATURE_DATA ] ]
  [ EFI_SIGNATURE_LIST:
    [ EFI_SIGNATURE_DATA ]
    ...
    [ EFI_SIGNATURE_DATA ] ]
  ... ]

So a full update looks something like this:

auth2.TimeStamp
1
00000000  da 07 03 06 13 11 15 00  00 00 00 00 00 00 00 00  |................|

2010-03-06 19:17:21 GMT+0000

auth2.AuthInfo.Hdr.DwLength
1
00000010  bd 0c 00 00                                       |....            |

It is 0x00000cbd bytes long6.

auth2.AuthInfo.Hdr.wRevision
1
00000010              00 02                                 |    ..          |

It’s revision is 2. It is always revision 2.

auth2.AuthInfo.Hdr.wCertificateType
1
00000010                    f1 0e                           |      ..        |

0x0ef1, which as we see above is WIN_CERT_TYPE_EFI_GUID. The interesting bit here is that .bCertificate isn’t quite a real thing. This is actually describing that auth2.AuthInfo is a WIN_CERTIFICATE_EFI_GUID, and .bCertificate is actually the fields other than auth2.AuthInfo.Hdrauth2.AuthInfo.CertType and auth2.AuthInfo.CertData. Again, this shouldn’t confuse you at all. As a result, next, in the place of .bCertificate, we have:

auth2.AuthInfo.CertType
1
2
00000010                           9d d2 af 4a df 68 ee 49  |        ...J.h.I|
00000020  8a a9 34 7d 37 56 65 a7                           |..4}7Ve.        |

4aafd29d-68df-49ee-8aa9-347d375665a7, aka EFI_GUID_PKCS7_CERT, which means that .CertData is verified against EFI_SIGNATURE_DATA entries in the Key Exchange Keys database (KEK), but only those entries which are contained in EFI_SIGNATURE_LIST structures with .SignatureType of EFI_CERT_X509_GUID. Whew.

auth2.AuthInfo.CertData
1
2
3
4
5
00000020                           30 82 0c a1 02 01 01 31  |        0......1|
00000030  0f 30 0d 06 09 60 86 48  01 65 03 04 02 01 05 00  |.0...`.H.e......|
[ what an X.509 certificate looks like is left as an exercise for the reader ]
00000cb0  b6 2b 89 02 73 c4 86 57  83 6f 28 57 e0 12 cb 05  |.+..s..W.o(W....|
00000cc0  6d d0 3e 60 8f 85 9f dd  fc 46 ac 54 44           |m.>`.....F.TD   |

Yep, that’s an ASN.1 DER encoding of a PKCS-7 Certificate.
That’s the end of the EFI_VARIABLE_AUTHENTICATION_2 structure, and so on to the actual data:

EFI_SIGNATURE_LIST.SignatureType
1
2
00000cc0                                          26 16 c4  |             &..|
00000cd0  c1 4c 50 92 40 ac a9 41  f9 36 93 43 28           |.LP.@..A.6.C(   |

That’s c1c41626-504c-4092-aca9-41f936934328, aka EFI_GUID_SHA256

EFI_SIGNATURE_LIST.SignatureListSize
1
2
00000cd0                                          cc 01 00  |             ...|
00000ce0  00                                                |.               |

The list size is 0x00001cc bytes

EFI_SIGNATURE_LIST.SignatureHeaderSize:
1
00000ce0     00 00 00 00                                    | ....           |

This is actually always 0.

EFI_SIGNATURE_LIST.SignatureSize
1
00000ce0                 30 00 00  00                       |     0...       |

0x00000030 bytes. That’s 16 bytes for the efi_guid_t, and since .SignatureType was EFI_GUID_SHA256, 32 bytes of SHA-256 data.

EFI_SIGNATURE_DATA[0].SignatureOwner
1
2
00000ce0                              bd 9a fa 77 59 03 32  |.....0......wY.2|
00000cf0  4d bd 60 28 f4 e7 8f 78  4b                       |M.`(...xK       |

77fa9abd-0359-4d32-bd60-28f4e78f784b , which is the GUID Microsoft uses to identify themselves7

EFI_SIGNATURE_DATA[0].SignatureData
1
2
3
00000cf0                              80 b4 d9 69 31 bf 0d  |         ...i1..|
00000d00  02 fd 91 a6 1e 19 d1 4f  1d a4 52 e6 6d b2 40 8c  |.......O..R.m.@.|
00000d10  a8 60 4d 41 1f 92 65 9f  0a                       |.`MA..e..       |

This is the actual SHA-256 data. It goes on like this:

EFI_SIGNATURE_DATA[1..8]
1
2
3
4
5
6
7
8
9
10
11
12
00000d10                              bd 9a fa 77 59 03 32  |         ...wY.2|
00000d20  4d bd 60 28 f4 e7 8f 78  4b f5 2f 83 a3 fa 9c fb  |M.`(...xK./.....|
00000d30  d6 92 0f 72 28 24 db e4  03 45 34 d2 5b 85 07 24  |...r($...E4.[..$|
00000d40  6b 3b 95 7d ac 6e 1b ce  7a bd 9a fa 77 59 03 32  |k;.}.n..z...wY.2|
00000d50  4d bd 60 28 f4 e7 8f 78  4b c5 d9 d8 a1 86 e2 c8  |M.`(...xK.......|
00000d60  2d 09 af aa 2a 6f 7f 2e  73 87 0d 3e 64 f7 2c 4e  |-...*o..s..>d.,N|
00000d70  08 ef 67 79 6a 84 0f 0f  bd                       |..gyj....       |
...
00000e60                              bd 9a fa 77 59 03 32  |         ...wY.2|
00000e70  4d bd 60 28 f4 e7 8f 78  4b 53 91 c3 a2 fb 11 21  |M.`(...xKS.....!|
00000e80  02 a6 aa 1e dc 25 ae 77  e1 9f 5d 6f 09 cd 09 ee  |.....%.w..]o....|
00000e90  b2 50 99 22 bf cd 59 92  ea                       |.P."..Y..|

How the structure is used

When you apply a database update, you’re basically doing a SetVariable() call to UEFI with a couple of flags set:

1
2
3
4
5
6
  rc = efi_set_variable(EFI_IMAGE_SECURITY_DATABASE_GUID, "dbx", data, len,
                        EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS |
                        EFI_VARIABLE_APPEND_WRITE |
                        EFI_VARIABLE_BOOTSERVICE_ACCESS |
                        EFI_VARIABLE_RUNTIME_ACCESS |
                        EFI_VARIABLE_NON_VOLATILE);

These flags tell the firmware some crucial things – that this variable is authenticated with the EFI_VARIABLE_AUTHENTICATION_2 structure, that this is an append, that both Boot Services (i.e. the firmware) and Runtime Services (i.e. the OS) should have access to it, and that it should persist across a reboot. As a special case in the spec, an append has a special meaning for the UEFI security databases:

For variables with the GUID EFI_IMAGE_SECURITY_DATABASE_GUID (i.e. where the data buffer is formatted as EFI_SIGNATURE_LIST), the driver shallnot perform an append of EFI_SIGNATURE_DATA values that are already part of the existing variable value.
Note: This situation is not considered an error, and shall in itself not cause a status code other than EFI_SUCCESS to be returned or the timestamp associated with the variable not to be updated.

UEFI Specification section 7.2.1 Revision 2.4

As a result, what happens here is that the first time you write to dbx, any EFI_SIGNATURE_LIST structures and the EFI_SIGNATURE_DATA entries they contain get added to the variable, but not the EFI_VARIABLE_AUTHENTICATION_2 structure. Then when later dbx updates are issued, they contain a superset of the previous ones. When you apply them, the firmware only appends the difference to the variable.

Tools

Obviously a system this complex needs some tools. To manage these databases on linux, I’ve written a tool called dbxtool, which may be available in your linux distribution of choice. It can be used to apply dbx changes8, to list the contents of the UEFI Security Databases, and to list the contents of updates files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fenchurch:~$ dbxtool -l
   1: {microsoft} {sha256} 80b4d96931bf0d02fd91a61e19d14f1da452e66db2408ca8604d411f92659f0a
   2: {microsoft} {sha256} f52f83a3fa9cfbd6920f722824dbe4034534d25b8507246b3b957dac6e1bce7a
   3: {microsoft} {sha256} c5d9d8a186e2c82d09afaa2a6f7f2e73870d3e64f72c4e08ef67796a840f0fbd
   4: {microsoft} {sha256} 363384d14d1f2e0b7815626484c459ad57a318ef4396266048d058c5a19bbf76
   5: {microsoft} {sha256} 1aec84b84b6c65a51220a9be7181965230210d62d6d33c48999c6b295a2b0a06
   6: {microsoft} {sha256} e6ca68e94146629af03f69c2f86e6bef62f930b37c6fbcc878b78df98c0334e5
   7: {microsoft} {sha256} c3a99a460da464a057c3586d83cef5f4ae08b7103979ed8932742df0ed530c66
   8: {microsoft} {sha256} 58fb941aef95a25943b3fb5f2510a0df3fe44c58c95e0ab80487297568ab9771
   9: {microsoft} {sha256} 5391c3a2fb112102a6aa1edc25ae77e19f5d6f09cd09eeb2509922bfcd5992ea
  10: {microsoft} {sha256} d626157e1d6a718bc124ab8da27cbb65072ca03a7b6b257dbdcbbd60f65ef3d1
  11: {microsoft} {sha256} d063ec28f67eba53f1642dbf7dff33c6a32add869f6013fe162e2c32f1cbe56d
  12: {microsoft} {sha256} 29c6eb52b43c3aa18b2cd8ed6ea8607cef3cfae1bafe1165755cf2e614844a44
  13: {microsoft} {sha256} 90fbe70e69d633408d3e170c6832dbb2d209e0272527dfb63d49d29572a6f44c

As usual, there are a couple of places where vendors have not gotten everything quite right, and sometimes things fail to work correctly—but that’s for another post.


  1. There is actually a third in this set, dbt, which can be used in revocation processing.

  2. Strictly speaking these are only an analog to Authenticated Variables, which can only be appended to, replaced, or deleted by updates signed with the key that created them. Key database updates are instead controlled by a list of keys stored in another variable called KEK – the Key Exchange Keys. Otherwise the mechanism is the same.

  3. That is, the digest of the certificate’s TBSCertificate as defined in RFC 5280 section 4.1.1.1, using the digest specified in the database entry itself.

  4. This is currently disabled by default in Fedora. I’m looking at enabling this as an F22 feature. Getting these things right is important, and it takes time.

  5. I have left out the definitions of EFI_TIME and efi_guid_t; they are quite boring.

  6. Here dwLength includes the size of Hdr itself (that is, the size of dwLength, wRevision, and wCertificateType) as well as the data following it (bCertificate). Because we live in the best of all possible worlds, Authenticode signatures—the signatures on binaries themselves—use the same structure but only include the size of Hdr.bCertificate. This hasn’t ever confused anybody.

  7. Big congratulations to Acer, who used 55555555-5555-5555-5555-555555555555 for this in one of their db entries. Not only did they win the random number generator lottery to get that, but also experienced a minimum of 3 single bit errors, since the closest valid GUID to that is 55555555-5555-4555-9555-555555555555.

  8. Also theoretically db updates, dbt updates, and KEK updates, but those are much more rare.

Secure Boot — Fedora, RHEL, and Shim Upstream Maintenance: Government Involvement or Lack Thereof

You probably remember when I said some things about Secure Boot in June of 2014. I said there’d be more along those lines, and there is.

So there’s another statement about that here.

I’m going to try to remember to post a message like this once per month or so. If I miss one, keep an eye out, but maybe don’t get terribly suspicious unless I miss several in a row.

Note that there are parts of this chain I’m not a part of, and obviously linux distributions I’m not involved in that support Secure Boot. I encourage other maintainers to offer similar statements for their respective involvement.

Secure Boot — Fedora, RHEL, and Shim Upstream Maintenance: Government Involvement or Lack Thereof

You probably remember when I said some things about Secure Boot in June of 2014. I said there’d be more along those lines, and there is.

So there’s another statement about that here.

I’m going to try to remember to post a message like this once per month or so. If I miss one, keep an eye out, but maybe don’t get terribly suspicious unless I miss several in a row.

Note that there are parts of this chain I’m not a part of, and obviously linux distributions I’m not involved in that support Secure Boot. I encourage other maintainers to offer similar statements for their respective involvement.

Secure Boot — Fedora, RHEL, and Shim Upstream Maintenance: Government Involvement or Lack Thereof

You probably remember when I said some things about Secure Boot in June of 2014. I said there’d be more along those lines, and there is.

So there’s another statement about that here.

I’m going to try to remember to post a message like this once per month or so. If I miss one, keep an eye out, but maybe don’t get terribly suspicious unless I miss several in a row.

Note that there are parts of this chain I’m not a part of, and obviously linux distributions I’m not involved in that support Secure Boot. I encourage other maintainers to offer similar statements for their respective involvement.

Secure Boot — Fedora, RHEL, and Shim Upstream Maintenance: Government Involvement or Lack Thereof

You probably remember when I said some things about Secure Boot in June of 2014. I said there’d be more along those lines, and there is.

So there’s another statement about that here.

This message fixes a small grammar error from the previous one and adds a section about CentOS and Secure Boot.

I’m going to try to remember to post a message like this once per month or so. If I miss one, keep an eye out, but maybe don’t get terribly suspicious unless I miss several in a row.

Note that there are parts of this chain I’m not a part of, and obviously linux distributions I’m not involved in that support Secure Boot. I encourage other maintainers to offer similar statements for their respective involvement.

Secure Boot — Fedora, RHEL, and Shim Upstream Maintenance: Government Involvement or Lack Thereof

After the big kerfuffle with TrueCrypt, it seems clear to me that I need to make some statements about Secure Boot and any interaction with governments whose regimes I fall under.

So there’s a statement about that here.

I’m going to try to remember to post a message like this once per month or so. If I miss one, keep an eye out, but maybe don’t get terribly suspicious unless I miss several in a row.

Note that there are parts of this chain I’m not a part of, and obviously linux distributions I’m not involved in that support Secure Boot. I encourage other maintainers to offer similar statements for their respective involvement.

The EFI System Partition and the Default Boot Behavior

A lot of the time, we talk about creating a partition to serve as the EFI System Partition. This partition is mandated by the UEFI specification for several tasks. Adam covered what’s going on at a relatively high level on his recent blog post, and you should read the whole thing:

An ‘EFI system partition’ is really just any partition formatted with
one of the UEFI spec-defined variants of FAT and given a specific GPT
partition type to help the firmware find it. And the purpose of this is
just as described above: allow everyone to rely on the fact that the
firmware layer will definitely be able to read data from a pretty
‘normal’ disk partition.

There’s nothing truly special about an ESP. It isn’t an ESP because of the GPT GUID and label, nor because of the file system type. Those are how the firmware identifies a partition, and the file system it contains, as candidates to treat as the ESP, when it really needs to find one. The only factor in determining if a partition is the ESP is this: is the firmware attempting to use it as the ESP?

At the same time, the requirements for the ESP give us latitude; we know that we can use UEFI’s APIs to find correctly constructed FAT file systems, but there’s no need for those to be the ESP. In fact, even when we create multiple partitions with the ESP’s GUID and label, there’s no requirement that the firmware looks at more than one of them if it needs to find the ESP, and there’s no guarantee as to which one it will pick, either.

Normal booting

Thankfully, most of the time we don’t care. Under normal circumstances, the system isn’t actually looking for something to fill the role of the ESP. When the system starts up, it consults the BootOrder, which is just a list of 16-bit numbers, such as BootOrder: 0001,001A,0003. When this is found, the firmware will iterate the list, as you might expect. For each entry in the list, it looks for a corresponding Boot#### variable — Boot0001 for 0001, Boot001A for 0x001a, and so on, with the value from BootOrder rendered in capitalized hexadecimal. If it doesn’t find the variable, it continues to the next entry in BootOrder. If it does find the variable, it reads the contents of the variable to try to decide what to do. Generally speaking, what’s in there is a human friendly label, a Device Path like ACPI(a0341d0,0)PCI(1f,2)SATA(0,0,0)HD(1,800,64000,12029cda-8961-470d-82ba-aeb17dba91a5)File(\EFI\fedora\shim.efi), and optional data to be passed to whatever program is to be loaded. This Device Path reflects the series of parts that need to be initialized in order to find the ultimate media device HD(1,800,64000,12029cda-8961-470d-82ba-aeb17dba91a5)1, which specifies a partition number, the partition’s offset and size, and the partition’s GUID.

This partition need not be an “EFI System Partition” properly; the firmware doesn’t care if the GUID in the partition table is the ESP GUID, only that it matches the GUID in the Device Path. Likewise, it doesn’t care about the label. This file system may be the ESP, but there’s no requirement. The only real requirement here is that we have to use FAT, because it’s the only file system the firmware is guaranteed to know about.

At this point, the system firmware is going to initialize each device that’s part of that Device Path, in order, that hasn’t already been initialized. Some things will always be initialized — it’s unlikely that ACPI tables and the PCI root hub specified in this particular path2 weren’t already needed to get this far — but other things, like the SATA controller or its port 0, port multiplier 0, LUN 0 device, SATA(0,0,0)3, will probably need to be initialized. Once that is done, the firmware will examine the disk and see if it has a partition matching the HD() path above. If it finds that, and it contains a FAT file system, it looks for File(\EFI\fedora\shim.efi).

If any of that goes wrong, it moves on to the next Boot#### entry, and follow the exact same process, initializing one peripheral at a time until it can load a file from the FAT it finds.

What if everything we know is wrong?

If all of that fails, and there is no working boot entry found by traversing BootOrder, there are still things to do. At this point, the firmware will start initializing all peripherals it can find, in whatever order it happens to choose — for some peripherals this will be linear and predictable, for some it will start things all at once and they’ll respond in whatever order they become ready. For each media device it finds, it will check if it’s a removable device, such as optical media, or a fixed device like a hard drive. At this phase, the firmware only considers removable devices. On each of those, it looks for an EFI System Partition4 with a FAT file system using whatever method it knows for that type of media, and if it finds one, it checks for\EFI\BOOT\BOOTX64.EFI5.

Typically, if it finds a removable device it can boot, that’s because you have your OS install image connected in some way, which may well be an indicator that you’re trying to start the OS installer. It’s also possible you’ve installed to removable media, or you’re running off a live image.

If it doesn’t find suitable removable media, or if a failure condition is returned by the application it tries to start, it continues traversing all removable media until it has exhausted the possibilities.

If it still hasn’t found any removable media it can boot, the firmware will move on to fixed media. At this point, all media is probably discovered — you may have hot-plugged a USB stick or something, but don’t plan on that sort of action working reliably at this stage. Since there’s no real device or media discovery left to do, the firmware is going to iterate back over the devices once more, and will try to start each fixed media device in the same manner it previously did with the removable devices – it will look for an ESP, and try to start \EFI\BOOT\BOOTX64.efi. This is known as the “Default Boot Behavior”.

The Default Boot Behavior

On Fedora systems, and likely any other OS using shim, we handle the Default Boot Behavior through a utility shim provides named fallback.efi. Our signed shim package provides several files, all of which reside on whichever partition we’ve mounted as the ESP, which is mounted at /boot/efi:

1
2
3
4
5
6
/boot/efi/EFI/BOOT/BOOTX64.EFI
/boot/efi/EFI/BOOT/fallback.efi
/boot/efi/EFI/fedora/BOOT.CSV
/boot/efi/EFI/fedora/MokManager.efi
/boot/efi/EFI/fedora/shim-fedora.efi
/boot/efi/EFI/fedora/shim.efi

When we’re operating under the Default Boot Behavior, the system firmware launches the Device Path File(EFI\BOOT\BOOTX64.EFI), which corresponds to /boot/efi/EFI/BOOT/BOOTX64.EFI on our file system. This is actually just another copy of /boot/efi/EFI/fedora/shim.efi, but it behaves slightly differently.

Upon startup, shim checks to see if it’s been launched from \EFI\BOOT6. If it has, it checks to see if there’s another file in this directory called fallback.efi, which our shim package provides on an installed system, but which we purposefully omit from removable media. If it finds fallback.efi, it executes it as a normal UEFI application.

fallback.efi

It is expected that this default boot will load an operating system or a maintenance utility. If this is an operating system setup program it is then responsible for setting the requisite environment variables for subsequent boots.

UEFI Specification section 3.3 Revision 2.4

As you may well have come to realize, the purpose of fallback.efi is to rebuild boot options in the case that BootOrder or the Boot#### variables it references have been destroyed, or the case that you’ve moved a fixed disk between machines with the intent on booting from it.

When fallback runs, it queries the firmware for the disk from which it was loaded. It then iterates over every subdirectory of \EFI on that disk that isn’t BOOT, looking for files named BOOT.CSV. On Fedora, we provide such a file in \EFI\fedora\BOOT.CSV. This file is a UCS-2 file of comma separated values, and its contents look like this:

1
shim.efi,Fedora,,This is the boot entry for Fedora

For each valid entry in each BOOT.CSV file it finds, fallback creates a new Boot#### variable and appends it to BootOrder. In this case it creates a boot entry with the label ‘Fedora’, with a Device Path that points to the disk fallback was run from, and the file path corresponding to the directory in which shim found this particular BOOT.CSV, with the first entry in the CSV appended: File(\EFI\fedora\shim.efi)

Once fallback has finished iterating all the CSV files in all the directories other than BOOT, it boots the first option it has added. On the next reboot, there will be a BootOrder variable, and its first entries will be whichever Boot#### variables fallback created.


  1. Astute readers will no doubt notice that often we have device paths with only the HD(1,800,64000,12029cda-8961-470d-82ba-aeb17dba91a5) and File(\EFI\fedora\shim.efi) components. The spec allows this explicitly, and in this case it initializes every peripheral in no particular order until it finds a partition matching that Hard Disk Media Device Path, and the appropriate file on it.

  2. In this case ACPI(a0341d0,0) represents the PCI Express Root Port on my CPU, and PCI(1f,2) is what lspci whould show as 00:1f.2 SATA controller: Intel Corporation 6 Series/C200 Series Chipset Family SATA AHCI Controller (rev 05).

  3. Yes, seriously, SATA(0,0,0) represents the disk and HD(1,800,64000,12029cda-8961-470d-82ba-aeb17dba91a5) represents the partition on it. I’m so sorry.

  4. Strictly speaking, the firmware is required to check one partition which has the correct GUID and label in GPT and is a FAT file system. It is allowed to check partitions without that GUID and label, but you can’t depend on it, and it is allowed to check other partitions that have file systems it understands, but you can’t depend on that either.

  5. Or whatever file name is appropriate on your architecture.

  6. I’m using backslashes for paths seen by code that’s running under UEFI, and forward slashes for code running under Linux, because that’s what those parts of the system typically use.

UEFI Binary Signature Alignment Requirements

James Bottomley recently posted an item regarding problems he’s seen involving UEFI binaries’ signature alignment, and I’d like to fill in a bit more detail.

UEFI systems use Microsoft’s PE/COFF binary format, with a modification in the headers to reflect the status as UEFI binaries rather than Windows Executables or DSOs. When the UEFI Spec Working Group (USWG) began considering how to specify Secure Boot, the obvious route was to use Microsoft’s binary signing system, Authenticode. Authenticode appears, at first glance, to be a straightforward signing system. Specific parts of a binary are digested with a cryptographic hash — in Secure Boot’s case, generally SHA-256 — and that hash is used in a PKCS#7 signature. The result is embedded in a special section of the binary, the “Certificate Table”. Neither the pointer to the Certificate Table, nor the table itself are included in the hashing, and so the same algorithm can be used during verification. Hash the binary, find a signature in the Certificate Table, and compare the stored hash from that signature to the one you’ve computed. If they match, the signature is for this binary, if they don’t, the binary has been modified. Once the hash has been verified, it’s a simple matter of checking the authorization of the signer against your system’s certificate databases, and you know whether to allow execution.

Except it’s not that simple.

In a PE/COFF binary, the Certificate Table is found by looking at a particular entry in an array known as the Data Directory. This entry contains a file offset for the table, as well as the size that the table takes up in the binary. Inside the table itself, there’s a header with a size, a version code, and a GUID to show that the body is an PKCS#7 signature. Given that it’s described as a table and clearly set up so the container can be larger than a given signature, the natural conclusion is that this is an array, and multiple signatures are allowed.

When I implemented pesign, I erroneously assumed that this was a packed array of signatures. When multiple signature support was added to Tiano, the UEFI reference implementation, the same assumption was made. Testing commenced, and the two worked together just fine.

Some time later, somebody noticed that the Tiano code was not in compliance with the specification. PE/COFF version 6.0 states:

Notice that certificates always start on an octaword boundary. If a certificate is not an even number of octawords long, it is zero padded to the next octaword boundary. However, the length of the certificate does not include this padding and so any certificate navigation software must be sure to round up to the next octaword to locate another certificate.

Aside from the constant use of the word “certificate” to refer to a signature, or the use of “octaword”, which has never confused anybody, I’m sure, what this says is that if you compute the address to put the Certificate Table, and that address isn’t on an 8 byte boundary — that is, the address modulo 8 is not zero — then you should round it up to the next 8 byte boundary. But it doesn’t really cover the array-like nature of the table — in fact, this version of the specification doesn’t really treat it as more than a single entry, though the data structure to allow you to treat it as an array is present.

In version 8.0, which UEFI binaries were standardized against, the format slightly changed, as did the language. The only mention of this alignment requirement is now (in 8.0 and 8.3):

The attribute certificate table is composed of a set of contiguous, octaword-aligned attribute certificate entries.

and

Notice that certificates always start on an octaword boundary. If a certificate is not an even number of octawords long, it is zero padded to the next octaword boundary. However, the length of the certificate does not include this padding and so any certificate navigation software must be sure to round up to the next octaword to locate another certificate.

Ignoring for the moment that “contiguous” and “octaword-aligned” stand in opposition to each other, this spells out how to structure the list in more detail than older versions of the specification.

This all brings us to Tiano revisision 14141:

Update the DxeImageVerificationLib to support for Authenticode-signed UEFI images with multiple signatures.
Signed-off-by: Fu Siyuan siyuan.fu@intel.com
Reviewed-by: Ye Ting ting.ye@intel.com
Reviewed-by: Dong Guo guo.dong@intel.com

Among other changes is this:

1
2
3
4
5
6
7
-  //
-  // Verify the image's Authenticode signature, only DER-encoded PKCS#7 signed data is supported.
-  //
-  if (WinCertificate->wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
+  for (OffSet = SecDataDir->VirtualAddress;
+       OffSet < (SecDataDir->VirtualAddress + SecDataDir->Size);
+       OffSet += WinCertificate->dwLength, OffSet += ALIGN_SIZE (OffSet)) {

This, for the first time in UEFI code, enforce alignment requirements on the signatures embedded in the binary.

As a result, signers have needed to be fixed to properly reflect this requirement. In pesign, that work was done in March of 2012. But this, once again, isn’t as simple as it seems.

Typically the Certificate Table is the very last thing in the binary — after all, it’s the last change you’re going to make, or else it’s going to be full of invalid signatures. In UEFI, you can either enroll a certificate into the security databases, or you can enroll the hash of a specific binary. When you have an unsigned binary, there’s no guarantee of what the size of the binary will be. If the last section happens to be full of structured data, it will probably end on an alignment that matches that of that data. If the last thing is a string table or executable code for an 8086 CPU, it could end at any offset. So there’s basically only a one in eight chance that it’s aligned correctly for appending the Certificate table. If it isn’t, then you pad to the right alignment before appending the table.

When the signature is calculated on a binary with a Certificate Table, that padding is included in the data being digested. The inevitable conclusion is that the Authenticode hash of a signed binary is not guaranteed to be the same as the hash of the exact same binary if it has never been signed.

So now in addition to “pesign —sign”, the command to sign a binary, and “pesign —hash”, which outputs the hash of a binary so you can enroll it specifically, pesign now also supports “pesign —hash —pad”, which you can use to find out what the hash of an unsigned binary would be, were it signed.

This all went in to pesign 0.104, released Wed Mar 27 09:41:52 2013 -0400 .

Edit:

Just in case anybody is interested, in Fedora, we tracked this issue with bug 963361.

An Open Letter to Prospective Cambridge City Councilor Jefferson R. Smith

Sir,

Today I received a flier for your campaign for City Council. This in itself is not particularly surprising. The method by which it was delivered, however, is wholly unacceptable.

When campaign workers stuff campaign fliers haphazardly into the edges of urban mail boxes, it sends a clear signal: this campaign is about the candidate, not the voters. There are several problems with stuffing fliers into mail boxes this way: illegality and dangers you’re placing on voters being the most obvious.

Make no mistake — this behavior is illegal. Under DMM 508, section 3.1.3, your campaign owes postage on fliers delivered this way. Defrauding the government is a fairly bad sign for your dedication to the rule of law. If you’re willing to violate such simple regulations for a quick buck, why would a voter think you’re not willing to violate other laws?

Additionally, leaving mail dangling from a mail box places the recipient’s property in danger. Most urban mail boxes are closed and locked for a reason. If you leave mail dangling outside an urban mail box, you’re sending a message regarding that residence was last checked in on. Especially in the late Summer, this is a clear way to measure the vulnerability of a residence to theft.

Aside from the above, it’s sloppy and insulting to have your flier include a signature and a hand written note which are clearly in different hand writing, especially if the names in the note clearly include outdated entries from voter roles which don’t match the names printed on the actual mail box. I know your campaign workers are just going door to door and stuffing them in mail boxes, and that these fliers have been prepared ahead of time, but the careless failure of attention to detail just seems patronizing.