Merge pull request #16504 from poettering/read-file-ipc

fileio: teach read_full_file() the ability to read data from AF_UNIX stream socket
This commit is contained in:
Lennart Poettering 2020-07-21 14:16:32 +02:00 committed by GitHub
commit 6456dafa12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 175 additions and 66 deletions

View File

@ -58,26 +58,25 @@
<varlistentry>
<term><option>--cert=</option></term>
<listitem><para>Specify the path to a file containing a server
certificate in PEM format. This option switches
<command>systemd-journal-gatewayd</command> into HTTPS mode
and must be used together with
<listitem><para>Specify the path to a file or <constant>AF_UNIX</constant> stream socket to read the
server certificate from. The certificate must be in PEM format. This option switches
<command>systemd-journal-gatewayd</command> into HTTPS mode and must be used together with
<option>--key=</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--key=</option></term>
<listitem><para>Specify the path to a file containing a server
key in PEM format corresponding to the certificate specified
with <option>--cert=</option>.</para></listitem>
<listitem><para>Specify the path to a file or <constant>AF_UNIX</constant> stream socket to read the
server key corresponding to the certificate specified with <option>--cert=</option> from. The key
must be in PEM format.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--trust=</option></term>
<listitem><para>Specify the path to a file containing a
CA certificate in PEM format.</para></listitem>
<listitem><para>Specify the path to a file or <constant>AF_UNIX</constant> stream socket to read a CA
certificate from. The certificate must be in PEM format.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -180,33 +180,29 @@
<varlistentry>
<term><option>--key=</option></term>
<listitem><para>
Takes a path to a SSL key file in PEM format.
Defaults to <filename>&CERTIFICATE_ROOT;/private/journal-remote.pem</filename>.
This option can be used with <option>--listen-https=</option>.
</para></listitem>
<listitem><para> Takes a path to a SSL key file in PEM format. Defaults to
<filename>&CERTIFICATE_ROOT;/private/journal-remote.pem</filename>. This option can be used with
<option>--listen-https=</option>. If the path refers to an <constant>AF_UNIX</constant> stream socket
in the file system a connection is made to it and the key read from it.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--cert=</option></term>
<listitem><para>
Takes a path to a SSL certificate file in PEM format.
Defaults to <filename>&CERTIFICATE_ROOT;/certs/journal-remote.pem</filename>.
This option can be used with <option>--listen-https=</option>.
</para></listitem>
<listitem><para> Takes a path to a SSL certificate file in PEM format. Defaults to
<filename>&CERTIFICATE_ROOT;/certs/journal-remote.pem</filename>. This option can be used with
<option>--listen-https=</option>. If the path refers to an <constant>AF_UNIX</constant> stream socket
in the file system a connection is made to it and the certificate read from it.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--trust=</option></term>
<listitem><para>
Takes a path to a SSL CA certificate file in PEM format,
or <option>all</option>. If <option>all</option> is set,
then certificate checking will be disabled.
Defaults to <filename>&CERTIFICATE_ROOT;/ca/trusted.pem</filename>.
This option can be used with <option>--listen-https=</option>.
</para></listitem>
<listitem><para> Takes a path to a SSL CA certificate file in PEM format, or <option>all</option>. If
<option>all</option> is set, then certificate checking will be disabled. Defaults to
<filename>&CERTIFICATE_ROOT;/ca/trusted.pem</filename>. This option can be used with
<option>--listen-https=</option>. If the path refers to an <constant>AF_UNIX</constant> stream socket
in the file system a connection is made to it and the certificate read from it.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -1028,11 +1028,13 @@
<varlistentry>
<term><varname>KeyFile=</varname></term>
<listitem>
<para>Takes a absolute path to a file which contains a 128-bit key encoded in a hexadecimal
string, which will be used in the transmission channel. When this option is specified,
<para>Takes a absolute path to a file which contains a 128-bit key encoded in a hexadecimal string,
which will be used in the transmission channel. When this option is specified,
<varname>Key=</varname> is ignored. Note that the file must be readable by the user
<literal>systemd-network</literal>, so it should be, e.g., owned by
<literal>root:systemd-network</literal> with a <literal>0640</literal> file mode.</para>
<literal>root:systemd-network</literal> with a <literal>0640</literal> file mode. If the path
refers to an <constant>AF_UNIX</constant> stream socket in the file system a connection is made to
it and the key read from it.</para>
</listitem>
</varlistentry>
<varlistentry>
@ -1518,11 +1520,12 @@
<varlistentry>
<term><varname>PrivateKeyFile=</varname></term>
<listitem>
<para>Takes an absolute path to a file which contains the Base64 encoded private key for the interface.
When this option is specified, then <varname>PrivateKey=</varname> is ignored.
Note that the file must be readable by the user <literal>systemd-network</literal>, so it
should be, e.g., owned by <literal>root:systemd-network</literal> with a
<literal>0640</literal> file mode.</para>
<para>Takes an absolute path to a file which contains the Base64 encoded private key for the
interface. When this option is specified, then <varname>PrivateKey=</varname> is ignored. Note
that the file must be readable by the user <literal>systemd-network</literal>, so it should be,
e.g., owned by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode. If
the path refers to an <constant>AF_UNIX</constant> stream socket in the file system a connection is
made to it and the key read from it.</para>
</listitem>
</varlistentry>
<varlistentry>
@ -1577,10 +1580,11 @@
<term><varname>PresharedKeyFile=</varname></term>
<listitem>
<para>Takes an absolute path to a file which contains the Base64 encoded preshared key for the
peer. When this option is specified, then <varname>PresharedKey=</varname> is ignored.
Note that the file must be readable by the user <literal>systemd-network</literal>, so it
should be, e.g., owned by <literal>root:systemd-network</literal> with a
<literal>0640</literal> file mode.</para>
peer. When this option is specified, then <varname>PresharedKey=</varname> is ignored. Note that
the file must be readable by the user <literal>systemd-network</literal>, so it should be, e.g.,
owned by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode. If the
path refers to an <constant>AF_UNIX</constant> stream socket in the file system a connection is
made to it and the key read from it.</para>
</listitem>
</varlistentry>
<varlistentry>

