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:
parent
4b5ccbf768
commit
044bf69fd5
@ -24,7 +24,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" $FreeBSD$
|
.\" $FreeBSD$
|
||||||
.\"
|
.\"
|
||||||
.Dd August 29, 2008
|
.Dd September 20, 2010
|
||||||
.Dt GELI 8
|
.Dt GELI 8
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -111,10 +111,15 @@ utility:
|
|||||||
.Ar file
|
.Ar file
|
||||||
.Nm
|
.Nm
|
||||||
.Cm restore
|
.Cm restore
|
||||||
.Op Fl v
|
.Op Fl fv
|
||||||
.Ar file
|
.Ar file
|
||||||
.Ar prov
|
.Ar prov
|
||||||
.Nm
|
.Nm
|
||||||
|
.Cm resize
|
||||||
|
.Op Fl v
|
||||||
|
.Fl s Ar oldsize
|
||||||
|
.Ar prov
|
||||||
|
.Nm
|
||||||
.Cm clear
|
.Cm clear
|
||||||
.Op Fl v
|
.Op Fl v
|
||||||
.Ar prov ...
|
.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.
|
Backup metadata from the given provider to the given file.
|
||||||
.It Cm restore
|
.It Cm restore
|
||||||
Restore metadata from the given file to the given provider.
|
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
|
.It Cm clear
|
||||||
Clear metadata from the given providers.
|
Clear metadata from the given providers.
|
||||||
.It Cm dump
|
.It Cm dump
|
||||||
@ -665,6 +698,17 @@ geli: Cannot read metadata from /dev/da0: Invalid argument.
|
|||||||
# geli attach /dev/da0
|
# geli attach /dev/da0
|
||||||
Enter passphrase:
|
Enter passphrase:
|
||||||
.Ed
|
.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
|
.Sh DATA AUTHENTICATION
|
||||||
.Nm
|
.Nm
|
||||||
can verify data integrity when an authentication algorithm is specified.
|
can verify data integrity when an authentication algorithm is specified.
|
||||||
|
@ -66,6 +66,7 @@ static void eli_delkey(struct gctl_req *req);
|
|||||||
static void eli_kill(struct gctl_req *req);
|
static void eli_kill(struct gctl_req *req);
|
||||||
static void eli_backup(struct gctl_req *req);
|
static void eli_backup(struct gctl_req *req);
|
||||||
static void eli_restore(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_clear(struct gctl_req *req);
|
||||||
static void eli_dump(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
|
* delkey [-afv] [-n keyno] prov
|
||||||
* kill [-av] [prov ...]
|
* kill [-av] [prov ...]
|
||||||
* backup [-v] prov file
|
* backup [-v] prov file
|
||||||
* restore [-v] file prov
|
* restore [-fv] file prov
|
||||||
|
* resize [-v] -s oldsize prov
|
||||||
* clear [-v] prov ...
|
* clear [-v] prov ...
|
||||||
* dump [-v] prov ...
|
* dump [-v] prov ...
|
||||||
*/
|
*/
|
||||||
@ -197,8 +199,19 @@ struct g_command class_commands[] = {
|
|||||||
{ "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
|
{ "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
|
||||||
"[-v] prov file"
|
"[-v] prov file"
|
||||||
},
|
},
|
||||||
{ "restore", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
|
{ "restore", G_FLAG_VERBOSE, eli_main,
|
||||||
"[-v] file prov"
|
{
|
||||||
|
{ '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,
|
{ "clear", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
|
||||||
"[-v] prov ..."
|
"[-v] prov ..."
|
||||||
@ -264,6 +277,8 @@ eli_main(struct gctl_req *req, unsigned flags)
|
|||||||
eli_backup(req);
|
eli_backup(req);
|
||||||
else if (strcmp(name, "restore") == 0)
|
else if (strcmp(name, "restore") == 0)
|
||||||
eli_restore(req);
|
eli_restore(req);
|
||||||
|
else if (strcmp(name, "resize") == 0)
|
||||||
|
eli_resize(req);
|
||||||
else if (strcmp(name, "dump") == 0)
|
else if (strcmp(name, "dump") == 0)
|
||||||
eli_dump(req);
|
eli_dump(req);
|
||||||
else if (strcmp(name, "clear") == 0)
|
else if (strcmp(name, "clear") == 0)
|
||||||
@ -683,6 +698,7 @@ eli_attach(struct gctl_req *req)
|
|||||||
struct g_eli_metadata md;
|
struct g_eli_metadata md;
|
||||||
unsigned char key[G_ELI_USERKEYLEN];
|
unsigned char key[G_ELI_USERKEYLEN];
|
||||||
const char *prov;
|
const char *prov;
|
||||||
|
off_t mediasize;
|
||||||
int nargs;
|
int nargs;
|
||||||
|
|
||||||
nargs = gctl_get_int(req, "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)
|
if (eli_metadata_read(req, prov, &md) == -1)
|
||||||
return;
|
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) {
|
if (eli_genkey(req, &md, key, 0) == NULL) {
|
||||||
bzero(key, sizeof(key));
|
bzero(key, sizeof(key));
|
||||||
return;
|
return;
|
||||||
@ -1212,6 +1234,17 @@ eli_restore(struct gctl_req *req)
|
|||||||
gctl_error(req, "MD5 hash mismatch: not a geli backup file?");
|
gctl_error(req, "MD5 hash mismatch: not a geli backup file?");
|
||||||
goto out;
|
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. */
|
/* Write metadata from the provider. */
|
||||||
if (pwrite(provfd, sector, secsize, mediasize - secsize) !=
|
if (pwrite(provfd, sector, secsize, mediasize - secsize) !=
|
||||||
(ssize_t)secsize) {
|
(ssize_t)secsize) {
|
||||||
@ -1229,6 +1262,111 @@ out:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
static void
|
||||||
eli_clear(struct gctl_req *req)
|
eli_clear(struct gctl_req *req)
|
||||||
{
|
{
|
||||||
|
149
tools/regression/geom_eli/resize.t
Normal file
149
tools/regression/geom_eli/resize.t
Normal 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.*
|
Loading…
x
Reference in New Issue
Block a user