<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Blog on Petr Mánek – personal homepage</title>
    <link>https://petrmanek.cz/blog/</link>
    <description>Recent content in Blog on Petr Mánek – personal homepage</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-gb</language>
    
        <atom:link href="https://petrmanek.cz/blog/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Why I keep a gigabyte-sized file on my file system</title>
      <link>https://petrmanek.cz/blog/2023/gigabyte-file/</link>
      <pubDate>Sat, 14 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://petrmanek.cz/blog/2023/gigabyte-file/</guid>
      <description>&lt;p&gt;More and more often these days I find myself approaching the limit of available
space on my laptop&amp;rsquo;s solid-state drive. With only a gigabyte left and a lot of
development activity ongoing, it is not uncommon for my system to run out of
space every now and then.&lt;/p&gt;
&lt;p&gt;On Linux systems having no storage left is far from fatal. The kernel resides in
memory, swapping relies on a dedicated partition, and any self-respecting I/O
activities can be expected to fail gracefully. That being said, there are
indirect effects that are annoying as hell. For instance, my shell maintains a
history file containing recent commands. If this file cannot be modified for
some reason, an error message is printed every single time I execute a command;
even worse, in some cases new shells cannot even be opened. For that reason
alone, having no free space for extended periods of time can be viewed as
potentially dangerous, as it can effectively lock me out of my shell.&lt;/p&gt;
&lt;p&gt;At some point in the future, my current situation will inevitably trigger a
spring cleanup, a storage upgrade, or ideally both. Until such time, however, I
found&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; a low-effort mitigation method: on my root file system I created a
dummy file filled with one gigabyte of zeros. Most of the time this file just
sits there, but whenever I run out of space I can alleviate the problem by
removing it. This immediately restores some stability to my system, and usually
buys me enough time to diagnose the issue and find a longer-term solution. I am
aware that this approach is rather primitive. However, thus far it has proven
surprisingly effective in making my work life bearable even though I am living
on the edge.&lt;/p&gt;
&lt;p&gt;If you would like to try it, here are some instructions. Inside my root shell, I
create the dummy file at &lt;code&gt;/opt/gigabyte&lt;/code&gt; using the following command:&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# dd if=/dev/zero of=/opt/gigabyte bs=1M count=1024
&lt;/code&gt;&lt;/pre&gt;&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I remember having read about this technique somewhere online in the past.
Unfortunately, at the time of writing this I was unable to track it down. If
I ever re-discover it, I will update this post with a link.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Disclaimer: &lt;code&gt;dd&lt;/code&gt;  can wreck your system if used without caution. Please
stay safe by thinking twice and dd&amp;rsquo;ing once!&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Unlocking gpg-agent using pam_gnupg</title>
      <link>https://petrmanek.cz/blog/2022/unlocking-gpg-agent/</link>
      <pubDate>Thu, 29 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://petrmanek.cz/blog/2022/unlocking-gpg-agent/</guid>
      <description>&lt;h2 id=&#34;background-how-i-manage-my-passwords&#34;&gt;Background: how I manage my passwords&lt;/h2&gt;
