Skip to content

Instantly share code, notes, and snippets.

@insulsa
Last active February 3, 2021 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save insulsa/18b7d31bd82ddade14db07f413c0b2d2 to your computer and use it in GitHub Desktop.
Save insulsa/18b7d31bd82ddade14db07f413c0b2d2 to your computer and use it in GitHub Desktop.
/* Usage: write sparse image to block device, padded to the logical block size (BLKSSZGET).
TODO: Combine neighbor logical blocks into larger chunks, possibly taking advantage of BLKALIGNOFF BLKPBSZGET BLKIOOPT.
Rationale:
I created a LUKS crypto block device with integrity. I'd want to wipe
each sector (i.e. logical block) so the sector's integrity checks,
before I could read from the sector. Upon "luksFormat", cryptsetup(8)
offers to wipe all sectors by default. If I'd overwrite a sector, the
sector's integrity bits'd be overwritten, too, so I'd no need to care
whether its previous integrity checks. However, if a partial write'd
cause the kernel to read this sector, and possibly along with neighbor
sectors within page size, and the related integrity'd fail to check,
the write'd fail. Unfortunately, mke2fs writes partial sectors. So I
could not mke2fs on a not wiped such device. So I wrote this utility.
I could create a fs image on a plain file, then write the plain file
to the device. This utility skips holes in its input by lseek(fd,
offset, SEEK_DATA), and pads data to the sector boundary when writing.
The kernel writes whole sectors after the fs is mounted.
hypothetical scenario: https://gitlab.com/cryptsetup/cryptsetup/issues/335#note_270050959
truncate -s 1G /tmp/1.img /tmp/2.img
truncate -s 980M /tmp/1.img /tmp/2.img
mke2fs /tmp/2.img
cryptsetup luksFormat \
--key-file /etc/motd \
--sector-size $(getconf PAGESIZE) \
--cipher chacha20-plain \
--integrity poly1305 \
--integrity-no-wipe \
/tmp/1.img
cryptsetup open --key-file /etc/motd /tmp/1.img dm-0
a.out < /tmp/2.img > /dev/mapper/dm-0
mount /dev/mapper/dm-0 /mnt/1
I've not tested it with e2fsck and resize2fs, etc., yet.
Anyway, if I'd be serious about security, I'd wipe the whole device.
You may use this utility for other purposes. You may ignore the "logical
block size not a multiple of page size" warning if you use this utility
for other purposes. */
/* Copying1: This computer software is dedicated to the public domain. I grant permission to use, modify and or distribute it.
Copying2, the MIT license: (Copyright year 2020, author love@mailinator.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* c99 and posix headers */
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
/* other documented headers */
#include <sys/ioctl.h>
/* ad hoc headers */
#include <sysexits.h> /* EX_USAGE EX_NOINPUT EX_IOERR */
#include <linux/fs.h> /* SEEK_DATA BLKSSZGET BLKGETSIZE64 */
/* posix is adopting SEEK_DATA SEEK_HOLE: http://austingroupbugs.net/view.php?id=415 */
/* I don't find ioctl BLKSSZGET BLKGETSIZE64 in manual pages.
The sole documentation seem to be the kernel src itself.
TODO: what about bsd, solaris, etc.? */
#define see(...) fprintf(stderr, __VA_ARGS__)
int main(int argc, char **argv) {
if (argc > 1) {
fprintf(stderr, "%s: extraneous cmdline arg received, aborting!\n", argv[0]);
exit(EX_USAGE);
}
int const fd0 = 0;
int const fd1 = 1;
off_t const i_size = lseek(fd0, (off_t)0, SEEK_END);
if (i_size == -1) {
perror("error getting input size");
exit(EX_NOINPUT);
}
assert(i_size >= 0);
int logical_block_size;
switch (ioctl(fd1 , BLKSSZGET, &logical_block_size)) {
case 0: break;
case -1: perror("error getting logical block size"); exit(EX_IOERR);
default: abort();
}
assert(logical_block_size > 0);
errno = 0;
long const page_size = sysconf(_SC_PAGESIZE);
switch (page_size) {
default:
assert(page_size > 0);
if (logical_block_size % page_size)
fprintf(stderr,"warn: logical block size %d not a multiple of page size %ld\n", logical_block_size, page_size);
break;
case -1:
if (errno) perror("error getting pagesize");
fprintf(stderr,"warn: page size not available\n");
}
uint64_t o_size;
switch (ioctl(fd1, BLKGETSIZE64, &o_size)) {
case 0: break;
case -1: perror("error getting output size"); exit(EX_IOERR);
default: abort();
}
assert(o_size > 0);
if (i_size > o_size) {
fprintf(stderr, "input is larger than output\n");
exit(EXIT_FAILURE);
}
char * buf = malloc(logical_block_size);
if (buf == NULL) {
perror("buffer malloc error");
exit(EXIT_FAILURE);
}
for (off_t p = 0;;) {
if (p == i_size) {
eof:
switch (fsync(fd1)) {
case 0: exit(0);
case -1:
perror("output sync error");
exit(EX_IOERR);
default: abort();
}
}
off_t const y = lseek(fd0, p, SEEK_DATA);
if (y == -1) {
switch (errno) {
case ENXIO: goto eof;
}
fprintf(stderr, "input seek error at 0x%llx:", (long long)p);
perror("");
exit(EX_IOERR);
}
assert(p<=y && y<i_size);
off_t const x = y-(y%logical_block_size);
ssize_t const r = pread(fd0, buf, logical_block_size, x);
if (r == logical_block_size) {
goto write;
}
if (0<=r && r<logical_block_size && x+r==i_size) {
if (
memset(buf+r, (char)0, logical_block_size-r)
!= buf+r) abort();
goto write;
}
if (0<=r && r<logical_block_size && x+r<i_size) {
fprintf(stderr, "short read at 0x%llx\n", (long long)(x+r));
exit(EX_IOERR);
}
if (r==-1) {
fprintf(stderr, "read error at 0x%llx:", (long long)x);
perror("");
exit(EX_IOERR);
}
abort();
write:
;
ssize_t const w = pwrite(1, buf, logical_block_size, x);
if (w == logical_block_size) {
p = x+r;
continue;
}
if (0<=w && w<logical_block_size) {
fprintf(stderr, "short write at 0x%llx\n", (long long)(x+w));
exit(EX_IOERR);
}
if (w == -1) {
fprintf(stderr, "write error at 0x%llx:", (long long)x);
perror("");
exit(EX_IOERR);
}
abort();
}
abort();
}
@letmaik
Copy link

letmaik commented Feb 5, 2020

Could you add a license to this code? Preferably BSD/MIT?

@insulsa
Copy link
Author

insulsa commented Feb 6, 2020

MIT license added. @letmaik

@AntonioND
Copy link

Thanks a lot for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment