Coverage Report

Created: 2025-11-24 06:59

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