[4285] in BarnOwl Developers

home help back first fref pref prev next nref lref last post

Re: BarnOwl 1.10: a zephyr's CC: line is used as a log file name

daemon@ATHENA.MIT.EDU (Daniel Clark)
Sun Nov 1 01:17:42 2026

In-Reply-To: <CAKObCapg0Lqb6Hw9k6Hdo++p04Z9ewr0KGyTixoPknSb9VbPCA@mail.gmail.com>
From: Daniel Clark <djbclark@gmail.com>
Date: Sun, 1 Nov 2026 01:15:22 -0400
To: Jason Gross <jgross@mit.edu>
Cc: barnowl-dev@mit.edu

--000000000000676cb1065f301eb9
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

Looks like my update email and your email crossed the wires in transit.

Re: using barnowl, I haven't been around much since back then, so if there
is some new Zephyr client to use, LMK. I tried getting. some of the others
I saw mentioned set up - Zabbix I think? - but that one has 404s on all the
auth pages. Some other one I forget the name of now faired little better.

On Sun, Nov 1, 2026 at 1:10=E2=80=AFAM Jason Gross <jgross@mit.edu> 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 gues=
s
> 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>=
 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 (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(tem=
p))
>> {
>>         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
>>
>

--000000000000676cb1065f301eb9
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div class=3D"gmail_default" style=3D"font-family:arial,sa=
ns-serif">Looks like my update email and your email crossed the wires in tr=
ansit.</div><div class=3D"gmail_default" style=3D"font-family:arial,sans-se=
rif"><br></div><div class=3D"gmail_default" style=3D"font-family:arial,sans=
-serif">Re: using barnowl, I haven&#39;t been around much since back then, =
so if there is some new Zephyr client to use, LMK. I tried getting. some of=
 the others I saw mentioned set up - Zabbix I think? - but that one has 404=
s on all the auth pages. Some other one I forget the name of now faired lit=
tle better.</div></div><br><div class=3D"gmail_quote gmail_quote_container"=
><div dir=3D"ltr" class=3D"gmail_attr">On Sun, Nov 1, 2026 at 1:10=E2=80=AF=
AM Jason Gross &lt;<a href=3D"mailto:jgross@mit.edu">jgross@mit.edu</a>&gt;=
 wrote:<br></div><blockquote class=3D"gmail_quote" style=3D"margin:0px 0px =
0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir=
=3D"ltr">I believe this issue is not present in the <a href=3D"https://gith=
ub.com/barnowl/barnowl/pull/54" target=3D"_blank">Perl-based logging code</=
a>, which was merged 6 months after the 1.10 release, 9 years ago. If anyon=
e still uses 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_quote"><div dir=3D"ltr" class=3D"gmail_attr">On Sat, Oct 31, 2026 at =
9:50=E2=80=AFPM Daniel Clark &lt;<a href=3D"mailto:djbclark@gmail.com" targ=
et=3D"_blank">djbclark@gmail.com</a>&gt; wrote:<br></div><blockquote class=
=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-left:1px solid rg=
b(204,204,204);padding-left:1ex">Hello BarnOwl developers,<br>
<br>
I found a security bug in BarnOwl 1.10 logging: a zephyr&#39;s CC: line is<=
br>
used as a log file name without the checks that the sender&#39;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&#39;s name before using it a=
s<br>
a file name under logpath: 1 to 35 printable ASCII characters, first<br>
character alphanumeric, no &#39;/&#39;, not &quot;.&quot; or &quot;..&quot;=
. 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 &quot;CC:&quot;). 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(..., &quot;a&quot;) on the logger thread. owl_log_outgoing has the sa=
me<br>
loop, for the user&#39;s own CC lines.<br>
<br>
IMPACT<br>
<br>
A personal zephyr whose body starts with the line &quot;CC:<br>
../../.ssh/authorized_keys&quot;, 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 (&quot;set logging on&quot;, loggingdirection &quot;in&quot; or &quot;bo=
th&quot;, 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-&gt;new(<br>
=C2=A0 =C2=A0 =C2=A0 type=3D&gt;&#39;zephyr&#39;, direction=3D&gt;&#39;in&#=
39;, class=3D&gt;&#39;message&#39;,<br>
=C2=A0 =C2=A0 =C2=A0 instance=3D&gt;&#39;personal&#39;, sender=3D&gt;&#39;<=
a href=3D"mailto:eve@ATHENA.MIT.EDU" target=3D"_blank">eve@ATHENA.MIT.EDU</=
a>&#39;,<br>
=C2=A0 =C2=A0 =C2=A0 recipient=3D&gt;&#39;<a href=3D"mailto:user@ATHENA.MIT=
.EDU" target=3D"_blank">user@ATHENA.MIT.EDU</a>&#39;, private=3D&gt;1,<br>
=C2=A0 =C2=A0 =C2=A0 isprivate=3D&gt;&#39;true&#39;, realm=3D&gt;&#39;<a hr=
ef=3D"http://ATHENA.MIT.EDU" rel=3D"noreferrer" target=3D"_blank">ATHENA.MI=
T.EDU</a>&#39;, opcode=3D&gt;&#39;&#39;,<br>
=C2=A0 =C2=A0 =C2=A0 auth=3D&gt;&#39;NO&#39;, host=3D&gt;&#39;h&#39;, hostn=
ame=3D&gt;&#39;h&#39;, zsig=3D&gt;&#39;z&#39;,<br>
=C2=A0 =C2=A0 =C2=A0 body=3D&gt;&quot;CC: ../../victim\nssh-ed25519 AAAAFAK=
EKEY attacker\n&quot;,<br>
=C2=A0 =C2=A0 =C2=A0 time=3D&gt;scalar(localtime), unix_time=3D&gt;time()))=
<br>
=C2=A0 =C2=A0 perl our $t =3D BarnOwl::Timer-&gt;new({after=3D&gt;4,<br>
=C2=A0 =C2=A0 =C2=A0 cb=3D&gt;sub{BarnOwl::command(&quot;quit&quot;)}})<br>
<br>
Then run: HOME=3D/tmp/T/home script -qfc &#39;barnowl -n -s /tmp/T/home/.ow=
l&#39;<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 &quot;set logging off&quot; in ~/.owl/startup (replacing &quot=
;set logging on&quot;)<br>
=C2=A0 =C2=A0 and run &quot;:set logging off&quot; in a running client. Per=
sonal zephyrs are<br>
=C2=A0 =C2=A0 then not logged, in either direction. Class logging (&quot;se=
t<br>
=C2=A0 =C2=A0 classlogging on&quot;) is unaffected, because class names get=
 the sender-<br>
=C2=A0 =C2=A0 name checks and have no CC handling.<br>
=C2=A0 - &quot;set loggingdirection out&quot; 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 &quot;logging off&quot; 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 &#39;/&#39;, and not &quot;.&quot; or &quot;..&quot;. The CC: line o=
f a zephyr is written<br>
by its sender, so<br>
+ * without this a CC of &quot;../../.ssh/authorized_keys&quot; 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 &lt; 1 || len &gt; 35) return 0;<br>
+=C2=A0 if (!g_ascii_isalnum(name[0])) return 0;<br>
+=C2=A0 for (i =3D 0; i &lt; len; i++) {<br>
+=C2=A0 =C2=A0 if (name[i] &lt; &#39;!&#39; || name[i] &gt;=3D &#39;~&#39; =
|| name[i] =3D=3D &#39;/&#39;) return 0;<br>
+=C2=A0 }<br>
+=C2=A0 if (!strcmp(name, &quot;.&quot;) || !strcmp(name, &quot;..&quot;)) =
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-&gt;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-&gt;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-&gt;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 &amp;&amp; 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>
</blockquote></div>

--000000000000676cb1065f301eb9--

home help back first fref pref prev next nref lref last post