Linux supports accessing kernel crypto API via AF_ALG since version 2.6.38. Coreutils uses libcrypto when available and fallbacks to generic C implementation of various hashing functions.
Add a generic afalg_stream() function which uses AF_ALG to calculate the hash of a stream and use sendfile() when possible (regular file with size less or equal than 0x7ffff000 (2,147,479,552) bytes, AKA MAX_RW_COUNT). Use afalg_stream() only in sha1sum for now, but other hashes are possible. The speed gain really depends on the CPU type, on systems which doesn't use libcrypto ranges from ~10% to 320%. This is a test on a Intel(R) Xeon(R) CPU E3-1265L V2 and Debian stretch: $ truncate -s 2GB 2g.bin $ time sha1sum 2g.bin 752ef2367f479e79e4f0cded9c270c2890506ab0 2g.bin real 0m4.829s user 0m4.437s sys 0m0.391s $ time ./sha1sum-afalg 2g.bin 752ef2367f479e79e4f0cded9c270c2890506ab0 2g.bin real 0m3.164s user 0m0.000s sys 0m3.162s Signed-off-by: Matteo Croce <mcr...@redhat.com> --- lib/af_alg.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/af_alg.h | 51 +++++++++++++++++++++ lib/sha1.c | 19 +++++++- modules/crypto/sha1 | 4 +- 4 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 lib/af_alg.c create mode 100644 lib/af_alg.h diff --git a/lib/af_alg.c b/lib/af_alg.c new file mode 100644 index 000000000..cb6086b4d --- /dev/null +++ b/lib/af_alg.c @@ -0,0 +1,124 @@ +/* af_alg.c - Functions to compute message digest from file streams using + Linux kernel crypto API. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + 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; either version 2, or (at your option) any + later version. + + 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, see <https://www.gnu.org/licenses/>. */ + +/* Written by Matteo Croce <mcr...@redhat.com>, 2018. */ + +#include <config.h> + +#include <sys/socket.h> + +#ifdef AF_ALG + +#include <unistd.h> +#include <string.h> +#include <linux/if_alg.h> +#include <sys/stat.h> +#include <sys/sendfile.h> + +#include "af_alg.h" + +/* from linux/include/linux/fs.h: (INT_MAX & PAGE_MASK) */ +#define MAX_RW_COUNT 0x7FFFF000 +#define BLOCKSIZE 32768 + +int +afalg_stream (FILE * stream, void *resblock, const char *alg, ssize_t hashlen) +{ + struct sockaddr_alg salg = { + .salg_family = AF_ALG, + .salg_type = "hash", + }; + int ret, cfd, ofd; + char buf[BLOCKSIZE]; + ssize_t size; + struct stat st; + + if (strlen (alg) >= sizeof(salg.salg_name)) + return -EINVAL; + + cfd = socket (AF_ALG, SOCK_SEQPACKET, 0); + if (cfd < 0) + return -EAFNOSUPPORT; + + strcpy ((char *) salg.salg_name, alg); + + ret = bind (cfd, (struct sockaddr *) &salg, sizeof (salg)); + if (ret < 0) + { + ret = -EAFNOSUPPORT; + goto out_cfd; + } + + ofd = accept (cfd, NULL, 0); + if (ofd < 0) + { + ret = -EAFNOSUPPORT; + goto out_cfd; + } + + /* if file is a regular file, attempt sendfile() to pipe the data */ + if (!fstat (fileno (stream), &st) && S_ISREG (st.st_mode) && + st.st_size <= MAX_RW_COUNT) + { + if (sendfile (ofd, fileno (stream), NULL, st.st_size) != st.st_size) + { + ret = -EIO; + goto out_ofd; + } + else + { + ret = 0; + } + } + else + { + /* sendfile() not possible, do a classic read-write loop */ + while ((size = fread (buf, 1, sizeof (buf), stream))) + { + if (send (ofd, buf, size, size == sizeof (buf) ? MSG_MORE : 0) == -1) + { + ret = -EIO; + goto out_ofd; + } + } + if (ferror (stream)) + { + ret = -EIO; + goto out_ofd; + } + } + + size = read (ofd, resblock, hashlen); + if (size != hashlen) + { + fprintf (stderr, "Error from read (%zd vs %zd bytes): %s\n", + size, hashlen, strerror (errno)); + ret = -EIO; + } + else + { + ret = 0; + } +out_ofd: + close (ofd); +out_cfd: + close (cfd); + return ret; +} + +#endif diff --git a/lib/af_alg.h b/lib/af_alg.h new file mode 100644 index 000000000..36a95f4bc --- /dev/null +++ b/lib/af_alg.h @@ -0,0 +1,51 @@ +/* af_alg.h - Declaration of functions to compute message digest from + file streams using Linux kernel crypto API. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + 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; either version 2, or (at your option) any + later version. + + 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, see <https://www.gnu.org/licenses/>. */ + +/* Written by Matteo Croce <mcr...@redhat.com>, 2018. */ + +#ifndef AF_ALG_H +# define AF_ALG_H 1 + +# include <stdio.h> +# include <errno.h> +# include <sys/socket.h> + +# ifdef __cplusplus +extern "C" { +# endif + +#ifdef AF_ALG + +int +afalg_stream (FILE * stream, void *resblock, const char *alg, ssize_t hashlen); + +#else + +static int +afalg_stream (FILE * stream, void *resblock, const char *alg, ssize_t hashlen) +{ + return -EAFNOSUPPORT; +} + +#endif + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/lib/sha1.c b/lib/sha1.c index 37d46b68e..21b4b323a 100644 --- a/lib/sha1.c +++ b/lib/sha1.c @@ -33,6 +33,12 @@ #include <stdlib.h> #include <string.h> +#include <sys/socket.h> + +#ifdef AF_ALG +# include "af_alg.h" +#endif + #if USE_UNLOCKED_IO # include "unlocked-io.h" #endif @@ -130,8 +136,19 @@ sha1_stream (FILE *stream, void *resblock) { struct sha1_ctx ctx; size_t sum; + char *buffer; +#ifdef AF_ALG + int ret; + + ret = afalg_stream(stream, resblock, "sha1", SHA1_DIGEST_SIZE); + if (!ret) + return 0; + + if (ret == -EIO) + return 1; +#endif - char *buffer = malloc (BLOCKSIZE + 72); + buffer = malloc (BLOCKSIZE + 72); if (!buffer) return 1; diff --git a/modules/crypto/sha1 b/modules/crypto/sha1 index d65f99418..30b7153e2 100644 --- a/modules/crypto/sha1 +++ b/modules/crypto/sha1 @@ -5,6 +5,8 @@ Files: lib/gl_openssl.h lib/sha1.h lib/sha1.c +lib/af_alg.h +lib/af_alg.c m4/gl-openssl.m4 m4/sha1.m4 @@ -17,7 +19,7 @@ configure.ac: gl_SHA1 Makefile.am: -lib_SOURCES += sha1.c +lib_SOURCES += sha1.c af_alg.c Include: "sha1.h" -- 2.14.3