summaryrefslogtreecommitdiffstats
path: root/src/boot/efi/shim.c
blob: d272b6ccd99a25b23e8d355ed753183abc91b84c (plain)
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
/* SPDX-License-Identifier: LGPL-2.1+ */
/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Port to systemd-boot
 * Copyright © 2017 Max Resch <resch.max@gmail.com>
 *
 * Security Policy Handling
 * Copyright © 2012 <James.Bottomley@HansenPartnership.com>
 * https://github.com/mjg59/efitools
 */

#include <efi.h>
#include <efilib.h>

#include "util.h"
#include "shim.h"

/* well known shim lock guid */
#define SHIM_LOCK_GUID

struct ShimLock {
        EFI_STATUS __attribute__((sysv_abi)) (*shim_verify) (VOID *buffer, UINT32 size);

        /* context is actually a struct for the PE header, but it isn't needed so void is sufficient just do define the interface
         * see shim.c/shim.h and PeHeader.h in the github shim repo */
        EFI_STATUS __attribute__((sysv_abi)) (*generate_hash) (VOID *data, UINT32 datasize, VOID *context, UINT8 *sha256hash, UINT8 *sha1hash);

        EFI_STATUS __attribute__((sysv_abi)) (*read_header) (VOID *data, UINT32 datasize, VOID *context);
};

static const EFI_GUID simple_fs_guid = SIMPLE_FILE_SYSTEM_PROTOCOL;
static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE;

static const EFI_GUID security_protocol_guid = { 0xa46423e3, 0x4617, 0x49f1, {0xb9, 0xff, 0xd1, 0xbf, 0xa9, 0x11, 0x58, 0x39 } };
static const EFI_GUID security2_protocol_guid = { 0x94ab2f58, 0x1438, 0x4ef1, {0x91, 0x52, 0x18, 0x94, 0x1a, 0x3a, 0x0e, 0x68 } };
static const EFI_GUID shim_lock_guid = { 0x605dab50, 0xe046, 0x4300, {0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23} };

BOOLEAN shim_loaded(void) {
        struct ShimLock *shim_lock;

        return uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &shim_lock_guid, NULL, (VOID**) &shim_lock) == EFI_SUCCESS;
}

static BOOLEAN shim_validate(VOID *data, UINT32 size) {
        struct ShimLock *shim_lock;

        if (!data)
                return FALSE;

        if (uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &shim_lock_guid, NULL, (VOID**) &shim_lock) != EFI_SUCCESS)
                return FALSE;

        if (!shim_lock)
                return FALSE;

        return shim_lock->shim_verify(data, size) == EFI_SUCCESS;
}

BOOLEAN secure_boot_enabled(void) {
        _cleanup_freepool_ CHAR8 *b = NULL;
        UINTN size;

        if (efivar_get_raw(&global_guid, L"SecureBoot", &b, &size) == EFI_SUCCESS)
                return *b > 0;

        return FALSE;
}

/*
 * See the UEFI Platform Initialization manual (Vol2: DXE) for this
 */
struct _EFI_SECURITY2_PROTOCOL;
struct _EFI_SECURITY_PROTOCOL;
struct _EFI_DEVICE_PATH_PROTOCOL;

typedef struct _EFI_SECURITY2_PROTOCOL EFI_SECURITY2_PROTOCOL;
typedef struct _EFI_SECURITY_PROTOCOL EFI_SECURITY_PROTOCOL;
typedef struct _EFI_DEVICE_PATH_PROTOCOL EFI_DEVICE_PATH_PROTOCOL;

typedef EFI_STATUS (EFIAPI *EFI_SECURITY_FILE_AUTHENTICATION_STATE) (
        const EFI_SECURITY_PROTOCOL *This,
        UINT32 AuthenticationStatus,
        const EFI_DEVICE_PATH_PROTOCOL *File
);

typedef EFI_STATUS (EFIAPI *EFI_SECURITY2_FILE_AUTHENTICATION) (
        const EFI_SECURITY2_PROTOCOL *This,
        const EFI_DEVICE_PATH_PROTOCOL *DevicePath,
        VOID *FileBuffer,
        UINTN FileSize,
        BOOLEAN  BootPolicy
);

struct _EFI_SECURITY2_PROTOCOL {
        EFI_SECURITY2_FILE_AUTHENTICATION FileAuthentication;
};

struct _EFI_SECURITY_PROTOCOL {
        EFI_SECURITY_FILE_AUTHENTICATION_STATE  FileAuthenticationState;
};

/* Handle to the original authenticator for security1 protocol */
static EFI_SECURITY_FILE_AUTHENTICATION_STATE esfas = NULL;

/* Handle to the original authenticator for security2 protocol */
static EFI_SECURITY2_FILE_AUTHENTICATION es2fa = NULL;

