Imported Upstream version 2.6.1
[platform/upstream/cryptsetup.git] / lib / utils_wipe.c
index 210c566..1df46c1 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * utils_wipe - wipe a device
  *
- * Copyright (C) 2004-2007, Clemens Fruhwirth <clemens@endorphin.org>
- * Copyright (C) 2011-2012, Red Hat, Inc. All rights reserved.
- * Copyright (C) 2009-2012, Milan Broz
+ * Copyright (C) 2004-2007 Clemens Fruhwirth <clemens@endorphin.org>
+ * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2009-2023 Milan Broz
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include <stdio.h>
-#include <string.h>
 #include <stdlib.h>
 #include <errno.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
 #include <sys/ioctl.h>
-#include <fcntl.h>
-
-#include "libcryptsetup.h"
+#include <sys/stat.h>
+#include <linux/fs.h>
 #include "internal.h"
 
-#define MAXIMUM_WIPE_BYTES     1024 * 1024 * 32 /* 32 MiB */
+/* block device zeroout ioctls, introduced in Linux kernel 3.7 */
+#ifndef BLKZEROOUT
+#define BLKZEROOUT _IO(0x12,127)
+#endif
 
-static ssize_t _crypt_wipe_zero(int fd, int bsize, char *buffer,
-                               uint64_t offset, uint64_t size)
+static int wipe_zeroout(struct crypt_device *cd, int devfd,
+                       uint64_t offset, uint64_t length)
 {
-       memset(buffer, 0, size);
-       return write_lseek_blockwise(fd, bsize, buffer, size, offset);
-}
+       static bool zeroout_available = true;
+       uint64_t range[2] = { offset, length };
+       int r;
 
-static ssize_t _crypt_wipe_random(int fd, int bsize, char *buffer,
-                                 uint64_t offset, uint64_t size)
-{
-       if (crypt_random_get(NULL, buffer, size, CRYPT_RND_NORMAL) < 0)
-               return -EINVAL;
+       if (!zeroout_available)
+               return -ENOTSUP;
+
+       r = ioctl(devfd, BLKZEROOUT, &range);
+       if (r < 0) {
+               log_dbg(cd, "BLKZEROOUT ioctl not available (error %i), disabling.", r);
+               zeroout_available = false;
+               return -ENOTSUP;
+       }
 
-       return write_lseek_blockwise(fd, bsize, buffer, size, offset);
+       return 0;
 }
 
 /*
  * Wipe using Peter Gutmann method described in
- * http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html
+ * https://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html
+ * Note: used only for rotational device (and even there it is not needed today...)
  */
 static void wipeSpecial(char *buffer, size_t buffer_size, unsigned int turn)
 {
@@ -72,126 +73,239 @@ static void wipeSpecial(char *buffer, size_t buffer_size, unsigned int turn)
                {"\x6d\xb6\xdb"}, {"\xb6\xdb\x6d"}, {"\xdb\x6d\xb6"}
        };
 
-       for(i = 0; i < buffer_size / 3; ++i) {
+       for (i = 0; i < buffer_size / 3; ++i) {
                memcpy(buffer, write_modes[turn], 3);
                buffer += 3;
        }
 }
 
