Add a geli resize subcommand to resize encrypted filesystems prior

to growing the filesystem.

Refuse to attach providers where the metadata provider size is
wrong.  This makes post-boot attaches behave consistently with
pre-boot attaches.  Also refuse to restore metadata to a provider
of the wrong size without the new -f switch.  The new -f switch
forces the metadata restoration despite the provider size, and
updates the provider size in the restored metadata to the correct
value.

Helped by:	pjd
Reviewed by:	pjd
This commit is contained in:
Brian Somers 2010-09-20 22:04:59 +00:00
parent 4b5ccbf768
commit 044bf69fd5
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=212934
3 changed files with 336 additions and 5 deletions

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd August 29, 2008
.Dd September 20, 2010
.Dt GELI 8
.Os
.Sh NAME
@ -111,10 +111,15 @@ utility:
.Ar file
.Nm
.Cm restore
.Op Fl v
.Op Fl fv
.Ar file
.Ar prov
.Nm
.Cm resize
.Op Fl v
.Fl s Ar oldsize
.Ar prov
.Nm
.Cm clear
.Op Fl v
.Ar prov ...
@ -464,6 +469,34 @@ If specified, all currently attached providers will be killed.
Backup metadata from the given provider to the given file.
.It Cm restore
Restore metadata from the given file to the given provider.
.Bl -tag -width ".Fl f"
.It Fl f
Metadata contains the size of the provider to ensure that the correct
partition or slice is attached.
If an attempt is made to restore metadata to a provider that has a different
size,
.Nm
will refuse to restore the data unless the
.Fl f
switch is used.
If the partition or slice has been grown, the
.Cm resize
subcommand should be used rather than attempting to relocate the metadata
through
.Cm backup
and
.Cm restore .
.El
.It Cm resize
Inform
.Nm
that the provider has been resized.
The old metadata block is relocated to the correct position at the end of the
provider and the provider size is updated.
.Bl -tag -width ".Fl s Ar oldsize"
.It Fl s Ar oldsize
The size of the provider before it was resized.
.El
.It Cm clear
Clear metadata from the given providers.
.It Cm dump
@ -665,6 +698,17 @@ geli: Cannot read metadata from /dev/da0: Invalid argument.
# geli attach /dev/da0
Enter passphrase:
.Ed
.Pp
If an encrypted filesystem is extended, it is necessary to relocate and
update the metadata:
.Bd -literal -offset indent
# gpart create -s GPT ada0
# gpart add -s 1g -t freebsd-ufs -i 1 ada0
# geli init -K keyfile -P ada0p1
# gpart resize -s 2g -i 1 ada0
# geli resize -s 1g ada0p1
# geli attach -k keyfile -p ada0p1
.Ed
.Sh DATA AUTHENTICATION
.Nm
can verify data integrity when an authentication algorithm is specified.

View File