View File

@ -22,6 +22,7 @@
#include "mkdir.h"
#include "parse-util.h"
#include "path-util.h"
#include "socket-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "tmpfile-util.h"
@ -482,13 +483,12 @@ int read_full_stream_full(
assert(f);
assert(ret_contents);
assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX));
assert(!(flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) || ret_size);
n_next = LINE_MAX; /* Start size */
fd = fileno(f);
if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's
* optimize our buffering) */
if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen()), let's
* optimize our buffering */
if (fstat(fd, &st) < 0)
return -errno;
@ -505,7 +505,7 @@ int read_full_stream_full(
if (st.st_size > 0)
n_next = st.st_size + 1;
if (flags & READ_FULL_FILE_SECURE)
if (flags & READ_FULL_FILE_WARN_WORLD_READABLE)
(void) warn_file_is_world_accessible(filename, &st, NULL, 0);
}
}
@ -535,21 +535,18 @@ int read_full_stream_full(
errno = 0;
k = fread(buf + l, 1, n - l, f);
if (k > 0)
l += k;
assert(k <= n - l);
l += k;
if (ferror(f)) {
r = errno_or_else(EIO);
goto finalize;
}
if (feof(f))
break;
/* We aren't expecting fread() to return a short read outside
* of (error && eof), assert buffer is full and enlarge buffer.
*/
assert(l == n);
assert(k > 0); /* we can't have read zero bytes because that would have been EOF */
/* Safety check */
if (n >= READ_FULL_BYTES_MAX) {
@ -561,12 +558,21 @@ int read_full_stream_full(
}
if (flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) {
_cleanup_free_ void *decoded = NULL;
size_t decoded_size;
buf[l++] = 0;
if (flags & READ_FULL_FILE_UNBASE64)
r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size);
r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, &decoded, &decoded_size);
else
r = unhexmem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size);
goto finalize;
r = unhexmem_full(buf, l, flags & READ_FULL_FILE_SECURE, &decoded, &decoded_size);
if (r < 0)
goto finalize;
if (flags & READ_FULL_FILE_SECURE)
explicit_bzero_safe(buf, n);
free_and_replace(buf, decoded);
n = l = decoded_size;
}
if (!ret_size) {
@ -603,8 +609,54 @@ int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flag
assert(contents);
r = xfopenat(dir_fd, filename, "re", 0, &f);
if (r < 0)
return r;
if (r < 0) {
_cleanup_close_ int dfd = -1, sk = -1;
union sockaddr_union sa;
/* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
if (r != -ENXIO)
return r;
/* If this is enabled, let's try to connect to it */
if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
return -ENXIO;
if (dir_fd == AT_FDCWD)
r = sockaddr_un_set_path(&sa.un, filename);
else {
char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
/* If we shall operate relative to some directory, then let's use O_PATH first to
* open the socket inode, and then connect to it via /proc/self/fd/. We have to do
* this since there's not connectat() that takes a directory fd as first arg. */
dfd = openat(dir_fd, filename, O_PATH|O_CLOEXEC);
if (dfd < 0)
return -errno;
xsprintf(procfs_path, "/proc/self/fd/%i", dfd);
r = sockaddr_un_set_path(&sa.un, procfs_path);
}
if (r < 0)
return r;
sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (sk < 0)
return -errno;
if (connect(sk, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
return errno == ENOTSOCK ? -ENXIO : -errno; /* propagate original error if this is
* not a socket after all */
if (shutdown(sk, SHUT_WR) < 0)
return -errno;
f = fdopen(sk, "r");
if (!f)
return -errno;
TAKE_FD(sk);
}
(void) __fsetlocking(f, FSETLOCKING_BYCALLER);

View File

@ -32,9 +32,11 @@ typedef enum {
} WriteStringFileFlags;
typedef enum {
READ_FULL_FILE_SECURE = 1 << 0,
READ_FULL_FILE_UNBASE64 = 1 << 1,
READ_FULL_FILE_UNHEX = 1 << 2,
READ_FULL_FILE_SECURE = 1 << 0, /* erase any buffers we employ internally, after use */
READ_FULL_FILE_UNBASE64 = 1 << 1, /* base64 decode what we read */
READ_FULL_FILE_UNHEX = 1 << 2, /* hex decode what we read */
READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3, /* if regular file, log at LOG_WARNING level if access mode above 0700 */
READ_FULL_FILE_CONNECT_SOCKET = 1 << 4, /* if socket inode, connect to it and read off it */
} ReadFullFileFlags;
int fopen_unlocked(const char *path, const char *options, FILE **ret);

View File

@ -906,7 +906,7 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_key_pem)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Key file specified twice");
r = read_full_file(optarg, &arg_key_pem, NULL);
r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, &arg_key_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read key file: %m");
assert(arg_key_pem);
@ -916,7 +916,7 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_cert_pem)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Certificate file specified twice");
r = read_full_file(optarg, &arg_cert_pem, NULL);
r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, &arg_cert_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read certificate file: %m");
assert(arg_cert_pem);
@ -927,7 +927,7 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_trust_pem)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"CA certificate file specified twice");
r = read_full_file(optarg, &arg_trust_pem, NULL);
r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, &arg_trust_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read CA certificate file: %m");
assert(arg_trust_pem);

