[4283] in BarnOwl Developers
Re: BarnOwl 1.10: a zephyr's CC: line is used as a log file name
daemon@ATHENA.MIT.EDU (Jason Gross)
Sun Nov 1 01:10:18 2026
In-Reply-To: <CAL_n8HJ=vi5Oi8TxEnJk6Oien4hOCZ+y4CFxCdFBrvy=9-PH2g@mail.gmail.com>
From: "Jason Gross" <jgross@mit.edu>
Date: Sat, 31 Oct 2026 22:09:38 -0700
To: Daniel Clark <djbclark@gmail.com>
Cc: barnowl-dev@mit.edu
--000000000000d6d06a065f300a2a
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
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=E2=80=AFPM Daniel Clark <djbclark@gmail.com> w=
rote:
> 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 (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=3D>'zephyr', direction=3D>'in', class=3D>'message',
> instance=3D>'personal', sender=3D>'eve@ATHENA.MIT.EDU',
> recipient=3D>'user@ATHENA.MIT.EDU', private=3D>1,
> isprivate=3D>'true', realm=3D>'ATHENA.MIT.EDU', opcode=3D>'',
> auth=3D>'NO', host=3D>'h', hostname=3D>'h', zsig=3D>'z',
> body=3D>"CC: ../../victim\nssh-ed25519 AAAAFAKEKEY attacker\n",
> time=3D>scalar(localtime), unix_time=3D>time()))
> perl our $t =3D BarnOwl::Timer->new({after=3D>4,
> cb=3D>sub{BarnOwl::command("quit")}})
>
> Then run: HOME=3D/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 =3D strlen(name);
> + if (len < 1 || len > 35) return 0;
> + if (!g_ascii_isalnum(name[0])) return 0;
> + for (i =3D 0; i < len; i++) {
> + if (name[i] < '!' || name[i] >=3D '~' || name[i] =3D=3D '/') 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 =3D owl_message_get_cc_without_recipient(m);
> while (cc !=3D NULL) {
> temp =3D short_zuser(cc->data);
> - filename =3D g_build_filename(logpath, temp, NULL);
> - owl_log_append(m, filename);
> + if (owl_log_name_is_safe(temp)) {
> + filename =3D 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 =3D g_list_delete_link(cc, cc);
> @@ -410,7 +432,7 @@
> cc =3D owl_message_get_cc_without_recipient(m);
> while (cc !=3D NULL) {
> temp =3D short_zuser(cc->data);
> - if (strcasecmp(temp, frombuff) !=3D 0) {
> + if (strcasecmp(temp, frombuff) !=3D 0 && owl_log_name_is_safe(temp=
)) {
> filename =3D 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
> djbclark@mit.edu
>
--000000000000d6d06a065f300a2a
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr">I believe this issue is not present in the <a href=3D"http=
s://github.com/barnowl/barnowl/pull/54">Perl-based logging code</a>, which =
was merged 6 months after the 1.10 release, 9 years ago. If anyone still us=
es BarnOwl, I guess it may be worth doing another release. (There has only =
been one commit in the past seven years.)</div><br><div class=3D"gmail_quot=
e gmail_quote_container"><div dir=3D"ltr" class=3D"gmail_attr">On Sat, Oct =
31, 2026 at 9:50=E2=80=AFPM Daniel Clark <<a href=3D"mailto:djbclark@gma=
il.com">djbclark@gmail.com</a>> wrote:<br></div><blockquote class=3D"gma=
il_quote" style=3D"margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,2=
04,204);padding-left:1ex">Hello BarnOwl developers,<br>
<br>
I found a security bug in BarnOwl 1.10 logging: a zephyr's CC: line is<=
br>
used as a log file name without the checks that the sender's own name<b=
r>
gets. Any zephyr sender can make the receiving client append a message<br>
to any file the user can write, or create it. I have also reported it<br>
to <a href=3D"mailto:security@debian.org" target=3D"_blank">security@debian=
.org</a> (2026-11-01). I am sending it to you so that it<br>
is fixed upstream and so that you can decide how to tell BarnOwl users.<br>
<br>
WHAT HAPPENS<br>
<br>
owl_log_incoming (logging.c) checks the sender's name before using it a=
s<br>
a file name under logpath: 1 to 35 printable ASCII characters, first<br>
character alphanumeric, no '/', not "." or ".."=
. For a personal zephyr<br>
it then also logs the message to every name on its CC list<br>
(owl_message_get_cc_without_recipient: the first body line, if it starts<br=
>
with "CC:"). Those names come from the sender and get none of tho=
se<br>
checks: g_build_filename(logpath, short_zuser(name), NULL), then<br>
fopen(..., "a") on the logger thread. owl_log_outgoing has the sa=
me<br>
loop, for the user's own CC lines.<br>
<br>
IMPACT<br>
<br>
A personal zephyr whose body starts with the line "CC:<br>
../../.ssh/authorized_keys", followed by a line holding an SSH public<=
br>
key, makes the receiving client append the whole logged entry (header<br>
lines, then the body) to ~/.ssh/authorized_keys. sshd ignores the header<br=
>
lines and accepts the key line. Appending to ~/.bashrc or creating<br>
~/.ssh/rc gives code execution the same way. It needs personal logging<br>
on ("set logging on", loggingdirection "in" or "bo=
th", the documented<br>
setup), no authentication and no action by the user.<br>
<br>
AFFECTED<br>
<br>
BarnOwl 1.10 (tag barnowl-1.10), tested as Debian barnowl 1.10-2+b9 on<br>
Debian 13. I did not check other versions; the code looks old, so<br>
earlier ones are probably affected too.<br>
<br>
REPRODUCTION (scratch directory; nothing outside it is touched)<br>
<br>
Use a scratch HOME with the config dir /tmp/T/home/.owl and logpath<br>
/tmp/T/zlog/people. Put this in /tmp/T/home/.owl/startup:<br>
<br>
=C2=A0 =C2=A0 set logging on<br>
=C2=A0 =C2=A0 set loggingdirection both<br>
=C2=A0 =C2=A0 set logpath /tmp/T/zlog/people<br>
=C2=A0 =C2=A0 perl BarnOwl::queue_message(BarnOwl::Message->new(<br>
=C2=A0 =C2=A0 =C2=A0 type=3D>'zephyr', direction=3D>'in&#=
39;, class=3D>'message',<br>
=C2=A0 =C2=A0 =C2=A0 instance=3D>'personal', sender=3D>'<=
a href=3D"mailto:eve@ATHENA.MIT.EDU" target=3D"_blank">eve@ATHENA.MIT.EDU</=
a>',<br>
=C2=A0 =C2=A0 =C2=A0 recipient=3D>'<a href=3D"mailto:user@ATHENA.MIT=
.EDU" target=3D"_blank">user@ATHENA.MIT.EDU</a>', private=3D>1,<br>
=C2=A0 =C2=A0 =C2=A0 isprivate=3D>'true', realm=3D>'<a hr=
ef=3D"http://ATHENA.MIT.EDU" rel=3D"noreferrer" target=3D"_blank">ATHENA.MI=
T.EDU</a>', opcode=3D>'',<br>
=C2=A0 =C2=A0 =C2=A0 auth=3D>'NO', host=3D>'h', hostn=
ame=3D>'h', zsig=3D>'z',<br>
=C2=A0 =C2=A0 =C2=A0 body=3D>"CC: ../../victim\nssh-ed25519 AAAAFAK=
EKEY attacker\n",<br>
=C2=A0 =C2=A0 =C2=A0 time=3D>scalar(localtime), unix_time=3D>time()))=
<br>
=C2=A0 =C2=A0 perl our $t =3D BarnOwl::Timer->new({after=3D>4,<br>
=C2=A0 =C2=A0 =C2=A0 cb=3D>sub{BarnOwl::command("quit")}})<br>
<br>
Then run: HOME=3D/tmp/T/home script -qfc 'barnowl -n -s /tmp/T/home/.ow=
l'<br>
/dev/null. With 1.10-2+b9, /tmp/T/victim exists (outside logpath) and<br>
holds the logged entry. With the patch below it is not created, and a CC<br=
>
to an ordinary user name is still logged. A real zephyr goes through the<br=
>
same function.<br>
<br>
TEMPORARY WORKAROUND FOR USERS<br>
<br>
Until a fixed BarnOwl is installed, users can stop the bug by turning<br>
personal-zephyr logging off:<br>
<br>
=C2=A0 - Put "set logging off" in ~/.owl/startup (replacing "=
;set logging on")<br>
=C2=A0 =C2=A0 and run ":set logging off" in a running client. Per=
sonal zephyrs are<br>
=C2=A0 =C2=A0 then not logged, in either direction. Class logging ("se=
t<br>
=C2=A0 =C2=A0 classlogging on") is unaffected, because class names get=
the sender-<br>
=C2=A0 =C2=A0 name checks and have no CC handling.<br>
=C2=A0 - "set loggingdirection out" should also stop incoming per=
sonals from<br>
=C2=A0 =C2=A0 being logged while keeping a log of what you send. I have not=
tested<br>
=C2=A0 =C2=A0 that variant; the "logging off" one is based on rea=
ding the code and<br>
=C2=A0 =C2=A0 on how the logging variables gate the logger, and I did not r=
un it<br>
=C2=A0 =C2=A0 against the exploit.<br>
=C2=A0 - Weaker, partial measures: make ~/.ssh/authorized_keys, ~/.bashrc a=
nd<br>
=C2=A0 =C2=A0 ~/.profile read-only (chmod 400, or chattr +i as root). This =
does<br>
=C2=A0 =C2=A0 not help against files that do not exist yet, such as ~/.ssh/=
rc or<br>
=C2=A0 =C2=A0 ~/.bash_login, and any file the user can write stays reachabl=
e.<br>
=C2=A0 - Run the client in a sandbox (bwrap, firejail, a separate<br>
=C2=A0 =C2=A0 unprivileged account) that can write only ~/.owl and the log<=
br>
=C2=A0 =C2=A0 directory. I have not tried this.<br>
<br>
FIX<br>
<br>
Apply the sender-name rules to every CC name, in both loops. This patch<br>
against 1.10 does that (it also works as a Debian quilt patch,<br>
debian/patches/0001-log-validate-cc-names.patch). I tested the name<br>
function on its own, and the behaviour above with a Debian 1.10-2 build<br>
plus this patch. I did not review the jabber and aim branches of the<br>
logger, which also build file names from message data.<br>
<br>
--- a/logging.c<br>
+++ b/logging.c<br>
@@ -205,6 +205,26 @@<br>
=C2=A0 =C2=A0g_free(buffer);<br>
=C2=A0}<br>
<br>
+/* A name taken from a message is used as a file name under the log<br>
directory only if<br>
+ * it could be a user name: 1-35 printable ASCII characters, the<br>
first alphanumeric,<br>
+ * no '/', and not "." or "..". The CC: line o=
f a zephyr is written<br>
by its sender, so<br>
+ * without this a CC of "../../.ssh/authorized_keys" makes us ap=
pend<br>
the message to<br>
+ * that file (CVE pending). */<br>
+static int owl_log_name_is_safe(const char *name)<br>
+{<br>
+=C2=A0 size_t i, len;<br>
+<br>
+=C2=A0 if (!name) return 0;<br>
+=C2=A0 len =3D strlen(name);<br>
+=C2=A0 if (len < 1 || len > 35) return 0;<br>
+=C2=A0 if (!g_ascii_isalnum(name[0])) return 0;<br>
+=C2=A0 for (i =3D 0; i < len; i++) {<br>
+=C2=A0 =C2=A0 if (name[i] < '!' || name[i] >=3D '~' =
|| name[i] =3D=3D '/') return 0;<br>
+=C2=A0 }<br>
+=C2=A0 if (!strcmp(name, ".") || !strcmp(name, "..")) =
return 0;<br>
+=C2=A0 return 1;<br>
+}<br>
+<br>
=C2=A0void owl_log_outgoing(const owl_message *m)<br>
=C2=A0{<br>
=C2=A0 =C2=A0char *filename, *logpath;<br>
@@ -220,10 +240,12 @@<br>
=C2=A0 =C2=A0 =C2=A0cc =3D owl_message_get_cc_without_recipient(m);<br>
=C2=A0 =C2=A0 =C2=A0while (cc !=3D NULL) {<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0temp =3D short_zuser(cc->data);<br>
-=C2=A0 =C2=A0 =C2=A0 filename =3D g_build_filename(logpath, temp, NULL);<b=
r>
-=C2=A0 =C2=A0 =C2=A0 owl_log_append(m, filename);<br>
+=C2=A0 =C2=A0 =C2=A0 if (owl_log_name_is_safe(temp)) {<br>
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 filename =3D g_build_filename(logpath, temp, N=
ULL);<br>
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 owl_log_append(m, filename);<br>
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 g_free(filename);<br>
+=C2=A0 =C2=A0 =C2=A0 }<br>
<br>
-=C2=A0 =C2=A0 =C2=A0 g_free(filename);<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0g_free(temp);<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0g_free(cc->data);<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0cc =3D g_list_delete_link(cc, cc);<br>
@@ -410,7 +432,7 @@<br>
=C2=A0 =C2=A0 =C2=A0cc =3D owl_message_get_cc_without_recipient(m);<br>
=C2=A0 =C2=A0 =C2=A0while (cc !=3D NULL) {<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0temp =3D short_zuser(cc->data);<br>
-=C2=A0 =C2=A0 =C2=A0 if (strcasecmp(temp, frombuff) !=3D 0) {<br>
+=C2=A0 =C2=A0 =C2=A0 if (strcasecmp(temp, frombuff) !=3D 0 && owl_=
log_name_is_safe(temp)) {<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 filename =3D g_build_filename(logpath, temp, NU=
LL);<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0owl_log_append(m, filename);<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 g_free(filename);<br>
<br>
I can send a pull request if you prefer that, or answer questions. A CVE<br=
>
has not been requested; Debian security may assign one, and you may want<br=
>
to coordinate with them on the timing of any announcement. If you need<br>
more detail, reply to me directly.<br>
<br>
Daniel J. B. Clark<br>
<a href=3D"mailto:djbclark@gmail.com" target=3D"_blank">djbclark@gmail.com<=
/a><br>
<a href=3D"mailto:djbclark@mit.edu" target=3D"_blank">djbclark@mit.edu</a><=
br>
</blockquote></div>
--000000000000d6d06a065f300a2a--