@ -66,6 +66,7 @@ static void eli_delkey(struct gctl_req *req);
static void eli_kill(struct gctl_req *req);
static void eli_backup(struct gctl_req *req);
static void eli_restore(struct gctl_req *req);
static void eli_resize(struct gctl_req *req);
static void eli_clear(struct gctl_req *req);
static void eli_dump(struct gctl_req *req);
@ -86,7 +87,8 @@ static int eli_backup_create(struct gctl_req *req, const char *prov,
* delkey [-afv] [-n keyno] prov
* kill [-av] [prov ...]
* backup [-v] prov file
* restore [-v] file prov
* restore [-fv] file prov
* resize [-v] -s oldsize prov
* clear [-v] prov ...
* dump [-v] prov ...
*/
@ -197,8 +199,19 @@ struct g_command class_commands[] = {
{ "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
"[-v] prov file"
},
{ "restore", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
"[-v] file prov"
{ "restore", G_FLAG_VERBOSE, eli_main,
{
{ 'f', "force", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-fv] file prov"
},
{ "resize", G_FLAG_VERBOSE, eli_main,
{
{ 's', "oldsize", NULL, G_TYPE_NUMBER },
G_OPT_SENTINEL
},
"[-v] -s oldsize prov"
},
{ "clear", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
"[-v] prov ..."
@ -264,6 +277,8 @@ eli_main(struct gctl_req *req, unsigned flags)
eli_backup(req);
else if (strcmp(name, "restore") == 0)
eli_restore(req);
else if (strcmp(name, "resize") == 0)
eli_resize(req);
else if (strcmp(name, "dump") == 0)
eli_dump(req);
else if (strcmp(name, "clear") == 0)
@ -683,6 +698,7 @@ eli_attach(struct gctl_req *req)
struct g_eli_metadata md;
unsigned char key[G_ELI_USERKEYLEN];
const char *prov;
off_t mediasize;
int nargs;
nargs = gctl_get_int(req, "nargs");
@ -695,6 +711,12 @@ eli_attach(struct gctl_req *req)
if (eli_metadata_read(req, prov, &md) == -1)
return;
mediasize = g_get_mediasize(prov);
if (md.md_provsize != (uint64_t)mediasize) {
gctl_error(req, "Provider size mismatch.");
return;
}
if (eli_genkey(req, &md, key, 0) == NULL) {
bzero(key, sizeof(key));
return;
@ -1212,6 +1234,17 @@ eli_restore(struct gctl_req *req)
gctl_error(req, "MD5 hash mismatch: not a geli backup file?");
goto out;
}
/* Check if the provider size has changed since we did the backup. */
if (md.md_provsize != (uint64_t)mediasize) {
if (gctl_get_int(req, "force")) {
md.md_provsize = mediasize;
eli_metadata_encode(&md, sector);
} else {
gctl_error(req, "Provider size mismatch: "
"wrong backup file?");
goto out;
}
}
/* Write metadata from the provider. */
if (pwrite(provfd, sector, secsize, mediasize - secsize) !=
(ssize_t)secsize) {
@ -1229,6 +1262,111 @@ eli_restore(struct gctl_req *req)
}
}
static void
eli_resize(struct gctl_req *req)
{
struct g_eli_metadata md;
const char *prov;
unsigned char *sector;
unsigned secsize;
off_t mediasize, oldsize;
int nargs, provfd;
nargs = gctl_get_int(req, "nargs");
if (nargs != 1) {
gctl_error(req, "Invalid number of arguments.");
return;
}
prov = gctl_get_ascii(req, "arg0");
provfd = -1;
sector = NULL;
secsize = 0;
provfd = open(prov, O_RDWR);
if (provfd == -1 && errno == ENOENT && prov[0] != '/') {
char devprov[MAXPATHLEN];
snprintf(devprov, sizeof(devprov), "%s%s", _PATH_DEV, prov);
provfd = open(devprov, O_RDWR);
}
if (provfd == -1) {
gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno));
goto out;
}
mediasize = g_get_mediasize(prov);
secsize = g_get_sectorsize(prov);
if (mediasize == 0 || secsize == 0) {
gctl_error(req, "Cannot get information about %s: %s.", prov,
strerror(errno));
goto out;
}
sector = malloc(secsize);
if (sector == NULL) {
gctl_error(req, "Cannot allocate memory.");
goto out;
}
oldsize = gctl_get_intmax(req, "oldsize");
if (oldsize < 0 || oldsize > mediasize) {
gctl_error(req, "Invalid oldsize: Out of range.");
goto out;
}
/* Read metadata from the 'oldsize' offset. */
if (pread(provfd, sector, secsize, oldsize - secsize) !=
(ssize_t)secsize) {
gctl_error(req, "Cannot read old metadata: %s.",
strerror(errno));
goto out;
}
/* Check if this sector contains geli metadata. */
if (eli_metadata_decode(sector, &md) != 0) {
gctl_error(req, "MD5 hash mismatch: no metadata for oldsize.");
goto out;
}
/*
* If the old metadata doesn't have a correct provider size, refuse
* to resize.
*/
if (md.md_provsize != (uint64_t)oldsize) {
gctl_error(req, "Provider size mismatch at oldsize.");
goto out;
}
/*
* Update the old metadata with the current provider size and write
* it back to the correct place on the provider.
*/
md.md_provsize = mediasize;
eli_metadata_encode(&md, sector);
if (pwrite(provfd, sector, secsize, mediasize - secsize) !=
(ssize_t)secsize) {
gctl_error(req, "Cannot write metadata: %s.", strerror(errno));
goto out;
}
/* Now trash the old metadata. */
arc4rand(sector, secsize);
if (pwrite(provfd, sector, secsize, oldsize - secsize) !=
(ssize_t)secsize) {
gctl_error(req, "Failed to clobber old metadata: %s.",
strerror(errno));
goto out;
}
out:
if (provfd > 0)
close(provfd);
if (sector != NULL) {
bzero(sector, secsize);
free(sector);
}
}
static void
eli_clear(struct gctl_req *req)
{

View File

@ -0,0 +1,149 @@
#! /bin/sh
#
# $FreeBSD$
echo 1..27
BLK=512
BLKS_PER_MB=2048
md=$(mdconfig -s40m) || exit 1
unit=${md#md}
i=1
setsize() {
partszMB=$1 unitszMB=$2
{
echo a: $(($partszMB * $BLKS_PER_MB)) 0 4.2BSD 1024 8192
echo c: $(($unitszMB * $BLKS_PER_MB)) 0 unused 0 0
} | disklabel -R $md /dev/stdin
}
# Initialise
kldload geom_eli >/dev/null 2>&1
setsize 10 40 || echo -n "not "
echo ok $i - "Sized ${md}a to 10m"
i=$((i + 1))
echo secret >tmp.key
geli init -Bnone -PKtmp.key ${md}a || echo -n "not "
echo ok $i - "Initialised geli on ${md}a"
i=$((i + 1))
geli attach -pk tmp.key ${md}a || echo -n "not "
echo ok $i - "Attached ${md}a as ${md}a.eli"
i=$((i + 1))
newfs -U ${md}a.eli >/dev/null || echo -n "not "
echo ok $i - "Initialised the filesystem on ${md}a.eli"
i=$((i + 1))
out=$(fsck -tufs -y ${md}a.eli)
echo "$out" | fgrep -q MODIFIED && echo -n "not "
echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
"lines of output"
i=$((i + 1))
# Doing a backup, resize & restore must be forced (with -f) as geli
# verifies that the provider size in the metadata matches the consumer.
geli backup ${md}a tmp.meta || echo -n "not "
echo ok $i - "Backed up ${md}a metadata"
i=$((i + 1))
geli detach ${md}a.eli || echo -n "not "
echo ok $i - "Detached ${md}a.eli"
i=$((i + 1))
setsize 20 40 || echo -n "not "
echo ok $i - "Sized ${md}a to 20m"
i=$((i + 1))
geli attach -pktmp.key ${md}a && echo -n "not "
echo ok $i - "Attaching ${md}a fails after resizing the consumer"
i=$((i + 1))
geli restore tmp.meta ${md}a && echo -n "not "
echo ok $i - "Restoring metadata on ${md}a.eli fails without -f"
i=$((i + 1))
geli restore -f tmp.meta ${md}a || echo -n "not "
echo ok $i - "Restoring metadata on ${md}a.eli can be forced"
i=$((i + 1))
geli attach -pktmp.key ${md}a || echo -n "not "
echo ok $i - "Attaching ${md}a is now possible"
i=$((i + 1))
growfs -y ${md}a.eli >/dev/null || echo -n "not "
echo ok $i - "Extended the filesystem on ${md}a.eli"
i=$((i + 1))
out=$(fsck -tufs -y ${md}a.eli)
echo "$out" | fgrep -q MODIFIED && echo -n "not "
echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
"lines of output"
i=$((i + 1))
# Now do the resize properly
geli detach ${md}a.eli || echo -n "not "
echo ok $i - "Detached ${md}a.eli"
i=$((i + 1))
setsize 30 40 || echo -n "not "
echo ok $i - "Sized ${md}a to 30m"
i=$((i + 1))
geli resize -s20m ${md}a || echo -n "not "
echo ok $i - "Resizing works ok"
i=$((i + 1))
geli resize -s20m ${md}a && echo -n "not "
echo ok $i - "Resizing doesn't work a 2nd time (no old metadata)"
i=$((i + 1))
geli attach -pktmp.key ${md}a || echo -n "not "
echo ok $i - "Attaching ${md}a works ok"
i=$((i + 1))
growfs -y ${md}a.eli >/dev/null || echo -n "not "
echo ok $i - "Extended the filesystem on ${md}a.eli"
i=$((i + 1))
out=$(fsck -tufs -y ${md}a.eli)
echo "$out" | fgrep -q MODIFIED && echo -n "not "
echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
"lines of output"
i=$((i + 1))
geli detach ${md}a.eli
# Verify that the man page example works, changing ada0 to $md,
# 1g to 20m, 2g to 30m and keyfile to tmp.key, and adding -B none
# to geli init.
gpart create -s GPT md0 || echo -n "not "
echo ok $i - "Installed an MBR on md0"
i=$((i + 1))
gpart add -s 20m -t freebsd-ufs -i 1 $md || echo -n "not "
echo ok $i - "Added a 20m partition in slot 1"
i=$((i + 1))
geli init -B none -K tmp.key -P ${md}p1 || echo -n "not "
echo ok $i - "Initialised geli on ${md}p1"
i=$((i + 1))
gpart resize -s 30m -i 1 $md || echo -n "not "
echo ok $i - "Resized partition ${md}p1 to 30m"
i=$((i + 1))
geli resize -s 20m ${md}p1 || echo -n "not "
echo ok $i - "Resized geli on ${md}p1 to 30m"
i=$((i + 1))
geli attach -k tmp.key -p ${md}p1 || echo -n "not "
echo ok $i - "Attached ${md}p1.eli"
i=$((i + 1))
geli detach ${md}p1.eli
mdconfig -du$unit
rm tmp.*