213 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			213 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* 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
 |