From: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>
To: Proxmox VE development discussion <pve-devel@lists.proxmox.com>
Subject: Re: [pve-devel] [PATCH pve-storage v4 1/3] recursively go through subdirs to find files
Date: Wed, 26 Jul 2023 11:34:55 +0200 [thread overview]
Message-ID: <1690360729.dvu5qvbgb9.astroid@yuna.none> (raw)
In-Reply-To: <20230721122314.80427-2-n.ullreich@proxmox.com>
On July 21, 2023 2:23 pm, Noel Ullreich wrote:
> This patch allows `get_subdir_files` to recursively call itself, so that
> subdirectories of set depth can be searched. We allow searching for
> isos, vztmpl and snippets but not backups.
>
> As a security measure, when parsing a given path, parent
> directories (`/../`) are forbidden.
>
> The feature is opt-in, i.e. the searchdepth is 0 by default. It can be
> changed via the API, the web interface and `pvesm` (see the other
> patches).
>
> Signed-off-by: Noel Ullreich <n.ullreich@proxmox.com>
> ---
> src/PVE/Storage.pm | 11 ++++++++
> src/PVE/Storage/Plugin.pm | 54 ++++++++++++++++++++++++---------------
> 2 files changed, 44 insertions(+), 21 deletions(-)
>
> diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm
> index b99ed35..02abacf 100755
> --- a/src/PVE/Storage.pm
> +++ b/src/PVE/Storage.pm
> @@ -113,6 +113,17 @@ our $VZTMPL_EXT_RE_1 = qr/\.tar\.(gz|xz|zst)/i;
>
> our $BACKUP_EXT_RE_2 = qr/\.(tgz|(?:tar|vma)(?:\.(${\PVE::Storage::Plugin::COMPRESSOR_RE}))?)/;
>
> +our $INTERMEDIATE_SUBDIR_EXT_RE_3 = qr/(?:[0-9A-z\_\-\.]+\/)*[^\/]+/i;
> +
> +our $SUBDIR_ANY_FILEEXTENSION_EXT_RE_4 = qr/(?:[0-9A-z\_\-\.]+\/)*.+/i;
> +
1. common RE pitfall: use a-z instead of A-z, or a-zA-Z and drop the
`/i` - else this matches the characters [\]^` as well, since they sit
between 'Z' and 'a'. not all of them will work in practice, especially
the '\' one would require special handling - but we don't want them
anyhow ;)
2. these two should end with _0 , right? (they don't have any capturing
groups)
3. the first regular expression matches any string that doesn't contain
a '/', or any string containing slashes, where everything up to the last
slash must be from the restricted char set - the name is a bit weird
IMHO ;) maybe it would make more sense to keep the [^\/]+ part out of
it, and leave that at the call site? then it would actually just match
the intermediate sub-dirs, like the name implies (although I would still
drop the `_EXT`, it refers to a file extension, and that is the only
thing it *does not* match at the moment ;))..
4. the second RE basically matches any string of length >=1 ?
if we do the above change, a single RE should be enough (and the one
usage of $SUBDIR_ANY_FILEEXTENSION_EXT_RE_4 could also just be
$INTERMEDIATE_SUBDIR_RE_0[^\/]+ , like the other ones, but without any
specific extension/suffix..). it would also mean that parse_volname and
get_subdir_files accept the same things, instead of the latter allowing
more characters in subdir components (although in practice, its return
value will likely be filtered through parse_volume_id -> parse_volname
anyhow - not sure of *all* the code paths though..)
other than these, the patches look good to me and seem to work as
expected, the above is mostly (except for number 1) code
hygiene/style/naming :) symlinks are also handled transparently as
expected (e.g., no resolved path leaks into path/volume id/.., and the
size is correctly retrieved from the target, dangling symlinks are just
ignored).
> +# '..' is forbidden at the beginning, between two '/' and at the end
> +my $dots = quotemeta('..');
> +my $beginning = qr!^$dots/!;
> +my $between = qr!/$dots/!;
> +my $end = qr!/$dots$!;
> +our $forbidden_double_dots_re = qr!(?:$beginning|$between|$end)!;
> +
> # FIXME remove with PVE 8.0, add versioned breaks for pve-manager
> our $vztmpl_extension_re = $VZTMPL_EXT_RE_1;
>
> diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm
> index 9d3b1ae..8831fbb 100644
> --- a/src/PVE/Storage/Plugin.pm
> +++ b/src/PVE/Storage/Plugin.pm
> @@ -621,6 +621,8 @@ sub parse_name_dir {
> sub parse_volname {
> my ($class, $volname) = @_;
>
> + die "volname must not contain parent directories '/../'\n" if $volname =~ $PVE::Storage::forbidden_double_dots_re;
> +
> if ($volname =~ m!^(\d+)/(\S+)/(\d+)/(\S+)$!) {
> my ($basedvmid, $basename) = ($1, $2);
> parse_name_dir($basename);
> @@ -631,9 +633,9 @@ sub parse_volname {
> my ($vmid, $name) = ($1, $2);
> my (undef, $format, $isBase) = parse_name_dir($name);
> return ('images', $name, $vmid, undef, undef, $isBase, $format);
> - } elsif ($volname =~ m!^iso/([^/]+$PVE::Storage::ISO_EXT_RE_0)$!) {
> + } elsif ($volname =~ m!^iso/($PVE::Storage::INTERMEDIATE_SUBDIR_EXT_RE_3$PVE::Storage::ISO_EXT_RE_0)$!) {
> return ('iso', $1);
> - } elsif ($volname =~ m!^vztmpl/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!) {
> + } elsif ($volname =~ m!^vztmpl/($PVE::Storage::INTERMEDIATE_SUBDIR_EXT_RE_3$PVE::Storage::VZTMPL_EXT_RE_1)$!) {
> return ('vztmpl', $1);
> } elsif ($volname =~ m!^rootdir/(\d+)$!) {
> return ('rootdir', $1, $1);
> @@ -643,7 +645,7 @@ sub parse_volname {
> return ('backup', $fn, $2);
> }
> return ('backup', $fn);
> - } elsif ($volname =~ m!^snippets/([^/]+)$!) {
> + } elsif ($volname =~ m!^snippets/($PVE::Storage::INTERMEDIATE_SUBDIR_EXT_RE_3)$!) {
> return ('snippets', $1);
> }
>
> @@ -1212,28 +1214,33 @@ sub list_images {
> }
>
> # list templates ($tt = <iso|vztmpl|backup|snippets>)
> -my $get_subdir_files = sub {
> - my ($sid, $path, $tt, $vmid) = @_;
> +sub get_subdir_files {
> + my ($sid, $path, $tt, $scfg, $vmid, $remaining_depth) = @_;
> + my $storage_path = $scfg->{path};
> + my $content_dir = $scfg->{"content-dirs"}->{$tt} // $vtype_subdirs->{$tt};
>
> my $res = [];
>
> foreach my $fn (<$path/*>) {
> my $st = File::stat::stat($fn);
>
> - next if (!$st || S_ISDIR($st->mode));
> + next if (!$st);
> +
> + if (S_ISDIR($st->mode)) {
> + if ($remaining_depth) {
> + push @$res, get_subdir_files($sid, $fn, $tt, $scfg, $vmid, $remaining_depth-1);
> + }
> + next;
> + }
>
> my $info;
>
> if ($tt eq 'iso') {
> - next if $fn !~ m!/([^/]+$PVE::Storage::ISO_EXT_RE_0)$!i;
> -
> + next if $fn !~ m/(?:^$storage_path\/$content_dir\/)($PVE::Storage::INTERMEDIATE_SUBDIR_EXT_RE_3$PVE::Storage::ISO_EXT_RE_0)/;
> $info = { volid => "$sid:iso/$1", format => 'iso' };
> -
> } elsif ($tt eq 'vztmpl') {
> - next if $fn !~ m!/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!;
> -
> + next if $fn !~ m/(?:^$storage_path\/$content_dir\/)($PVE::Storage::INTERMEDIATE_SUBDIR_EXT_RE_3$PVE::Storage::VZTMPL_EXT_RE_1)/;
> $info = { volid => "$sid:vztmpl/$1", format => "t$2" };
> -
> } elsif ($tt eq 'backup') {
> next if $fn !~ m!/([^/]+$PVE::Storage::BACKUP_EXT_RE_2)$!;
> my $original = $fn;
> @@ -1262,9 +1269,9 @@ my $get_subdir_files = sub {
>
> $info->{protected} = 1 if -e PVE::Storage::protection_file_path($original);
> } elsif ($tt eq 'snippets') {
> -
> + next if $fn !~ m/(?:^$storage_path\/$content_dir\/)($PVE::Storage::SUBDIR_ANY_FILEEXTENSION_EXT_RE_4)/;
> $info = {
> - volid => "$sid:snippets/". basename($fn),
> + volid => "$sid:snippets/$1", #basename($fn),
leftover comment? ;)
> format => 'snippet',
> };
> }
> @@ -1274,14 +1281,18 @@ my $get_subdir_files = sub {
>
> push @$res, $info;
> }
> -
> return $res;
> };
>
> +sub flatten {
> + map { ref eq 'ARRAY' ? flatten(@{$_}) : $_ } @_;
> +}
> +
> # If attributes are set on a volume, they should be included in the result.
> # See get_volume_attribute for a list of possible attributes.
> sub list_volumes {
> my ($class, $storeid, $scfg, $vmid, $content_types) = @_;
> + my $max_depth = $scfg->{'scan-depth'} // 0;
>
> my $res = [];
> my $vmlist = PVE::Cluster::get_vmlist();
> @@ -1294,17 +1305,19 @@ sub list_volumes {
> my $path = $class->get_subdir($scfg, $type);
>
> if ($type eq 'iso' && !defined($vmid)) {
> - $data = $get_subdir_files->($storeid, $path, 'iso');
> + $data = get_subdir_files($storeid, $path, 'iso', $scfg, undef, $max_depth);
> } elsif ($type eq 'vztmpl'&& !defined($vmid)) {
> - $data = $get_subdir_files->($storeid, $path, 'vztmpl');
> + $data = get_subdir_files($storeid, $path , 'vztmpl', $scfg, undef, $max_depth);
> } elsif ($type eq 'backup') {
> - $data = $get_subdir_files->($storeid, $path, 'backup', $vmid);
> + $data = get_subdir_files($storeid, $path, 'backup', $scfg, $vmid, $max_depth);
> } elsif ($type eq 'snippets') {
> - $data = $get_subdir_files->($storeid, $path, 'snippets');
> + $data = get_subdir_files($storeid, $path, 'snippets', $scfg, undef, $max_depth);
> }
> }
>
> - next if !$data;
> + $data = [flatten($data)];
> +
> + next if !@$data[0];
>
> foreach my $item (@$data) {
> if ($type eq 'images' || $type eq 'rootdir') {
> @@ -1322,7 +1335,6 @@ sub list_volumes {
> } else {
> $item->{content} = $type;
> }
> -
> push @$res, $item;
> }
> }
> --
> 2.39.2
>
>
>
> _______________________________________________
> pve-devel mailing list
> pve-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
>
>
>
next prev parent reply other threads:[~2023-07-26 9:35 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-07-21 12:23 [pve-devel] [PATCH pve-storage v4; pve-manager 0/4] fix #623: show isos/vztmpl/snippets in subdirs Noel Ullreich
2023-07-21 12:23 ` [pve-devel] [PATCH pve-storage v4 1/3] recursively go through subdirs to find files Noel Ullreich
2023-07-26 9:34 ` Fabian Grünbichler [this message]
2023-07-21 12:23 ` [pve-devel] [PATCH pve-storage v4 2/3] add `scan-depth` option to filesystems Noel Ullreich
2023-07-21 12:23 ` [pve-devel] [PATCH pve-storage v4 3/3] update test for recursive subdir search Noel Ullreich
2023-07-21 12:23 ` [pve-devel] [PATCH pve-manager v4 1/1] ui: add field to set subdir-depth in web interface Noel Ullreich
2023-07-25 8:00 ` [pve-devel] [PATCH pve-storage v4; pve-manager 0/4] fix #623: show isos/vztmpl/snippets in subdirs Noel Ullreich
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1690360729.dvu5qvbgb9.astroid@yuna.none \
--to=f.gruenbichler@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.