summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am10
-rw-r--r--lib/file/file.c77
-rw-r--r--lib/file/file.h3
-rw-r--r--lib/pb-protocol/pb-protocol.c13
-rw-r--r--lib/security/gpg.c337
-rw-r--r--lib/security/gpg.h72
-rw-r--r--lib/types/types.h2
7 files changed, 513 insertions, 1 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 09bc1aa..bb7dfe4 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -20,6 +20,13 @@ lib_libpbcore_la_CPPFLAGS = \
$(AM_CPPFLAGS) \
-DPREFIX='"$(prefix)"'
+if WITH_GPGME
+gpg_int_SOURCES = lib/security/gpg..h \
+ lib/security/gpg.c
+else
+gpg_int_SOURCES =
+endif
+
lib_libpbcore_la_SOURCES = \
lib/file/file.h \
lib/file/file.c \
@@ -50,7 +57,8 @@ lib_libpbcore_la_SOURCES = \
lib/util/util.c \
lib/util/util.h \
lib/flash/config.h \
- lib/flash/flash.h
+ lib/flash/flash.h \
+ $(gpg_int_SOURCES)
if ENABLE_MTD
lib_libpbcore_la_SOURCES += \
diff --git a/lib/file/file.c b/lib/file/file.c
index 1bde9fb..6a270a3 100644
--- a/lib/file/file.c
+++ b/lib/file/file.c
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2013 Jeremy Kerr <jk@ozlabs.org>
+ * Copyright (C) 2016 Raptor Engineering, LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,11 +24,87 @@
#include <sys/types.h>
#include <talloc/talloc.h>
+#include <log/log.h>
#include "file.h"
+#define MAX_FILENAME_SIZE 8192
+#define FILE_XFER_BUFFER_SIZE 8192
+
static const int max_file_size = 1024 * 1024;
+int copy_file_secure_dest(void *ctx,
+ const char *source_file, char **destination_file) {
+ int result = 0;
+ char template[] = "/tmp/petitbootXXXXXX";
+ char dest_filename[MAX_FILENAME_SIZE] = "";
+ FILE *source_handle = fopen(source_file, "r");
+ int destination_fd = mkstemp(template);
+ FILE *destination_handle = fdopen(destination_fd, "w");
+ if (!source_handle || !(destination_handle)) {
+ // handle open error
+ pb_log("%s: failed: unable to open source file '%s'\n",
+ __func__, source_file);
+ return -1;
+ }
+
+ size_t l1;
+ unsigned char *buffer;
+ buffer = talloc_array(ctx, unsigned char, FILE_XFER_BUFFER_SIZE);
+ if (!buffer) {
+ pb_log("%s: failed: unable to allocate file transfer buffer\n",
+ __func__);
+ return -1;
+ }
+
+ /* Copy data */
+ while ((l1 = fread(buffer, 1, sizeof buffer, source_handle)) > 0) {
+ size_t l2 = fwrite(buffer, 1, l1, destination_handle);
+ if (l2 < l1) {
+ if (ferror(destination_handle)) {
+ /* General error */
+ result = -1;
+ pb_log("%s: failed: unknown fault\n", __func__);
+ }
+ else {
+ /* No space on destination device */
+ result = -1;
+ pb_log("%s: failed: temporary storage full\n",
+ __func__);
+ }
+ break;
+ }
+ }
+
+ talloc_free(buffer);
+
+ if (result) {
+ dest_filename[0] = '\0';
+ }
+ else {
+ ssize_t r;
+ char readlink_buffer[MAX_FILENAME_SIZE];
+ snprintf(readlink_buffer, MAX_FILENAME_SIZE, "/proc/self/fd/%d",
+ destination_fd);
+ r = readlink(readlink_buffer, dest_filename,
+ MAX_FILENAME_SIZE);
+ if (r < 0) {
+ /* readlink failed */
+ result = -1;
+ pb_log("%s: failed: unable to obtain temporary filename"
+ "\n", __func__);
+ }
+ dest_filename[r] = '\0';
+ }
+
+ fclose(source_handle);
+ fclose(destination_handle);
+
+ *destination_file = talloc_strdup(ctx, dest_filename);
+
+ return result;
+}
+
int read_file(void *ctx, const char *filename, char **bufp, int *lenp)
{
struct stat statbuf;
diff --git a/lib/file/file.h b/lib/file/file.h
index 8aa7d3c..a2744a0 100644
--- a/lib/file/file.h
+++ b/lib/file/file.h
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2013 Jeremy Kerr <jk@ozlabs.org>
+ * Copyright (C) 2016 Raptor Engineering, LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,6 +18,8 @@
#ifndef FILE_H
#define FILE_H
+int copy_file_secure_dest(void *ctx,
+ const char * source_file, char ** destination_file);
int read_file(void *ctx, const char *filename, char **bufp, int *lenp);
int replace_file(const char *filename, char *buf, int len);
diff --git a/lib/pb-protocol/pb-protocol.c b/lib/pb-protocol/pb-protocol.c
index 7887fb0..1560ef7 100644
--- a/lib/pb-protocol/pb-protocol.c
+++ b/lib/pb-protocol/pb-protocol.c
@@ -37,6 +37,7 @@
* 4-byte len, initrd_file
* 4-byte len, dtb_file
* 4-byte len, boot_args
+ * 4-byte len, args_sig_file
*
* action = 0x2: device remove message
* payload:
@@ -49,6 +50,7 @@
* 4-byte len, initrd_file
* 4-byte len, dtb_file
* 4-byte len, boot_args
+ * 4-byte len, args_sig_file
*
*/
@@ -72,6 +74,7 @@ void pb_protocol_dump_device(const struct device *dev, const char *text,
fprintf(stream, "%s\t\tinit: %s\n", text, opt->initrd_file);
fprintf(stream, "%s\t\tdtb: %s\n", text, opt->dtb_file);
fprintf(stream, "%s\t\targs: %s\n", text, opt->boot_args);
+ fprintf(stream, "%s\t\tasig: %s\n", text, opt->args_sig_file);
}
}
@@ -197,6 +200,7 @@ int pb_protocol_boot_option_len(const struct boot_option *opt)
4 + optional_strlen(opt->initrd_file) +
4 + optional_strlen(opt->dtb_file) +
4 + optional_strlen(opt->boot_args) +
+ 4 + optional_strlen(opt->args_sig_file) +
sizeof(opt->is_default);
}
@@ -207,6 +211,7 @@ int pb_protocol_boot_len(const struct boot_command *boot)
4 + optional_strlen(boot->initrd_file) +
4 + optional_strlen(boot->dtb_file) +
4 + optional_strlen(boot->boot_args) +
+ 4 + optional_strlen(boot->args_sig_file) +
4 + optional_strlen(boot->tty);
}
@@ -360,6 +365,7 @@ int pb_protocol_serialise_boot_option(const struct boot_option *opt,
pos += pb_protocol_serialise_string(pos, opt->initrd_file);
pos += pb_protocol_serialise_string(pos, opt->dtb_file);
pos += pb_protocol_serialise_string(pos, opt->boot_args);
+ pos += pb_protocol_serialise_string(pos, opt->args_sig_file);
*(bool *)pos = opt->is_default;
pos += sizeof(bool);
@@ -380,6 +386,7 @@ int pb_protocol_serialise_boot_command(const struct boot_command *boot,
pos += pb_protocol_serialise_string(pos, boot->initrd_file);
pos += pb_protocol_serialise_string(pos, boot->dtb_file);
pos += pb_protocol_serialise_string(pos, boot->boot_args);
+ pos += pb_protocol_serialise_string(pos, boot->args_sig_file);
pos += pb_protocol_serialise_string(pos, boot->tty);
assert(pos <= buf + buf_len);
@@ -750,6 +757,9 @@ int pb_protocol_deserialise_boot_option(struct boot_option *opt,
if (read_string(opt, &pos, &len, &opt->boot_args))
goto out;
+ if (read_string(opt, &pos, &len, &opt->args_sig_file))
+ goto out;
+
if (len < sizeof(bool))
goto out;
opt->is_default = *(bool *)(pos);
@@ -785,6 +795,9 @@ int pb_protocol_deserialise_boot_command(struct boot_command *cmd,
if (read_string(cmd, &pos, &len, &cmd->boot_args))
goto out;
+ if (read_string(cmd, &pos, &len, &cmd->args_sig_file))
+ goto out;
+
if (read_string(cmd, &pos, &len, &cmd->tty))
goto out;
diff --git a/lib/security/gpg.c b/lib/security/gpg.c
new file mode 100644
index 0000000..a377b55
--- /dev/null
+++ b/lib/security/gpg.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2016 Raptor Engineering, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <dirent.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include <log/log.h>
+#include <file/file.h>
+#include <talloc/talloc.h>
+#include <url/url.h>
+#include <util/util.h>
+#include <i18n/i18n.h>
+
+#include "gpg.h"
+
+/*
+ * If --with-signed-boot is enabled lib/security provides the ability to handle
+ * gpg-signed and/or encrypted boot sources (kernel, initrd, etc).
+ * This can be used to enable a form of secure boot, but it is important to
+ * recognise that it depends on the security of the entire system, for example
+ * a full trusted-boot implementation. Petitboot can not and will not be able
+ * to guarantee secure boot by itself.
+ */
+
+struct pb_url * gpg_get_signature_url(void *ctx, struct pb_url *base_file)
+{
+ struct pb_url *signature_file = NULL;
+
+ signature_file = pb_url_copy(ctx, base_file);
+ talloc_free(signature_file->file);
+ signature_file->file = talloc_asprintf(signature_file,
+ "%s.sig", base_file->file);
+ talloc_free(signature_file->path);
+ signature_file->path = talloc_asprintf(signature_file,
+ "%s.sig", base_file->path);
+
+ return signature_file;
+}
+
+int verify_file_signature(const char *plaintext_filename,
+ const char *signature_filename, FILE *authorized_signatures_handle,
+ const char *keyring_path)
+{
+ int valid = 0;
+ gpgme_signature_t verification_signatures;
+ gpgme_verify_result_t verification_result;
+ gpgme_data_t plaintext_data;
+ gpgme_data_t signature_data;
+ gpgme_engine_info_t enginfo;
+ gpgme_ctx_t gpg_context;
+ gpgme_error_t err;
+
+ if (signature_filename == NULL)
+ return -1;
+
+ /* Initialize gpgme */
+ setlocale (LC_ALL, "");
+ gpgme_check_version(NULL);
+ gpgme_set_locale(NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
+ err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
+ if (err != GPG_ERR_NO_ERROR) {
+ pb_log("%s: OpenPGP support not available\n", __func__);
+ return -1;
+ }
+ err = gpgme_get_engine_info(&enginfo);
+ if (err != GPG_ERR_NO_ERROR) {
+ pb_log("%s: GPG engine failed to initialize\n", __func__);
+ return -1;
+ }
+ err = gpgme_new(&gpg_context);
+ if (err != GPG_ERR_NO_ERROR) {
+ pb_log("%s: GPG context could not be created\n", __func__);
+ return -1;
+ }
+ err = gpgme_set_protocol(gpg_context, GPGME_PROTOCOL_OpenPGP);
+ if (err != GPG_ERR_NO_ERROR) {
+ pb_log("%s: GPG protocol could not be set\n", __func__);
+ return -1;
+ }
+ if (keyring_path)
+ err = gpgme_ctx_set_engine_info (gpg_context,
+ GPGME_PROTOCOL_OpenPGP, enginfo->file_name,
+ keyring_path);
+ else
+ err = gpgme_ctx_set_engine_info (gpg_context,
+ GPGME_PROTOCOL_OpenPGP, enginfo->file_name,
+ enginfo->home_dir);
+ if (err != GPG_ERR_NO_ERROR) {
+ pb_log("%s: Could not set GPG engine information\n", __func__);
+ return -1;
+ }
+ err = gpgme_data_new_from_file(&plaintext_data, plaintext_filename, 1);
+ if (err != GPG_ERR_NO_ERROR) {
+ pb_log("%s: Could not create GPG plaintext data buffer"
+ " from file '%s'\n", __func__, plaintext_filename);
+ return -1;
+ }
+ err = gpgme_data_new_from_file(&signature_data, signature_filename, 1);
+ if (err != GPG_ERR_NO_ERROR) {
+ pb_log("%s: Could not create GPG signature data buffer"
+ " from file '%s'\n", __func__, signature_filename);
+ return -1;
+ }
+
+ /* Check signature */
+ err = gpgme_op_verify(gpg_context, signature_data, plaintext_data,
+ NULL);
+ if (err != GPG_ERR_NO_ERROR) {
+ pb_log("%s: Could not verify file using GPG signature '%s'\n",
+ __func__, signature_filename);
+ return -1;
+ }
+ verification_result = gpgme_op_verify_result(gpg_context);
+ verification_signatures = verification_result->signatures;
+ while (verification_signatures) {
+ if (verification_signatures->status != GPG_ERR_NO_ERROR) {
+ /* Signature verification failure */
+ pb_log("%s: Signature for key ID '%s' ('%s') invalid."
+ " Status: %08x\n", __func__,
+ verification_signatures->fpr,
+ signature_filename,
+ verification_signatures->status);
+ verification_signatures = verification_signatures->next;
+ continue;
+ }
+
+ /* Signature check passed with no error */
+ pb_log("%s: Good signature for key ID '%s' ('%s')\n",
+ __func__, verification_signatures->fpr,
+ signature_filename);
+ /* Verify fingerprint is present in
+ * authorized signatures file
+ */
+ char *auth_sig_line = NULL;
+ size_t auth_sig_len = 0;
+ ssize_t auth_sig_read;
+ rewind(authorized_signatures_handle);
+ while ((auth_sig_read = getline(&auth_sig_line,
+ &auth_sig_len,
+ authorized_signatures_handle)) != -1) {
+ auth_sig_len = strlen(auth_sig_line);
+ while ((auth_sig_line[auth_sig_len-1] == '\n')
+ || (auth_sig_line[auth_sig_len-1] == '\r'))
+ auth_sig_len--;
+ auth_sig_line[auth_sig_len] = '\0';
+ if (strcmp(auth_sig_line,
+ verification_signatures->fpr) == 0)
+ valid = 1;
+ }
+ free(auth_sig_line);
+ verification_signatures = verification_signatures->next;
+ }
+
+ /* Clean up */
+ gpgme_data_release(plaintext_data);
+ gpgme_data_release(signature_data);
+ gpgme_release(gpg_context);
+
+ if (!valid) {
+ pb_log("%s: Incorrect GPG signature\n", __func__);
+ return -1;
+ }
+
+ pb_log("%s: GPG signature '%s' for file '%s' verified\n",
+ __func__, signature_filename, plaintext_filename);
+
+ return 0;
+}
+
+int gpg_validate_boot_files(struct boot_task *boot_task) {
+ int result = 0;
+ char *kernel_filename = NULL;
+ char *initrd_filename = NULL;
+ char *dtb_filename = NULL;
+
+ FILE *authorized_signatures_handle = NULL;
+
+ char cmdline_template[] = "/tmp/petitbootXXXXXX";
+ int cmdline_fd = mkstemp(cmdline_template);
+ FILE *cmdline_handle = NULL;
+
+ const char* local_initrd_signature = (boot_task->verify_signature) ?
+ boot_task->local_initrd_signature : NULL;
+ const char* local_dtb_signature = (boot_task->verify_signature) ?
+ boot_task->local_dtb_signature : NULL;
+ const char* local_image_signature = (boot_task->verify_signature) ?
+ boot_task->local_image_signature : NULL;
+ const char* local_cmdline_signature = (boot_task->verify_signature) ?
+ boot_task->local_cmdline_signature : NULL;
+
+ if (!boot_task->verify_signature)
+ return result;
+
+ /* Load authorized signatures file */
+ authorized_signatures_handle = fopen(LOCKDOWN_FILE, "r");
+ if (!authorized_signatures_handle) {
+ pb_log("%s: unable to read lockdown file\n", __func__);
+ return KEXEC_LOAD_SIG_SETUP_INVALID;
+ }
+
+ /* Copy files to temporary directory for verification / boot */
+ result = copy_file_secure_dest(boot_task,
+ boot_task->local_image,
+ &kernel_filename);
+ if (result) {
+ pb_log("%s: image copy failed: (%d)\n",
+ __func__, result);
+ return result;
+ }
+ if (boot_task->local_initrd) {
+ result = copy_file_secure_dest(boot_task,
+ boot_task->local_initrd,
+ &initrd_filename);
+ if (result) {
+ pb_log("%s: initrd copy failed: (%d)\n",
+ __func__, result);
+ return result;
+ }
+ }
+ if (boot_task->local_dtb) {
+ result = copy_file_secure_dest(boot_task,
+ boot_task->local_dtb,
+ &dtb_filename);
+ if (result) {
+ pb_log("%s: dtb copy failed: (%d)\n",
+ __func__, result);
+ return result;
+ }
+ }
+ boot_task->local_image_override = talloc_strdup(boot_task,
+ kernel_filename);
+ if (boot_task->local_initrd)
+ boot_task->local_initrd_override = talloc_strdup(boot_task,
+ initrd_filename);
+ if (boot_task->local_dtb)
+ boot_task->local_dtb_override = talloc_strdup(boot_task,
+ dtb_filename);
+
+ /* Write command line to temporary file for verification */
+ if (cmdline_fd < 0) {
+ /* mkstemp failed */
+ pb_log("%s: failed: unable to create command line"
+ " temporary file for verification\n",
+ __func__);
+ result = -1;
+ }
+ else {
+ cmdline_handle = fdopen(cmdline_fd, "w");
+ }
+ if (!cmdline_handle) {
+ /* Failed to open file */
+ pb_log("%s: failed: unable to write command line"
+ " temporary file for verification\n",
+ __func__);
+ result = -1;
+ }
+ else {
+ fwrite(boot_task->args, sizeof(char),
+ strlen(boot_task->args), cmdline_handle);
+ fflush(cmdline_handle);
+ }
+
+ /* Check signatures */
+ if (verify_file_signature(kernel_filename,
+ local_image_signature,
+ authorized_signatures_handle, "/etc/gpg"))
+ result = KEXEC_LOAD_SIGNATURE_FAILURE;
+ if (verify_file_signature(cmdline_template,
+ local_cmdline_signature,
+ authorized_signatures_handle, "/etc/gpg"))
+ result = KEXEC_LOAD_SIGNATURE_FAILURE;
+ if (boot_task->local_initrd_signature)
+ if (verify_file_signature(initrd_filename,
+ local_initrd_signature,
+ authorized_signatures_handle, "/etc/gpg"))
+ result = KEXEC_LOAD_SIGNATURE_FAILURE;
+ if (boot_task->local_dtb_signature)
+ if (verify_file_signature(dtb_filename,
+ local_dtb_signature,
+ authorized_signatures_handle, "/etc/gpg"))
+ result = KEXEC_LOAD_SIGNATURE_FAILURE;
+
+ /* Clean up */
+ if (cmdline_handle) {
+ fclose(cmdline_handle);
+ unlink(cmdline_template);
+ }
+ fclose(authorized_signatures_handle);
+
+ return result;
+}
+
+void gpg_validate_boot_files_cleanup(struct boot_task *boot_task) {
+ if (boot_task->verify_signature) {
+ unlink(boot_task->local_image_override);
+ if (boot_task->local_initrd_override)
+ unlink(boot_task->local_initrd_override);
+ if (boot_task->local_dtb_override)
+ unlink(boot_task->local_dtb_override);
+
+ talloc_free(boot_task->local_image_override);
+ if (boot_task->local_initrd_override)
+ talloc_free(boot_task->local_initrd_override);
+ if (boot_task->local_dtb_override)
+ talloc_free(boot_task->local_dtb_override);
+ }
+}
+
+int lockdown_status() {
+ if (access(LOCKDOWN_FILE, F_OK) == -1)
+ return PB_LOCKDOWN_NONE;
+ else
+ return PB_LOCKDOWN_SIGN;
+} \ No newline at end of file
diff --git a/lib/security/gpg.h b/lib/security/gpg.h
new file mode 100644
index 0000000..fb418bb
--- /dev/null
+++ b/lib/security/gpg.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 Raptor Engineering, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PB_GPG_H
+#define _PB_GPG_H
+
+#include <discover/boot.h>
+
+enum {
+ PB_LOCKDOWN_NONE = 0,
+ PB_LOCKDOWN_SIGN = 1,
+};
+
+#if defined(HAVE_LIBGPGME)
+#include <gpgme.h>
+#endif /* HAVE_LIBGPGME */
+
+int lockdown_status(void);
+
+struct pb_url * gpg_get_signature_url(void *ctx, struct pb_url *base_file);
+
+int verify_file_signature(const char *plaintext_filename,
+ const char *signature_filename, FILE *authorized_signatures_handle,
+ const char *keyring_path);
+
+int gpg_validate_boot_files(struct boot_task *boot_task);
+
+void gpg_validate_boot_files_cleanup(struct boot_task *boot_task);
+
+#if !defined(HAVE_LIBGPGME)
+
+int lockdown_status(void) { return PB_LOCKDOWN_NONE; }
+
+struct pb_url * gpg_get_signature_url(void *ctx __attribute__((unused)),
+ struct pb_url *base_file __attribute__((unused)))
+{
+ return NULL;
+}
+
+int verify_file_signature(const char *plaintext_filename __attribute__((unused)),
+ const char *signature_filename __attribute__((unused)),
+ FILE *authorized_signatures_handle __attribute__((unused)),
+ const char *keyring_path __attribute__((unused)))
+{
+ return -1;
+}
+
+int gpg_validate_boot_files(struct boot_task *boot_task __attribute__((unused)))
+{
+ return 0;
+}
+
+void gpg_validate_boot_files_cleanup(struct boot_task *boot_task __attribute__((unused)))
+{}
+
+#endif /* HAVE_LIBGPGME */
+
+#endif /* _PB_GPG_H */ \ No newline at end of file
diff --git a/lib/types/types.h b/lib/types/types.h
index 5c5f6ed..6b607cd 100644
--- a/lib/types/types.h
+++ b/lib/types/types.h
@@ -52,6 +52,7 @@ struct boot_option {
char *initrd_file;
char *dtb_file;
char *boot_args;
+ char *args_sig_file;
bool is_default;
struct list_item list;
@@ -65,6 +66,7 @@ struct boot_command {
char *initrd_file;
char *dtb_file;
char *boot_args;
+ char *args_sig_file;
char *tty;
};
OpenPOWER on IntegriCloud