Coverage Report

Created: 2026-02-26 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-kernel.c
Line
Count
Source
1
/*
2
 * Copyright 2017 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuCommon"
8
9
#include "config.h"
10
11
#include <errno.h>
12
#include <glib/gstdio.h>
13
#ifdef HAVE_UTSNAME_H
14
#include <sys/utsname.h>
15
#endif
16
17
#include "fu-input-stream.h"
18
#include "fu-kernel.h"
19
#include "fu-path.h"
20
#include "fu-string.h"
21
#include "fu-version-common.h"
22
23
/**
24
 * fu_kernel_locked_down:
25
 * @pstore: a #FuPathStore
26
 *
27
 * Determines if kernel lockdown in effect
28
 *
29
 * Since: 1.8.2
30
 **/
31
gboolean
32
fu_kernel_locked_down(FuPathStore *pstore)
33
0
{
34
0
#ifdef __linux__
35
0
  gsize len = 0;
36
0
  g_autofree gchar *fname = NULL;
37
0
  g_autofree gchar *data = NULL;
38
0
  g_auto(GStrv) options = NULL;
39
40
0
  fname = fu_path_store_build_filename(pstore,
41
0
               NULL,
42
0
               FU_PATH_KIND_SYSFSDIR_SECURITY,
43
0
               "lockdown",
44
0
               NULL);
45
0
  if (fname == NULL)
46
0
    return FALSE;
47
0
  if (!g_file_test(fname, G_FILE_TEST_EXISTS))
48
0
    return FALSE;
49
0
  if (!g_file_get_contents(fname, &data, &len, NULL))
50
0
    return FALSE;
51
0
  if (len < 1)
52
0
    return FALSE;
53
0
  options = g_strsplit(data, " ", -1);
54
0
  for (guint i = 0; options[i] != NULL; i++) {
55
0
    if (g_strcmp0(options[i], "[none]") == 0)
56
0
      return FALSE;
57
0
  }
58
0
  return TRUE;
59
#else
60
  return FALSE;
61
#endif
62
0
}
63
64
/**
65
 * fu_kernel_check_version:
66
 * @minimum_kernel: (not nullable): The minimum kernel version to check against
67
 * @error: (nullable): optional return location for an error
68
 *
69
 * Determines if the system is running at least a certain required kernel version
70
 *
71
 * Since: 1.8.2
72
 **/
73
gboolean
74
fu_kernel_check_version(const gchar *minimum_kernel, GError **error)
75
0
{
76
#ifdef HAVE_UTSNAME_H
77
  struct utsname name_tmp = {0};
78
79
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
80
  g_return_val_if_fail(minimum_kernel != NULL, FALSE);
81
82
  if (uname(&name_tmp) < 0) {
83
    g_set_error_literal(error,
84
            FWUPD_ERROR,
85
            FWUPD_ERROR_INTERNAL,
86
            "failed to read kernel version");
87
    return FALSE;
88
  }
89
  if (fu_version_compare(name_tmp.release, minimum_kernel, FWUPD_VERSION_FORMAT_TRIPLET) <
90
      0) {
91
    g_set_error(error,
92
          FWUPD_ERROR,
93
          FWUPD_ERROR_INTERNAL,
94
          "kernel %s doesn't meet minimum %s",
95
          name_tmp.release,
96
          minimum_kernel);
97
    return FALSE;
98
  }
99
100
  return TRUE;
101
#else
102
0
  g_set_error_literal(error,
103
0
          FWUPD_ERROR,
104
0
          FWUPD_ERROR_INTERNAL,
105
0
          "platform doesn't support checking for minimum Linux kernel");
106
0
  return FALSE;
107
0
#endif
108
0
}
109
110
typedef struct {
111
  GHashTable *hash;
112
  GHashTable *values;
113
} FuKernelConfigHelper;
114
115
static gboolean
116
fu_kernel_parse_config_line_cb(GString *token, guint token_idx, gpointer user_data, GError **error)
117
0
{
118
0
  g_auto(GStrv) kv = NULL;
119
0
  FuKernelConfigHelper *helper = (FuKernelConfigHelper *)user_data;
120
0
  GRefString *value;
121
122
0
  if (token->len == 0)
123
0
    return TRUE;
124
0
  if (token->str[0] == '#')
125
0
    return TRUE;
126
127
0
  kv = g_strsplit(token->str, "=", 2);
128
0
  if (g_strv_length(kv) != 2) {
129
0
    g_set_error(error,
130
0
          FWUPD_ERROR,
131
0
          FWUPD_ERROR_INVALID_DATA,
132
0
          "invalid format for '%s'",
133
0
          token->str);
134
0
    return FALSE;
135
0
  }
136
0
  value = g_hash_table_lookup(helper->values, kv[1]);
137
0
  if (value != NULL) {
138
0
    g_hash_table_insert(helper->hash, g_strdup(kv[0]), g_ref_string_acquire(value));
139
0
  } else {
140
0
    g_hash_table_insert(helper->hash, g_strdup(kv[0]), g_ref_string_new(kv[1]));
141
0
  }
142
0
  return TRUE;
143
0
}
144
145
/**
146
 * fu_kernel_parse_config:
147
 * @buf: (not nullable): cmdline to parse
148
 * @bufsz: size of @bufsz
149
 *
150
 * Parses all the kernel options into a hash table. Commented out options are not included.
151
 *
152
 * Returns: (transfer container) (element-type utf8 utf8): config keys
153
 *
154
 * Since: 1.9.6
155
 **/
