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 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
/* af_alg.c - Compute message digests from file streams and buffers.
Copyright (C) 2018-2023 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* Written by Matteo Croce <mcroce@redhat.com>, 2018. */
#include <config.h>
#include "af_alg.h"
#if USE_LINUX_CRYPTO_API
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <linux/if_alg.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include "sys-limits.h"
#define BLOCKSIZE 32768
/* Return a newly created socket for ALG.
On error, return a negative error number. */
static int
alg_socket (char const *alg)
{
struct sockaddr_alg salg = {
.salg_family = AF_ALG,
.salg_type = "hash",
};
/* Copy alg into salg.salg_name, without calling strcpy nor strlen. */
for (size_t i = 0; (salg.salg_name[i] = alg[i]) != '\0'; i++)
if (i == sizeof salg.salg_name - 1)
/* alg is too long. */
return -EINVAL;
int cfd = socket (AF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
if (cfd < 0)
return -EAFNOSUPPORT;
int ofd = (bind (cfd, (struct sockaddr *) &salg, sizeof salg) == 0
? accept4 (cfd, NULL, 0, SOCK_CLOEXEC)
: -1);
close (cfd);
return ofd < 0 ? -EAFNOSUPPORT : ofd;
}
int
afalg_buffer (const char *buffer, size_t len, const char *alg,
void *resblock, ssize_t hashlen)
{
/* On Linux < 4.9, the value for an empty stream is wrong (all zeroes).
See <https://patchwork.kernel.org/patch/9308641/>.
This was not fixed properly until November 2016,
see <https://patchwork.kernel.org/patch/9434741/>. */
if (len == 0)
return -EAFNOSUPPORT;
int ofd = alg_socket (alg);
if (ofd < 0)
return ofd;
int result;
for (;;)
{
ssize_t size = (len > BLOCKSIZE ? BLOCKSIZE : len);
if (send (ofd, buffer, size, MSG_MORE) != size)
{
result = -EAFNOSUPPORT;
break;
}
buffer += size;
len -= size;
if (len == 0)
{
result = read (ofd, resblock, hashlen) == hashlen ? 0 : -EAFNOSUPPORT;
break;
}
}
close (ofd);
return result;
}
int
afalg_stream (FILE *stream, const char *alg,
void *resblock, ssize_t hashlen)
{
int ofd = alg_socket (alg);
if (ofd < 0)
return ofd;
/* If STREAM's size is known and nonzero and not too large, attempt
sendfile to pipe the data. The nonzero restriction avoids issues
with /proc files that pretend to be empty, and lets the classic
read-write loop work around an empty-input bug noted below. */
int fd = fileno (stream);
int result;
struct stat st;
off_t off = ftello (stream);
if (0 <= off && fstat (fd, &st) == 0
&& (S_ISREG (st.st_mode) || S_TYPEISSHM (&st) || S_TYPEISTMO (&st))
&& off < st.st_size && st.st_size - off < SYS_BUFSIZE_MAX)
{
/* Make sure the offset of fileno (stream) reflects how many bytes
have been read from stream before this function got invoked.
Note: fflush on an input stream after ungetc does not work as expected
on some platforms. Therefore this situation is not supported here. */
if (fflush (stream))
result = -EIO;
else
{
off_t nbytes = st.st_size - off;
if (sendfile (ofd, fd, &off, nbytes) == nbytes)
{
if (read (ofd, resblock, hashlen) == hashlen)
{
/* The input buffers of stream are no longer valid. */
if (lseek (fd, off, SEEK_SET) != (off_t)-1)
result = 0;
else
/* The file position of fd has not changed. */
result = -EAFNOSUPPORT;
}
else
/* The file position of fd has not changed. */
result = -EAFNOSUPPORT;
}
else
/* The file position of fd has not changed. */
result = -EAFNOSUPPORT;
}
}
else
{
/* sendfile not possible, do a classic read-write loop. */
/* Number of bytes to seek (backwards) in case of error. */
off_t nseek = 0;
for (;;)
{
char buf[BLOCKSIZE];
/* When the stream is not seekable, start with a single-byte block,
so that we can use ungetc() in the case that send() fails. */
size_t blocksize = (nseek == 0 && off < 0 ? 1 : BLOCKSIZE);
ssize_t size = fread (buf, 1, blocksize, stream);
if (size == 0)
{
/* On Linux < 4.9, the value for an empty stream is wrong (all 0).
See <https://patchwork.kernel.org/patch/9308641/>.
This was not fixed properly until November 2016,
see <https://patchwork.kernel.org/patch/9434741/>. */
result = ferror (stream) ? -EIO : nseek == 0 ? -EAFNOSUPPORT : 0;
break;
}
nseek -= size;
if (send (ofd, buf, size, MSG_MORE) != size)
{
if (nseek == -1)
{
/* 1 byte of pushback buffer is guaranteed on stream, even
if stream is not seekable. */
ungetc ((unsigned char) buf[0], stream);
result = -EAFNOSUPPORT;
}
else if (fseeko (stream, nseek, SEEK_CUR) == 0)
/* The position of stream has been restored. */
result = -EAFNOSUPPORT;
else
result = -EIO;
break;
}
/* Don't assume that EOF is sticky. See:
<https://sourceware.org/bugzilla/show_bug.cgi?id=19476>. */
if (feof (stream))
{
result = 0;
break;
}
}
if (result == 0 && read (ofd, resblock, hashlen) != hashlen)
{
if (nseek == 0 || fseeko (stream, nseek, SEEK_CUR) == 0)
/* The position of stream has been restored. */
result = -EAFNOSUPPORT;
else
result = -EIO;
}
}
close (ofd);
return result;
}
#endif
|