&lt;p&gt;To manage sensitive data I use &lt;a href=&#34;https://www.passwordstore.org/&#34;&gt;pass&lt;/a&gt;, a password manager that encrypts
secrets using asymmetric cryptography implemented by &lt;a href=&#34;https://gnupg.org/&#34;&gt;GnuPG&lt;/a&gt;. By design
this means that whenever I want to use a secret, I am prompted to reveal the
passphrase of my private key, so that the secret can be decrypted on the spot.
As secure as this practice may be, one easily grows tired of entering one&amp;rsquo;s
passphrase &lt;em&gt;every&lt;/em&gt;.  &lt;em&gt;single&lt;/em&gt;. &lt;em&gt;time&lt;/em&gt;. in order to do anything meaningful. In my
humble opinion, basic security should &lt;em&gt;not&lt;/em&gt; be cumbersome.&lt;/p&gt;
&lt;p&gt;GnuPG offers an easy remedy: you can &lt;a href=&#34;https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html&#34;&gt;leave gpg-agent running&lt;/a&gt; in the
background of your session, and it will securely remember all your previously
entered passphrases. As long as your passphrase is cached, you will not be
bothered to enter it again&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. This is convenient for sure but it comes with a
security trade-off: a malicious actor could potentially exploit your laziness by
decrypting all your cached passphrases from a memory dump&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. Fortunately,
GnuPG permits to mitigate this risk to some extent by tuning the cache duration
in your &lt;code&gt;~/.gnupg/gpg-agent.conf&lt;/code&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. This forces gpg-agent to eventually
forget all passphrases that are not used too frequently, thereby limiting the
secrets any attackers may be able to exfiltrate.&lt;/p&gt;
&lt;p&gt;For instance, the following configuration will have gpg-agent cache your
passphrases for an hour.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;default-cache-ttl   3600
max-cache-ttl       3600
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Side note:&lt;/strong&gt; If you just pasted the above lines in your gpg-agent.conf, your
gpg-agent is currently running and you expect to see changes immediately, hold
your horses!  You will need to restart it, or at least force it to reload its
settings by running the following command. Coincidentally this command will also
clear your gpg-agent&amp;rsquo;s cache, which will be useful later.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ gpg-connect-agent --no-autostart reloadagent /bye
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;automatic-unlocking-with-pam_gnupg&#34;&gt;Automatic unlocking with pam_gnupg&lt;/h2&gt;
&lt;p&gt;I have been happily using GnuPG like this for a long time. Recently, however, I
discovered a neat improvement: if your GnuPG passphrase can be derived&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; from
your login password, you can configure &lt;a href=&#34;https://github.com/linux-pam/linux-pam&#34;&gt;PAM&lt;/a&gt; to automatically pass it on to
gpg-agent upon login. The agent will cache the passphrase, and not bother you to
enter it for the first time after rebooting; not even once. This feature is
called &lt;em&gt;presetting&lt;/em&gt; and can save you some time. Let me demonstrate how it can be
set up.&lt;/p&gt;
&lt;p&gt;First of all, presetting has to be explicitly enabled in your gpg-agent.conf by
adding the following line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;allow-preset-passphrase
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once enabled, passphrases can be preset from many sources. To this end GnuPG
ships a simple program called gpg-preset-passphrase that I found convenient for
testing. You may have to look for it on your file system as it usually lives in
the &lt;em&gt;libexec&lt;/em&gt; directory rather than the conventional &lt;em&gt;bin&lt;/em&gt; directory. My
distribution places it in &lt;code&gt;/usr/lib/gnupg/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When you manage to locate the program, its invocation is simple enough:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ /usr/lib/gnupg/gpg-preset-passphrase --preset &lt;span style=&#34;color:#000&#34;&gt;$KEYGRIP&lt;/span&gt; &amp;lt;passfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here &lt;code&gt;$KEYGRIP&lt;/code&gt; is the hexadecimal string that uniquely identifies the GnuPG key
that you wish to unlock, and for testing I temporarily placed&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; my passphrase
in a file called &lt;code&gt;passfile&lt;/code&gt;&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;When you confirm that your gpg-agent can receive and successfully cache
passphrases, it is time to cut out the middle man. There is a pluggable
authentication module (&lt;a href=&#34;https://github.com/linux-pam/linux-pam&#34;&gt;PAM&lt;/a&gt;) called &lt;a href=&#34;https://github.com/cruegge/pam-gnupg&#34;&gt;pam_gnupg&lt;/a&gt; that can
harvest your password at the time of login, and pass it on to gpg-agent just
like gpg-preset-passphrase. The way PAM works is outside of the scope of this
article (if it is new to you have a look in the &lt;a href=&#34;https://man.archlinux.org/man/PAM.8.en&#34;&gt;manual&lt;/a&gt;,
&lt;a href=&#34;https://beausanders.org/linux_shared_files/Linux-PAM_System_Administrators_Guide.pdf&#34;&gt;here&lt;/a&gt; or &lt;a href=&#34;https://web.mit.edu/rhel-doc/5/RHEL-5-manual/Deployment_Guide-en-US/ch-pam.html&#34;&gt;here&lt;/a&gt;), however suffice to say that it is an
extendable system that facilitates user authentication in Linux. There are many
plug-ins that can perform various tasks and behaviors in PAM, and one of them is
&lt;a href=&#34;https://github.com/cruegge/pam-gnupg&#34;&gt;pam_gnupg&lt;/a&gt;. As it is an open-source project, it is possible to
install it without much effort.&lt;/p&gt;
&lt;p&gt;First you will need to create the &lt;code&gt;~/.pam-gnupg&lt;/code&gt; fill that should contain the
value of &lt;code&gt;$KEYGRIP&lt;/code&gt; (if you want to unlock more than one key, you will need to
put their identifiers on separate lines). After that, you will need to spend a
little bit of time as root tinkering in your &lt;code&gt;/etc/pam.d/&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caution!&lt;/strong&gt; Modifying PAM chains is an excellent way to lock yourself out of your
own computer. As a rule of thumb, when doing these changes I always like to test
individual modifications immediately after making them. And just in case, I keep
a secondary root shell in the background should I need to roll something back.
If you want to err on the side of caution, you should consider backing up your system
before you proceed any further. You have been warned!&lt;/p&gt;
&lt;p&gt;According to pam_gnupg&amp;rsquo;s README you need to add the following two lines in your
&lt;code&gt;/etc/pam.d/system-local-login&lt;/code&gt; file. One at the end of the &lt;code&gt;auth&lt;/code&gt; section, the
other at the end of the &lt;code&gt;session&lt;/code&gt; section.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auth     optional  pam_gnupg.so store-only
session  optional  pam_gnupg.so
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These will act as hooks that grab your password when you login to the system. In
my case, since I also happen to use &lt;a href=&#34;https://github.com/swaywm/swaylock&#34;&gt;swaylock&lt;/a&gt;, I inserted the following
line at the end of the &lt;code&gt;auth&lt;/code&gt; section in my &lt;code&gt;/etc/pam.d/swaylock&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auth     optional  pam_gnupg.so
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The idea behind this is that the same hook can also run when the session is
unlocked, e.g. after I stroll away for a long time or after powering up my
laptop from sleep. When this happens it is quite likely that gpg-agent will have
already forgotten my passphrases, so I may just as well preset them again to
save myself some typing.&lt;/p&gt;
&lt;p&gt;At this point, it may be wise to verify that your PAM configuration (still)
works. The most convenient place to do this for me was with swaylock. I would
clear my passphrase cache, lock my session, unlock it and check if there were
any passphrases cached. A couple of attempts and one mistyped key identifier
later, everything was working as expected (if your mileage differs it is useful
to know that pam_gnupg offers a &lt;code&gt;debug&lt;/code&gt; option that makes it appear in syslog).
Encouraged by this I decided to take a leap of faith and log out completely.
After logging back in I was pleased to see that everything worked reliably out
of the box. If you run into problems, be sure to check pam_gnupg&amp;rsquo;s README &amp;ndash; it
has some suggestions on race conditions etc.&lt;/p&gt;
&lt;p&gt;Finally, as a little cherry on top I configured my system to explicitly clear
gpg-agent&amp;rsquo;s cache every time my lockscreen is about to be executed. This should
make my laptop less susceptible to memory dumping attacks whenever it is about
to go to sleep or I am away from keyboard. This behavior can be implemented
using the above-listed gpg-connect-agent command that reloads gpg-agent. In my
case I simply wrapped a call to swaylock in a shell script that runs this
command first, and substituted it in all references to swaylock in my
configuration files.&lt;/p&gt;
&lt;h2 id=&#34;final-thoughts&#34;&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;And there you have it! With this setup your gpg-agent should be automatically
preset with your favorite passphrase when you login, it should forget it
whenever you lock your session, and re-discover it after unlocking. Depending on
your system, password manager and threat model it may or may &lt;em&gt;not&lt;/em&gt; be not be a
good solution for you.&lt;/p&gt;
&lt;p&gt;This method without doubt introduces some vulnerabilities in the system.
Specifically all of this hinges on the ability to derive one passphrase from
another, which I would generally never recommend to anyone. For the strongest
security, passphrases should be chosen as independently randomly as possible.
That being said, rather than achieving the strongest security I was aiming to
strike a reasonable balance between acceptable security and day-to-day
usability, which is why I believe that in my case this makes sense.&lt;/p&gt;
&lt;p&gt;In any case, if nothing else I hope that this helped you learn a bit about
GnuPG, PAM and friends.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You may recognize this behavior from other password managers, e.g. macOS
Keychain or gnome-keyring, which can both be &amp;ldquo;unlocked&amp;rdquo; by the user. I may
occasionally allude to this terminology from now on.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It is also worth contemplating whether this could be done &lt;em&gt;after&lt;/em&gt; the
cached passphrases were evicted from memory.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or more generally, &lt;code&gt;$GNUPGHOME/gpg-agent.conf&lt;/code&gt;. See:
&lt;a href=&#34;https://man.archlinux.org/man/gpg-agent.1&#34;&gt;gpg-agent(1)&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The referenced implementation of pam_gnupg only supports passing the login
password to gpg-agent as-is, which requires both strings to be identical. I
am generally opposed to sharing keys and passwords for different purposes,
so here is my take: since pam_gnupg is open-source, nobody can stop you from
forking it and implementing your own transformation function!&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Note that this is just for testing! I do not store my secrets unencrypted
as that defeats the entire purpose of having a password manager in the first
place.  And even though this is a test, I would certainly recommend setting
the mode of &lt;code&gt;passfile&lt;/code&gt; to 0600 before placing anything sensitive inside.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;gpg-preset-passphrase can also read in your passphrase using the
&lt;code&gt;--passphrase&lt;/code&gt; option. However, I would advise against that as it risks
revealing it to all users of your system (generally, anyone with access to
your procfs can access the arguments of the process).&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Fun with sassy sudo</title>
      <link>https://petrmanek.cz/blog/2022/sassy-sudo/</link>
      <pubDate>Mon, 25 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://petrmanek.cz/blog/2022/sassy-sudo/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://wiki.archlinux.org/title/Sudo&#34;&gt;Sudo&lt;/a&gt; is often used in system administration but is otherwise a pretty boring tool. If you mistype your password, you get to see the following message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[sudo] password for user:
Sorry, try again.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Only recently I discovered that there exists an easter egg that permits sudo to randomly insult users upon failed password attempts. This is by default disabled. However, if you explicitly enable it and mess up your password, sudo may print:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[sudo] password for user:
Wrong!  You cheating scum!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&amp;hellip;or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[sudo] password for user:
stty: unknown mode: doofus
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&amp;hellip;or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[sudo] password for user:
You type like i drive.
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;how-to-enable-this&#34;&gt;How to enable this&lt;/h2&gt;
&lt;p&gt;If you would like to give your sudo a bit of sass, you can enable this easter egg by editing &lt;code&gt;/etc/sudoers&lt;/code&gt; (using &lt;code&gt;visudo&lt;/code&gt; as root) and adding the following line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Defaults insults
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As always with anything related to sudo, take extra care when modifying the contents of the file otherwise you may end up locking yourself out of root access.&lt;/p&gt;
&lt;p&gt;If you are curious like me and do not want to wait until a failed password attempt, you can find all insulting messages embedded inside the &lt;code&gt;/usr/lib/sudo/sudoers.so&lt;/code&gt; binary. They can be displayed along with all other strings using the &lt;code&gt;strings&lt;/code&gt; tool:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ strings /usr/lib/sudo/sudoers.so &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;|&lt;/span&gt; less
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hope this little tip makes your sudoing a bit more fun!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sandboxing Zoom using AppArmor and Firejail</title>
      <link>https://petrmanek.cz/blog/2022/sandboxing-zoom/</link>
      <pubDate>Sat, 23 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://petrmanek.cz/blog/2022/sandboxing-zoom/</guid>
      <description>&lt;p&gt;It is no secret that the Zoom video conferencing system has dubious reputation for &lt;a href=&#34;https://arstechnica.com/tech-policy/2020/11/zoom-lied-to-users-about-end-to-end-encryption-for-years-ftc-says/&#34;&gt;lying to its users&lt;/a&gt;, &lt;a href=&#34;https://www.vice.com/en/article/k7e599/zoom-ios-app-sends-data-to-facebook-even-if-you-dont-have-a-facebook-account&#34;&gt;mistreating their data&lt;/a&gt; and &lt;a href=&#34;https://www.theguardian.com/technology/2020/jun/11/zoom-shuts-account-of-us-based-rights-group-after-tiananmen-anniversary-meeting&#34;&gt;censoring their speech&lt;/a&gt;. In spite of all that, it has become a standard communication tool that I am expected to use at work several times a week. Since avoiding it unfortunately is &lt;em&gt;not&lt;/em&gt; an option that would be available to me, I decided to at least mitigate any potential damage from Zoom by running it inside a sandbox. I was inspired to do this after reading &lt;a href=&#34;https://ar.al/2020/06/25/how-to-use-the-zoom-malware-safely-on-linux-if-you-absolutely-have-to/&#34;&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While there exist various technical implementations, all sandboxes generally achieve the same result &amp;ndash; they let the user control individual capabilities of programs that are confined inside. Since Zoom relies on active Internet connection, I had no illusions that I would be able to increase the protection of my calls or deny the app from sending telemetry. Instead I decided to focus mostly on restricting its capability to snoop around my file system and collect information about me without my permission. The rest of this post documents the steps I took to achieve that.&lt;/p&gt;
&lt;h2 id=&#34;step-1-install-apparmor&#34;&gt;Step 1: Install AppArmor&lt;/h2&gt;
&lt;p&gt;For this effort, I am using &lt;a href=&#34;https://wiki.archlinux.org/title/AppArmor&#34;&gt;AppArmor&lt;/a&gt; &amp;ndash; a &lt;a href=&#34;https://en.wikipedia.org/wiki/Linux_Security_Modules&#34;&gt;Linux Security Module&lt;/a&gt; that runs directly in the kernel and acts as a middle man between user-space programs and the rest of the system. This gives it the capability to restrict anything interesting that programs might want to do (for instance read files or communicate with the rest of the World) while introducing as little overhead as possible.&lt;/p&gt;
&lt;p&gt;After installing the AppArmor kernel module using your favorite package manager, bear in mind that you also need to ensure that it gets loaded on boot. In Archlinux this is done by enabling the &lt;code&gt;apparmor.service&lt;/code&gt; in systemd and adding the following kernel parameters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lsm=landlock,lockdown,yama,apparmor,bpf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Following a reboot, AppArmor should be loaded and ready for use. This can be verified by running:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ aa-enabled
Yes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;step-2-install-firejail&#34;&gt;Step 2: Install Firejail&lt;/h2&gt;
&lt;p&gt;AppArmor on its own is just a kernel module, not a sandbox. To supplement it I am using &lt;a href=&#34;https://wiki.archlinux.org/title/Firejail&#34;&gt;Firejail&lt;/a&gt; &amp;ndash; a namespace-driven sandboxing program that can exploit AppArmor to efficiently enforce user-defined policies. These policies can be viewed as lists of rules that explicitly define what the confined program is allowed to do.&lt;/p&gt;
&lt;p&gt;After Firejail is installed using your favorite package manager, you will need to put together a profile that works for Zoom. This is the balancing act that entails the most trial and error. Fortunately, Firejail profile syntax is really easy to learn, and there exist many examples for inspiration. Based on &lt;a href=&#34;https://github.com/netblue30/firejail/blob/master/etc/profile-m-z/zoom.profile&#34;&gt;what I found on GitHub&lt;/a&gt;, I put together the following profile that I saved in &lt;code&gt;~/.config/firejail/zoom.local&lt;/code&gt;, ensuring that it is automatically applied whenever the &lt;code&gt;zoom&lt;/code&gt; binary is executed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol unix,inet,inet6,netlink
ignore seccomp
seccomp !chroot
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is what the profile above translates to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zoom is permitted to use network sockets of the UNIX, IPv4, IPV6 and Netlink families.&lt;/li&gt;
&lt;li&gt;Override any previous setting enabling seccomp syscall filters.&lt;/li&gt;
&lt;li&gt;Explicitly enable seccomp filter and configure it to disallow the &lt;code&gt;chroot&lt;/code&gt; syscall.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that this configuration is overlaid on any other default profiles that come with your installation of Firejail. In my environment, Firejail applies 13 other profiles that blacklist user directories and whitelist common library paths etc.&lt;/p&gt;
&lt;h2 id=&#34;step-3-create-a-wrapper-script&#34;&gt;Step 3: Create a wrapper script&lt;/h2&gt;
&lt;p&gt;The next step is to create a shell script that runs Zoom within the Firejail sandbox. This script can then be used instead of the Zoom client&amp;rsquo;s original binary.&lt;/p&gt;
&lt;p&gt;My script looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# A wrapper that confines zoom in a sandbox, faking home directory in ~/.zoom&lt;/span&gt;

&lt;span style=&#34;color:#000&#34;&gt;QT_QPA_PLATFORM&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;xcb &lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# NOTE: needed otherwise zoom crashes on Wayland :/&lt;/span&gt;

&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;function&lt;/span&gt; cleanup&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;()&lt;/span&gt;
&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;{&lt;/span&gt;
	&lt;span style=&#34;color:#8f5902;font-style:italic&#34;&gt;# Auto-mute after zoom terminates&lt;/span&gt;
	pactl set-source-mute &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#0000cf;font-weight:bold&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;}&lt;/span&gt;

&lt;span style=&#34;color:#204a87&#34;&gt;trap&lt;/span&gt; cleanup EXIT
firejail --apparmor --private&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#000&#34;&gt;HOME&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;}&lt;/span&gt;/.zoom zoom &lt;span style=&#34;color:#4e9a06&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let me explain the code above bit by bit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;QT_QPA_PLATFORM=xcb&lt;/code&gt; is a compatibility hack that is needed because I use a Wayland compositor. It forces Zoom to fallback to a X11 frontend with Xwayland instead of using its native Wayland frontend. While the latter option would of course be preferable, it unfortunately seems to be unstable at this time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;cleanup&lt;/code&gt; function always runs after Zoom exits, and makes sure that my microphone stays muted when I am not in a meeting. I have noticed that Zoom has the nasty habit of unmuting the microphone whenever I unmute myself in meetings, and not bothering to restore the original state afterwards. I would often forget to do this manually, so I created a function that does this on my behalf using PulseAudio&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;firejail&lt;/code&gt; call is where the most time is spent, as it blocks throughout the entire lifetime of the Zoom client. As you suspect, the &lt;code&gt;--apparmor&lt;/code&gt; flag instructs Firejail to enforce the policy using the AppArmor kernel module&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. The &lt;code&gt;--private&lt;/code&gt; flag hides my home directory from Zoom and replaces it with a blank directory that I created in &lt;code&gt;~/.zoom&lt;/code&gt;. The &lt;code&gt;${@}&lt;/code&gt; variable forwards all shell options to the &lt;code&gt;zoom&lt;/code&gt; executable; more about that later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You may be interested to know that over my multi-year usage of Zoom, it has created the following file tree inside my decoy home directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.zoom
├── data
│   ├── com.zoom.ipc.assistantapp__req
│   ├── com.zoom.ipc.assistantapp__res
│   ├── com.zoom.ipc.confapp__req
│   ├── com.zoom.ipc.confapp__res
│   ├── zoomus.enc.db
│   ├── zoomus.enc.db.malformed
│   └── zoomus.tmp.enc.db
├── Downloads
├── im
├── logs
│   └── zoom_stdout_stderr.log
├── reports
└── screenCapture
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;step-4-replace-zoom-with-the-wrapper&#34;&gt;Step 4: Replace Zoom with the wrapper&lt;/h2&gt;
&lt;p&gt;The final step involves replacing all links to the original Zoom client with links to the wrapper script. In my case, this meant creating a secondary &lt;a href=&#34;https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html&#34;&gt;XDG desktop entry&lt;/a&gt; that mimics the contents of &lt;code&gt;/usr/share/applications/Zoom.desktop&lt;/code&gt; that is by default installed along with Zoom.&lt;/p&gt;
&lt;p&gt;I saved my variant to &lt;code&gt;~/.local/share/applications/JailedZoom.desktop&lt;/code&gt;, so that it is discovered automatically by launchers etc. The contents of the file are as follows.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;color:#204a87;font-weight:bold&#34;&gt;[Desktop Entry]&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Name&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;Zoom (jailed)&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Comment&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;Zoom Video Conference (confined to firejail)&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Exec&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;/home/petr/bin/pm_jailed_zoom %U&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Icon&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;Zoom&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Terminal&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;false&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Type&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;Application&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Encoding&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;UTF-8&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Categories&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;Network;Application;&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;StartupWMClass&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;zoom&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;MimeType&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;x-scheme-handler/zoommtg;x-scheme-handler/zoomus;x-scheme-handler/tel;x-scheme-handler/callto;x-scheme-handler/zoomphonecall;application/x-zoom&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;X-KDE-Protocols&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;zoommtg;zoomus;tel;callto;zoomphonecall;&lt;/span&gt;
&lt;span style=&#34;color:#c4a000&#34;&gt;Name[en_US]&lt;/span&gt;&lt;span style=&#34;color:#ce5c00;font-weight:bold&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4e9a06&#34;&gt;Zoom&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that my wrapper script is located at &lt;code&gt;~/bin/pm_jailed_zoom&lt;/code&gt;, you may want to customize the &lt;code&gt;Exec&lt;/code&gt; path on your system. Also notice that the &lt;code&gt;%U&lt;/code&gt; wildcard makes it possible for meeting URLs to be passed to the wrapper, which then forwards it to Zoom inside Firejail through the &lt;code&gt;${@}&lt;/code&gt; variable. This means that one-click meeting URLs should still work with this setup.&lt;/p&gt;
&lt;p&gt;After adding the custom desktop entry it is necessary to rebuild the desktop database using the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ update-desktop-database ~/.local/share/applications
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can then set &lt;code&gt;JailedZoom.desktop&lt;/code&gt; to be the default handler for Zoom URLs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ xdg-mime default JailedZoom.desktop x-scheme-handler/zoommtg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip;and verify by re-querying:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;$ xdg-mime query default x-scheme-handler/zoommtg
JailedZoom.desktop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you reached this point and did everything right, you now have the capability to confine Zoom in a sandbox that keeps it away from your data. In my opinion, this is the least worst way of running Zoom on your system &lt;em&gt;if you have to&lt;/em&gt; like me.&lt;/p&gt;
&lt;p&gt;Just a couple of obligatory security disclaimers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Even with this setup Zoom is still present on your system, and can be executed outside of the sandbox if you (or a malicious actor) run it directly without the wrapper script.&lt;/li&gt;
&lt;li&gt;While these measures increase protection of your file system, they do not affect your communications conducted over Zoom. You should still treat Zoom as an untrustworthy channel.&lt;/li&gt;
&lt;li&gt;I am not a security engineer. This setup may be sufficient for my purposes, but you may want to consider consulting an expert.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In spite of all this effort I would still recommend not using Zoom &lt;em&gt;at all&lt;/em&gt;. To anyone who listens, you can get the same set of features without any of the downsides with &lt;a href=&#34;https://jitsi.org/&#34;&gt;Jitsi&lt;/a&gt; for free.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The &lt;code&gt;&amp;quot;1 1&amp;quot;&lt;/code&gt; audio source corresponds to my laptop&amp;rsquo;s built-in microphone. If you would like to use the auto-muting feature, you may need to choose a different identifier. Run &lt;code&gt;pactl list sources&lt;/code&gt; to retrieve a complete list of sinks available in your system.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Firejail can sandbox programs even without AppArmor. This, however, comes at the expense of narrower rule support and slightly increased user-space overhead.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
  </channel>
</rss>