156
GHashTable *
157
fu_kernel_parse_config(const gchar *buf, gsize bufsz, GError **error)
158
0
{
159
0
  g_autoptr(GHashTable) hash = g_hash_table_new_full(g_str_hash,
160
0
                 g_str_equal,
161
0
                 g_free,
162
0
                 (GDestroyNotify)g_ref_string_release);
163
0
  g_autoptr(GHashTable) values = g_hash_table_new_full(g_str_hash,
164
0
                   g_str_equal,
165
0
                   NULL,
166
0
                   (GDestroyNotify)g_ref_string_release);
167
0
  FuKernelConfigHelper helper = {.hash = hash, .values = values};
168
0
  const gchar *value_keys[] = {"y", "m", "0", NULL};
169
170
0
  g_return_val_if_fail(buf != NULL, NULL);
171
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
172
173
  /* add 99.9% of the most common keys to avoid thousands of small allocations */
174
0
  for (guint i = 0; value_keys[i] != NULL; i++) {
175
0
    g_hash_table_insert(values,
176
0
            (gpointer)value_keys[i],
177
0
            g_ref_string_new(value_keys[i]));
178
0
  }
179
0
  if (!fu_strsplit_full(buf, bufsz, "\n", fu_kernel_parse_config_line_cb, &helper, error))
180
0
    return NULL;
181
0
  return g_steal_pointer(&hash);
182
0
}
183
184
#ifdef __linux__
185
static gchar *
186
fu_kernel_get_config_path(FuPathStore *pstore, GError **error)
187
0
{
188
#ifdef HAVE_UTSNAME_H
189
  struct utsname name_tmp;
190
  g_autofree gchar *config_fn = NULL;
191
192
  memset(&name_tmp, 0, sizeof(struct utsname));
193
  if (uname(&name_tmp) < 0) {
194
    g_set_error_literal(error,
195
            FWUPD_ERROR,
196
            FWUPD_ERROR_INTERNAL,
197
            "failed to read kernel version");
198
    return NULL;
199
  }
200
  config_fn = g_strdup_printf("config-%s", name_tmp.release);
201
  return fu_path_store_build_filename(pstore,
202
              error,
203
              FU_PATH_KIND_HOSTFS_BOOT,
204
              config_fn,
205
              NULL);
206
#else
207
0
  g_set_error_literal(error,
208
0
          FWUPD_ERROR,
209
0
          FWUPD_ERROR_INTERNAL,
210
0
          "platform does not support uname");
211
0
  return NULL;
212
0
#endif
213
0
}
214
#endif
215
216
/**
217
 * fu_kernel_get_config:
218
 * @pstore: a #FuPathStore
219
 * @error: (nullable): optional return location for an error
220
 *
221
 * Loads all the kernel options into a hash table. Commented out options are not included.
222
 *
223
 * Returns: (transfer container) (element-type utf8 utf8): options from the kernel
224
 *
225
 * Since: 1.8.5
226
 **/
227
GHashTable *
228
fu_kernel_get_config(FuPathStore *pstore, GError **error)
229
0
{
230
0
#ifdef __linux__
231
0
  gsize bufsz = 0;
232
0
  g_autofree gchar *buf = NULL;
233
0
  g_autofree gchar *fn = NULL;
234
0
  g_autofree gchar *config_fngz = NULL;
235
236
0
  g_return_val_if_fail(FU_IS_PATH_STORE(pstore), NULL);
237
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
238
239
  /* try /proc/config.gz -- which will only work with CONFIG_IKCONFIG */
240
0
  config_fngz =
241
0
      fu_path_store_build_filename(pstore, NULL, FU_PATH_KIND_PROCFS, "config.gz", NULL);
242
0
  if (config_fngz != NULL && g_file_test(config_fngz, G_FILE_TEST_EXISTS)) {
243
0
    g_autoptr(GBytes) payload = NULL;
244
0
    g_autoptr(GConverter) conv = NULL;
245
0
    g_autoptr(GFile) file = g_file_new_for_path(config_fngz);
246
0
    g_autoptr(GInputStream) istream1 = NULL;
247
0
    g_autoptr(GInputStream) istream2 = NULL;
248
249
0
    istream1 = G_INPUT_STREAM(g_file_read(file, NULL, error));
250
0
    if (istream1 == NULL)
251
0
      return NULL;
252
0
    conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP));
