diff --git a/Makefile b/Makefile index 37c5d9d..2e05edb 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared -Bsymbolic -L$(EFI_PATH VERSION = 0.1 TARGET = shim.efi MokManager.efi -OBJS = shim.o netboot.o cert.o +OBJS = shim.o netboot.o cert.o dbx.o SOURCES = shim.c shim.h netboot.c signature.h PeImage.h MOK_OBJS = MokManager.o MOK_SOURCES = MokManager.c shim.h @@ -41,7 +41,10 @@ shim.o: $(SOURCES) cert.o : cert.S $(CC) $(CFLAGS) -c -o $@ $< -shim.so: $(OBJS) Cryptlib/libcryptlib.a Cryptlib/OpenSSL/libopenssl.a cert.o +dbx.o : dbx.S + $(CC) $(CFLAGS) -c -o $@ $< + +shim.so: $(OBJS) Cryptlib/libcryptlib.a Cryptlib/OpenSSL/libopenssl.a $(LD) -o $@ $(LDFLAGS) $^ $(EFI_LIBS) MokManager.o: $(SOURCES) diff --git a/MokManager.c b/MokManager.c index 1d84a2d..5802d27 100644 --- a/MokManager.c +++ b/MokManager.c @@ -8,6 +8,7 @@ #define PASSWORD_MAX 16 #define PASSWORD_MIN 8 +#define SB_PASSWORD_LEN 8 #ifndef SHIM_VENDOR #define SHIM_VENDOR L"Shim" @@ -15,6 +16,9 @@ #define EFI_VARIABLE_APPEND_WRITE 0x00000040 +#define CERT_STRING L"Select an X509 certificate to enroll:\n\n" +#define HASH_STRING L"Select a file to trust:\n\n" + struct menu_item { CHAR16 *text; INTN (* callback)(void *data, void *data2, void *data3); @@ -29,6 +33,12 @@ typedef struct { UINT8 *Mok; } __attribute__ ((packed)) MokListNode; +typedef struct { + UINT32 MokSBState; + UINT32 PWLen; + CHAR16 Password[PASSWORD_MAX]; +} __attribute__ ((packed)) MokSBvar; + static EFI_INPUT_KEY get_keystroke (void) { EFI_INPUT_KEY key; @@ -99,7 +109,7 @@ static MokListNode *build_mok_list(UINT32 num, void *Data, UINTN DataSize) { (CompareGuid (&CertList->SignatureType, &HashType) != 0)) { dbsize -= CertList->SignatureListSize; CertList = (EFI_SIGNATURE_LIST *)((UINT8 *) CertList + - CertList->SignatureSize); + CertList->SignatureListSize); continue; } @@ -107,7 +117,7 @@ static MokListNode *build_mok_list(UINT32 num, void *Data, UINTN DataSize) { (CertList->SignatureSize != 48)) { dbsize -= CertList->SignatureListSize; CertList = (EFI_SIGNATURE_LIST *)((UINT8 *) CertList + - CertList->SignatureSize); + CertList->SignatureListSize); continue; } @@ -120,7 +130,7 @@ static MokListNode *build_mok_list(UINT32 num, void *Data, UINTN DataSize) { count++; dbsize -= CertList->SignatureListSize; CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + - CertList->SignatureSize); + CertList->SignatureListSize); } return list; @@ -308,8 +318,8 @@ static void show_mok_info (void *Mok, UINTN MokSize) return; if (MokSize != 48) { - if (X509ConstructCertificate(Mok, MokSize, (UINT8 **) &X509Cert) && - X509Cert != NULL) { + if (X509ConstructCertificate(Mok, MokSize, + (UINT8 **) &X509Cert) && X509Cert != NULL) { show_x509_info(X509Cert); X509_free(X509Cert); } else { @@ -317,6 +327,20 @@ static void show_mok_info (void *Mok, UINTN MokSize) ((UINT32 *)Mok)[0]); return; } + + efi_status = get_sha1sum(Mok, MokSize, hash); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to compute MOK fingerprint\n"); + return; + } + + Print(L" Fingerprint (SHA1):\n "); + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + Print(L" %02x", hash[i]); + if (i % 10 == 9) + Print(L"\n "); + } } else { Print(L"SHA256 hash:\n "); for (i = 0; i < SHA256_DIGEST_SIZE; i++) { @@ -326,19 +350,7 @@ static void show_mok_info (void *Mok, UINTN MokSize) } Print(L"\n"); } - efi_status = get_sha1sum(Mok, MokSize, hash); - if (efi_status != EFI_SUCCESS) { - Print(L"Failed to compute MOK fingerprint\n"); - return; - } - - Print(L" Fingerprint (SHA1):\n "); - for (i = 0; i < SHA1_DIGEST_SIZE; i++) { - Print(L" %02x", hash[i]); - if (i % 10 == 9) - Print(L"\n "); - } Print(L"\n"); } @@ -403,7 +415,7 @@ static UINT8 list_keys (void *MokNew, UINTN MokNewSize) Print(L"Doesn't look like a key or hash\n"); dbsize -= CertList->SignatureListSize; CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + - CertList->SignatureSize); + CertList->SignatureListSize); continue; } @@ -412,14 +424,14 @@ static UINT8 list_keys (void *MokNew, UINTN MokNewSize) Print(L"Doesn't look like a valid hash\n"); dbsize -= CertList->SignatureListSize; CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + - CertList->SignatureSize); + CertList->SignatureListSize); continue; } MokNum++; dbsize -= CertList->SignatureListSize; CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + - CertList->SignatureSize); + CertList->SignatureListSize); } keys = build_mok_list(MokNum, MokNew, MokNewSize); @@ -604,8 +616,7 @@ static EFI_STATUS store_keys (void *MokNew, UINTN MokNewSize, int authenticate) efi_status = uefi_call_wrapper(RT->SetVariable, 5, L"MokList", &shim_lock_guid, EFI_VARIABLE_NON_VOLATILE - | EFI_VARIABLE_BOOTSERVICE_ACCESS - | EFI_VARIABLE_APPEND_WRITE, + | EFI_VARIABLE_BOOTSERVICE_ACCESS, 0, NULL); } else { /* Write new MOK */ @@ -653,7 +664,9 @@ static UINTN mok_enrollment_prompt (void *MokNew, UINTN MokNewSize, int auth) { } static INTN mok_enrollment_prompt_callback (void *MokNew, void *data2, - void *data3) { + void *data3) +{ + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); return mok_enrollment_prompt(MokNew, (UINTN)data2, TRUE); } @@ -678,7 +691,203 @@ static INTN mok_deletion_prompt (void *MokNew, void *data2, void *data3) { return 0; } -static UINTN draw_menu (struct menu_item *items, UINTN count) { +static INTN mok_sb_prompt (void *MokSB, void *data2, void *data3) { + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS efi_status; + UINTN MokSBSize = (UINTN)data2; + MokSBvar *var = MokSB; + CHAR16 pass1, pass2, pass3; + UINT8 fail_count = 0; + UINT32 length; + CHAR16 line[1]; + UINT8 sbval = 1; + UINT8 pos1, pos2, pos3; + + if (MokSBSize != sizeof(MokSBvar)) { + Print(L"Invalid MokSB variable contents\n"); + return -1; + } + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + while (fail_count < 3) { + RandomBytes (&pos1, sizeof(pos1)); + pos1 = (pos1 % var->PWLen); + + do { + RandomBytes (&pos2, sizeof(pos2)); + pos2 = (pos2 % var->PWLen); + } while (pos2 == pos1); + + do { + RandomBytes (&pos3, sizeof(pos3)); + pos3 = (pos3 % var->PWLen) ; + } while (pos3 == pos2 || pos3 == pos1); + + Print(L"Enter password character %d: ", pos1 + 1); + get_line(&length, &pass1, 1, 0); + + Print(L"Enter password character %d: ", pos2 + 1); + get_line(&length, &pass2, 1, 0); + + Print(L"Enter password character %d: ", pos3 + 1); + get_line(&length, &pass3, 1, 0); + + if (pass1 != var->Password[pos1] || + pass2 != var->Password[pos2] || + pass3 != var->Password[pos3]) { + Print(L"Invalid character\n"); + fail_count++; + } else { + break; + } + } + + if (fail_count >= 3) { + Print(L"Password limit reached\n"); + return -1; + } + + if (var->MokSBState == 0) { + Print(L"Disable Secure Boot? (y/n): "); + } else { + Print(L"Enable Secure Boot? (y/n): "); + } + + do { + get_line (&length, line, 1, 1); + + if (line[0] == 'Y' || line[0] == 'y') { + if (var->MokSBState == 0) { + efi_status = uefi_call_wrapper(RT->SetVariable, + 5, L"MokSBState", + &shim_lock_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS, + 1, &sbval); + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to set Secure Boot state\n"); + return -1; + } + } else { + LibDeleteVariable(L"MokSBState", + &shim_lock_guid); + } + + LibDeleteVariable(L"MokSB", &shim_lock_guid); + + Print(L"Press a key to reboot system\n"); + Pause(); + uefi_call_wrapper(RT->ResetSystem, 4, EfiResetWarm, + EFI_SUCCESS, 0, NULL); + Print(L"Failed to reboot\n"); + return -1; + } + } while (line[0] != 'N' && line[0] != 'n'); + + return -1; +} + + +static INTN mok_pw_prompt (void *MokPW, void *data2, void *data3) { + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS efi_status; + UINTN MokPWSize = (UINTN)data2; + UINT8 fail_count = 0; + UINT8 hash[SHA256_DIGEST_SIZE]; + CHAR16 password[PASSWORD_MAX]; + UINT32 length; + CHAR16 line[1]; + + if (MokPWSize != SHA256_DIGEST_SIZE) { + Print(L"Invalid MokPW variable contents\n"); + return -1; + } + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + SetMem(hash, SHA256_DIGEST_SIZE, 0); + + if (CompareMem(MokPW, hash, SHA256_DIGEST_SIZE) == 0) { + Print(L"Clear MOK password? (y/n): "); + + do { + get_line (&length, line, 1, 1); + + if (line[0] == 'Y' || line[0] == 'y') { + LibDeleteVariable(L"MokPWStore", &shim_lock_guid); + LibDeleteVariable(L"MokPW", &shim_lock_guid); + } + } while (line[0] != 'N' && line[0] != 'n'); + + return 0; + } + + while (fail_count < 3) { + Print(L"Confirm MOK passphrase: "); + get_line(&length, password, PASSWORD_MAX, 0); + + if ((length < PASSWORD_MIN) || (length > PASSWORD_MAX)) { + Print(L"Invalid password length\n"); + fail_count++; + continue; + } + + efi_status = compute_pw_hash(NULL, 0, password, length, hash); + + if (efi_status != EFI_SUCCESS) { + Print(L"Unable to generate password hash\n"); + fail_count++; + continue; + } + + if (CompareMem(MokPW, hash, SHA256_DIGEST_SIZE) != 0) { + Print(L"Password doesn't match\n"); + fail_count++; + continue; + } + + break; + } + + if (fail_count >= 3) { + Print(L"Password limit reached\n"); + return -1; + } + + Print(L"Set MOK password? (y/n): "); + + do { + get_line (&length, line, 1, 1); + + if (line[0] == 'Y' || line[0] == 'y') { + efi_status = uefi_call_wrapper(RT->SetVariable, 5, + L"MokPWStore", + &shim_lock_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS, + MokPWSize, MokPW); + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to set MOK password\n"); + return -1; + } + + LibDeleteVariable(L"MokPW", &shim_lock_guid); + + Print(L"Press a key to reboot system\n"); + Pause(); + uefi_call_wrapper(RT->ResetSystem, 4, EfiResetWarm, + EFI_SUCCESS, 0, NULL); + Print(L"Failed to reboot\n"); + return -1; + } + } while (line[0] != 'N' && line[0] != 'n'); + + return 0; +} + +static UINTN draw_menu (CHAR16 *header, UINTN lines, struct menu_item *items, + UINTN count) { UINTN i; uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); @@ -688,6 +897,9 @@ static UINTN draw_menu (struct menu_item *items, UINTN count) { Print(L"%s UEFI key management\n\n", SHIM_VENDOR); + if (header) + Print(L"%s", header); + for (i = 0; i < count; i++) { uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, items[i].colour | EFI_BACKGROUND_BLACK); @@ -697,7 +909,7 @@ static UINTN draw_menu (struct menu_item *items, UINTN count) { uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, 0); uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE); - return 2; + return 2 + lines; } static void free_menu (struct menu_item *items, UINTN count) { @@ -711,31 +923,42 @@ static void free_menu (struct menu_item *items, UINTN count) { FreePool(items); } -static void run_menu (struct menu_item *items, UINTN count, UINTN timeout) { +static void update_time (UINTN position, UINTN timeout) +{ + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, + position); + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_BLACK | EFI_BACKGROUND_BLACK); + + Print(L" ", timeout); + + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, + position); + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_WHITE | EFI_BACKGROUND_BLACK); + + if (timeout > 1) + Print(L"Booting in %d seconds\n", timeout); + else if (timeout) + Print(L"Booting in %d second\n", timeout); +} + +static void run_menu (CHAR16 *header, UINTN lines, struct menu_item *items, + UINTN count, UINTN timeout) { UINTN index, pos = 0, wait = 0, offset; EFI_INPUT_KEY key; EFI_STATUS status; + INTN ret; if (timeout) wait = 10000000; + offset = draw_menu (header, lines, items, count); + while (1) { - uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); - - offset = draw_menu (items, count); - - uefi_call_wrapper(ST->ConOut->SetAttribute, 2, - ST->ConOut, - EFI_WHITE | EFI_BACKGROUND_BLACK); - - if (timeout) { - uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, - ST->ConOut, 0, count + 1 + offset); - if (timeout > 1) - Print(L"Booting in %d seconds\n", timeout); - else - Print(L"Booting in %d second\n", timeout); - } + update_time(count + offset + 1, timeout); uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, pos + offset); @@ -781,9 +1004,14 @@ static void run_menu (struct menu_item *items, UINTN count, UINTN timeout) { return; } - items[pos].callback(items[pos].data, items[pos].data2, - items[pos].data3); - draw_menu (items, count); + ret = items[pos].callback(items[pos].data, + items[pos].data2, + items[pos].data3); + if (ret < 0) { + Print(L"Press a key to continue\n"); + Pause(); + } + draw_menu (header, lines, items, count); pos = 0; break; } @@ -937,6 +1165,7 @@ static INTN directory_callback (void *data, void *data2, void *data3) { EFI_FILE *dir; CHAR16 *filename = data; EFI_FILE *root = data2; + BOOLEAN hash = !!data3; status = uefi_call_wrapper(root->Open, 5, root, &dir, filename, EFI_FILE_MODE_READ, 0); @@ -1023,7 +1252,10 @@ static INTN directory_callback (void *data, void *data2, void *data3) { buffer = NULL; } - run_menu(dircontent, dircount, 0); + if (hash) + run_menu(HASH_STRING, 2, dircontent, dircount, 0); + else + run_menu(CERT_STRING, 2, dircontent, dircount, 0); return 0; } @@ -1035,6 +1267,7 @@ static INTN filesystem_callback (void *data, void *data2, void *data3) { UINTN dircount = 0, i = 0; struct menu_item *dircontent; EFI_FILE *root = data; + BOOLEAN hash = !!data3; uefi_call_wrapper(root->SetPosition, 2, root, 0); @@ -1117,7 +1350,10 @@ static INTN filesystem_callback (void *data, void *data2, void *data3) { buffersize = 0; } - run_menu(dircontent, dircount, 0); + if (hash) + run_menu(HASH_STRING, 2, dircontent, dircount, 0); + else + run_menu(CERT_STRING, 2, dircontent, dircount, 0); return 0; } @@ -1126,8 +1362,9 @@ static INTN find_fs (void *data, void *data2, void *data3) { EFI_GUID fs_guid = SIMPLE_FILE_SYSTEM_PROTOCOL; UINTN count, i; UINTN OldSize, NewSize; - EFI_HANDLE **filesystem_handles; + EFI_HANDLE *filesystem_handles = NULL; struct menu_item *filesystems; + BOOLEAN hash = !!data3; uefi_call_wrapper(BS->LocateHandleBuffer, 5, ByProtocol, &fs_guid, NULL, &count, &filesystem_handles); @@ -1146,7 +1383,7 @@ static INTN find_fs (void *data, void *data2, void *data3) { filesystems[0].colour = EFI_YELLOW; for (i=1; iHandleProtocol, 3, fs, &fs_guid, - &fs_interface); + (void **)&fs_interface); if (status != EFI_SUCCESS || !fs_interface) continue; @@ -1208,23 +1445,90 @@ static INTN find_fs (void *data, void *data2, void *data3) { uefi_call_wrapper(BS->FreePool, 1, filesystem_handles); - run_menu(filesystems, count, 0); + if (hash) + run_menu(HASH_STRING, 2, filesystems, count, 0); + else + run_menu(CERT_STRING, 2, filesystems, count, 0); return 0; } +static BOOLEAN verify_pw(void) +{ + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS efi_status; + CHAR16 password[PASSWORD_MAX]; + UINT8 fail_count = 0; + UINT8 hash[SHA256_DIGEST_SIZE]; + UINT8 pwhash[SHA256_DIGEST_SIZE]; + UINTN size = SHA256_DIGEST_SIZE; + UINT32 length; + UINT32 attributes; + + efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"MokPWStore", + &shim_lock_guid, &attributes, &size, + pwhash); + + /* + * If anything can attack the password it could just set it to a + * known value, so there's no safety advantage in failing to validate + * purely because of a failure to read the variable + */ + if (efi_status != EFI_SUCCESS) + return TRUE; + + if (attributes & EFI_VARIABLE_RUNTIME_ACCESS) + return TRUE; + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + while (fail_count < 3) { + Print(L"Enter MOK password: "); + get_line(&length, password, PASSWORD_MAX, 0); + + if (length < PASSWORD_MIN || length > PASSWORD_MAX) { + Print(L"Invalid password length\n"); + fail_count++; + continue; + } + + efi_status = compute_pw_hash(NULL, 0, password, length, hash); + + if (efi_status != EFI_SUCCESS) { + Print(L"Unable to generate password hash\n"); + fail_count++; + continue; + } + + if (CompareMem(pwhash, hash, SHA256_DIGEST_SIZE) != 0) { + Print(L"Password doesn't match\n"); + fail_count++; + continue; + } + + return TRUE; + } + + Print(L"Password limit reached\n"); + return FALSE; +} + static EFI_STATUS enter_mok_menu(EFI_HANDLE image_handle, void *MokNew, - UINTN MokNewSize) + UINTN MokNewSize, void *MokSB, + UINTN MokSBSize, void *MokPW, UINTN MokPWSize) { struct menu_item *menu_item; UINT32 MokAuth = 0; - UINTN menucount = 0; + UINTN menucount = 3, i = 0; EFI_STATUS efi_status; EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; UINT8 auth[SHA256_DIGEST_SIZE]; UINTN auth_size = SHA256_DIGEST_SIZE; UINT32 attributes; + if (verify_pw() == FALSE) + return EFI_ACCESS_DENIED; + efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"MokAuth", &shim_lock_guid, &attributes, &auth_size, auth); @@ -1233,49 +1537,73 @@ static EFI_STATUS enter_mok_menu(EFI_HANDLE image_handle, void *MokNew, MokAuth = 1; if (MokNew || MokAuth) - menu_item = AllocateZeroPool(sizeof(struct menu_item) * 4); - else - menu_item = AllocateZeroPool(sizeof(struct menu_item) * 3); + menucount++; + + if (MokSB) + menucount++; + + if (MokPW) + menucount++; + + menu_item = AllocateZeroPool(sizeof(struct menu_item) * menucount); if (!menu_item) return EFI_OUT_OF_RESOURCES; - menu_item[0].text = StrDuplicate(L"Continue boot"); - menu_item[0].colour = EFI_WHITE; - menu_item[0].callback = NULL; + menu_item[i].text = StrDuplicate(L"Continue boot"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = NULL; - menucount++; + i++; if (MokNew || MokAuth) { if (!MokNew) { - menu_item[1].text = StrDuplicate(L"Delete MOK"); - menu_item[1].colour = EFI_WHITE; - menu_item[1].callback = mok_deletion_prompt; + menu_item[i].text = StrDuplicate(L"Delete MOK"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = mok_deletion_prompt; } else { - menu_item[1].text = StrDuplicate(L"Enroll MOK"); - menu_item[1].colour = EFI_WHITE; - menu_item[1].data = MokNew; - menu_item[1].data2 = (void *)MokNewSize; - menu_item[1].callback = mok_enrollment_prompt_callback; + menu_item[i].text = StrDuplicate(L"Enroll MOK"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].data = MokNew; + menu_item[i].data2 = (void *)MokNewSize; + menu_item[i].callback = mok_enrollment_prompt_callback; } - menucount++; + i++; } - menu_item[menucount].text = StrDuplicate(L"Enroll key from disk"); - menu_item[menucount].colour = EFI_WHITE; - menu_item[menucount].callback = find_fs; - menu_item[menucount].data3 = (void *)FALSE; + if (MokSB) { + menu_item[i].text = StrDuplicate(L"Change Secure Boot state"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = mok_sb_prompt; + menu_item[i].data = MokSB; + menu_item[i].data2 = (void *)MokSBSize; + i++; + } - menucount++; + if (MokPW) { + menu_item[i].text = StrDuplicate(L"Set MOK password"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = mok_pw_prompt; + menu_item[i].data = MokPW; + menu_item[i].data2 = (void *)MokPWSize; + i++; + } - menu_item[menucount].text = StrDuplicate(L"Enroll hash from disk"); - menu_item[menucount].colour = EFI_WHITE; - menu_item[menucount].callback = find_fs; - menu_item[menucount].data3 = (void *)TRUE; + menu_item[i].text = StrDuplicate(L"Enroll key from disk"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = find_fs; + menu_item[i].data3 = (void *)FALSE; - menucount++; + i++; - run_menu(menu_item, menucount, 10); + menu_item[i].text = StrDuplicate(L"Enroll hash from disk"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = find_fs; + menu_item[i].data3 = (void *)TRUE; + + i++; + + run_menu(NULL, 0, menu_item, menucount, 10); uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); @@ -1285,12 +1613,19 @@ static EFI_STATUS enter_mok_menu(EFI_HANDLE image_handle, void *MokNew, static EFI_STATUS check_mok_request(EFI_HANDLE image_handle) { EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; - UINTN MokNewSize = 0; + UINTN MokNewSize = 0, MokSBSize = 0, MokPWSize = 0; void *MokNew = NULL; + void *MokSB = NULL; + void *MokPW = NULL; MokNew = LibGetVariableAndSize(L"MokNew", &shim_lock_guid, &MokNewSize); - enter_mok_menu(image_handle, MokNew, MokNewSize); + MokSB = LibGetVariableAndSize(L"MokSB", &shim_lock_guid, &MokSBSize); + + MokPW = LibGetVariableAndSize(L"MokPW", &shim_lock_guid, &MokPWSize); + + enter_mok_menu(image_handle, MokNew, MokNewSize, MokSB, MokSBSize, + MokPW, MokPWSize); if (MokNew) { if (LibDeleteVariable(L"MokNew", &shim_lock_guid) != EFI_SUCCESS) { @@ -1298,17 +1633,59 @@ static EFI_STATUS check_mok_request(EFI_HANDLE image_handle) } FreePool (MokNew); } + + if (MokSB) { + if (LibDeleteVariable(L"MokSB", &shim_lock_guid) != EFI_SUCCESS) { + Print(L"Failed to delete MokSB\n"); + } + FreePool (MokNew); + } + + if (MokPW) { + if (LibDeleteVariable(L"MokPW", &shim_lock_guid) != EFI_SUCCESS) { + Print(L"Failed to delete MokPW\n"); + } + FreePool (MokNew); + } + LibDeleteVariable(L"MokAuth", &shim_lock_guid); return EFI_SUCCESS; } +static EFI_STATUS setup_rand (void) +{ + EFI_TIME time; + EFI_STATUS efi_status; + UINT64 seed; + BOOLEAN status; + + efi_status = uefi_call_wrapper(RT->GetTime, 2, &time, NULL); + + if (efi_status != EFI_SUCCESS) + return efi_status; + + seed = ((UINT64)time.Year << 48) | ((UINT64)time.Month << 40) | + ((UINT64)time.Day << 32) | ((UINT64)time.Hour << 24) | + ((UINT64)time.Minute << 16) | ((UINT64)time.Second << 8) | + ((UINT64)time.Daylight); + + status = RandomSeed((UINT8 *)&seed, sizeof(seed)); + + if (!status) + return EFI_ABORTED; + + return EFI_SUCCESS; +} + EFI_STATUS efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *systab) { EFI_STATUS efi_status; InitializeLib(image_handle, systab); + setup_rand(); + efi_status = check_mok_request(image_handle); return efi_status; diff --git a/MokVars.txt b/MokVars.txt new file mode 100644 index 0000000..74f0908 --- /dev/null +++ b/MokVars.txt @@ -0,0 +1,51 @@ +Variables used by Shim and Mokmanager + +Request variables: + +MokPW: Set by MokUtil when setting a password. A SHA-256 hash of the +UCS-2 representation of the password. The user will be asked to +re-enter the password to confirm. If the hash of the entered password +matches the contents of MokPW, the user will be prompted to copy MokPW +into MokPWState. BS,RT,NV + +MokSB: Set by MokUtil when requesting a change in state of signature +validation. A packed structure as follows: + +typedef struct { + UINT32 MokSBState; + UINT32 PWLen; + CHAR16 Password[PASSWORD_MAX]; +} __attribute__ ((packed)) MokSBvar; + +If MokSBState is 0, the user will be prompted to disable signature +validation. Otherwise, the user will be prompted to enable it. PWLen +is the length of the password, in characters. Password is a UCS-2 +representation of the password. The user will be prompted to enter +three randomly chosen characters from the password. If successful, +they will then be prompted to change the signature validation +according to MokSBState. BS,RT,NV + +MokNew: Set by MokUtil when requesting the addition or removal of keys +from MokList. Is an EFI_SIGNATURE_LIST as described in the UEFI +specification. BS,RT,NV + +MokAuth: A hash dependent upon the contents of MokNew and the sealing +password. The user's password in UCS-2 form should be appended to the +contents of MokNew and a SHA-256 hash generated and stored in MokAuth. +The hash will be regenerated by MokManager after the user is requested +to enter their password to confirm enrolment of the keys. If the hash +matches MokAuth, the user will be prompted to enrol the keys. BS,RT,NV + +State variables: + +MokList: A list of whitelisted keys and hashes. An EFI_SIGNATURE_LIST +as described in the UEFI specification. BS,NV + +MokListRT: A copy of MokList made available to the kernel at runtime. RT + +MokSBState: An 8-bit unsigned integer. If 1, shim will switch to +insecure mode. BS,NV + +MokPWStore: A SHA-256 representation of the password set by the user +via MokPW. The user will be prompted to enter this password in order +to interact with MokManager. diff --git a/dbx.S b/dbx.S new file mode 100644 index 0000000..a26fc38 --- /dev/null +++ b/dbx.S @@ -0,0 +1,32 @@ +#if defined(VENDOR_DBX_FILE) + .globl vendor_dbx_size + .data + .align 1 + .type vendor_dbx_size, @object + .size vendor_dbx_size, 4 +vendor_dbx_size: + .long .L0 - vendor_dbx + .globl vendor_dbx + .data + .align 1 + .type vendor_dbx, @object + .size vendor_dbx_size, vendor_dbx_size-vendor_dbx +vendor_dbx: +.incbin VENDOR_DBX_FILE +.L0: +#else + .globl vendor_dbx + .bss + .type vendor_dbx, @object + .size vendor_dbx, 1 +vendor_dbx: + .zero 1 + + .globl vendor_dbx_size + .data + .align 4 + .type vendor_dbx_size, @object + .size vendor_dbx_size, 4 +vendor_dbx_size: + .long 0 +#endif diff --git a/shim.c b/shim.c index c87579a..5f1d6a2 100644 --- a/shim.c +++ b/shim.c @@ -52,9 +52,13 @@ static EFI_STATUS (EFIAPI *entry_point) (EFI_HANDLE image_handle, EFI_SYSTEM_TAB */ extern UINT8 vendor_cert[]; extern UINT32 vendor_cert_size; +extern EFI_SIGNATURE_LIST *vendor_dbx; +extern UINT32 vendor_dbx_size; #define EFI_IMAGE_SECURITY_DATABASE_GUID { 0xd719b2cb, 0x3d3a, 0x4596, { 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f }} +static UINT8 insecure_mode; + typedef enum { DATA_FOUND, DATA_NOT_FOUND, @@ -79,8 +83,7 @@ static EFI_STATUS get_variable (CHAR16 *name, EFI_GUID guid, UINT32 *attributes, return efi_status; } - if (allocate) - *buffer = AllocatePool(*size); + *buffer = AllocatePool(*size); if (!*buffer) { Print(L"Unable to allocate variable buffer\n"); @@ -208,26 +211,16 @@ static EFI_STATUS relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, return EFI_SUCCESS; } -static CHECK_STATUS check_db_cert(CHAR16 *dbname, EFI_GUID guid, - WIN_CERTIFICATE_EFI_PKCS *data, UINT8 *hash) +static CHECK_STATUS check_db_cert_in_ram(EFI_SIGNATURE_LIST *CertList, + UINTN dbsize, + WIN_CERTIFICATE_EFI_PKCS *data, + UINT8 *hash) { - EFI_STATUS efi_status; - EFI_SIGNATURE_LIST *CertList; EFI_SIGNATURE_DATA *Cert; - UINTN dbsize = 0; UINTN CertCount, Index; - UINT32 attributes; BOOLEAN IsFound = FALSE; - void *db; EFI_GUID CertType = EfiCertX509Guid; - efi_status = get_variable(dbname, guid, &attributes, &dbsize, &db); - - if (efi_status != EFI_SUCCESS) - return VAR_NOT_FOUND; - - CertList = db; - while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { if (CompareGuid (&CertList->SignatureType, &CertType) == 0) { CertCount = (CertList->SignatureListSize - CertList->SignatureHeaderSize) / CertList->SignatureSize; @@ -240,43 +233,57 @@ static CHECK_STATUS check_db_cert(CHAR16 *dbname, EFI_GUID guid, hash, SHA256_DIGEST_SIZE); if (IsFound) break; + + Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) Cert + CertList->SignatureSize); } - Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) Cert + CertList->SignatureSize); } + if (IsFound) + break; + dbsize -= CertList->SignatureListSize; CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + CertList->SignatureListSize); } - FreePool(db); - if (IsFound) return DATA_FOUND; return DATA_NOT_FOUND; } -static CHECK_STATUS check_db_hash(CHAR16 *dbname, EFI_GUID guid, UINT8 *data, - int SignatureSize, EFI_GUID CertType) +static CHECK_STATUS check_db_cert(CHAR16 *dbname, EFI_GUID guid, + WIN_CERTIFICATE_EFI_PKCS *data, UINT8 *hash) { + CHECK_STATUS rc; EFI_STATUS efi_status; EFI_SIGNATURE_LIST *CertList; - EFI_SIGNATURE_DATA *Cert; UINTN dbsize = 0; - UINTN CertCount, Index; UINT32 attributes; - BOOLEAN IsFound = FALSE; void *db; efi_status = get_variable(dbname, guid, &attributes, &dbsize, &db); - if (efi_status != EFI_SUCCESS) { + if (efi_status != EFI_SUCCESS) return VAR_NOT_FOUND; - } CertList = db; + rc = check_db_cert_in_ram(CertList, dbsize, data, hash); + + FreePool(db); + + return rc; +} + +static CHECK_STATUS check_db_hash_in_ram(EFI_SIGNATURE_LIST *CertList, + UINTN dbsize, UINT8 *data, + int SignatureSize, EFI_GUID CertType) +{ + EFI_SIGNATURE_DATA *Cert; + UINTN CertCount, Index; + BOOLEAN IsFound = FALSE; + while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { CertCount = (CertList->SignatureListSize - CertList->SignatureHeaderSize) / CertList->SignatureSize; Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); @@ -301,19 +308,53 @@ static CHECK_STATUS check_db_hash(CHAR16 *dbname, EFI_GUID guid, UINT8 *data, CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + CertList->SignatureListSize); } - FreePool(db); - if (IsFound) return DATA_FOUND; return DATA_NOT_FOUND; } +static CHECK_STATUS check_db_hash(CHAR16 *dbname, EFI_GUID guid, UINT8 *data, + int SignatureSize, EFI_GUID CertType) +{ + EFI_STATUS efi_status; + EFI_SIGNATURE_LIST *CertList; + UINT32 attributes; + UINTN dbsize = 0; + void *db; + + efi_status = get_variable(dbname, guid, &attributes, &dbsize, &db); + + if (efi_status != EFI_SUCCESS) { + return VAR_NOT_FOUND; + } + + CertList = db; + + CHECK_STATUS rc = check_db_hash_in_ram(CertList, dbsize, data, + SignatureSize, CertType); + FreePool(db); + return rc; + +} + static EFI_STATUS check_blacklist (WIN_CERTIFICATE_EFI_PKCS *cert, UINT8 *sha256hash, UINT8 *sha1hash) { EFI_GUID secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; + if (check_db_hash_in_ram(vendor_dbx, vendor_dbx_size, sha256hash, + SHA256_DIGEST_SIZE, EfiHashSha256Guid) == + DATA_FOUND) + return EFI_ACCESS_DENIED; + if (check_db_hash_in_ram(vendor_dbx, vendor_dbx_size, sha1hash, + SHA1_DIGEST_SIZE, EfiHashSha1Guid) == + DATA_FOUND) + return EFI_ACCESS_DENIED; + if (check_db_cert_in_ram(vendor_dbx, vendor_dbx_size, cert, + sha256hash) == DATA_FOUND) + return EFI_ACCESS_DENIED; + if (check_db_hash(L"dbx", secure_var, sha256hash, SHA256_DIGEST_SIZE, EfiHashSha256Guid) == DATA_FOUND) return EFI_ACCESS_DENIED; @@ -361,6 +402,9 @@ static BOOLEAN secure_mode (void) UINT8 sb, setupmode; UINT32 attributes; + if (insecure_mode) + return FALSE; + status = get_variable(L"SecureBoot", global_var, &attributes, &charsize, (void *)&sb); @@ -505,7 +549,8 @@ static EFI_STATUS generate_hash (char *data, int datasize, if (!hashbase) { Print(L"Malformed section header\n"); - return EFI_INVALID_PARAMETER; + status = EFI_INVALID_PARAMETER; + goto done; } if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || @@ -585,6 +630,11 @@ static EFI_STATUS verify_buffer (char *data, int datasize, WIN_CERTIFICATE_EFI_PKCS *cert; unsigned int size = datasize; + if (context->SecDir->Size == 0) { + Print(L"Empty security header\n"); + return EFI_INVALID_PARAMETER; + } + cert = ImageAddress (data, size, context->SecDir->VirtualAddress); if (!cert) { @@ -643,9 +693,19 @@ static EFI_STATUS read_header(void *data, unsigned int datasize, EFI_IMAGE_DOS_HEADER *DosHdr = data; EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; + if (datasize < sizeof(EFI_IMAGE_DOS_HEADER)) { + Print(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew); + if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) { + Print(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) { Print(L"Unsupported image type\n"); return EFI_UNSUPPORTED; @@ -672,13 +732,18 @@ static EFI_STATUS read_header(void *data, unsigned int datasize, context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER)); context->SecDir = (EFI_IMAGE_DATA_DIRECTORY *) &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; - if (context->SecDir->VirtualAddress >= datasize) { - Print(L"Malformed security header\n"); - return EFI_INVALID_PARAMETER; + if (context->ImageSize < context->SizeOfHeaders) { + Print(L"Invalid image\n"); + return EFI_UNSUPPORTED; } - if (context->SecDir->Size == 0) { - Print(L"Empty security header\n"); + if (((UINT8 *)context->SecDir - (UINT8 *)data) > (datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) { + Print(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (context->SecDir->VirtualAddress >= datasize) { + Print(L"Malformed security header\n"); return EFI_INVALID_PARAMETER; } return EFI_SUCCESS; @@ -790,7 +855,7 @@ static EFI_STATUS generate_path(EFI_LOADED_IMAGE *li, CHAR16 *ImagePath, bootpath[i+1] = '\0'; - if (bootpath[i-i] == '\\') + if (i == 0 || bootpath[i-i] == '\\') bootpath[i] = '\0'; *PathName = AllocatePool(StrSize(bootpath) + StrSize(ImagePath)); @@ -829,7 +894,8 @@ static EFI_STATUS load_image (EFI_LOADED_IMAGE *li, void **data, device = li->DeviceHandle; efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, device, - &simple_file_system_protocol, &drive); + &simple_file_system_protocol, + (void **)&drive); if (efi_status != EFI_SUCCESS) { Print(L"Failed to find fs\n"); @@ -863,6 +929,7 @@ static EFI_STATUS load_image (EFI_LOADED_IMAGE *li, void **data, &buffersize, fileinfo); if (efi_status == EFI_BUFFER_TOO_SMALL) { + FreePool(fileinfo); fileinfo = AllocatePool(buffersize); if (!fileinfo) { Print(L"Unable to allocate file info buffer\n"); @@ -905,14 +972,15 @@ static EFI_STATUS load_image (EFI_LOADED_IMAGE *li, void **data, *datasize = buffersize; + FreePool(fileinfo); + return EFI_SUCCESS; error: if (*data) { FreePool(*data); *data = NULL; } - if (PathName) - FreePool(PathName); + if (fileinfo) FreePool(fileinfo); return efi_status; @@ -949,7 +1017,7 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) int datasize; efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, image_handle, - &loaded_image_protocol, &li); + &loaded_image_protocol, (void **)&li); if (efi_status != EFI_SUCCESS) { Print(L"Unable to init protocol\n"); @@ -996,10 +1064,16 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) goto done; } - efi_status = uefi_call_wrapper(entry_point, 3, image_handle, systab); + efi_status = uefi_call_wrapper(entry_point, 2, image_handle, systab); CopyMem(li, &li_bak, sizeof(li_bak)); done: + if (PathName) + FreePool(PathName); + + if (data) + FreePool(data); + return efi_status; } @@ -1048,35 +1122,71 @@ done: return efi_status; } -EFI_STATUS check_mok_request(EFI_HANDLE image_handle) +static BOOLEAN check_var(CHAR16 *varname) { - EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; EFI_STATUS efi_status; + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; UINTN size = sizeof(UINT32); - UINT32 MokNew; + UINT32 MokVar; UINT32 attributes; - if (!secure_mode()) - return EFI_SUCCESS; - - efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"MokNew", + efi_status = uefi_call_wrapper(RT->GetVariable, 5, varname, &shim_lock_guid, &attributes, - &size, (void *)&MokNew); + &size, (void *)&MokVar); - if (efi_status != EFI_SUCCESS && efi_status != EFI_BUFFER_TOO_SMALL) - goto done; + if (efi_status == EFI_SUCCESS || efi_status == EFI_BUFFER_TOO_SMALL) + return TRUE; - efi_status = start_image(image_handle, MOK_MANAGER); + return FALSE; +} - if (efi_status != EFI_SUCCESS) { - Print(L"Failed to start MokManager\n"); - goto done; +EFI_STATUS check_mok_request(EFI_HANDLE image_handle) +{ + EFI_STATUS efi_status; + + if (check_var(L"MokNew") || check_var(L"MokSB") || + check_var(L"MokPW") || check_var(L"MokAuth")) { + efi_status = start_image(image_handle, MOK_MANAGER); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to start MokManager\n"); + return efi_status; + } } -done: - return efi_status; + return EFI_SUCCESS; } +static EFI_STATUS check_mok_sb (void) +{ + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS status = EFI_SUCCESS; + void *MokSBState = NULL; + UINTN MokSBStateSize = 0; + UINT32 attributes; + + status = get_variable(L"MokSBState", shim_lock_guid, &attributes, + &MokSBStateSize, &MokSBState); + + if (status != EFI_SUCCESS) + return EFI_ACCESS_DENIED; + + if (attributes & EFI_VARIABLE_RUNTIME_ACCESS) { + Print(L"MokSBState is compromised! Clearing it\n"); + if (LibDeleteVariable(L"MokSBState", &shim_lock_guid) != EFI_SUCCESS) { + Print(L"Failed to erase MokSBState\n"); + } + status = EFI_ACCESS_DENIED; + } else { + if (*(UINT8 *)MokSBState == 1) { + insecure_mode = 1; + } + } + + return status; +} + + EFI_STATUS efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *passed_systab) { EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; @@ -1092,6 +1202,13 @@ EFI_STATUS efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *passed_systab) InitializeLib(image_handle, systab); + check_mok_sb(); + + if (insecure_mode) { + Print(L"Booting in insecure mode\n"); + uefi_call_wrapper(BS->Stall, 1, 2000000); + } + efi_status = check_mok_request(image_handle); efi_status = mirror_mok_list();