-static ssize_t _crypt_wipe_disk(int fd, int bsize, char *buffer,
-                               uint64_t offset, uint64_t size)
+static int crypt_wipe_special(struct crypt_device *cd, int fd, size_t bsize,
+                             size_t alignment, char *buffer,
+                             uint64_t offset, size_t size)
 {
-       int r;
+       int r = 0;
        unsigned int i;
        ssize_t written;
 
-       for(i = 0; i < 39; ++i) {
+       for (i = 0; i < 39; ++i) {
                if (i <  5) {
-                       r = crypt_random_get(NULL, buffer, size, CRYPT_RND_NORMAL);
-               } else if(i >=  5 && i < 32) {
+                       r = crypt_random_get(cd, buffer, size, CRYPT_RND_NORMAL);
+               } else if (i >=  5 && i < 32) {
                        wipeSpecial(buffer, size, i - 5);
                        r = 0;
-               } else if(i >= 32 && i < 38) {
-                       r = crypt_random_get(NULL, buffer, size, CRYPT_RND_NORMAL);
-               } else if(i >= 38 && i < 39) {
+               } else if (i >= 32 && i < 38) {
+                       r = crypt_random_get(cd, buffer, size, CRYPT_RND_NORMAL);
+               } else if (i >= 38 && i < 39) {
                        memset(buffer, 0xFF, size);
                        r = 0;
                }
                if (r < 0)
-                       return r;
+                       return -EIO;
 
-               written = write_lseek_blockwise(fd, bsize, buffer, size, offset);
+               written = write_lseek_blockwise(fd, bsize, alignment,
+                                               buffer, size, offset);
                if (written < 0 || written != (ssize_t)size)
-                       return written;
+                       return -EIO;
        }
 
        /* Rewrite it finally with random */
-       return _crypt_wipe_random(fd, bsize, buffer, offset, size);
+       if (crypt_random_get(cd, buffer, size, CRYPT_RND_NORMAL) < 0)
+               return -EIO;
+
+       written = write_lseek_blockwise(fd, bsize, alignment, buffer, size, offset);
+       if (written < 0 || written != (ssize_t)size)
+               return -EIO;
+
+       return 0;
 }
 
-static ssize_t _crypt_wipe_ssd(int fd, int bsize, char *buffer,
-                              uint64_t offset, uint64_t size)
+static int wipe_block(struct crypt_device *cd, int devfd, crypt_wipe_pattern pattern,
+                     char *sf, size_t device_block_size, size_t alignment,
+                     size_t wipe_block_size, uint64_t offset, bool *need_block_init,
+                     bool blockdev)
 {
-       // FIXME: for now just rewrite it by random
-       return _crypt_wipe_random(fd, bsize, buffer, offset, size);
+       int r;
+
+       if (pattern == CRYPT_WIPE_SPECIAL)
+               return crypt_wipe_special(cd, devfd, device_block_size, alignment,
+                                         sf, offset, wipe_block_size);
+
+       if (*need_block_init) {
+               if (pattern == CRYPT_WIPE_ZERO) {
+                       memset(sf, 0, wipe_block_size);
+                       *need_block_init = false;
+                       r = 0;
+               } else if (pattern == CRYPT_WIPE_RANDOM ||
+                          pattern == CRYPT_WIPE_ENCRYPTED_ZERO) {
+                       r = crypt_random_get(cd, sf, wipe_block_size,
+                                            CRYPT_RND_NORMAL) ? -EIO : 0;
+                       *need_block_init = true;
+               } else
+                       r = -EINVAL;
+
+               if (r)
+                       return r;
+       }
+
+       if (blockdev && pattern == CRYPT_WIPE_ZERO &&
+           !wipe_zeroout(cd, devfd, offset, wipe_block_size)) {
+               /* zeroout ioctl does not move offset */
+               if (lseek(devfd, offset + wipe_block_size, SEEK_SET) < 0) {
+                       log_err(cd, _("Cannot seek to device offset."));
+                       return -EINVAL;
+               }
+               return 0;
+       }
+
+       if (write_blockwise(devfd, device_block_size, alignment, sf,
+                           wipe_block_size) == (ssize_t)wipe_block_size)
+               return 0;
+
+       return -EIO;
 }
 
-int crypt_wipe(struct device *device,
-              uint64_t offset,
-              uint64_t size,
-              crypt_wipe_type type,
-              int exclusive)
+int crypt_wipe_device(struct crypt_device *cd,
+       struct device *device,
+       crypt_wipe_pattern pattern,
+       uint64_t offset,
+       uint64_t length,
+       size_t wipe_block_size,
+       int (*progress)(uint64_t size, uint64_t offset, void *usrptr),
+       void *usrptr)
 {
+       int r, devfd;
        struct stat st;
-       char *buffer;
-       int devfd, flags, bsize;
-       ssize_t written;
+       size_t bsize, alignment;
+       char *sf = NULL;
+       uint64_t dev_size;
+       bool need_block_init = true;
 
-       if (!size || size % SECTOR_SIZE || (size > MAXIMUM_WIPE_BYTES)) {
-               log_dbg("Unsuported wipe size for device %s: %ld.",
-                       device_path(device), (unsigned long)size);
+       /* Note: LUKS1 calls it with wipe_block not aligned to multiple of bsize */
+       bsize = device_block_size(cd, device);
+       alignment = device_alignment(device);
+       if (!bsize || !alignment || !wipe_block_size)
                return -EINVAL;
-       }
 
-       if (stat(device_path(device), &st) < 0) {
-               log_dbg("Device %s not found.", device_path(device));
+       /* if wipe_block_size < bsize, then a wipe is highly ineffective */
+
+       /* Everything must be aligned to SECTOR_SIZE */
+       if (MISALIGNED_512(offset) || MISALIGNED_512(length) || MISALIGNED_512(wipe_block_size))
                return -EINVAL;
-       }
 
-       if (type == CRYPT_WIPE_DISK && S_ISBLK(st.st_mode)) {
-               if (!crypt_dev_is_rotational(major(st.st_rdev),
-                                               minor(st.st_rdev))) {
-                       type = CRYPT_WIPE_SSD;
-                       log_dbg("Non-rotational device, using SSD wipe mode.");
-               } else
-                       log_dbg("Rotational device, using normal wipe mode.");
+       if (device_is_locked(device))
+               devfd = device_open_locked(cd, device, O_RDWR);
+       else
+               devfd = device_open(cd, device, O_RDWR);
+       if (devfd < 0)
+               return errno ? -errno : -EINVAL;
+
+       if (fstat(devfd, &st) < 0) {
+               r = -EINVAL;
+               goto out;
        }
 
-       bsize = device_block_size(device);
-       if (bsize <= 0)
-               return -EINVAL;
+       if (length)
+               dev_size = offset + length;
+       else {
+               r = device_size(device, &dev_size);
+               if (r)
+                       goto out;
 
-       buffer = malloc(size);
-       if (!buffer)
-               return -ENOMEM;
+               if (dev_size <= offset) {
+                       r = -EINVAL;
+                       goto out;
+               }
+       }
 
-       flags = O_RDWR;
+       r = posix_memalign((void **)&sf, alignment, wipe_block_size);
+       if (r)
+               goto out;
 
-       /* use O_EXCL only for block devices */
-       if (exclusive && S_ISBLK(st.st_mode))
-               flags |= O_EXCL;
+       if (lseek(devfd, offset, SEEK_SET) < 0) {
+               log_err(cd, _("Cannot seek to device offset."));
+               r = -EINVAL;
+               goto out;
+       }
 
-       /* coverity[toctou] */
-       devfd = device_open(device, flags);
-       if (devfd == -1) {
-               free(buffer);
-               return errno ? -errno : -EINVAL;
+       if (progress && progress(dev_size, offset, usrptr)) {
+               r = -EINVAL; /* No change yet, treat this as a parameter error */
+               goto out;
        }
 
-       // FIXME: use fixed block size and loop here
-       switch (type) {
-               case CRYPT_WIPE_ZERO:
-                       written = _crypt_wipe_zero(devfd, bsize, buffer, offset, size);
-                       break;
-               case CRYPT_WIPE_DISK:
-                       written = _crypt_wipe_disk(devfd, bsize, buffer, offset, size);
-                       break;
-               case CRYPT_WIPE_SSD:
-                       written = _crypt_wipe_ssd(devfd, bsize, buffer, offset, size);
+       if (pattern == CRYPT_WIPE_SPECIAL && !device_is_rotational(device)) {
+               log_dbg(cd, "Non-rotational device, using random data wipe mode.");
+               pattern = CRYPT_WIPE_RANDOM;
+       }
+
+       while (offset < dev_size) {
+               if ((offset + wipe_block_size) > dev_size)
+                       wipe_block_size = dev_size - offset;
+
+               r = wipe_block(cd, devfd, pattern, sf, bsize, alignment,
+                              wipe_block_size, offset, &need_block_init, S_ISBLK(st.st_mode));
+               if (r) {
+                       log_err(cd,_("Device wipe error, offset %" PRIu64 "."), offset);
                        break;
-               case CRYPT_WIPE_RANDOM:
-                       written = _crypt_wipe_random(devfd, bsize, buffer, offset, size);
+               }
+
+               offset += wipe_block_size;
+
+               if (progress && progress(dev_size, offset, usrptr)) {
+                       r = -EINTR;
                        break;
-               default:
-                       log_dbg("Unsuported wipe type requested: (%d)", type);
-                       written = -1;
+               }
+       }
+
+       device_sync(cd, device);
+out:
+       free(sf);
+       return r;
+}
+
+int crypt_wipe(struct crypt_device *cd,
+       const char *dev_path,
+       crypt_wipe_pattern pattern,
+       uint64_t offset,
+       uint64_t length,
+       size_t wipe_block_size,
+       uint32_t flags,
+       int (*progress)(uint64_t size, uint64_t offset, void *usrptr),
+       void *usrptr)
+{
+       struct device *device;
+       int r;
+
+       if (!cd)
+               return -EINVAL;
+
+       r = init_crypto(cd);
+       if (r < 0)
+               return r;
+
+       if (!dev_path)
+               device = crypt_data_device(cd);
+       else {
+               r = device_alloc_no_check(&device, dev_path);
+               if (r < 0)
+                       return r;
+
+               if (flags & CRYPT_WIPE_NO_DIRECT_IO)
+                       device_disable_direct_io(device);
        }
+       if (!device)
+               return -EINVAL;
 
-       close(devfd);
-       free(buffer);
+       if (!wipe_block_size)
+               wipe_block_size = 1024*1024;
 
-       if (written != (ssize_t)size || written < 0)
-               return -EIO;
+       log_dbg(cd, "Wipe [%u] device %s, offset %" PRIu64 ", length %" PRIu64 ", block %zu.",
+               (unsigned)pattern, device_path(device), offset, length, wipe_block_size);
 
-       return 0;
+       r = crypt_wipe_device(cd, device, pattern, offset, length,
+                             wipe_block_size, progress, usrptr);
+
+       if (dev_path)
+               device_free(cd, device);
+
+       return r;
 }