253
0
    istream2 = g_converter_input_stream_new(istream1, conv);
254
0
    payload = fu_input_stream_read_bytes(istream2, 0, G_MAXSIZE, NULL, error);
255
0
    if (payload == NULL)
256
0
      return NULL;
257
0
    return fu_kernel_parse_config(g_bytes_get_data(payload, NULL),
258
0
                g_bytes_get_size(payload),
259
0
                error);
260
0
  }
261
262
  /* fall back to /boot/config-$(uname -r) */
263
0
  fn = fu_kernel_get_config_path(pstore, error);
264
0
  if (fn == NULL)
265
0
    return NULL;
266
0
  if (!g_file_get_contents(fn, &buf, &bufsz, error))
267
0
    return NULL;
268
0
  return fu_kernel_parse_config(buf, bufsz, error);
269
#else
270
  g_set_error_literal(error,
271
          FWUPD_ERROR,
272
          FWUPD_ERROR_INTERNAL,
273
          "platform does not support getting the kernel config");
274
  return NULL;
275
#endif
276
0
}
277
278
/**
279
 * fu_kernel_parse_cmdline:
280
 * @buf: (not nullable): cmdline to parse
281
 * @bufsz: size of @bufsz
282
 *
283
 * Parses all the kernel key/values into a hash table, respecting double quotes when required.
284
 *
285
 * Returns: (transfer container) (element-type utf8 utf8): keys from the cmdline
286
 *
287
 * Since: 1.9.1
288
 **/
289
GHashTable *
290
fu_kernel_parse_cmdline(const gchar *buf, gsize bufsz)
291
0
{
292
0
  gboolean is_escape = FALSE;
293
0
  g_autoptr(GHashTable) hash = NULL;
294
0
  g_autoptr(GString) acc = g_string_new(NULL);
295
296
0
  g_return_val_if_fail(buf != NULL, NULL);
297
298
0
  hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
299
0
  if (bufsz == 0)
300
0
    return g_steal_pointer(&hash);
301
0
  for (gsize i = 0; i < bufsz; i++) {
302
0
    if (!is_escape && (buf[i] == ' ' || buf[i] == '\n') && acc->len > 0) {
303
0
      g_auto(GStrv) kv = g_strsplit(acc->str, "=", 2);
304
0
      g_hash_table_insert(hash, g_strdup(kv[0]), g_strdup(kv[1]));
305
0
      g_string_set_size(acc, 0);
306
0
      continue;
307
0
    }
308
0
    if (buf[i] == '"') {
309
0
      is_escape = !is_escape;
310
0
      continue;
311
0
    }
312
0
    g_string_append_c(acc, buf[i]);
313
0
  }
314
0
  if (acc->len > 0) {
315
0
    g_auto(GStrv) kv = g_strsplit(acc->str, "=", 2);
316
0
    g_hash_table_insert(hash, g_strdup(kv[0]), g_strdup(kv[1]));
317
0
  }
318
319
  /* success */
320
0
  return g_steal_pointer(&hash);
321
0
}
322
323
/**
324
 * fu_kernel_get_cmdline:
325
 * @error: (nullable): optional return location for an error
326
 *
327
 * Loads all the kernel /proc/cmdline key/values into a hash table.
328
 *
329
 * Returns: (transfer container) (element-type utf8 utf8): keys from the kernel command line
330
 *
331
 * Since: 1.8.5
332
 **/
