summaryrefslogtreecommitdiffstats
path: root/lib/af_alg.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/af_alg.c')
-rw-r--r--lib/af_alg.c213
1 files changed, 213 insertions, 0 deletions
diff --git a/lib/af_alg.c b/lib/af_alg.c
new file mode 100644
index 0000000..084bff6
--- /dev/null
+++ b/lib/af_alg.c
@@ -0,0 +1,213 @@
+/* af_alg.c - Compute message digests from file streams and buffers.
+ Copyright (C) 2018-2022 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