[4290] in BarnOwl Developers
Re: BarnOwl 1.10: a zephyr's CC: line is used as a log file name
daemon@ATHENA.MIT.EDU (Alex Dehnert)
Sun Nov 1 23:42:17 2026
Date: Sun, 1 Nov 2026 23:42:07 -0500
To: Jason Gross <jgross@mit.edu>, Daniel Clark <djbclark@gmail.com>
Cc: barnowl-dev@mit.edu
From: Alex Dehnert <alex@dehnerts.com>
In-Reply-To: <CAKObCapg0Lqb6Hw9k6Hdo++p04Z9ewr0KGyTixoPknSb9VbPCA@mail.gmail.com>
It is possible we should uh finally do a 1.11 release, with whatever is
on master. Does anybody know a reason we shouldn't? (I suppose maybe I
should try running Barnowl from master, though I don't use barnowl much
these days so I wouldn't be great at testing it.)
On 11/1/26 01:09, Jason Gross wrote:
> I believe this issue is not present in the Perl-based logging code
> <https://github.com/barnowl/barnowl/pull/54>, which was merged 6 months
> after the 1.10 release, 9 years ago. If anyone still uses BarnOwl, I
> guess it may be worth doing another release. (There has only been one
> commit in the past seven years.)
>
> On Sat, Oct 31, 2026 at 9:50 PM Daniel Clark <djbclark@gmail.com
> <mailto:djbclark@gmail.com>> wrote:
>
> Hello BarnOwl developers,
>
> I found a security bug in BarnOwl 1.10 logging: a zephyr's CC: line is
> used as a log file name without the checks that the sender's own name
> gets. Any zephyr sender can make the receiving client append a message
> to any file the user can write, or create it. I have also reported it
> to security@debian.org <mailto:security@debian.org> (2026-11-01). I
> am sending it to you so that it
> is fixed upstream and so that you can decide how to tell BarnOwl users.
>
> WHAT HAPPENS
>
> owl_log_incoming (logging.c) checks the sender's name before using it as
> a file name under logpath: 1 to 35 printable ASCII characters, first
> character alphanumeric, no '/', not "." or "..". For a personal zephyr
> it then also logs the message to every name on its CC list
> (owl_message_get_cc_without_recipient: the first body line, if it starts
> with "CC:"). Those names come from the sender and get none of those
> checks: g_build_filename(logpath, short_zuser(name), NULL), then
> fopen(..., "a") on the logger thread. owl_log_outgoing has the same
> loop, for the user's own CC lines.
>
> IMPACT
>
> A personal zephyr whose body starts with the line "CC:
> ../../.ssh/authorized_keys", followed by a line holding an SSH public
> key, makes the receiving client append the whole logged entry (header
> lines, then the body) to ~/.ssh/authorized_keys. sshd ignores the header
> lines and accepts the key line. Appending to ~/.bashrc or creating
> ~/.ssh/rc gives code execution the same way. It needs personal logging
> on ("set logging on", loggingdirection "in" or "both", the documented
> setup), no authentication and no action by the user.
>
> AFFECTED
>
> BarnOwl 1.10 (tag barnowl-1.10), tested as Debian barnowl 1.10-2+b9 on
> Debian 13. I did not check other versions; the code looks old, so
> earlier ones are probably affected too.
>
> REPRODUCTION (scratch directory; nothing outside it is touched)
>
> Use a scratch HOME with the config dir /tmp/T/home/.owl and logpath
> /tmp/T/zlog/people. Put this in /tmp/T/home/.owl/startup:
>
> set logging on
> set loggingdirection both
> set logpath /tmp/T/zlog/people
> perl BarnOwl::queue_message(BarnOwl::Message->new(
> type=>'zephyr', direction=>'in', class=>'message',
> instance=>'personal', sender=>'eve@ATHENA.MIT.EDU
> <mailto:eve@ATHENA.MIT.EDU>',
> recipient=>'user@ATHENA.MIT.EDU
> <mailto:user@ATHENA.MIT.EDU>', private=>1,
> isprivate=>'true', realm=>'ATHENA.MIT.EDU <http://
> ATHENA.MIT.EDU>', opcode=>'',
> auth=>'NO', host=>'h', hostname=>'h', zsig=>'z',
> body=>"CC: ../../victim\nssh-ed25519 AAAAFAKEKEY attacker\n",
> time=>scalar(localtime), unix_time=>time()))
> perl our $t = BarnOwl::Timer->new({after=>4,
> cb=>sub{BarnOwl::command("quit")}})
>
> Then run: HOME=/tmp/T/home script -qfc 'barnowl -n -s /tmp/T/home/.owl'
> /dev/null. With 1.10-2+b9, /tmp/T/victim exists (outside logpath) and
> holds the logged entry. With the patch below it is not created, and a CC
> to an ordinary user name is still logged. A real zephyr goes through the
> same function.
>
> TEMPORARY WORKAROUND FOR USERS
>
> Until a fixed BarnOwl is installed, users can stop the bug by turning
> personal-zephyr logging off:
>
> - Put "set logging off" in ~/.owl/startup (replacing "set logging
> on")
> and run ":set logging off" in a running client. Personal
> zephyrs are
> then not logged, in either direction. Class logging ("set
> classlogging on") is unaffected, because class names get the
> sender-
> name checks and have no CC handling.
> - "set loggingdirection out" should also stop incoming personals from
> being logged while keeping a log of what you send. I have not
> tested
> that variant; the "logging off" one is based on reading the
> code and
> on how the logging variables gate the logger, and I did not run it
> against the exploit.
> - Weaker, partial measures: make ~/.ssh/authorized_keys,
> ~/.bashrc and
> ~/.profile read-only (chmod 400, or chattr +i as root). This does
> not help against files that do not exist yet, such as ~/.ssh/rc or
> ~/.bash_login, and any file the user can write stays reachable.
> - Run the client in a sandbox (bwrap, firejail, a separate
> unprivileged account) that can write only ~/.owl and the log
> directory. I have not tried this.
>
> FIX
>
> Apply the sender-name rules to every CC name, in both loops. This patch
> against 1.10 does that (it also works as a Debian quilt patch,
> debian/patches/0001-log-validate-cc-names.patch). I tested the name
> function on its own, and the behaviour above with a Debian 1.10-2 build
> plus this patch. I did not review the jabber and aim branches of the
> logger, which also build file names from message data.
>
> --- a/logging.c
> +++ b/logging.c
> @@ -205,6 +205,26 @@
> g_free(buffer);
> }
>
> +/* A name taken from a message is used as a file name under the log
> directory only if
> + * it could be a user name: 1-35 printable ASCII characters, the
> first alphanumeric,
> + * no '/', and not "." or "..". The CC: line of a zephyr is written
> by its sender, so
> + * without this a CC of "../../.ssh/authorized_keys" makes us append
> the message to
> + * that file (CVE pending). */
> +static int owl_log_name_is_safe(const char *name)
> +{
> + size_t i, len;
> +
> + if (!name) return 0;
> + len = strlen(name);
> + if (len < 1 || len > 35) return 0;
> + if (!g_ascii_isalnum(name[0])) return 0;
> + for (i = 0; i < len; i++) {
> + if (name[i] < '!' || name[i] >= '~' || name[i] == '/') return 0;
> + }
> + if (!strcmp(name, ".") || !strcmp(name, "..")) return 0;
> + return 1;
> +}
> +
> void owl_log_outgoing(const owl_message *m)
> {
> char *filename, *logpath;
> @@ -220,10 +240,12 @@
> cc = owl_message_get_cc_without_recipient(m);
> while (cc != NULL) {
> temp = short_zuser(cc->data);
> - filename = g_build_filename(logpath, temp, NULL);
> - owl_log_append(m, filename);
> + if (owl_log_name_is_safe(temp)) {
> + filename = g_build_filename(logpath, temp, NULL);
> + owl_log_append(m, filename);
> + g_free(filename);
> + }
>
> - g_free(filename);
> g_free(temp);
> g_free(cc->data);
> cc = g_list_delete_link(cc, cc);
> @@ -410,7 +432,7 @@
> cc = owl_message_get_cc_without_recipient(m);
> while (cc != NULL) {
> temp = short_zuser(cc->data);
> - if (strcasecmp(temp, frombuff) != 0) {
> + if (strcasecmp(temp, frombuff) != 0 &&
> owl_log_name_is_safe(temp)) {
> filename = g_build_filename(logpath, temp, NULL);
> owl_log_append(m, filename);
> g_free(filename);
>
> I can send a pull request if you prefer that, or answer questions. A CVE
> has not been requested; Debian security may assign one, and you may want
> to coordinate with them on the timing of any announcement. If you need
> more detail, reply to me directly.
>
> Daniel J. B. Clark
> djbclark@gmail.com <mailto:djbclark@gmail.com>
> djbclark@mit.edu <mailto:djbclark@mit.edu>
>