333
GHashTable *
334
fu_kernel_get_cmdline(GError **error)
335
0
{
336
0
#ifdef __linux__
337
0
  gsize bufsz = 0;
338
0
  g_autofree gchar *buf = NULL;
339
340
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
341
342
0
  if (!g_file_get_contents("/proc/cmdline", &buf, &bufsz, error))
343
0
    return NULL;
344
0
  return fu_kernel_parse_cmdline(buf, bufsz);
345
#else
346
  g_set_error_literal(error,
347
          FWUPD_ERROR,
348
          FWUPD_ERROR_INTERNAL,
349
          "platform does not support getting the kernel cmdline");
350
  return NULL;
351
#endif
352
0
}
353
354
gboolean
355
fu_kernel_check_cmdline_mutable(FuPathStore *pstore, GError **error)
356
0
{
357
0
  g_autofree gchar *grubby_path = NULL;
358
0
  g_auto(GStrv) config_files = g_new0(gchar *, 3);
359
360
0
  g_return_val_if_fail(FU_IS_PATH_STORE(pstore), FALSE);
361
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
362
363
  /* not found */
364
0
  grubby_path = fu_path_find_program("grubby", error);
365
0
  if (grubby_path == NULL)
366
0
    return FALSE;
367
368
  /* check all the config files are writable */
369
0
  config_files[0] = fu_path_store_build_filename(pstore,
370
0
                   error,
371
0
                   FU_PATH_KIND_HOSTFS_BOOT,
372
0
                   "grub2",
373
0
                   "grub.cfg",
374
0
                   NULL);
375
0
  if (config_files[0] == NULL)
376
0
    return FALSE;
377
0
  config_files[1] =
378
0
      fu_path_store_build_filename(pstore, error, FU_PATH_KIND_SYSCONFDIR, "grub.cfg", NULL);
379
0
  if (config_files[1] == NULL)
380
0
    return FALSE;
381
0
  for (guint i = 0; config_files[i] != NULL; i++) {
382
0
    g_autoptr(GFile) file = g_file_new_for_path(config_files[i]);
383
0
    g_autoptr(GFileInfo) info = NULL;
384
0
    g_autoptr(GError) error_local = NULL;
385
386
0
    if (!g_file_query_exists(file, NULL))
387
0
      continue;
388
0
    info = g_file_query_info(file,
389
0
           G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
390
0
           G_FILE_QUERY_INFO_NONE,
391
0
           NULL,
392
0
           &error_local);
393
0
    if (info == NULL) {
394
0
      g_warning("failed to get info for %s: %s",
395
0
          config_files[i],
396
0
          error_local->message);
397
0
      continue;
398
0
    }
399
0
    if (!g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) {
400
0
      g_set_error(error,
401
0
            FWUPD_ERROR,
402
0
            FWUPD_ERROR_NOT_SUPPORTED,
403
0
            "%s is not writable",
404
0
            config_files[i]);
405
0
      return FALSE;
406
0
    }
407
0
  }
408
409
  /* success */
410
0
  return TRUE;
411
0
}
412
413
static gboolean
414
fu_kernel_set_commandline(const gchar *arg, gboolean enable, GError **error)
415
0
{
416
0
  g_autofree gchar *output = NULL;
417
0
  g_autofree gchar *arg_string = NULL;
418
0
  g_autofree gchar *grubby_path = NULL;
419
0
  const gchar *argv_grubby[] = {"", "--update-kernel=DEFAULT", "", NULL};
420
421
0
  grubby_path = fu_path_find_program("grubby", error);
422
0
  if (grubby_path == NULL) {
423
0
    g_prefix_error_literal(error, "failed to find grubby: ");
424
0
    return FALSE;
425
0
  }
426
0
  if (enable)
427
0
    arg_string = g_strdup_printf("--args=%s", arg);
428
0
  else
429
0
    arg_string = g_strdup_printf("--remove-args=%s", arg);
430
431
0
  argv_grubby[0] = grubby_path;
432
0
  argv_grubby[2] = arg_string;
433
0
  return g_spawn_sync(NULL,
434
0
          (gchar **)argv_grubby,
435
0
          NULL,
436
0
          G_SPAWN_DEFAULT,
437
0
          NULL,
438
0
          NULL,
439
0
          &output,
440
0
          NULL,
441
0
          NULL,
442
0
          error);
443
0
}
444
445
/**
446
 * fu_kernel_add_cmdline_arg:
447
 * @arg: (not nullable): key to set
448
 * @error: (nullable): optional return location for an error
449
 *
450
 * Add a kernel command line argument.
451
 *
452
 * Returns: %TRUE if successful
453
 *
454
 * Since: 1.9.5
455
 **/
456
gboolean
457
fu_kernel_add_cmdline_arg(const gchar *arg, GError **error)
458
0
{
459
0
  return fu_kernel_set_commandline(arg, TRUE, error);
460
0
}
461
462
/**
463
 * fu_kernel_remove_cmdline_arg:
464
 * @arg: (not nullable): key to set
465
 * @error: (nullable): optional return location for an error
466
 *
467
 * Remove a kernel command line argument.
468
 *
469
 * Returns: %TRUE if successful
470
 *
471
 * Since: 1.9.5
472
 **/
473
gboolean
474
fu_kernel_remove_cmdline_arg(const gchar *arg, GError **error)
475
0
{
476
0
  return fu_kernel_set_commandline(arg, FALSE, error);
477
0
}