summaryrefslogtreecommitdiff
path: root/plugins/fsverity.c
blob: f1557d078e0cef37f8ebe537b408b346608debdc (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
/**
 * Copyright (C) 2020 Facebook
 *
 * Author: Jes Sorensen <jsorensen@fb.com>
 */

#include "system.h"

#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fsverity.h>

#include <rpm/rpmfi.h>
#include <rpm/rpmte.h>
#include <rpm/rpmfiles.h>
#include <rpm/rpmtypes.h>
#include <rpm/rpmlog.h>
#include <rpmio/rpmstring.h>
#include <rpmio/rpmmacro.h>
#include <rpmio/rpmpgp.h>

#include "lib/rpmfs.h"
#include "lib/rpmplugin.h"
#include "lib/rpmte_internal.h"

#include "sign/rpmsignverity.h"

static int sign_config_files = 0;

/*
 * This unconditionally tries to apply the fsverity signature to a file,
 * but fails gracefully if the file system doesn't support it or the
 * verity feature flag isn't enabled in the file system (ext4).
 */
static rpmRC fsverity_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, int fd,
				       const char *path, const char *dest,
				       mode_t file_mode, rpmFsmOp op)
{
    struct fsverity_enable_arg arg;
    const unsigned char * signature = NULL;
    size_t len;
    uint16_t algo = 0;
    int rc = RPMRC_OK;
    rpmFileAction action = XFO_ACTION(op);
    char *buffer;

    /* Ignore skipped files and unowned directories */
    if (XFA_SKIPPING(action) || (op & FAF_UNOWNED)) {
	rpmlog(RPMLOG_DEBUG, "fsverity skipping early: path %s dest %s\n",
	       path, dest);
	goto exit;
    }

    /*
     * Do not install signatures for config files unless the
     * user explicitly asks for it.
     */
    if (rpmfiFFlags(fi) & RPMFILE_CONFIG) {
	if (!(rpmfiFMode(fi) & (S_IXUSR|S_IXGRP|S_IXOTH)) &&
	    !sign_config_files) {
	    rpmlog(RPMLOG_DEBUG, "fsverity skipping: path %s dest %s\n",
		   path, dest);

	    goto exit;
	}
    }

    /*
     * Right now fsverity doesn't deal with symlinks or directories, so do
     * not try to install signatures for non regular files.
     */
    if (!S_ISREG(rpmfiFMode(fi))) {
	rpmlog(RPMLOG_DEBUG, "fsverity skipping non regular: path %s dest %s\n",
	       path, dest);
	goto exit;
    }

    signature = rpmfiVSignature(fi, &len, &algo);
    if (!signature || !len) {
	rpmlog(RPMLOG_DEBUG, "fsverity no signature for: path %s dest %s\n",
	       path, dest);
	goto exit;
    }

    memset(&arg, 0, sizeof(arg));
    arg.version = 1;
    if (algo)
	arg.hash_algorithm = algo;
    else
	arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
    arg.block_size = RPM_FSVERITY_BLKSZ;
    arg.sig_ptr = (uintptr_t)signature;
    arg.sig_size = len;

    buffer = rpmhex(signature, arg.sig_size);
    rpmlog(RPMLOG_DEBUG, "applying signature: %s\n", buffer);
    free(buffer);

    /*
     * Enable fsverity on the file.
     * fsverity not supported by file system (ENOTTY) and fsverity not
     * enabled on file system are expected and not considered
     * errors. Every other non-zero error code will result in the
     * installation failing.
     */
    if (ioctl(fd, FS_IOC_ENABLE_VERITY, &arg) != 0) {
	switch(errno) {
	case EBADMSG:
	    rpmlog(RPMLOG_DEBUG, "invalid or malformed fsverity signature for %s\n", path);
	    rc = RPMRC_FAIL;
	    break;
	case EEXIST:
	    rpmlog(RPMLOG_DEBUG, "fsverity signature already enabled %s\n",
		   path);
	    rc = RPMRC_FAIL;
	    break;
	case EINVAL:
	    rpmlog(RPMLOG_DEBUG, "invalid arguments for ioctl %s\n", path);
	    rc = RPMRC_FAIL;
	    break;
	case EKEYREJECTED:
	    rpmlog(RPMLOG_DEBUG, "signature doesn't match file %s\n", path);
	    rc = RPMRC_FAIL;
	    break;
	case EMSGSIZE:
	    rpmlog(RPMLOG_DEBUG, "invalid signature size for %s\n", path);
	    rc = RPMRC_FAIL;
	    break;
	case ENOPKG:
	    rpmlog(RPMLOG_DEBUG, "unsupported signature algoritm (%i) for %s\n",
		   arg.hash_algorithm, path);
	    rc = RPMRC_FAIL;
	    break;
	case ETXTBSY:
	    rpmlog(RPMLOG_DEBUG, "file is open by other process %s\n",
		   path);
	    rc = RPMRC_FAIL;
	    break;
	case ENOTTY:
	    rpmlog(RPMLOG_DEBUG, "fsverity not supported by file system for %s\n",
		   path);
	    break;
	case EOPNOTSUPP:
	    rpmlog(RPMLOG_DEBUG, "fsverity not enabled on file system for %s\n",
		   path);
	    break;
	default:
	    rpmlog(RPMLOG_DEBUG, "failed to enable verity (errno %i) for %s\n",
		   errno, path);
	    rc = RPMRC_FAIL;
	    break;
	}
    }

    rpmlog(RPMLOG_DEBUG, "fsverity enabled signature for: path %s dest %s\n",
	   path, dest);
exit:
    return rc;
}

static rpmRC fsverity_init(rpmPlugin plugin, rpmts ts)
{
    sign_config_files = rpmExpandNumeric("%{?_fsverity_sign_config_files}");

    rpmlog(RPMLOG_DEBUG, "fsverity_init\n");

    return RPMRC_OK;
}

struct rpmPluginHooks_s fsverity_hooks = {
    .init = fsverity_init,
    .fsm_file_prepare = fsverity_fsm_file_prepare,
};