/*
 * Perform shim/MOK and Secure Boot authentication on a binary that's already been
 * loaded into memory. This function does the platform SB authentication first
 * but preserves its return value in case of its failure, so that it can be
 * returned in case of a shim/MOK authentication failure. This is done because
 * the SB failure code seems to vary from one implementation to another, and I
 * don't want to interfere with that at this time.
 */
static EFIAPI EFI_STATUS security2_policy_authentication (const EFI_SECURITY2_PROTOCOL *this,
                                                          const EFI_DEVICE_PATH_PROTOCOL *device_path,
                                                          VOID *file_buffer, UINTN file_size, BOOLEAN boot_policy) {
        EFI_STATUS status;

        /* Chain original security policy */
        status = uefi_call_wrapper(es2fa, 5, this, device_path, file_buffer, file_size, boot_policy);

        /* if OK, don't bother with MOK check */
        if (status == EFI_SUCCESS)
                return status;

        if (shim_validate(file_buffer, file_size))
                return EFI_SUCCESS;

        return status;
}

/*
 * Perform both shim/MOK and platform Secure Boot authentication. This function loads
 * the file and performs shim/MOK authentication first simply to avoid double loads
 * of Linux kernels, which are much more likely to be shim/MOK-signed than platform-signed,
 * since kernels are big and can take several seconds to load on some computers and
 * filesystems. This also has the effect of returning whatever the platform code is for
 * authentication failure, be it EFI_ACCESS_DENIED, EFI_SECURITY_VIOLATION, or something
 * else. (This seems to vary between implementations.)
 */
static EFIAPI EFI_STATUS security_policy_authentication (const EFI_SECURITY_PROTOCOL *this, UINT32 authentication_status,
                                                         const EFI_DEVICE_PATH_PROTOCOL *device_path_const) {
        EFI_STATUS status;
        _cleanup_freepool_ EFI_DEVICE_PATH *dev_path = NULL;
        _cleanup_freepool_ CHAR16 *dev_path_str = NULL;
        EFI_HANDLE h;
        EFI_FILE *root;
        _cleanup_freepool_ CHAR8 *file_buffer = NULL;
        UINTN file_size;

        if (!device_path_const)
                return EFI_INVALID_PARAMETER;

        dev_path = DuplicateDevicePath((EFI_DEVICE_PATH*) device_path_const);

        status = uefi_call_wrapper(BS->LocateDevicePath, 3, (EFI_GUID*) &simple_fs_guid, &dev_path, &h);
        if (status != EFI_SUCCESS)
                return status;

        /* No need to check return value, this already happend in efi_main() */
        root = LibOpenRoot(h);
        dev_path_str = DevicePathToStr(dev_path);

        status = file_read(root, dev_path_str, 0, 0, &file_buffer, &file_size);
        if (EFI_ERROR(status))
                return status;
        uefi_call_wrapper(root->Close, 1, root);

        if (shim_validate(file_buffer, file_size))
                return EFI_SUCCESS;

        /* Try using the platform's native policy.... */
        return uefi_call_wrapper(esfas, 3, this, authentication_status, device_path_const);
}

EFI_STATUS security_policy_install(void) {
        EFI_SECURITY_PROTOCOL *security_protocol;
        EFI_SECURITY2_PROTOCOL *security2_protocol = NULL;
        EFI_STATUS status;

        /* Already Installed */
        if (esfas)
                return EFI_ALREADY_STARTED;

        /*
         * Don't bother with status here. The call is allowed
         * to fail, since SECURITY2 was introduced in PI 1.2.1.
         * Use security2_protocol == NULL as indicator.
         */
        uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &security2_protocol_guid, NULL, (VOID**) &security2_protocol);

        status = uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &security_protocol_guid, NULL, (VOID**) &security_protocol);
         /* This one is mandatory, so there's a serious problem */
        if (status != EFI_SUCCESS)
                return status;

        esfas = security_protocol->FileAuthenticationState;
        security_protocol->FileAuthenticationState = security_policy_authentication;

        if (security2_protocol) {
                es2fa = security2_protocol->FileAuthentication;
                security2_protocol->FileAuthentication = security2_policy_authentication;
        }

        return EFI_SUCCESS;
}

EFI_STATUS security_policy_uninstall(void) {
        EFI_STATUS status;

        if (esfas) {
                EFI_SECURITY_PROTOCOL *security_protocol;

                status = uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &security_protocol_guid, NULL, (VOID**) &security_protocol);

                if (status != EFI_SUCCESS)
                        return status;

                security_protocol->FileAuthenticationState = esfas;
                esfas = NULL;
        } else
                /* nothing installed */
                return EFI_NOT_STARTED;

        if (es2fa) {
                EFI_SECURITY2_PROTOCOL *security2_protocol;

                status = uefi_call_wrapper(BS->LocateProtocol, 3, (EFI_GUID*) &security2_protocol_guid, NULL, (VOID**) &security2_protocol);

                if (status != EFI_SUCCESS)
                        return status;

                security2_protocol->FileAuthentication = es2fa;
                es2fa = NULL;
        }

        return EFI_SUCCESS;
}