From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=none (p=none dis=none) header.from=gibson.dropbear.id.au Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=gibson.dropbear.id.au header.i=@gibson.dropbear.id.au header.a=rsa-sha256 header.s=202608 header.b=BQ3UVgKI; dkim-atps=neutral Received: from mail.ozlabs.org (gandalf.ozlabs.org [150.107.74.76]) by passt.top (Postfix) with ESMTPS id 482475A0265 for ; Thu, 06 Aug 2026 08:34:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202608; t=1785998055; bh=lFEDu5lhidNAhLz1WkfottgeaSXhO8S6FkOAghwVDzA=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=BQ3UVgKIXhpg/zJxBuYLKPvoEQTFY+pPYLPNI2V9JkR7aa6rHBAsAgnnZaNAfYT/K ftjKHDkmvngriwI5EHtJ2NAGMrdgPaKRYJ9jirtPQgZDul4NxYONeG3WMsRK8rD0Eu Cr8wKxJLqrYLfyVnmLOrZmeb5q15y8M4iBeW25MB6hHmfELXGcmiS10heY/WEp/e/5 ozYUEbwIbtreqRqDYwpGXY2EnWVVe96OOOCnk6Xo0bDkhxTW5o9Rzn4Lsxs2TplIpL 5A9eGqaHpvKdjWMSnGMrMxS9to3hYL3PN4VCEN5dSoUfpjD3Am/hhMWJV9d8E17CHZ zY3HwZX20LPcw== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4hFyDR0xwQz4wBF; Thu, 06 Aug 2026 16:34:15 +1000 (AEST) Date: Thu, 6 Aug 2026 13:20:27 +1000 From: David Gibson To: "Lawrence, Richard E" Subject: Re: [PATCH v2] feat: Pass open files to child in pasta mode Message-ID: References: <20260731132601.422518-1-rlawrence@tamu.edu> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="Rl/yzfbHB3F5PTjg" Content-Disposition: inline In-Reply-To: Message-ID-Hash: PJIMTVTK7JFEBPXPTTQX5W2A4L7C3JQ7 X-Message-ID-Hash: PJIMTVTK7JFEBPXPTTQX5W2A4L7C3JQ7 X-MailFrom: dgibson@gandalf.ozlabs.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: "passt-dev@passt.top" X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: --Rl/yzfbHB3F5PTjg Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Mon, Aug 03, 2026 at 08:34:19PM +0000, Lawrence, Richard E wrote: > Howdy David, >=20 > Why enumerate inherited fds? > =E2=80=82=E2=80=82=E2=80=82=E2=80=82Well, I first tried using close_range= () from the tap fd up to > max, as you suggested, and I found that it was closing file > descriptors that passt had itself opened (like the log file), which > is obviously not correct. Oh. Right. Of course. Because this has moved later, we've now opened a bunch of stuff before it runs, meaning that closing everything no longer works. > =E2=80=82=E2=80=82=E2=80=82=E2=80=82I considered enumerating all the poss= ible file descriptors that > passt *might* have opened, and pass them to isolate_fds as > fds_to_keep: it turns out that there are around 10 or so file > descriptors that may or may not have been opened before the call to > isolate_fds. I decided I didn't like that strategy, because it would > create a maintenance burden: Right, that doesn't seem practical. > every time someone tinkers with a file > descriptor or reorders the startup sequence, they would have to > remember to also check the list of file descriptors to keep. And in > particular, some of the file descriptors are non-intuitive. Maybe > not everyone realizes that a socket counts a file descriptor. What > if there's a bug in someone's new feature where sockets are just > disappearing without a trace? I would hate to waste someone's time > chasing a subtle bug like that. So, I decided that enumerating > fds_to_close at runtime time would be less of a maintenance burden > than enumerating fds_to_keep in code. > =E2=80=82=E2=80=82=E2=80=82=E2=80=82I also considered reordering the star= tup so that forking happens > before opening any files, so isolate fds can run with only the tap > fd as its exclusion list, but I didn't see how to make that change > without significant refactoring. It might end up being necessary, > but I can't make that decision on my own. I don't understand the > code well enough. Interested in talking that through? Fair enough. My best guess is that this would be the best approach, but as you say it needs pretty in depth understanding of the code. And, as you've seen I've now guessed wrong several times about the best way to approach things. Let me have a look into this and see if I can come up with something that actually works. > I don't understand your comment about conf_tap_fd becoming > static. My implementation parses -F at the same time as the other > arguments (not early anymore). Did you mean that you would inline > the string to int conversion in conf()? That seems reasonable. I > just felt that conf_tap_fd was overall too complex to inline in its > current form. I just meant that since it is now only used in conf.c, it can become local to that module, a static function in C terminology. > While relocating tap fd to 3+ could easily happen later, I still > don't think it should happen during isolation, because it actually > conceptually has nothing to do with isolation. It's about avoiding a > corner case that would cause tap device chatter accidentally being > printed to std err or something like that. Isolation should be > focused on closing unused fds, not on managing fds that are in > use. Perhaps we could relocate the tap fd to 3+ in main, around the > same time that we are populating 0=E2=80=942, for clarity (since those two > steps need to coordinate). >=20 > I was not aware that dynamic memory would be off the table. My main > reason for doing it that way is because I didn't want a very large > array (max fds) to be always in memory even though most of the time > it would be unused. I will have to re-think the strategy. Maybe > there is another way to avoid a large unused array. That array isn't particularly large compared to many others we already have. And even those are usually only a small fraction of our total effective memory usage - most of that comes in the form of kernel memory for sockets and buffers. In principle we could also use MADV_DONTNEED to discard the allocated memory once we're done with it. > Thank you for pointing out the flaw in my logic regarding the max > open files limit. I will have to think harder about a correct way to > close fds. >=20 > I do not believe that my patch disables your feature of populating > fds 0=E2=80=942 with devnull. That happens in main, not in isolate fds. My > implementation of enumerating the inherited fds excludes 0=E2=80=942. You're right, sorry. I got muddled because it was inside isolate_fds() in a bunch of draft versions of my patches (I eventually realised that wouldn't quite work). >=20 > I hope to hear from you again soon > Richard >=20 > PS. In case anyone else who's reading along has concerns about the > potential performance hit caused by abandoning close_range(), I have > an argument prepared to explain why that is not a serious concern. So, fwiw, the concern isn't the cost of close() on the actually open files - as a one time cose that will generally be trivial. The conern is discovering the open fds: 2^31 close()s or other syscalls to discover if there's an fd there _would_ be too much. Using /proc/self/fd is an interesting approach. It's not portable, but then neither is close_range() (although FreeBSD does have close_range() apparently). Working out how to size things is the tricky bit with the /proc/self/fd approach, though. >=20 > ________________________________ > From: David Gibson > Sent: Sunday, August 2, 2026 11:44 PM > To: Lawrence, Richard E > Cc: passt-dev@passt.top > Subject: Re: [PATCH v2] feat: Pass open files to child in pasta mode >=20 > On Fri, Jul 31, 2026 at 08:=E2=80=8A26:=E2=80=8A01AM -0500, Richard Lawre= nce wrote: > When pasta mode is used to launch an executable (`pasta [COMMA= ND]`) and > that executable accepts inputs in the form of arbitrary file de= scriptors > (such as `bwrap`), > ZjQcmQRYFpfptBannerStart > This Message Is From an External Sender > This message came from outside your organization. >=20 > ZjQcmQRYFpfptBannerEnd >=20 > On Fri, Jul 31, 2026 at 08:26:01AM -0500, Richard Lawrence wrote: > > When pasta mode is used to launch an executable (`pasta [COMMAND]`) and > > that executable accepts inputs in the form of arbitrary file descriptors > > (such as `bwrap`), then passt should not stand in the way of the parent > > process handing off those file descriptors to the child process. > > See bug 204 for additional discussion. > > > > The strategy used here is to delay the closing of inherited file > > descriptors until after pasta has forked to execute the COMMAND, > > which happens during the call to `conf()`. > > > > A nice side-effect is that the `--fd` argument no longer needs to be > > parsed early, and can rejoin the other args parsed normally in `conf()`. > > > > Highlighted changes: > > - `conf_tap_fd()` is now called directly by `conf()`. > > - The tap fd is relocated to a number at least 3, rather than exactly 3. > > - `snapshot_initial_fds()` memorizes inherited fds early in startup. > > - Inherited fds are discovered by reading from `/proc` if available. > > - `isolate_fds()` is now called after `conf()` in `main()`. >=20 > Thanks for the new version, I like the behaviour of this one much > better. However.. maybe I'm missing something, but it seems like it's > more complicated than it needs to be. >=20 > In particular, you've completely replaced the implementation of > isolate_fds() - rather than using close_range(), it's enumerating the > open fds via /proc and close()ing them individually. Is there a > reason to do this? AFAICT now that that isolate_fds() is moved after > the spawning of the the pasta shell (or whatever), we're free to close > every fd except tap_fd, and the existing implementation should do that > just fine. (close_range() is non-portable, of course, but that's true > of /proc/self/fd too). >=20 > Some more minor points noted below. >=20 > > Signed-off-by: Richard Lawrence > > --- > > conf.c | 39 ++++++++------- > > conf.h | 1 - > > isolation.c | 135 ++++++++++++++++++++++++++++++++++++---------------- > > isolation.h | 9 +++- > > passt.c | 7 ++- > > 5 files changed, 129 insertions(+), 62 deletions(-) > > > > diff --git a/conf.c b/conf.c > > index faf2681..6bfdfb6 100644 > > --- a/conf.c > > +++ b/conf.c > > @@ -1169,26 +1169,20 @@ static void conf_sock_listen(const struct ctx *= c) > > } > > > > /** > > - * conf_tap_fd() - Read tap fd as supplied by -F command line option > > - * @argc: Argument count > > - * @argv: Command line options > > + * conf_tap_fd() - Read and relocate tap fd as supplied by -F command = line option > > + * @fdarg: String containing fd > > + * > > + * Should: > > + * - move the --fd descriptor out of the range 0-2 > > * > > * Return: fd number from --fd option, or -1 if not supplied > > */ > > -int conf_tap_fd(int argc, char **argv) > > +int conf_tap_fd(const char *fdarg) >=20 > Now that isolate_fds() is after conf(), we shouldn't need to preparse > -F any more - we can use the normal parse in conf(). I think that > means this can become static. >=20 > > { > > - const struct option optfd[] =3D { { "fd", required_argument, NULL= , 'F' }, > > - { 0 }, }; > > - const char *fdarg =3D NULL, *p; > > unsigned long val; > > - int name; > > - > > - optind =3D 0; > > - do { > > - name =3D getopt_long(argc, argv, "-:F:", optfd, NULL); > > - if (name =3D=3D 'F') > > - fdarg =3D optarg; > > - } while (name !=3D -1); > > + const char *p; > > + int new_fd; > > + int fd; > > > > if (!fdarg) > > return -1; > > @@ -1197,7 +1191,18 @@ int conf_tap_fd(int argc, char **argv) > > if (!parse_unsigned(&p, 0, &val) || !parse_eoi(p) || val > INT_MA= X) > > die("Invalid --fd: %s", fdarg); > > > > - return val; > > + fd =3D (int)val; > > + if (fd >=3D 0 && fd < 3) { > > + new_fd =3D fcntl(fd, F_DUPFD, 3); > > + > > + if (new_fd < 0) > > + die_perror("Could not relocate --fd descriptor"); > > + > > + close(fd); > > + fd =3D new_fd; >=20 > I think we can delay this relocation to isolate_fds() - in which case, > again, the existing implementation should already do what we need. >=20 > > + } > > + > > + return fd; > > } > > > > /** > > @@ -1625,7 +1630,7 @@ void conf(struct ctx *c, int argc, char **argv) > > c->fd_control_listen =3D c->fd_control =3D -1; > > break; > > case 'F': > > - /* --fd was parsed early and c->fd_tap set in mai= n() */ > > + c->fd_tap =3D conf_tap_fd(optarg); > > c->one_off =3D true; > > *c->sock_path =3D 0; > > break; > > diff --git a/conf.h b/conf.h > > index 19bf9bc..16f9718 100644 > > --- a/conf.h > > +++ b/conf.h > > @@ -7,7 +7,6 @@ > > #define CONF_H > > > > enum passt_modes conf_mode(int argc, char *argv[]); > > -int conf_tap_fd(int argc, char **argv); > > void conf(struct ctx *c, int argc, char **argv); > > void conf_listen_handler(struct ctx *c, uint32_t events); > > void conf_handler(struct ctx *c, uint32_t events); > > diff --git a/isolation.c b/isolation.c > > index 94cbe7f..2434748 100644 > > --- a/isolation.c > > +++ b/isolation.c > > @@ -24,19 +24,13 @@ > > * done anything we need to do with those resources, so we have > > * multiple stages of self-isolation. In order these are: > > * > > - * 1a. isolate_initial() > > + * 1. isolate_initial() > > * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > > * > > * Executed immediately after startup, drops capabilities we don't > > * need at any point during execution (or which we gain back when we > > * need by joining other namespaces). > > * > > - * 1b. isolate_fds() > > - * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > > - * > > - * Executed immediately after isolate_initial(). Closes any leaked > > - * files we might have inherited from the parent process. > > - * > > * 2. isolate_user() > > * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > > * > > @@ -44,14 +38,20 @@ > > * operate in. Sets our final UID & GID, and enters the correct user > > * namespace. > > * > > - * 3. isolate_prefork() > > + * 3. isolate_fds() > > + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > > + * > > + * Executed after conf(). Closes any leaked > > + * files we might have inherited from the parent process. > > + * > > + * 4. isolate_prefork() > > * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > > * > > * Executed after all setup, but before daemonising (fork()ing into > > * the background). Uses mount namespace and pivot_root() to remove > > * our access to the filesystem. > > * > > - * 4. isolate_postfork() > > + * 5. isolate_postfork() > > * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > > * > > * Executed immediately after daemonizing, but before entering the > > @@ -61,6 +61,7 @@ > > * runtime operation. > > */ > > > > +#include > > #include > > #include > > #include > > @@ -77,6 +78,7 @@ > > #include > > #include > > #include > > +#include > > #include > > #include > > #include > > @@ -248,47 +250,96 @@ void isolate_initial(void) > > drop_caps_ep_except(keep); > > } > > > > -/* > > - * isolate_fds() - Close leaked files, but not --fd, stdin, stdout, st= derr > > - * @argc: Argument count > > - * @argv: Command line options, as we need to skip any file given v= ia --fd > > - * > > - * Should: > > - * - close all open files except for standard streams and the one fro= m --fd > > - * - move the --fd descriptor out of the range 0-2 > > - * > > - * Return: new fd number for descriptor from --fd, or -1 if not specif= ied > > +/** > > + * snapshot_initial_fds() - Snapshot initial file descriptors inherite= d from parent > > + * other than standard streams (stdin, stdout, stderr) > > + * @ifds: Snapshot struct of initial file descriptors to populate > > */ > > -int isolate_fds(int argc, char **argv) > > +void snapshot_initial_fds(struct initial_fd_snapshot *ifds) > > { > > - int fd, close_from =3D STDERR_FILENO + 1; > > + struct dirent *entry; > > + size_t capacity =3D 16; > > + int max_fd =3D 1024; > > + struct rlimit rl; > > + long parsed_fd; > > + char *endptr; > > + int dir_fd; > > + DIR *dir; > > + int fd; > > + > > + ifds->arr =3D NULL; > > + ifds->count =3D 0; > > + > > + dir =3D opendir("/proc/self/fd"); > > + if (dir) { > > + dir_fd =3D dirfd(dir); > > + ifds->arr =3D malloc(sizeof(int) * capacity); >=20 > We don't allocate() memory in passt as a matter of policy. We enforce > that with seccomp, although you'll get away with it here because the > seccomp filter is only applied in isolate_postfork(). Nonetheless a > static array should be used instead. If this function is even needed, > which as noted above, I don't actually see a reason for at the moment. >=20 > > + if (!ifds->arr) > > + die_perror("Failed to allocate memory for inherit= ed fds"); > > + > > + while ((entry =3D readdir(dir)) !=3D NULL) { > > + if (entry->d_name[0] < '0' || entry->d_name[0] > = '9') > > + continue; > > + parsed_fd =3D strtol(entry->d_name, &endptr, 10); > > + if (*endptr !=3D '\0' || parsed_fd < 0 || parsed_= fd > INT_MAX) > > + continue; > > + fd =3D (int)parsed_fd; > > + /* Ignore stdin/stdout/stderr and the opendir han= dle itself */ > > + if (fd > STDERR_FILENO && fd !=3D dir_fd) { > > + if (ifds->count >=3D capacity) { > > + size_t new_cap =3D capacity * 2; > > + int *new_arr =3D realloc(ifds->ar= r, sizeof(int) * new_cap); > > + > > + if (!new_arr) > > + die_perror("Failed to rea= llocate memory for inherited fds"); > > + > > + ifds->arr =3D new_arr; > > + capacity =3D new_cap; > > + } > > + ifds->arr[ifds->count++] =3D fd; > > + } > > + } > > + closedir(dir); > > + return; > > + } > > + > > + /* Fallback for environments without /proc (e.g. minimal chroots)= */ > > + if (getrlimit(RLIMIT_NOFILE, &rl) =3D=3D 0 && rl.rlim_cur !=3D RL= IM_INFINITY) > > + max_fd =3D (int)rl.rlim_cur; > > > > - fd =3D conf_tap_fd(argc, argv); > > + ifds->arr =3D malloc(sizeof(int) * max_fd); > > + if (!ifds->arr) > > + die_perror("Failed to allocate memory for inherited fds f= allback"); > > > > - if (fd >=3D 0) { > > - /* Move the passed fd to a more convenient location */ > > - if (fd !=3D close_from && > > - (dup2(fd, close_from) !=3D close_from || > > - close(fd))) > > - die_perror("Could not move --fd descriptor"); > > - fd =3D close_from++; > > + for (fd =3D STDERR_FILENO + 1; fd < max_fd; fd++) { >=20 > I don't think the RLIMIT_NOFILE guarantees that all fds will be less > than it. They generally will be, but I don't think anything will stop > you using dup2() to move a single fd to some very high fd number. >=20 > > + if (fcntl(fd, F_GETFD) >=3D 0) > > + ifds->arr[ifds->count++] =3D fd; > > } > > +} > > > > - if (close_range(close_from, ~0U, CLOSE_RANGE_UNSHARE)) { > > - if (errno =3D=3D ENOSYS || errno =3D=3D EINVAL) { > > - /* This probably means close_range() or the > > - * CLOSE_RANGE_UNSHARE flag is not supported by t= he > > - * kernel. Not much we can do here except carry = on and > > - * hope for the best. > > - */ > > - warn( > > -"Can't use close_range() to ensure no files leaked by parent"); > > - } else { > > - die_perror("Failed to close files leaked by paren= t"); > > - } > > +/** > > + * isolate_fds() - Close leaked files from the parent process > > + * @ifds: Snapshot of initial file descriptors > > + * @keep_fd: File descriptor to keep open, if any > > + * > > + * Should: > > + * - close all file descriptors that were open at startup, except for= keep_fd > > + */ > > +void isolate_fds(struct initial_fd_snapshot *ifds, int keep_fd) >=20 > The rewritten isolate_fds() no longer ensures that fds 0, 1, 2 are > populated (with /dev/null, if necessary). That's not strictly > necessary, but I did implement it on purpose, because I think it > avoids confusion. >=20 > > +{ > > + size_t i; > > + > > + if (!ifds || !ifds->arr) > > + return; > > + > > + for (i =3D 0; i < ifds->count; i++) { > > + if (ifds->arr[i] !=3D keep_fd) > > + close(ifds->arr[i]); > > } > > > > - return fd; > > + free(ifds->arr); > > + ifds->arr =3D NULL; > > + ifds->count =3D 0; > > } > > > > /** > > diff --git a/isolation.h b/isolation.h > > index ec47038..8ada988 100644 > > --- a/isolation.h > > +++ b/isolation.h > > @@ -8,10 +8,17 @@ > > #define ISOLATION_H > > > > #include > > +#include > > #include > > > > +struct initial_fd_snapshot { > > + int *arr; > > + size_t count; > > +}; > > + > > void isolate_initial(void); > > -int isolate_fds(int argc, char **argv); > > +void snapshot_initial_fds(struct initial_fd_snapshot *ifds); > > +void isolate_fds(struct initial_fd_snapshot *ifds, int keep_fd); > > void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_= userns, > > const char *userns); > > int isolate_prefork(const struct ctx *c); > > diff --git a/passt.c b/passt.c > > index 5054551..c4335bf 100644 > > --- a/passt.c > > +++ b/passt.c > > @@ -335,6 +335,7 @@ int main(int argc, char **argv) > > struct epoll_event events[NUM_EPOLL_EVENTS]; > > int nfds, devnull_fd =3D -1, fd; > > struct ctx *c =3D &passt_ctx; > > + struct initial_fd_snapshot ifds; > > struct rlimit limit; > > struct timespec now; > > struct sigaction sa; > > @@ -344,8 +345,9 @@ int main(int argc, char **argv) > > > > arch_avx2_exec(argv); > > > > + snapshot_initial_fds(&ifds); > > + > > isolate_initial(); > > - c->fd_tap =3D isolate_fds(argc, argv); > > > > if ((devnull_fd =3D open("/dev/null", O_RDWR | O_CLOEXEC)) < 0) > > die_perror("Failed to open /dev/null"); > > @@ -390,6 +392,9 @@ int main(int argc, char **argv) > > sock_probe_features(c); > > > > conf(c, argc, argv); > > + > > + isolate_fds(&ifds, c->fd_tap); > > + > > trace_init(c->trace); > > > > pasta_netns_quit_init(c); > > -- > > 2.52.0 > > >=20 > -- > David Gibson (he or they) | I'll have my music baroque, and my code > david AT gibson.dropbear.id.au | minimalist, thank you, not the other way > | around. > http://www.ozlabs.org/~dgibson >=20 --=20 David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson --Rl/yzfbHB3F5PTjg Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEO+dNsU4E3yXUXRK2zQJF27ox2GcFAmpz/WoACgkQzQJF27ox 2GfpIRAAnrhNlOid6WShBTZXGJLsT3fkTV303lG9xoUe6xnd+Fvl8WbVOFwZ9JpK W+6QVSqukxU0PDp49m7XrC+xLSVCt4P+dRfS6NugQ/2fuUAAFcs2TdboMlpj7xvN 0RY+vkBfnJYu34WyOVS49CKuwblM9gC9NxiLBuMxgxjqGuZCSB28DUTJOCRyt+yS bF3FB6a7zRZUVkcogJqbJziySEN0nnj241rhYye9w/OG0Bhcjb3Uj+yy8lVwllrS Vkk++GP0T8IwB8XHgImOAQk0KWfajn0O2ybphaci8t2w49glEp1BztV0ffr2RaWn 4za72taS5q+uDXDZvAztEfcgbeWgovroACyQ0cDTaP+XAQ5q627lT65SW6laOJKR 8WUsbQoyC9a4RqtH02vaSNrVqqfa6hQGERvrHy/9RhUKsKUK70S58qb7TdyPxsTC ISt34oduBVyRk0OhEN8SYlUjYEhXO3ubkreEqtjxroQmaU7FOUCMsf7/YP0svyUS 1CgjZqZc9ed7p/iQNa7rY6bEfMah5NOZQ0VS1TE78yF45O2YEIA0KVAcfcAKkfGu BPg/w4vgIwmqQg+Tw8PzdEdMLUXOmY+MFRH49EbJ2L4FBRllIvN8SypVCbbaqBkw 6vJ5CIBgRR6Gb9hYgI57DukwVJwSVs0XY0GnvU4B6bRTG+fh1xI= =d/k/ -----END PGP SIGNATURE----- --Rl/yzfbHB3F5PTjg--