View File

@ -1077,12 +1077,12 @@ static int parse_argv(int argc, char *argv[]) {
static int load_certificates(char **key, char **cert, char **trust) {
int r;
r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL);
r = read_full_file_full(AT_FDCWD, arg_key ?: PRIV_KEY_FILE, READ_FULL_FILE_CONNECT_SOCKET, key, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read key from file '%s': %m",
arg_key ?: PRIV_KEY_FILE);
r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL);
r = read_full_file_full(AT_FDCWD, arg_cert ?: CERT_FILE, READ_FULL_FILE_CONNECT_SOCKET, cert, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read certificate from file '%s': %m",
arg_cert ?: CERT_FILE);
@ -1090,7 +1090,7 @@ static int load_certificates(char **key, char **cert, char **trust) {
if (arg_trust_all)
log_info("Certificate checking disabled.");
else {
r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL);
r = read_full_file_full(AT_FDCWD, arg_trust ?: TRUST_FILE, READ_FULL_FILE_CONNECT_SOCKET, trust, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read CA certificate file '%s': %m",
arg_trust ?: TRUST_FILE);

View File

@ -983,7 +983,10 @@ static int macsec_read_key_file(NetDev *netdev, SecurityAssociation *sa) {
(void) warn_file_is_world_accessible(sa->key_file, NULL, NULL, 0);
r = read_full_file_full(AT_FDCWD, sa->key_file, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX, (char **) &key, &key_len);
r = read_full_file_full(
AT_FDCWD, sa->key_file,
READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET,
(char **) &key, &key_len);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"Failed to read key from '%s', ignoring: %m",

View File

@ -888,7 +888,10 @@ static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_
(void) warn_file_is_world_accessible(filename, NULL, NULL, 0);
r = read_full_file_full(AT_FDCWD, filename, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64, &key, &key_len);
r = read_full_file_full(
AT_FDCWD, filename,
READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET,
&key, &key_len);
if (r < 0)
return r;

View File

@ -15,6 +15,8 @@
#include "io-util.h"
#include "parse-util.h"
#include "process-util.h"
#include "rm-rf.h"
#include "socket-util.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
@ -842,6 +844,53 @@ static void test_read_nul_string(void) {
assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 0 && streq_ptr(s, ""));
}
static void test_read_full_file_socket(void) {
_cleanup_(rm_rf_physical_and_freep) char *z = NULL;
_cleanup_close_ int listener = -1;
_cleanup_free_ char *data = NULL;
union sockaddr_union sa;
const char *j;
size_t size;
pid_t pid;
int r;
log_info("/* %s */", __func__);
listener = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
assert_se(listener >= 0);
assert_se(mkdtemp_malloc(NULL, &z) >= 0);
j = strjoina(z, "/socket");
assert_se(sockaddr_un_set_path(&sa.un, j) >= 0);
assert_se(bind(listener, &sa.sa, SOCKADDR_UN_LEN(sa.un)) >= 0);
assert_se(listen(listener, 1) >= 0);
r = safe_fork("(server)", FORK_DEATHSIG|FORK_LOG, &pid);
assert_se(r >= 0);
if (r == 0) {
_cleanup_close_ int rfd = -1;
/* child */
rfd = accept4(listener, NULL, 0, SOCK_CLOEXEC);
assert_se(rfd >= 0);
#define TEST_STR "This is a test\nreally."
assert_se(write(rfd, TEST_STR, strlen(TEST_STR)) == strlen(TEST_STR));
_exit(EXIT_SUCCESS);
}
assert_se(read_full_file_full(AT_FDCWD, j, 0, &data, &size) == -ENXIO);
assert_se(read_full_file_full(AT_FDCWD, j, READ_FULL_FILE_CONNECT_SOCKET, &data, &size) >= 0);
assert_se(size == strlen(TEST_STR));
assert_se(streq(data, TEST_STR));
assert_se(wait_for_terminate_and_check("(server)", pid, WAIT_LOG) >= 0);
#undef TEST_STR
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
@ -867,6 +916,7 @@ int main(int argc, char *argv[]) {
test_read_line3();
test_read_line4();
test_read_nul_string();
test_read_full_file_socket();
return 0;
}

View File

@ -100,7 +100,7 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", argv[6]);
} else {
r = read_full_file_full(AT_FDCWD, argv[6], 0, &hash_sig, &hash_sig_size);
r = read_full_file_full(AT_FDCWD, argv[6], READ_FULL_FILE_CONNECT_SOCKET, &hash_sig, &hash_sig_size);
if (r < 0)
return log_error_errno(r, "Failed to read root hash signature: %m");
}