<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">

<channel>
  <title>lpcvoid's homepage</title>
  <link>https://Lpcvoid.com/blog/</link>
  <description>My blog about computers</description>
  <lastBuildDate>2024-05-03</lastBuildDate>
  <language>en-us</language>
  
  <item>
    <title>Tuning power consumption and efficiency on Linux with AMD CPU/GPU</title>
    <link>https://lpcvoid.com/blog/0017_linux_amd_power_tuning</link>
    <description><![CDATA[<p>I recently got myself a Thinkpad Z13 Gen2 with an AMD 7840u CPU. It also comes with an integrated GPU (Radeon 780) which packs quite a punch for an iGPU. All in all, I am very happy with this system. I use it daily and have no complaints about the performance at all (there&#x27;s still the post I want to write about the eGPU setup I am using... any day now!).</p>
<p>What isn&#x27;t so great, though, is the default power efficiency when using a standard Debian install with it. Now you might ask yourself why I went with Debian on new hardware. I just like the stability, and I argue that you can still run fairly recent kernel versions using the Debian backport repo. You can compile relevant software yourself, and you can install binary firmware blobs manually. If you don&#x27;t use Debian Bookworm (12), then this post may not even be relevant for you. In any case, in this post, I want to outline what I did to minimize wattage, keep the laptop cool, and maximize battery life. Keep in mind - this post assumes that you&#x27;re running recent (Zen3+) AMD hardware.</p>
<h2 id="cpu-scaling-driver">CPU scaling driver</h2>
<p>First thing you should check which scaling driver you are using.</p>
<pre><code>cat &#x2F;sys&#x2F;devices&#x2F;system&#x2F;cpu&#x2F;cpu*&#x2F;cpufreq&#x2F;scaling_driver</code></pre>
<p>This will probably give you <code>acpi_cpufreq</code>. If that&#x27;s the case, then you should consider switching to <code>amd_pstate_epp</code>. Actually, there&#x27;s also <code>amd_pstate</code> (no <code>_epp</code>suffix) - the difference in my testing is large, and <code>amd_pstate_epp</code> is superior. The <a href="https://docs.kernel.org/admin-guide/pm/amd-pstate.html">kernel documentation</a> refers to this difference with different names (active, passive and guided). See the linked page, it explains the differences very nicely.</p>
<p>Anyhow, you&#x27;ll want to configure the kernel to use the active mode. For this, I added a kernel parameter: <code>initcall_blacklist=acpi_cpufreq_init amd_pstate=active</code>. The blacklisting is needed, because otherwise the <code>acpi_cpufreq</code> driver may be started beforehand and stop the <code>amd_pstate</code> driver from taking over. The second paramter sets the mode to active. Keep in mind, to use the active mode you&#x27;ll need a kernel newer than 6.5 (install from Backports).</p>
<p>After booting, the above sysfs file should return <code>amd-pstate-epp</code>. At this point, you should already notice a massive difference in terms of thermals and battery life, but we aren&#x27;t done yet.</p>
<h2 id="power-profiles-daemon">power-profiles-daemon</h2>
<p>If it&#x27;s not installed yet, you should <em>at least</em> install <code>power-profiles-daemon</code>. Essentially, it allows an easy way to switch between what&#x27;s called &quot;platform profiles&quot;. These profiles are defined within the ACPI spec. They <em>massively</em> influence Thinkpads computing performance and efficiency, since Lenovo uses this ACPI functionality to determine minimum&#x2F;maximum frequency as well as frequency boost duration.</p>
<p>You can, of course, set this yourself - valid options are &quot;low-power&quot;, &quot;balanced&quot; and &quot;performance&quot;, and you can write them directly into <code>&#x2F;sys&#x2F;firmware&#x2F;acpi&#x2F;platform_profile</code>. But power-profiles-daemon also manages the <a href="https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt">CPU frequency governor</a> for you.</p>
<p>Currently, <code>power-profiles-daemon</code> in Debian Bookworm is at <a href="https://tracker.debian.org/pkg/power-profiles-daemon">version 0.12</a>, which even for Debian is pretty ancient. You should update to the newest version - read the <a href="https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/compare/0.12...0.21?from_project_id=6840&straight=false">changelog</a> if you&#x27;re not convinced :).<br /></p>
<h2 id="upgrade-binary-firmware-manually">Upgrade binary firmware manually</h2>
<p>In order to fix <a href="https://gitlab.freedesktop.org/mesa/mesa/-/issues/10198">bugs</a> in newer iGPU firmware, you should update the firmware blobs. To do this, <a href="https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/amdgpu">download</a> the Linux firmware blobs, copy&#x2F;overwrite <code>&#x2F;lib&#x2F;firmware&#x2F;amdgpu</code> and re-create your initramfs with <code>update-initramfs -u -k all</code>. </p>
<p>After these points, your laptop should be much cooler, with longer battery life and better hardware decoding.</p>
<p>By the way - if you&#x27;re using Waybar, I added an element to my Waybar which allows me to easily toggle the <code>platform_profile</code>. Maybe it&#x27;s useful to you, here&#x27;s a <a href="https://github.com/lpcvoid/configs/blob/master/waybar/config#L81">Github link</a>.</p>
]]></description>
    <pubDate>2024-05-03</pubDate>
  </item>
  
  <item>
    <title>CORS on Firefox</title>
    <link>https://lpcvoid.com/blog/0016_fun_with_cors</link>
    <description><![CDATA[<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> is one of those things that I think are needed, important, and annoying at the same time. I am happy it exists, because it protects against a certain class of attacks where a website can &quot;pretend&quot; to be you and request third-party resources in your name. But it&#x27;s also a bit annoying, specially when things that <em>should</em> work, don&#x27;t. This blog post is short, but if it helps a single person on planet earth, then it has done its deed.</p>
<p>Imagine a web application which consists of many different endpoints (as most do). You secured one of them with <a href="https://lpcvoid.com/blog/0010_private_ca_client_certificates/index.html">TLS client auth</a> - also called mTLS.</p>
<p>Now, another web application on a different subdomain, requests (via GET) this resource on the client (browser) side. You anticipate this, and enable CORS headers on the protected endpoint.</p>
<pre><code>add_header &#x27;Access-Control-Allow-Origin&#x27; &#x27;https:&#x2F;&#x2F;$URL&#x27; always;
add_header &#x27;Access-Control-Allow-Credentials&#x27; &#x27;true&#x27;;
add_header &#x27;Access-Control-Allow-Methods&#x27; &#x27;GET, POST, OPTIONS&#x27;;</code></pre>
<p>Sweet, that should do it, right? Trembling with anticipation, you refresh the page of the web application which requests this URL, only for Firefox to greet you with:</p>
<pre><code>HTTP&#x2F;1.1 403 Forbidden
Server: nginx
Date: Sun, 14 Apr 2024 18:02:33 GMT
Content-Type: text&#x2F;html; charset=utf-8
Content-Length: 146
Connection: keep-alive</code></pre>
<p>No CORS headers, and a 403. What&#x27;s up with that? Turns out (after a while of debugging, as usual) that the CORS &quot;pre-flight&quot; check failed. This is an OPTIONS request toward the URL in question, in which the browser checks for the headers above. The problem is that this request doesn&#x27;t support mTLS authentication! As an added bonus, there&#x27;s a ten year old <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1019603">Firefox bug</a> about this behavior. To be fair, this seems to be an oversight in the CORS spec itself, and not in Firefox.</p>
<p>Anyhow, you can fix all of this by setting an about:config property <code>network.cors_preflight.allow_client_cert</code> to <code>true</code>. </p>
<p>After this, your CORS requests will use mTLS authenticated pre-flight requests to obtain the Access-Control-Allow-* headers, and Bob&#x27;s your uncle.</p>
]]></description>
    <pubDate>2024-04-14</pubDate>
  </item>
  
  <item>
    <title>Improving my DynDNS setup</title>
    <link>https://lpcvoid.com/blog/0015_improving_dyndns</link>
    <description><![CDATA[<p>I run quite a few services for friends and family. At some point, I will write a blog post about this, but today I want to write about a problem I have been having and how I solved it.</p>
<p>Within the last few months, I had spontaneous hiccups within my infrastructure with regards to DNS lookups. Every now and then, at different points in time, DNS lookups for certain subdomains where different services are hosted on, timed out or only resolved with considerable delay.</p>
<p>I tried to reproduce this, which didn&#x27;t work well, as the problem only reared its head now and then. I setup a cronjob to be run every hour, and logged the output. That worked, and led to this:</p>
<pre><code>dig a nxsubdomain.lpcvoid.com</code></pre>
<pre><code>; &lt;&lt;&gt;&gt; DiG 9.18.19-1~deb12u1-Debian &lt;&lt;&gt;&gt; a nxsubdomain.lpcvoid.com
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: SERVFAIL, id: 54406
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1</code></pre>
<pre><code>;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; EDE: 22 (No Reachable Authority): (at delegation duckdns.org.)
; EDE: 23 (Network Error): (3.97.58.28:53 timed out for lpcvoid.duckdns.org A)
;; QUESTION SECTION:
;nxsubdomain.lpcvoid.com.		IN	A</code></pre>
<pre><code>;; Query time: 4160 msec
;; SERVER: 192.168.1.1#53(192.168.1.1) (UDP)
;; WHEN: Sat Jan 27 22:01:27 CET 2024
;; MSG SIZE  rcvd: 132</code></pre>
<p>Turns out, DuckDNS, the dyndns provider I use, was the root of these timeouts. Every now and then, it would time out randomly. Now, this isn&#x27;t any attack on them, DuckDNS is a one-player show offering a service for free to everybody. I am incredibly grateful for their continued work, and these sort of problems are normal once you become as widely used as they are.</p>
<p>But I cannot live with this, since I use domains everywhere, even within my LAN. I could of course configure my DNS server to cache more aggressively, or use internal LAN addresses when the request comes from within the LAN (compared to my WAN IP when requests arrive via the WAN). But this would also entail maintaining a <a href="https://en.wikipedia.org/wiki/Split-horizon_DNS">&quot;split horizon&quot;</a> DNS. I would like to avoid that, and use my WAN IP internally within my LAN. This is called <a href="https://docs.netgate.com/pfsense/en/latest/nat/reflection.html">reflection</a> in the NAT world. I really feel like I am approaching the point in time where an IPv6 setup would reduce my LAN complexity by a considerable amount. Some day...</p>
<p>In any case, I needed an alternative. There&#x27;s an elegant solution to this problem, which is technically superior to a dedicated dyndns provider. </p>
<p>I already use Cloudflare for my domains DNS setup. Like all other DNS management platforms, Cloudflare allows you to configure various DNS zones which point somewhere. In the past, I had a DuckDNS subdomain (lpcvoid.duckdns.org) point to my WAN IP, and CNAME&#x27;ed a wildcard zone rule to point to the DuckDNS domain for the actual address resolution. This worked, but is inefficient: You need to first lookup the domain you&#x27;re interested in (let&#x27;s say blabla.lpcvoid.com). This info is returned by Cloudflare, and is pretty fast. Then, your browser will lookup lpcvoid.duckdns.org (which blabla.lpcvoid.com points to via the CNAME entry, remember) and that will in turn result in my WAN IP. So you have two lookups in total, and one of those (the DuckDNS one in my case) is potentially slow.</p>
<p>Ideally, Cloudflare would always know my WAN IP, and update DNS A records whenever my WAN IP changes. This would completely remove the DynDNS provider from the equation, and result in very fast, single lookups which Cloudflare can respond to instantly. </p>
<p>Thankfully, OpenWRT, which runs on my router, maintains a script which can do exactly this automatically as part of the <a href="https://openwrt.org/packages/pkgdata/ddns-scripts-cloudflare">ddns-scripts-cloudflare</a> package. For this to work, you need a Cloudflare API key (which you can generate <a href="https://dash.cloudflare.com/profile/api-tokens">here</a>). Generate a token which can only change DNS zones - you should in general be quite restrictive with Cloudflare API tokens, since they are a pretty high value target for attackers. Afterwards, use the token and the username &quot;Bearer&quot; (yes, capital &quot;B&quot;), along with the record you wish to update on WAN IP change. So if your subdomain is foobar.example.com, and the record is thus called &quot;foobar&quot;, you configure your &quot;domain&quot; in the script as &quot;foobar@example.com&quot; in order for Cloudflare to resolve it properly.</p>
<p>After doing this, you should have an A record which points to your WAN IP. Now, if you&#x27;re lazy like me, and have dozens of subdomains, you obviously want to have a wildcard record. The trick is to have your wildcard CNAME record (*) point to the A record you just configured. This lookup, by the way, will be handled internally via Cloudflare&#x27;s resolvers, and not result in two DNS queries.</p>
<p>In the end, you should have something like this:</p>
<pre><code>dig a nxsubdomain.lpcvoid.com</code></pre>
<pre><code>; &lt;&lt;&gt;&gt; DiG 9.18.19-1~deb12u1-Debian &lt;&lt;&gt;&gt; a nxsubdomain.lpcvoid.com
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 49990
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1</code></pre>
<pre><code>;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;nxsubdomain.lpcvoid.com.	IN	A</code></pre>
<pre><code>;; ANSWER SECTION:
nxsubdomain.lpcvoid.com. 600	IN	CNAME	homeserver.lpcvoid.com.
homeserver.lpcvoid.com.	600	IN	A	79.235.213.105</code></pre>
<pre><code>;; Query time: 20 msec
;; SERVER: 192.168.1.1#53(192.168.1.1) (UDP)
;; WHEN: Mon Jan 29 14:55:48 CET 2024
;; MSG SIZE  rcvd: 93</code></pre>
<p>Beautiful, problem solved. DNS lookups are now snappy.</p>
<p>In my next post I guess I will write about my complete setup.</p>
]]></description>
    <pubDate>2024-01-29</pubDate>
  </item>
  
  <item>
    <title>The ultimate Linux laptop</title>
    <link>https://lpcvoid.com/blog/0014_the_ultimate_linux_laptop</link>
    <description><![CDATA[<p>Well, this is kind of awkward. I wrote a blog post around a year ago where I claimed to have found the perfect Linux laptop. </p>
<p>It turns out, dear reader, that I lied to you. I was never able to fix the glaring suspend&#x2F;resume problem that I hinted at in the updates to the article. So, I did what no self-respecting
Tinkerer would do after months of troubleshooting: I decided to give up and throw money at the problem, hoping it would go away.</p>
<p>Like the last time, I had a list of must-haves (I really dislike the word &quot;requirements&quot;) that the new laptop should bring in order to contend:</p>
<h3 id="point-1---amd-based">Point 1 - AMD based</h3>
<p>This was born out of the fact that I have a desktop machine running full AMD (5600x + 6700xt on an AM4 mainboard), which has been working absolutely flawlessly for about 3 years now. I bought it during the pandemic to play Valheim, and it&#x27;s just been so insanely unproblematic (running Ubuntu) that I hope to mirror that experience for my laptop. The plan is to only have a single computer.</p>
<p>The no NVIDIA part <a href="https://drewdevault.com/2017/10/26/Fuck-you-nvidia.html">should be obvious</a>. </p>
<h3 id="point-2---thunderbolt">Point 2 - Thunderbolt</h3>
<p>I <em>love</em> how you can simply connect your laptop with a docking station and have a desktop replacement with a single wire. USB4 works also (Thunderbolt 4 is basically just USB4 with all features). There&#x27;s unfortunately some ambiguity around PCIe passthrough in USB4 (vendors don&#x27;t need to pass PCIe lanes to be USB4 conform). Thankfully, Microsoft (those two words in close proximity, wow) <a href="https://learn.microsoft.com/en-us/windows-hardware/test/hlk/testref/7d627bf0-25f3-4564-b554-b2a3450e2bcf">specified</a> that every laptop that wants to be &quot;Windows 11 ready&quot; must expose PCIe lanes via USB4 (this is called &quot;Thunderbolt 3 compatibility&quot;). </p>
<p>Also, I want to try out an eGPU setup. There&#x27;s going to be a separate blog post about this, but as a small teaser, I am writing this blog post with a working, hotpluggable eGPU setup :)</p>
<h3 id="point-3---powerful-and-fast">Point 3 - powerful and fast</h3>
<p>I want to replace my desktop with this laptop. My desktop isn&#x27;t an insane speed machine by any stretch, but it does compile code pretty fast. Ideally, this laptop can also do that, and even
chomp through some games. </p>
<h3 id="point-4---portable--ideally-13-3-inches">Point 4 - portable, ideally 13.3 inches</h3>
<p>Yeah, I like small laptops. For some reason, even though I have been sitting in front of computers for over 20 years now, I still have pretty good eyesight. This means I don&#x27;t need to go to 15&quot; laptops just yet. This may change in the future when I grow older, but for now, I will enjoy smaller form factors for as long as I can.</p>
<h3 id="point-5---no-soldered-ram">Point 5 - no soldered RAM</h3>
<p>Okay, at that point, I had to compromise. It turns out there&#x27;s no LPDDR5-capable machine with SODIMM bars. One reason for that is the low number of pins that are present in SODIMM connectors. Imagine having a 2048-bit memory bus (like they are used on <a href="https://en.wikipedia.org/wiki/High_Bandwidth_Memory">HBM</a> modules) on a SODIMM interface; there&#x27;s simply nobody who specified such a standard for pluggable SODIMM modules. The LP in LPDDR5 stands for &quot;Low Power,&quot; which can&#x27;t deal with the signal integrity losses of SODIMM connectors. It needs a nice, clean PCB trace without some noisy physical mounting connector. The upside of this is that LPDDR consumes a lot less power than the normal SODIMM-mounted DDR RAM found in other notebooks. By the way, the LPDDR standard is not just DDR with some added specs; it&#x27;s a completely different specification from DDR and was developed independently.</p>
<p>The laptop I went for in the end has 64GB LPDDR5. I don&#x27;t foresee me needing to upgrade RAM for a long time.</p>
<h3 id="point-6---it-shall-be-chargeable-via-usbc-pd">Point 6 - It shall be chargeable via USBC-PD</h3>
<p>Pretty self-explaining, I guess. When traveling (and even at home, if I think about it), I want to have one charger for everything. USBC-PD makes life easy. Probably one of the best technological improvements for me is the unification of charging infrastructure that USBC made possible.</p>
<h3 id="point-7---i-want-a-touchscreen">Point 7 - I want a touchscreen</h3>
<p>Yeah, I know, this point is a bit weird. But I love how I can lay on the sofa and scroll through a website with the laptop on my chest. It&#x27;s purely a personal opinion, but touchscreens are awesome. I learned to love them with my older Dell Latitude, which also had one. Once you have one, I am pretty sure you&#x27;ll find that one situation that makes a touchscreen worth it. It&#x27;s different for everybody, though.</p>
<h3 id="point-8---fullhd--1080p-1200p--screen">Point 8 - FullHD (1080p&#x2F;1200p) screen</h3>
<p>I can&#x27;t stand 4k screens on a tiny form factor such as 13.3&quot;. I see pixels only when I look very, very closely. The added strain on the GPU and battery just isn&#x27;t worth it to me. Also, fractional scaling is nice within most desktop environments under Wayland, but it&#x27;s not very nice in GRUB or other older textmode interfaces.</p>
<h3 id="point-9---ansi-us--qwerty--keyboard-layout">Point 9 - ANSI-US (QWERTY) keyboard layout</h3>
<p>I love ANSI-US. Easy access to brackets, special characters, and no wasted keys on local language features such as the German umlauts I use those incredibly rarely.</p>
<h1 id="manufacturer">Manufacturer</h1>
<h2 id="frame-work">Frame.Work</h2>
<p>I have read many positive things about the Frame.Work laptop, and when I heard that they would offer an AMD version, I was really interested. It is <em>almost</em> an ideal candidate. It&#x27;s running Coreboot, has an AMD CPU, is repairable, extendable and upgradable (specifically, the &quot;expansion card&quot; system is really nice!). But they don&#x27;t offer a touchscreen. I thought very long about accepting that drawback and going the Frame.work route - but I couldn&#x27;t get myself to ignore that one drawback. If they ever offer a touchscreen, I may reconsider.</p>
<h2 id="dell">Dell</h2>
<p>I liked my Dell Latitude very much. I looked for a laptop from Dell, but they wouldn&#x27;t even offer a thin or light 13.3&quot; laptop with 32+GB RAM and an AMD CPU. All their XPS13 are Intel. Added to that, the new XPS13 has some sort of touch-buttons (instead of using the fn toggle + Fx keys), and I like physical buttons. Maybe they work well in practice, but I didn&#x27;t want to risk it.</p>
<p>For reference, this was the laptop I looked at, which seemed like the best fit for me: https:&#x2F;&#x2F;www.dell.com&#x2F;de-de&#x2F;shop&#x2F;dell-notebooks&#x2F;xps-13-plus-laptop&#x2F;spd&#x2F;xps-13-9320-laptop&#x2F;cn93352cc</p>
<h2 id="hp">HP</h2>
<p>Next manufacturer I looked at was HP. I owned a HP Spectre x360 some time ago and liked it a lot too. They don&#x27;t exist with AMD CPUs, at least not at the time I looked. The HP Pro x360 435 looked like a potential match, however: https:&#x2F;&#x2F;www.hp.com&#x2F;de-de&#x2F;shop&#x2F;product.aspx?id=816D9EA&amp;opt=ABD&amp;sel=NTB</p>
<p>The only problem was that HP simply doesn&#x27;t allow configuration of the keyboard. In Germany, you&#x27;re seemingly forced to buy a German layout. In the end, I decided against it and looked at the third manufacturer.</p>
<h2 id="lenovo">Lenovo</h2>
<p>Now, I try not to buy Chinese. I am wary about the <a href="https://www.bloomberg.com/features/2021-supermicro/">security</a> <a href="https://myce.wiki/news/lenovo-laptops-come-with-preinstalled-advertisement-injecting-adware-74290/">implications</a> of doing so. I don&#x27;t like their human rights track record (and yes, I think the US is better in this regard). I think the <a href="https://www.reuters.com/world/german-spy-chief-russia-is-storm-china-is-climate-change-2022-10-17/">chinese</a> <a href="https://www.aljazeera.com/news/2022/1/13/chinas-sea-claims-gravely-undermines-rule-of-law-us-study">political</a> <a href="https://www.bbc.com/news/world-asia-china-22278037">system</a> <a href="https://en.wikipedia.org/wiki/Nine-dash_line">is</a> <a href="https://www.washingtonpost.com/opinions/2019/06/27/chinas-efforts-undermine-democracy-are-expanding-worldwide/">a</a> <a href="https://networkcontagion.us/wp-content/uploads/A-Tik-Tok-ing-Timebomb_12.21.23.pdf">threat</a> to the free world.</p>
<p>I even messaged the <a href="https://en.wikipedia.org/wiki/Federal_Office_for_Information_Security">BSI</a> via email and asked what their opinion is on Lenovo. They weren&#x27;t very helpful, though, and replied back with a template that stated that I could check their security advisories. I mean, yeah, but still, I had hoped for a more nuanced opinion. Oh well.</p>
<p>Also, as far as I was able to research, all the big American companies have at least some supply chain in China (Even though that&#x27;s <a href="https://www.pcmag.com/news/dell-to-stop-using-chips-made-in-china-before-the-end-of-2024">changing</a>). You can&#x27;t really avoid it, unless you go back in time and use a device which can run <a href="https://www.coreboot.org">Coreboot</a>. Alternatively, you can also go for something like the <a href="https://www.raptorcs.com/">Talos II</a> - but that&#x27;s very far outside of my budget (and it&#x27;s no laptop :)). </p>
<p>If you search for this discussion online, there&#x27;s a lot of people who aren&#x27;t worried, or are worried about things I am not worried about. There&#x27;s lots of argumentation going into the direction that people should just not use Windows and Chrome (I don&#x27;t use either) if they are so worried, and that smartphones are so much worse (mine isn&#x27;t, I run GrapheneOS). I feel like this discussion is misdirected; the real Lenovo backdoors (if there are any) are going to be in the embedded controller firmware onboard the laptop, or in the UEFI code. This is something I see discussed very rarely. The only &quot;comforting&quot; thought here is that I really doubt that ex-filtration of data via the internet would be easy to hide in properly firewalled corporate networks. It would make the news pretty fast, if some corporation figured out weird traffic originating from their fleet of Thinkpads. Maybe someday I will try to reverse engineer a Lenovo firmware blob and attempt to answer this question for myself. </p>
<p>In the end, I decided that it&#x27;s worth at least checking what they are offering (especially since I was able to get Lenovo store access with ~20% discount). I am not complacent here - I still think that the laptop could be backdoor-ed in a way that&#x27;s hard to detect. I just chose to live with that thought, since I fail to see clandestine extraction methods that wouldn&#x27;t be noticed in various cooperate environments (perhaps even by me in my heavily segregated VLANs where I regularly check for unknown traffic).</p>
<p>Anyhow, it turns out, they offer a machine that 100% fits what I am after: The Thinkpad Z13 Gen 2.</p>
<p>I could configure it with 7840U, 64GB RAM, touchscreen, and ANSI-US layout (called &quot;Backlit, Black&#x2F;Arctic Grey with Fingerprint Reader - English (Euro) &quot;). It was also about 300 Euros cheaper than Dell and HP devices, while offering 64GB RAM. </p>
<p>It took quite a while to arrive in Germany (around two weeks). Most of that time was UPS (they have <em>horrible</em> tracking; it&#x27;s really insanely bad), and it seemed that the laptop was in stock in my configuration.</p>
<h1 id="the-laptop">The laptop</h1>
<p>Here are the exact specs:</p>
<p><code>
H&#x2F;W path            Device          Class          Description
==============================================================
                                    system         21JVCTO1WW (LENOVO_MT_21JV_BU_Think_FM_ThinkPad Z13 Gen 2)
&#x2F;0                                  bus            21JVCTO1WW
&#x2F;0&#x2F;0                                memory         512KiB L1 cache
&#x2F;0&#x2F;1                                memory         8MiB L2 cache
&#x2F;0&#x2F;2                                memory         16MiB L3 cache
&#x2F;0&#x2F;3                                processor      AMD Ryzen 7 PRO 7840U w&#x2F; Radeon 780M Graphics
&#x2F;0&#x2F;5                                memory         64GiB System Memory
&#x2F;0&#x2F;5&#x2F;0                              memory         16GiB Synchronous Unbuffered (Unregistered) 7500 MHz (0.1 ns)
&#x2F;0&#x2F;5&#x2F;1                              memory         16GiB Synchronous Unbuffered (Unregistered) 7500 MHz (0.1 ns)
&#x2F;0&#x2F;5&#x2F;2                              memory         16GiB Synchronous Unbuffered (Unregistered) 7500 MHz (0.1 ns)
&#x2F;0&#x2F;5&#x2F;3                              memory         16GiB Synchronous Unbuffered (Unregistered) 7500 MHz (0.1 ns)
&#x2F;0&#x2F;15                               memory         128KiB BIOS
&#x2F;0&#x2F;100                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;0.2                          generic        Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;2.2                          bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;2.2&#x2F;0        wlp1s0          network        MT7922 802.11ax PCI Express Wireless Network Adapter
&#x2F;0&#x2F;100&#x2F;2.4                          bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;2.4&#x2F;0        &#x2F;dev&#x2F;nvme0      storage        WD PC SN740 SDDQMQE-2T00-1201
&#x2F;0&#x2F;100&#x2F;2.4&#x2F;0&#x2F;0      hwmon2          disk           NVMe disk
&#x2F;0&#x2F;100&#x2F;2.4&#x2F;0&#x2F;2      &#x2F;dev&#x2F;ng0n1      disk           NVMe disk
&#x2F;0&#x2F;100&#x2F;2.4&#x2F;0&#x2F;1      &#x2F;dev&#x2F;nvme0n1    disk           2048GB NVMe disk
&#x2F;0&#x2F;100&#x2F;2.4&#x2F;0&#x2F;1&#x2F;1                    volume         511MiB Windows FAT volume
&#x2F;0&#x2F;100&#x2F;2.4&#x2F;0&#x2F;1&#x2F;2    &#x2F;dev&#x2F;nvme0n1p2  volume         488MiB EFI partition
&#x2F;0&#x2F;100&#x2F;2.4&#x2F;0&#x2F;1&#x2F;3    &#x2F;dev&#x2F;nvme0n1p3  volume         1906GiB EFI partition
&#x2F;0&#x2F;100&#x2F;3.1                          bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;4.1                          bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.1                          bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0        &#x2F;dev&#x2F;fb0        display        Phoenix1
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.1      card0           multimedia     Rembrandt Radeon High Definition Audio Controller
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.1&#x2F;0    input23         input          HD-Audio Generic HDMI&#x2F;DP,pcm=3
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.1&#x2F;1    input24         input          HD-Audio Generic HDMI&#x2F;DP,pcm=7
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.1&#x2F;2    input25         input          HD-Audio Generic HDMI&#x2F;DP,pcm=8
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.1&#x2F;3    input26         input          HD-Audio Generic HDMI&#x2F;DP,pcm=9
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.2                      generic        Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.3                      bus            Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.3&#x2F;0    usb1            bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.3&#x2F;0&#x2F;3                  communication  Wireless_Device
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.3&#x2F;0&#x2F;5                  generic        Generic USB device
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.3&#x2F;1    usb2            bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.4                      bus            Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.4&#x2F;0    usb3            bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.4&#x2F;0&#x2F;1  input28         multimedia     Integrated RGB Camera: Integrat
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.4&#x2F;1    usb4            bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.5                      multimedia     ACP&#x2F;ACP3X&#x2F;ACP6x Audio Coprocessor
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.6      card1           multimedia     Family 17h&#x2F;19h HD Audio Controller
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.6&#x2F;0    input27         input          HDA Digital PCBeep
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.6&#x2F;1    input30         input          HD-Audio Generic Mic
&#x2F;0&#x2F;100&#x2F;8.1&#x2F;0.6&#x2F;2    input31         input          HD-Audio Generic Headphone
&#x2F;0&#x2F;100&#x2F;8.2                          bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.2&#x2F;0                        generic        Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.2&#x2F;0.1                      generic        Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.3                          bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0                        generic        Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0.3                      bus            Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0.3&#x2F;0    usb5            bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0.3&#x2F;1    usb6            bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0.4                      bus            Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0.4&#x2F;0    usb7            bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0.4&#x2F;1    usb8            bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0.5                      bus            Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;8.3&#x2F;0.6                      bus            Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;100&#x2F;14                           bus            FCH SMBus Controller
&#x2F;0&#x2F;100&#x2F;14.3                         bridge         FCH LPC Bridge
&#x2F;0&#x2F;100&#x2F;14.3&#x2F;0                       system         PnP device PNP0c02
&#x2F;0&#x2F;100&#x2F;14.3&#x2F;1                       system         PnP device PNP0b00
&#x2F;0&#x2F;100&#x2F;14.3&#x2F;2                       generic        PnP device LEN0071
&#x2F;0&#x2F;100&#x2F;14.3&#x2F;3                       generic        PnP device LEN0316
&#x2F;0&#x2F;100&#x2F;14.3&#x2F;4                       system         PnP device PNP0c02
&#x2F;0&#x2F;100&#x2F;14.3&#x2F;5                       system         PnP device PNP0c01
&#x2F;0&#x2F;101                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;102                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;103                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;104                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;105                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;106                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;107                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;108                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;109                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;10a                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;10b                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;10c                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;0&#x2F;10d                              bridge         Advanced Micro Devices, Inc. [AMD]
&#x2F;1                                  power          5B10W51881
&#x2F;2                  input0          input          AT Translated Set 2 keyboard
&#x2F;3                  input15         input          Wacom HID 5385 Pen
&#x2F;4                  input16         input          Wacom HID 5385 Finger
&#x2F;5                  input18         input          PC Speaker
&#x2F;6                  input19         input          ThinkPad Extra Buttons
&#x2F;7                  input2          input          Power Button
&#x2F;8                  input20         input          SNSL0028:00 2C2F:0028 Mouse
&#x2F;9                  input21         input          SNSL0028:00 2C2F:0028 Touchpad
&#x2F;a                  input3          input          Lid Switch
&#x2F;b                  input4          input          TPPS&#x2F;2 Elan TrackPoint
&#x2F;c                  input5          input          Sleep Button
&#x2F;d                  input6          input          Video Bus
</code></p>
<p>(The output here comes from <code>lshw -short -sanitize</code> btw, since I was asked.) It&#x27;s an absolute beast in a 13.3-inch form factor.</p>
<p>I read quite a few comments on this laptop before deciding to buy it, and many people weren&#x27;t sure about the keyboard. I can confidently say that I love it. It&#x27;s quiet, has very good travel (1.35mm on paper, but feels like much more). Very nice feedback, and it is full-sized (it stretches the complete width of the chassis). Also, no weird fn &lt;&gt; CTRL swapping is needed (sorry, old-school ThinkPad fans!). CTRL is where it&#x27;s supposed to be—in the leftmost pinkie position.</p>
<p>The touchpad was a bit annoying at first (pinching and zooming sometimes weren&#x27;t working). Having checked libinputs bug tracker for something similar, I found <a href="https://gitlab.freedesktop.org/libinput/libinput/-/issues/905">an issue</a> from somebody who had the inverse problem. He found that kernel parameter <code>psmouse.synaptics_intertouch=0</code> solved his problem. I booted with that option, and yeah. It really does solve all my problems with the touchpad. It is insane that the secondary bus for Synaptics devices is still such a problem. </p>
<p>If you have this laptop, try it yourself: run <code>libinput debug-events --verbose</code> without the above kernel parameter, and try pinch-zooming a bit. You&#x27;ll see a lot of times that libinput will detect only a <code>POINTER_MOTION</code> event even though you pinched, while this completely stops with the kernel parameter. I am not sure, but this seems like a bug in the device firmware, which would need to be fixed by Lenovo.</p>
<p>So, how does suspend&#x2F;resume work now? Wasn&#x27;t that the main reason for even buying this? To make a long story short, it works absolutely flawlessly on Debian 12 (6.1.0-16-amd64 #1 SMP PREEMPT<em>DYNAMIC Debian 6.1.67-1 (2023-12-12) x86</em>64 GNU&#x2F;Linux). It instantly suspends and wakes up in about one second. Overnight power draw when suspended is ~4%, which is superb for me.</p>
<p>The only remaining problem for me is that hardware decode of VP9 currently doesn&#x27;t work correctly - Youtube uses that a lot. There&#x27;s a <a href="https://gitlab.freedesktop.org/mesa/mesa/-/issues/8044#note_2195102">Mesa bug</a> logged about this, and at time of writing there exists a preliminary firmware fix for it. Let&#x27;s hope it finds its way into Debian at some point :). Also, I had a amdgpu hang&#x2F;reset randomly when opening a webpage. I hope these sort of things get fixed as time goes by for this relatively new platform.</p>
<p>I will keep you informed if any of this changes.</p>
]]></description>
    <pubDate>2023-12-26</pubDate>
  </item>
  
  <item>
    <title>The perfect Linux laptop</title>
    <link>https://lpcvoid.com/blog/0013_the_perfect_linux_laptop</link>
    <description><![CDATA[<p>Ever since I moved my main computing environment over to a desktop at the start of the pandemic (which by now is already like 70 years ago, time flies man), I have 
regretted not being mobile anymore - even if that means sitting on the sofa instead of somewhere outside.</p>
<p>So, a few weeks ago, I started looking for a laptop. I wanted something small-ish, max 14 inch. It shall have a 1080p display, since I can&#x27;t stand the waste of
energy and problems with nonstandard DPI with these high resolution but small displays. I wanted it to have at least DDR4 RAM, since I had some laying around. Also,
I personally find Thunderbolt support really important, for those situations where I do want to connect it to my docking station. 
This also implies that I want USB-C power delivery, which makes the docking situation less painful, and also makes it possible to only carry around a small
charger which can at the same time charge my phone. Oh, and I didn&#x27;t want an <a href="https://www.youtube.com/watch?v=OF_5EKNX0Eg">Nvidia</a> GPU, 
since they don&#x27;t offer open source drivers and <a href="https://swaywm.org">Sway</a> doesn&#x27;t support anything proprietary, which is a good thing. So, ideally Intel, because I never 
had any problems with <code>i915</code>.</p>
<p>So, with all these criterias set, long story short I settled on a Dell Latitude 7390. The decision was actually easy - I just searched through used laptops on Ebay, while
ignoring the Thinkpad series (I cannot stand their swapping of fn and ctrl [I know you can remap it, but still]). Also, HP Elitebooks where way more expensive than Dell
Latitudes. I paid 271 EUR for the following configuration:</p>
<p><code>
H&#x2F;W path       Device           Class          Description
==========================================================
                                system         Latitude 7390 (081B)
&#x2F;0                              bus            09386V
&#x2F;0&#x2F;0                            memory         64KiB BIOS
&#x2F;0&#x2F;4d                           memory         16GiB System Memory
&#x2F;0&#x2F;4d&#x2F;0                         memory         16GiB SODIMM DDR4 Synchronous Unbuffered (Unregistered) 2400 MHz (0,4 ns)
&#x2F;0&#x2F;4d&#x2F;1                         memory         [empty]
&#x2F;0&#x2F;51                           memory         256KiB L1 cache
&#x2F;0&#x2F;52                           memory         1MiB L2 cache
&#x2F;0&#x2F;53                           memory         6MiB L3 cache
&#x2F;0&#x2F;54                           processor      Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz
&#x2F;0&#x2F;100                          bridge         Xeon E3-1200 v6&#x2F;7th Gen Core Processor Host Bridge&#x2F;DRAM Registers
&#x2F;0&#x2F;100&#x2F;2                        display        UHD Graphics 620
&#x2F;0&#x2F;100&#x2F;4                        generic        Xeon E3-1200 v5&#x2F;E3-1500 v5&#x2F;6th Gen Core Processor Thermal Subsystem
&#x2F;0&#x2F;100&#x2F;14                       bus            Sunrise Point-LP USB 3.0 xHCI Controller
&#x2F;0&#x2F;100&#x2F;14&#x2F;0    usb1             bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;14&#x2F;0&#x2F;4                   communication  N5321 gw
&#x2F;0&#x2F;100&#x2F;14&#x2F;0&#x2F;5                   multimedia     Integrated_Webcam_HD
&#x2F;0&#x2F;100&#x2F;14&#x2F;0&#x2F;7                   communication  Bluetooth wireless interface
&#x2F;0&#x2F;100&#x2F;14&#x2F;0&#x2F;8                   input          SiW HID Touch Controller
&#x2F;0&#x2F;100&#x2F;14&#x2F;1    usb2             bus            xHCI Host Controller
&#x2F;0&#x2F;100&#x2F;14.2                     generic        Sunrise Point-LP Thermal subsystem
&#x2F;0&#x2F;100&#x2F;15                       generic        Sunrise Point-LP Serial IO I2C Controller #0
&#x2F;0&#x2F;100&#x2F;15.1                     generic        Sunrise Point-LP Serial IO I2C Controller #1
&#x2F;0&#x2F;100&#x2F;15.2                     generic        Sunrise Point-LP Serial IO I2C Controller #2
&#x2F;0&#x2F;100&#x2F;15.3                     generic        Sunrise Point-LP Serial IO I2C Controller #3
&#x2F;0&#x2F;100&#x2F;16                       communication  Sunrise Point-LP CSME HECI #1
&#x2F;0&#x2F;100&#x2F;1c                       bridge         Sunrise Point-LP PCI Express Root Port #1
&#x2F;0&#x2F;100&#x2F;1c&#x2F;0                     generic        RTS525A PCI Express Card Reader
&#x2F;0&#x2F;100&#x2F;1c.2                     bridge         Sunrise Point-LP PCI Express Root Port #3
&#x2F;0&#x2F;100&#x2F;1c.2&#x2F;0  wlp2s0           network        Wireless 8265 &#x2F; 8275
&#x2F;0&#x2F;100&#x2F;1c.4                     bridge         Sunrise Point-LP PCI Express Root Port #5
&#x2F;0&#x2F;100&#x2F;1d                       bridge         Sunrise Point-LP PCI Express Root Port #9
&#x2F;0&#x2F;100&#x2F;1d&#x2F;0                     storage        A2000 NVMe SSD
&#x2F;0&#x2F;100&#x2F;1f                       bridge         Sunrise Point LPC Controller&#x2F;eSPI Controller
&#x2F;0&#x2F;100&#x2F;1f.2                     memory         Memory controller
&#x2F;0&#x2F;100&#x2F;1f.3                     multimedia     Sunrise Point-LP HD Audio
&#x2F;0&#x2F;100&#x2F;1f.4                     bus            Sunrise Point-LP SMBus
&#x2F;0&#x2F;100&#x2F;1f.6    enp0s31f6        network        Ethernet Connection (4) I219-LM
&#x2F;0&#x2F;1                            system         PnP device PNP0c02
&#x2F;0&#x2F;2                            system         PnP device PNP0b00
&#x2F;0&#x2F;3                            generic        PnP device INT3f0d
&#x2F;0&#x2F;4                            input          PnP device PNP0303
&#x2F;0&#x2F;5                            system         PnP device PNP0c02
&#x2F;0&#x2F;6                            system         PnP device PNP0c02
&#x2F;0&#x2F;7                            system         PnP device PNP0c02
&#x2F;0&#x2F;8                            system         PnP device PNP0c02
&#x2F;1                              power          DELL DM3WC64
&#x2F;2             wwx0215e0ec0100  network        Ethernet interface
</code></p>
<p>So, I got a 8. Gen i5, 16GB RAM, and Thunderbolt - and even a touchscreen. Pretty good deal! 
And I can report that eveything (Audio, Webcam, Touchscreen, USB ports, Thunderbolt) works with Debian 11 (Kernel 5.10).</p>
<p>Just one problem...</p>
<h3 id="keyboard-swap">Keyboard swap</h3>
<p>The Keyboard was the <a href="https://en.wikipedia.org/wiki/German_keyboard_layout">German QWERTZ layout</a>. I can touch type ANSI-US QWERTY without really caring all too 
much about the layout, but it still annoyed the hell out of me. So I looked at other pictures of internals of the laptop, with the plan to do a swap of the keyboard.
The good news first: yes, it&#x27;s possible, but you need to take care which keyboard you buy.</p>
<p>You need two things: an upper palmrest, which is one large plastic frame with the correct keyboard cutout, and the keyboard itself. </p>
<img src="https://lpcvoid.com/img/laptop/palmrest_top.jpg" style="max-height: 400px; max-width: 400px;"/>
<img src="https://lpcvoid.com/img/laptop/palmrest_bottom.jpg" style="max-height: 400px; max-width: 400px;"/>
<p>The palmrest was the hardest part, and I was lucky - only a single seller was selling one. The keyboard seemed easier, loads of ANSI US keyboards on eBay. 
But I had to try two until I found one that actually works completely, as the Windows&#x2F;Super key wasn&#x27;t working on the first one. If you&#x27;re ever in the same situation,
here&#x27;s the exact model I used:<br /></p>
<img src="https://lpcvoid.com/img/laptop/keyboard_back.jpg" style="max-height: 400px; max-width: 400px;"/>
<p>It&#x27;s a bit annoying to take it all apart, but be careful with the ribbons (and don&#x27;t strip any screws!), and you&#x27;ll be fine.</p>
<h3 id="all-in-all---">All in all...</h3>
<p>... a very good laptop. Everything works, battery lasts forever and display is great. I can absolutely recommend this setup. It&#x27;s rugged enough for throwing it into
a bag, and beefy enough for compiling code.</p>
<h3 id="update-2023-10-30">Update 2023-10-30</h3>
<p>It&#x27;s been a solid daily driver on the 5.10 kernel. On Debian Bookworm&#x27;s 6.10 though, suspend started being a problem. The laptop would &quot;freeze&quot; after resume, with the fan spinning, keyboard illumination being on, and the screen being off (no display illumination even). The only thing that &quot;helped&quot; was a hard-reset by power-button. I searched around for a solution for a fairly long time, until I stumbled on <a href="https://askubuntu.com/questions/1241771/dell-latitude-7390-ubuntu-20-04-sometimes-hangs-and-reboot-after-pressing-s">this AskUbuntu</a> post. </p>
<p>I can confirm the problem disappears when I boot with <code>snd_hda_intel.dmic_detect=0</code>. <code>&#x2F;sys&#x2F;power&#x2F;mem_sleep</code> was always on <code>deep</code> for me, so I didn&#x27;t need to override that parameter as suggested additionally in the post. I tried looking in the source what the parameter actually does (<a href="https://github.com/torvalds/linux/blob/master/sound/pci/hda/hda_intel.c#L159">link</a>). It seems to disable some DSP firmware probe, which concerned me because I thought it may impact my audio or something, but nope.</p>
<h3 id="update-2023-10-31--happy-helloween--">Update 2023-10-31 (Happy Helloween!)</h3>
<p>Of course, a day after writing an update that the above kernel parameter works, it stopped working. I spent another evening searching for a possible fix.
I tried to reproduce the suspend problem by running <code>systemctl suspend</code> in a loop, as well as manually setting system state via <code>echo mem &gt; &#x2F;sys&#x2F;power&#x2F;state</code> to rule out any potential bug in systemd (probably not needed, in hindsight). I managed to reproduce it using this method, in about ~10% of cases. I can confirm that it happens when suspending, and not when waking up.</p>
<p>After some more searching, I found a method which actually, so far, works. I have run 90+ suspend-and-resume iterations now, and it&#x27;s not a problem anymore.</p>
<p>The fix is yet another kernel parameter: <code>intel_iommu=off</code>. This disables the Intel Virtualization support within the Kernel. I messed a lot with this parameter in the past when I tried out iGPU passthrough within Proxmox, so it certainly rang a bell. I don&#x27;t plan on using virtualization on this laptop, so it&#x27;s fine for me - but be aware of this downside if you happen to stumble on these pages with the same problem.</p>
<p>I found a very interesting thread on the kernel bugzilla about this issue: <a href="https://bugzilla.kernel.org/show_bug.cgi?id=197029">link</a></p>
<p>I hope I don&#x27;t have to write another update here :)</p>
]]></description>
    <pubDate>2023-01-09</pubDate>
  </item>
  
  <item>
    <title>List of software I absolutely *love*</title>
    <link>https://lpcvoid.com/blog/0012_software_i_love</link>
    <description><![CDATA[<p>Looking back, my blogposts mostly revolve aroud complaining about things I dislike. There are also many things to like out there, so this post is dedicated to that: software I use often, love, and want to recommend to others.</p>
<h3 id="sway">Sway</h3>
<p>After some years of using GNOME as my primary desktop environment, I tried <a href="https://swaywm.org/">Sway</a>. You know that feeling when you try something new,
and it instantly hits that sweet spot of being both intuitive and feeling <em>right</em>? That&#x27;s Sway for me. I love how lightweight it is, the fact that it&#x27;s
running on Wayland so multiple displays with different resoultions aren&#x27;t painful anymore, and the best part: it&#x27;s highly configurable. I&#x27;ll upload my config somewhere
soon.</p>
<p>I recommend running it together with <code>swaylock</code> and <code>waybar</code>.</p>
<h3 id="sqlite">SQLite</h3>
<p>Whenever I have a situation within some project of mine where I need to save some data on disk, 9&#x2F;10 times I use sqlite. It&#x27;s fast, has bindings for every language
under the sun, well documented (the <a href="https://www.sqlite.org/docs.html">documentation</a> is interesting enough to be read like a book, IMHO) and <a href="https://www.sqlite.org/mostdeployed.html">battle tested</a>.</p>
<p>Next time you need to save some data - consider joining the fan club.</p>
<h3 id="fish">fish</h3>
<p>The first thing I do whenever I set up a new computer is install the fish shell. I have nothing against bash, in fact you can tune it to have a lot of the features that
fish has out of the box, but that&#x27;s the problem, I am lazy. Fish has a very good command history, and the built-in git support is amazing. </p>
<h3 id="thunderbird">Thunderbird</h3>
<p>I love Email in general. Open standard, widely supported, text first. Thunderbird brings a lot to the table - besides e-mail, it can also be used as an RSS reader, which
is my primary method of consuming news (...along with a rather large list of RSS filters - never having to read something about Elon Musk again by itself is already worth it).</p>
<h3 id="kde-okular">KDE Okular</h3>
<p>Okular is a document viewer. I use it to read (and edit!) PDFs. It is snappy, doesn&#x27;t annoy me, and is open source (okay, everything on this list is, but still!). Also,
did you know that it&#x27;s the first piece of software which is <a href="https://www.blauer-engel.de/en/products/kde-okular">certified</a> by &quot;Blauer Engel&quot; (a German Eco label) as being &quot;Energy efficient&quot;?
Their testing methology looks decent. I bet we won&#x27;t see any Electron stuff on that list anytime soon (and not on this list either).</p>
<h3 id="firefox">Firefox</h3>
<p>If this list where sorted by popularity, then Firefox would probably be a lot higher, but it isn&#x27;t, so it&#x27;s down here. Firefox is fantastic software. Mozilla,
the parent company, wasn&#x27;t always without missteps. 
We had the <a href="https://www.cnet.com/news/privacy/mozilla-investigates-mr-robot-firefox-extension-problem/">Mr. Robot marketing thing</a>, which was a pretty big WTF moment for me.
Or the <a href="https://calpaterson.com/mozilla.html">funny graph</a> about their CEO making more money while Firefox user numbers tanked. But all in all, I feel like they are doing
their best in a hypercommercialized world to stay afloat, while building an absolutely amazing piece of very complex technology.</p>
<h3 id="rclone">Rclone</h3>
<p>I use Rclone for backing up data (using cron for managing backup times). It supports <a href="https://rclone.org/#providers">many backends</a>, even though I only use it with sftp.
I especially love that I can layer backends. For example, Rclone allows to layer a crypto backend onto an sftp backend, so that any file operation is automatically encrypted.
If you&#x27;re looking for some backup solution - seriously consider Rclone + cron.</p>
]]></description>
    <pubDate>2023-01-03</pubDate>
  </item>
  
  <item>
    <title>Creating a private CA and enforcing client certificates</title>
    <link>https://lpcvoid.com/blog/0010_private_ca_client_certificates</link>
    <description><![CDATA[<p>I run a lot of services on my home server for friends and family. These services need to be authenticated, which is done via username&#x2F;password mainly. Due to some poeple not knowing much about technology, I need to expose some of it to the internet (in contrast to being behind a Wireguard VPN). I do this via nginx, which I use as a reverse proxy in order to ease up certificate management. </p>
<p>In addition to usernames and passwords, all users require client certificate authentication. This means that their browser must send a certificate when connecting and establishing a TLS session. That looks like this, on Firefox&#x2F;Ubuntu:</p>
<p><img src="https://lpcvoid.com/img/cert_user_id_request.png" alt="User identification request under Firefox/Ubuntu"></p>
<p>If they miss this certificate, their request does not even hit the backend, and instead a 403 error is sent. This allows an additional barrier, as people without this certificate can&#x27;t even attempt to exploit weaknesses in the services being run. Another nice advantage is that this makes username&#x2F;password leaks a lot less an issue, since there is not even a way to use stolen credentials without also stealing the certificate. Fortunately, on modern devices such as phones these certificates are stored in a secure hardware enclave, and are incredibly hard to extract, if even at all possible. Certainly not in the realm of a non-state actor. On desktop OSes, the certificates are easier to extract, but that&#x27;s unfortunately not easily solvable short of a USB or smart card enclave.</p>
<p>Anyhow, in case you&#x27;re interested in doing the same, the process is pretty simple. The following outlines the process. OpenSSL will ask you some details, just enter what you wish the certificate to say later on. If asked for passwords, make sure they are secure, and don&#x27;t forget them - specially when it comes to the CA private key password, since you&#x27;ll need that to sign new certificates!</p>
<p>First - generate a Certificate Authority. This is defined by a public&#x2F;private keypair and a x509 certificate.</p>
<pre><code>openssl genrsa -des3 -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt</code></pre>
<p>I chose 10 years validity - you may be interested in less, but that&#x27;s up to you. 4096 bits should be standard for new RSA keypairs. Next, we generate a keypair for the new user.</p>
<pre><code>openssl genrsa -des3 -out USERNAME.key 4096</code></pre>
<p>Following that, we generate a Certificate Signing Request (CSR).</p>
<pre><code>openssl req -new -key USERNAME.key -out USERNAME.csr</code></pre>
<p>Almost done - we now sign this CSR with the CA key you generated earlier.</p>
<pre><code>openssl x509 -req -days 1460 -in USERNAME.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out USERNAME.crt</code></pre>
<p>Here, I chose four years, so 1460 days. This is round about the lifetime that most of my users keep personal devices for, such as phones. We are a wasteful civilization :(. Also - the serial should be incremented each time you sign a new CSR.</p>
<p>Finally - bundle the signed certificate and keypair into a PKCS12 pfx file for easy import.</p>
<pre><code>openssl pkcs12 -export -out USERNAME.pfx -inkey USERNAME.key -in USERNAME.crt -certfile ca.crt</code></pre>
<p>Now, you have both *.pfx and a ca.crt files. The pfx can be imported into a browser or a phone. The ca.crt on the other hand gets used by your webserver. As I said, I use nginx - so we need to configure it properly. You just need to add the following to whatever <code>server</code> block you require the client to be certificate authenticated:</p>
<pre><code>ssl_client_certificate &#x2F;path&#x2F;to&#x2F;ca.crt;
ssl_verify_client optional;</code></pre>
<p>Then, within a <code>location</code> block, add the following conditional:</p>
<pre><code>if ($ssl_client_verify != SUCCESS) {
    return 403;
}</code></pre>
<p>That&#x27;s it - now you have one more barrier in place to protect services from outside access.</p>
<p>To make this a bit easier for you, here is a bash script. It expects a username and an optional parameter for the number of days the pfx shall be valid for. Default is one year. It makes no attempt to check edge cases though, such as if your CA cert is still valid or if the requested days are realistic.</p>
<pre><code>#!&#x2F;bin&#x2F;bash</code></pre>
<pre><code>echo &quot;CA&#x2F;user certificate generation script&quot;</code></pre>
<pre><code># generate new CA keys and cert if they don&#x27;t exist yet
if [ -f &quot;ca.key&quot; ] ; then
        echo &quot;CA cert and keys found, using these for CSR&quot;
else
        echo &quot;Generating new CA private keys and certificate...&quot;
        openssl genrsa -des3 -out ca.key 4096
        openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
fi</code></pre>
<pre><code>if [ -z $2 ] ; then
        userdaysvalid=365
else
        userdaysvalid=$2
fi</code></pre>
<pre><code>username=$1
if [ -z $username ] ; then
        echo &quot;Script expects a single username string as argument! Exiting.&quot;
        exit;
else
        echo &quot;Generating pfx for username $username, valid for $userdaysvalid days.&quot;
        echo &quot;Generating key...&quot;
        openssl genrsa -des3 -out $username.key 4096
        echo &quot;Generating CSR...&quot;
        openssl req -new -key $username.key -out $username.csr
        echo &quot;Signing the generated CSR...&quot;
        openssl x509 -req -days $userdaysvalid -in $username.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out $username.crt
        echo &quot;Generating pfx for import...&quot;
        openssl pkcs12 -export -out $username.pfx -inkey $username.key -in $username.crt -certfile ca.crt
        echo &quot;Done! You can now import $username.pfx into your cert store. You&#x27;ll need ca.crt on the server side.&quot;
fi</code></pre>
]]></description>
    <pubDate>2022-02-06</pubDate>
  </item>
  
  <item>
    <title>Sensible connected home</title>
    <link>https://lpcvoid.com/blog/0009_sensible_connected_home</link>
    <description><![CDATA[<p>Wew lads, it&#x27;s been a while since my last post on here. I have been meaning to write more, and this post is a result of a new strategy - whenever I research a new topic, I&#x27;ll try to document that and post the results here.</p>
<p>So, I wish to dive into the world of home automation. I wish to avoid the buzzword &quot;smart&quot; as much as possible, so I will refer to it as &quot;connected home&quot; instead. Soon, I will be moving into my first house, and so I set off to research. This post will be updated over the course of the next few weeks, as I get my research&#x2F;prototyping done.</p>
<p>There are a few points which are very important to me:</p>
<ul>
<li>I want to be able to see the current electrical load in Watts on certain wall sockets, and ideally also control their state.</li>
<li>Analog to the sockets, I want to be able to see and control light state.</li>
<li>It would be very cool to see which windows are open.</li>
<li>I have not, do not and will never like the cloud. Whatever I do, I want it locally in my LAN and 100% under my control. No outside communication unless I make it so.</li>
<li>I want to use only open source components. If I have to crack open some commercial device to reflash firmware via test points, that&#x27;s fine too.</li>
<li>I have no bus such as KNX built into my house. I would vastly perfer a bus to any RF technology, but I am not sure I can retrofit it easily. I do have empty tubing in the walls though (one per room, down the the power box). If anybody wants to chat about this, shoot me an email!</li>
</ul>
<p>There seem to be three basic categories that you can buy into: <a href="https://en.wikipedia.org/wiki/Zigbee">Zigbee</a>, <a href="https://en.wikipedia.org/wiki/Z-Wave">Z-Wave</a> and WLAN. TL;DR, I chose Z-Wave, and the following is my reasoning.</p>
<p><strong>WLAN</strong></p>
<table>
    <tbody>
        <tr>
            <td>+</td>
            <td>you probably already have one</td>
        </tr>
        <tr>
            <td>o</td>
            <td>fast, which is useless for sensors since they only send small data amounts</td>
        </tr>
        <tr>
            <td>o</td>
            <td>mostly proprietary, sometimes you can flash your own firmware</td>
        </tr>
        <tr>
            <td>-</td>
            <td>very energy hungry</td>
        </tr>
        <tr>
            <td>-</td>
            <td>many small IP routable devices in your home sounds bad</td>
        </tr>
        <tr>
            <td>-</td>
            <td>2.4 GHz congestion is bad</td>
        </tr>
    </tbody>
</table>
<p>This seems like the worst, so I&#x27;ll get it out of the way first. There are absolute shitloads of &quot;smart&quot; devices that implement connectivity using off-the-shelf chips such as ESP-8266 or <a href="https://developer.tuya.com/en/docs/iot/tuya-sandwich-wifi-and-ble-soc-board-BK7231N?id=Kao72e6net3bs">BK7231N</a>. They all have in common that they will happily connect to your WLAN and phone home into the cloud. The allure of these is that even people with absolutely no technical abilities can use them. The downside is... everything else. They are privacy invasive, energy hungry and not under your control. Sometimes, you can flash open source firmware on them (see <a href="https://github.com/ct-Open-Source/tuya-convert">here</a> as an example for a broad range of devices based off ESP-8266 and derivates), but this will still not solve another hard problem:  use a dozen of these devices and you&#x27;ll rapidly congest your 2.4GHz band. You (and your neighbors) won&#x27;t like that. Finally, WLAN is energy hungry and it&#x27;s not feasable to deploy small battery powered sensors, such as temperature or water sensors, that can run for years on a battery. So WLAN is out of the race.</p>
<p><strong>Zigbee</strong></p>
<table>
    <tbody>
        <tr>
            <td>+</td>
            <td>cheap</td>
        </tr>
        <tr>
            <td>+</td>
            <td>widespread</td>
        </tr>
        <tr>
            <td>+</td>
            <td>low energy</td>
        </tr>
        <tr>
            <td>-</td>
            <td>walled gardens by many different manufacturers, no guarantee that devices are compatible</td>
        </tr>
        <tr>
            <td>-</td>
            <td>based on 2.4GHz</td>
        </tr>
    </tbody>
</table>
<p>Zigbee is a step into the right direction. It&#x27;s a non-IP low energy protocol unfortunately based on ISM radio bands, which is 2.4GHz in most of the world. Its layers are defined in <a href="https://en.wikipedia.org/wiki/IEEE_802.15.4">IEEE 802.15.4</a>. It&#x27;s a mesh net, meaning that each mesh member uses other members to reach the gateway, which is the root of the mesh network and actually communicates with the outside. Zigbee devices on their own never communicate directly with any device outside of their mesh. There are many different (often proprietary) gateways, but of course we want the open source solutions. Fortunately, there are cheap USB dongles which work just fine and can work with a wide variety of devices. Zigbee is incredibly cheap, very widespread, and free for anybody to implement. Unfortunately, this also leads to large discrepancies in manufacturers and their implementation. Zigbee provides a means to transport data, and nothing else. The application layer is defined by the manufacturer, which implies a lot of walled gardens. In essence, any Zigbee network by a manufacturer will be exclusive to that one manufacturer. It gets more extreme when people buy into multiple manufacturers, and have multiple mesh networks at the same time, further congesting the 2.4 GHz band. The gateways have to do the heavy lifting here and speak many different protocols for each company&#x27;s devices, in order to expose them in a consistent manner to the gateway host. One product that claims to be able to do this is the Conbee II gateway, and I have ordered one to test.</p>
<p><strong>Z-Wave</strong></p>
<table>
    <tbody>
        <tr>
            <td>+</td>
            <td>widespread, but less so than Zigbee</td>
        </tr>
        <tr>
            <td>+</td>
            <td>very reliable due to mandatory certification and testing</td>
        </tr>
        <tr>
            <td>+</td>
            <td>low energy</td>
        </tr>
        <tr>
            <td>+</td>
            <td>certification includes security testing</td>
        </tr>
        <tr>
            <td>+</td>
            <td>runs on 868MHz in Europe</td>
        </tr>
        <tr>
            <td>o</td>
            <td>standard opened up recently, but too soon to see if that helps with adoption</td>
        </tr>
        <tr>
            <td>o</td>
            <td>around double/triple as expensive as Zigbee devices, but still okay-ish</td>
        </tr>
        <tr>
            <td>-</td>
            <td>could potentially be deplaced at some point due to large companies flocking to Zigbee</td>
        </tr>
    </tbody>
</table>
<p>Z-Wave is, like Zigbee, also a mesh network which is composed of a bunch of clients (the sensors&#x2F;actors and whatnot) and a gateway. Z-Wave is a proprietary standard and defined by Sigma Designs, which was then bought by Silicon Labs. In fact, it has quite a convoluted history, starting out as technology using an unlicenced frequency band in 1999. It really was the wild west back then it seems :). From an &quot;how open is the system?&quot; standpoint, it&#x27;s unfortunate that Z-Wave only has a single vendor producing chips, even though this <a href="https://z-wavealliance.org/z-wave-specification-press-release/">could change soon</a>. It feels like vendor lockin, but maybe i&#x27;m wrong here and rigirous standardization and gatekeeping makes sense from a product perspective. In order to be granted a licence to use Z-Wave, products have to undergo large amounts of certification for interoperability. The upside is that it&#x27;s supposedly guaranteed that devices will be interoperable with each other and with whatever gateway you want to operate. This is a sore point to me though, and I only accept it at all because I control the gateway and can verify nothing leaves my local mesh into the direction of the internet. Also - only the certification process is the proprietary part, most of the specifiation is <a href="https://www.embeddedcomputing.com/application/consumer/smart-home-tech/z-wave-opens-up-as-smart-home-connectivity-battle-closes-in">open</a> it seems.</p>
<p>Anyhow, the biggest advantage in my opinion is that it uses a 868 MHz frequency in Europe where I live, so no fighting with my computers over frequencies. The maximum device count for Z-Wave is 232, and this could be seen as a drawback, but I actually doubt I&#x27;ll ever exceed even 50 devices. And maybe the limit is even good - Z-Wave imposes some limitations, such as hop count to gateway (4 hops maximum, Zigbee does not impose limits here, which could lead to a large RTT). A downside is the increased price - due to certification, manufacturers can&#x27;t outprice Zigbee devices. This may hurt if you plan on using many sensors&#x2F;actors.</p>
<p><strong>Z-Wave setup</strong></p>
<p>I went with an Aeotec Z-Stick Gen5 as a gateway, and using <a href="https://github.com/domoticz/domoticz">Domoticz</a> on my little x86_64 server that&#x27;s sitting in my pantry, running in a Docker container. Still waiting for sensors to arrive for preliminary testing.</p>
<p>...to be continued...</p>
]]></description>
    <pubDate>2022-01-31</pubDate>
  </item>
  
  <item>
    <title>Dejunking and reconstructing bytecode</title>
    <link>https://lpcvoid.com/blog/0008_python_bytecode_dejunking</link>
    <description><![CDATA[<p>In my last blog post, <a href="https://lpcvoid.com/blog/0007_wows_python_reversing/index.html">here</a>, I figured that the Python bytecode could be obfuscated somewhat. Turns out it is, and this post will be a run down of my findings and the problems I encountered. It is a work in progress, so there could be updates at some point. I&#x27;ll change the title in that case.</p>
<p>This post concerns the obsolete 2.7 version of Python, since that&#x27;s what the target application embeds.</p>
<p>Python bytecode is pretty simple. You have one byte of opcode, followed by two optional bytes of <strong>argument</strong> (that&#x27;s what cPython, the reference implementation, calls it). A list of opcodes is contained in opcode.h. Opcodes that are smaller than 90 do not expect any argument, and are thus only one byte in size.</p>
<p>There exist disassemblers and even decompilers for this bytecode, and they work well for standard bytecode that was not messed with. The one I am looking at has two obfusications (that I am aware of at this point) though. Let&#x27;s look at them.</p>
<h4 id="insertion-of-junk-instructions-to-confuse-disassemblers">Insertion of junk instructions to confuse disassemblers</h4>
<p>Normally, a disassembler would look at a set of instructions and decode them into some sort of text representation sequentially. For example, take the following bytecode:</p>
<pre><code>6401006400006c00007d0000</code></pre>
<p>This decodes to:</p>
<pre><code>LOAD_CONST      1 (-1)
LOAD_CONST      0 (None)
IMPORT_NAME     0 (sys)
STORE_FAST      0 (sys)</code></pre>
<p>Which is simple enough. The disassembler relies on the bytecode being correct though for a number of factors. For one, it decides how many bytes of argument to read by looking at the integer value of the opcode. If it&#x27;s under 90, it treats the bytes after the opcode as a new opcode and not as argument data. This means that it&#x27;s possible to write a program that can stop disassemblers by taking bytecode, inserting junk instructions or random bytes into it at some place, and then recalculate jumps and branches to account for this. The VM does not care much when executing this manipulated bytecode, as long as the obfuscator took care to direct it around the junk using jumps or other means of control flow. But a disassembler that reads code top down without dynamic analysis will run into these junk areas and produce completely wrong output, or even crash.</p>
<p>So, what can we do against this? I decided to write a small program which takes bytecode and recursively traces it, running along all branches and marking code paths which can actually be executed. This is easy because there aren&#x27;t that many branching opcodes. In fact, these are the ones I identified:</p>
<pre><code>JUMP_FORWARD            110
JUMP_IF_FALSE_OR_POP    111
JUMP_IF_TRUE_OR_POP     112
JUMP_ABSOLUTE           113
POP_JUMP_IF_FALSE       114
POP_JUMP_IF_TRUE        115
FOR_ITER                93</code></pre>
<p>All non executed areas of code then get a <em>nop</em> (0x9), so the disassembler does not stumble over junk bytes anymore. This is simple, but actually does not solve another problem: What happens when the junk is located in an actually executable path? Most of the time, it is located behind a <em>JUMP_ABSOLUTE</em> instruction, and that&#x27;s easy enough to detect with the static algorithm I wrote. Consider this though:</p>
<pre><code>LOAD_CONST         3
POP_JUMP_IF_TRUE   42 
...junk bytes here...
IMPORT_NAME        1  #address 42</code></pre>
<p>This code pushes the const at index 3 onto the stack. <em>POP_JUMP_IF_TRUE</em> checks the top of the stack, and jumps to the absolute bytecode offset if it is (after popping the top). In this case, as long as the const array has a true value at offset 3 (which is easy to guarantee), it will always jump, thus being equivalent to <em>JUMP_ABSOLUTE</em>. This is something my small static analyzer does not know though currently. I need to teach it to read const values like that and evaluate those jumps, then it can skip those junk instructions too.</p>
<h4 id="insertion-of-junk-constants-and-local-names">Insertion of junk constants and local names</h4>
<p>Another thing that this obfuscator does is insert random strings into both the constant and local names. It then goes through the bytecode and increments all <em>LOAD_CONST</em> and <em>LOAD_NAME</em> opcode arguments by the number of junk strings added to the respective arrays. This is a simple but effective obfuscation. Fortunately, it is also simple to revert if you can identify junk locals and global names by some shared attribute. In this case, the junk names include at least one space character.</p>
<h4 id="open-questions">Open questions</h4>
<p>I wonder why they did not obfuscate opcodes, or even change the opcode mapping. That would have been a royal pain, since then I would have needed to figure out what function maps to what integer value. Or, why did they not obfuscate arguments or length of argument bytes? This too would have taken some time to reverse. Writing a junk code insertion routine is a lot more challenging than changing a bit of cPython code. So why did they not go for these low hanging fruit? Maybe I do not know the Python VM enough to understand this. If you know or want to wager a guess, I would be interested to hear.</p>
]]></description>
    <pubDate>2020-11-08</pubDate>
  </item>
  
  <item>
    <title>Deobfuscation and dumping the Python scripts of World of Warships</title>
    <link>https://lpcvoid.com/blog/0007_wows_python_reversing</link>
    <description><![CDATA[<p>A while ago I wrote a blog post about patching Wargaming&#x27;s World of Warships. Now, I was interested in attacking their Python subsystem, which might be interesting for various intents. This post attempts to document my research.</p>
<p>WoWs uses Python within their engine to program their game logic. These Python scripts are obfusicated, so that programs like <a href="https://github.com/rocky/python-uncompyle6/">decompyle6</a> will not work out of the box. They actually modified a few functions of cpython to include an obfusication scheme.</p>
<p>First of all, a quick look at the files that come with WoWs reveals that there exist a scripts.zip within a subdirectory. It is a regular zip file and can be opened with a utility such as 7zip. Kind of surprised that they did not mangle the zip format to dissalow this, but that was maybe decided against when developing this feature because it will stop the devs themselves to quickly check their archives. </p>
<p>The archive contains a boatload (pun intended) of pyc files, which are python scripts compiled to a bytecode representation. This format, under normal circumstances, only allows for faster loading of files, since the loader can skip the tokenizing&#x2F;parsing step and directly load the already processed bytecode for execution. In this case though, decompyle refuses to dissasemble the pyc files.</p>
<p>Time to fire up x64dbg and IDA.</p>
<p>It quickly becomes apparent that WoWs uses zipimport for processing that whole zip file at once. </p>
<p>As far as I can tell, they did not modify anything here, which makes sense because their obfusication regards Python internals. Fortunately for us, we are dealing with a Python function, which is thus open source. We can cheat here a bit by checking out <a href="https://github.com/qsnake/python/blob/master/src/Modules/zipimport.c">zipimport source code</a>.</p>
<p>Here, the function we are interested in is called <strong>zipimporter_load_module</strong>. This function loads an entire module and imports it into the current namespace. By placing a breakpoint on the <strong>PySys_WriteStderr</strong> function and writing a small x64dbg script, we can actually list the modules that are loaded by the game.</p>
<pre><code>bp 0x01CC1425,1
start:
log &quot;{s:[ebp-4]}&quot;
run
goto start</code></pre>
<p>The log tab then contains entires like this:</p>
<pre><code>153.INT3 breakpoint at worldofwarships32.01CC1405 (01CC1405)!
154.&quot;mbad083ab&quot;
155.INT3 breakpoint at worldofwarships32.01CC1405 (01CC1405)!
156.&quot;package_profiles_helpers&quot;
157.INT3 breakpoint at worldofwarships32.01CC1405 (01CC1405)!
158.&quot;GameParams&quot;
159.INT3 breakpoint at worldofwarships32.01CC1405 (01CC1405)!
160.&quot;mbd1b28d9&quot;
161.INT3 breakpoint at worldofwarships32.01CC1405 (01CC1405)!
162.&quot;m15fb041b&quot;
163.INT3 breakpoint at worldofwarships32.01CC1405 (01CC1405)!
164.&quot;ConsumablesConstants&quot;</code></pre>
<p>Which tells us not much, since these are actually just the files that we already saw within the zip, but at least we found an entry point to start digging around. </p>
<p>Upon digging around more, and following the control flow, I stumbled upon the <strong>unmarshal_code</strong> function from the <strong>zipimport</strong> cpython module. This looks exactly like the sort of thing we are looking for. </p>
<p>First off, a bit of information about the pyc file structure, it is very simple:</p>
<pre><code>int32   magic;
int32   timestamp;
uint8[] data;</code></pre>
<p>Since pyc files can be empty and still valid, it&#x27;s not surprising that the function checks the size first:</p>
<pre><code>.text:0104022B 014                 cmp     ebx, 8                  ; ebx contains size of pyc
.text:0104022E 014                 jge     short loc_1040271       ; check (size =&gt; 8); jump to loc_1040271 if true
.text:01040230 014                 mov     esi, dword_8F53DD4      ; 
.text:01040236 014                 mov     ecx, offset aBadPycData ; &quot;bad pyc data&quot;
.text:0104023B 014                 call    PyErr_SetString_0       ; document what&#x27;s wrong using cpython PyErr_SetString</code></pre>
<p>the next two checks are simple too, namely checks for the magic number and the timestamp. The magic number it expects is 0x0A0DF303, which corresponds to python 2.7. You are behind the times, Wargaming :)</p>
<p>Up to this point there where no surprises. Once all the trivial pyc checks are done, the game starts unmarshalling code.</p>
<pre><code>.text:0104033C 014                 lea     edx, [ebx-8]    ; length of code, minus the 8 byte header
.text:0104033F 014                 lea     ecx, [edi+8]    ; marshalled code starts at 8. byte
.text:01040342 014                 call    PyMarshal_ReadObjectFromString</code></pre>
<p>As far as I can see, <strong>PyMarshal_ReadObjectFromString</strong> does nothing out of the ordinary. It simply wraps <strong>r_object</strong> call which unmarshals a string value (string being a byte array here). But wait, what is that sneaky call below, a few bytes before <code>ret</code>?</p>
<p>Magic starts happening here. To understand what happens here, we need to look at the structure of a pyc file yet again.</p>
<p>The section I marked <strong>data</strong> in the struct is marshaled python bytecode. This marshaling is a recursive algorithm that wraps a lot of different data types (strings, longs, bytes, even complex types such as lists and tuples) within one large root <strong>code</strong> object. This code object has several different sub members. One of it is a section called <strong>co_code</strong>, which contains the raw bytecode. Another one is <strong>co_consts</strong>, which contains a list of indexed constants that opcodes such as <strong>LOAD_CONST</strong> use. Here, wargaming did something strange. It actually misuses the const section of the root code object as a data storage for storing encrypted data. This data then gets XORed bytewise with the content of the code section, using the size of the code section in modulus to prevent overflow.</p>
<pre><code>...
.text:00F3DF76 02C                 cmp     esi, eax             ; exit if we are at end of const section
.text:00F3DF78 02C                 jge     short loc_F3DF8E
.text:00F3DF7A 02C                 mov     eax, esi             ; move index to eax for idiv
.text:00F3DF7C 02C                 mov     ecx, [ebp+var_C]     ; offset to code buffer
.text:00F3DF7F 02C                 cdq                          ; sign extension
.text:00F3DF80 02C                 idiv    ebx                  ; int div for mod operation
.text:00F3DF82 02C                 mov     al, [edx+ecx]        ; edx contains remainder, get next code byte      
.text:00F3DF85 02C                 mov     ecx, [ebp+var_14]    ; get const buffer offset
.text:00F3DF88 02C                 xor     [esi+ecx], al        ; xor code byte with const byte
.text:00F3DF8B 02C                 inc     esi                  ; increment index
.text:00F3DF8C 02C                 jmp     short loc_F3DF30     ; continue for next index
...</code></pre>
<p>If we type this out (using python, how ironic), we get something like this:</p>
<pre><code>f = open(sys.argv[1], &quot;rb&quot;) #some pyc passed as parameter
magic = f.read(4)
moddate = f.read(4)
codeobj = marshal.loads(f.read()) #unmarshal the code object
size_code = len(codeobj.co_code)
size_const = len(codeobj.co_consts[3])
encrypted_code = codeobj.co_code[:]
decrypted_const = []
for x in range(size_const):
    decrypted_const.append(ord(codeobj.co_consts[3][x]) ^ ord(encrypted_code[x % size_code]))</code></pre>
<p>After this part is done, we can see a long string, which looks suspiciously like base64. And it actually is! After decoding, we get this:</p>
<pre><code>00000000  78 9C CD 59 0B 78 1C 55 15 3E B3 9B 36 EF F4 41  xœÍY.x.U.&gt;³›6ïôA
00000010  D3 24 6D 69 A7 05 4A 48 9B B6 69 A1 49 6B 5B 4A  Ó$mi§.JH›¶i¡Ik[J
00000020  A9 08 02 05 B6 20 48 29 61 B3 33 9B CC 66 B3 9B  ©...¶ H)a³3›Ìf³›
....</code></pre>
<p>Hold up, i&#x27;ve seen these two first bytes before! Indeed, they are zlib header bytes. They are the CMF and FLG bytes, defined in the <a href="https://tools.ietf.org/html/rfc1950#page-4">standard</a>. CMF means &quot;Compression Method and flags&quot; and describes both the method (such as deflate) and the compression window size. FLG are some flags regarding the compression. In short, 78 9C boil down to the default zlib compression with deflate.</p>
<p>After zlib decompression, we are greated with readable strings, yay!</p>
<pre><code>000005A0  6D 6F 64 75 6C 65 74 08 00 00 00 5F 5F 66 69 6C  modulet....__fil
000005B0  65 5F 5F 74 08 00 00 00 67 43 50 4C 42 78 38 36  e__t....gCPLBx86
000005C0  74 0A 00 00 00 31 36 30 31 36 36 39 33 33 32 69  t....1601669332i
000005D0  00 00 00 00 74 0B 00 00 00 63 6F 6C 6C 65 63 74  ....t....collect
000005E0  69 6F 6E 73 74 09 00 00 00 75 74 66 38 5F 74 65  ionst....utf8_te
000005F0  73 74 74 08 00 00 00 63 6F 70 79 5F 72 65 67 69  stt....copy_regi</code></pre>
<p>Now, lets add the 8 header bytes back to make a valid pyc file and try running the result through decompyle6.</p>
<pre><code>ImportError: Ill-formed bytecode file test.pyc
&lt;class &#x27;KeyError&#x27;&gt;; &quot;marshaltype key &#x27;{&#x27; (dict) not implemented&quot;  </code></pre>
<p>Well that&#x27;s a bummer. xdis actually did not implement dict unmarshalling, and I don&#x27;t know why. I need to do more research about Python bytecode, maybe this is the result of more obfusication at some bytecode level. I&#x27;ll get back here once I found out more.</p>
]]></description>
    <pubDate>2020-10-29</pubDate>
  </item>
  
  <item>
    <title>Patching the Wargaming launcher to allow multiple clients</title>
    <link>https://lpcvoid.com/blog/0006_wargaming_multiclient</link>
    <description><![CDATA[<p>This post concerns the Windows win32 API, and is probably not very interesting if you have patched a binary before. For anybody who wishes to read about an easy patch, I hope you enjoy.</p>
<p>I have been playing a video game called World of Tanks for quite some time now with my father. For a few weeks now, we have been going back and forth between World of Tanks, and Wargamings newer installment, World of Warships. Unfortunately, you cannot by default launch both game clients at the same time. It&#x27;s time we fix this.</p>
<p>When you want to implement a restriction like that, there are pretty many options:</p>
<ul>
<li>You can create some file on start that gets deleted when your tools exits. Crusty, comes with many problems.</li>
<li>You can search for the window title of your tool, or the name of your ELF&#x2F;PE file. Better, but what if a user uses a program with the same name?</li>
<li>You could use a mutex or an atom, or whatever global named object your OS offers that we can abuse for testing if something already exists.</li>
</ul>
<p>The last point has many advantages for the mutex. First, a mutex actually gets closed by the system once your application terminates (or crashes), while an atom does not. This automatically prevents a whole class of bugs that would arise if we needed to manage the flags lifecycle ourselves. Second, the implementation is extremely simple, we only need two functions on Win32: <a href="https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw">CreateMutexW</a> and <a href="https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-openmutexw">OpenMutexW</a>. Or the *A variants if you&#x27;re inclined.</p>
<p>The tool checks for a mutex with a globally known (and unique) name. If it does not already exist, it creates it and continues execution. If it exists, the tool can exit with some sort of nag screen. Simple right?</p>
<p>Well, that&#x27;s what the Wargaming launcher does. The API to this launcher lives in wgc_api.dll, which is loaded dynamically by every wargaming game. There, it tests for a mutex named <em>wgc_running_games_mtx</em>. Of course, there is lots of wrapping going on here, and the OpenMutexW function is called by 3 levels of indirection. It boils down to this:</p>
<pre><code>.text:251C43C0    push    edi
.text:251C43C1    push    eax
.text:251C43C2    lea     eax, [ebp+var_C]
.text:251C43C5    mov     large fs:0, eax
.text:251C43CB    mov     [ebp+var_10], esp
.text:251C43CE    mov     esi, ecx
.text:251C43D0    cmp     dword ptr [esi+40h], 0
.text:251C43D4    lea     eax, [ebp+var_15]
.text:251C43D7    mov     [ebp+var_4], 0
.text:251C43DE    lea     ecx, [ebp+var_24]
.text:251C43E1    mov     [ebp+var_24], eax
.text:251C43E4    setnz   [ebp+var_15]
.text:251C43E8    lea     eax, [ebp+arg_0]
.text:251C43EB    mov     [ebp+var_1C], esi
.text:251C43EE    push    offset wgc_api.252C5E7C
.text:251C43F3    mov     [ebp+var_20], eax
.text:251C43F6    call    sr_test_mutex   ; nop this and set eax to 0
.text:251C43FB    test    eax, eax
.text:251C43FD    js      short loc_251C4433
.text:251C43FF    cmp     dword ptr [esi+38h], 0
.text:251C4403    jz      short loc_251C4433
.text:251C4405    cmp     dword ptr [esi+3Ch], 8
.text:251C4409    lea     eax, [esi+28h]
.text:251C440C    jb      short loc_251C4410</code></pre>
<p>sr_test_mutex (my naming skills are sick) returns a non zero value if the mutex is found. This value is always static, and represents a static error code they must have definded in some header. Anyhow, we need to survive the <code>test eax, eax</code>. Fortunately, windows is a fan of <a href="https://docs.microsoft.com/en-us/cpp/cpp/cdecl?view=vs-2019">__cdecl</a>, which means the caller cleans up the stack and we can get away with nuking parametrized calls. So that&#x27;s what we do.</p>
<pre><code>.text:251C43F6    xor     eax,eax
.text:251C43F8    nop
.text:251C43F9    nop
.text:251C43FA    nop
.text:251C43FB    test    eax, eax
.text:251C43FD    js      wgc_api.251C4433   </code></pre>
<p>Since js tests the sign flag, we want <code>test eax,eax</code> to never set this flag, and this in turn means eax must always be 0. Since we nuked the CALL instruction, we have 5 bytes to set eax to 0. The shortest way to do that is xoring eax with itself, which sets it to 0 by the nature of the xor operation. This consumes two bytes, so the three <code>nop</code>s below are just padding and do nothing. That&#x27;s it, we are done.</p>
<p>Here is a patch file for x64dbg, in case anybody wants it. The wgc_api.dll it works on has a sha256 sum of c9d48273984bc369a19b44b01b671c629acb6ec2886e52231f1854402d559258.</p>
<pre><code>&gt;wgc_api.dll
000043F6:E8-&gt;33
000043F7:65-&gt;C0
000043F8:00-&gt;90
000043F9:00-&gt;90
000043FA:00-&gt;90</code></pre>
]]></description>
    <pubDate>2020-10-25</pubDate>
  </item>
  
  <item>
    <title>A quick rundown of the BEST2 instruction set.</title>
    <link>https://lpcvoid.com/blog/0005_best2_instructionset</link>
    <description><![CDATA[<p>Ever since I published <a href="https://github.com/lpcvoid/bmw-best2-vm">my first BEST2 VM</a>, I have gotten questions about the instruction set now and then. Recently I published my vastly improved and fairly complete <a href="https://github.com/lpcvoid/bmw-better-vm">second version</a>, which is fairly stable and written in C++. This post aims to explain what I know about the instruction set, some people perfer this format over reading code.</p>
<p>The BEST2 instruction set was initially designed (as far as I can tell) by <a href="https://softing.com">Softing</a>. It is used by a virtual machine that is part of a runtime environment which can execute automotive diagnostic jobs. These jobs are written by engineers in a C like syntax and compiled to bytecode. The API is fairly rich, and includes functions for communication, large amounts of helper functions for boolean and integer logic, rich output formatting and data manipulation functions, shared memory and a small embedded database that operates on tables. The API functions are partly written directly in BEST2 assembler and linked by the compiler at compiletime.</p>
<p>The VM itself is a stack and register machine. Arguments to functions are passed by registers. There are 8 long (32 bit) registers, 16 string registers and 8 float registers. The long registers are (simularly to x86) stacked in a way that allows easy access to individual bytes&#x2F;words.</p>
<pre><code>struct bmwBESTVMStackedRegister {
    union {
        uint32 rl;
        struct {
            uint16 rw0;
            uint16 rw1;
        };
        struct {
            uint8 rb0;
            uint8 rb1;
            uint8 rb2;
            uint8 rb3;
        };
    };
};</code></pre>
<p>The string registers are limited to 1024 bytes and initially empty. Operations can and will operate on whatever indices they please though, and the register is expected to grow to the index that was referenced. For example, consider following code:</p>
<pre><code>clear S1
move S1[2], S2[RL1:RL3]</code></pre>
<p>In the first line, we clear S1, which sets all bytes to 0. The length after this instruction is also 0x00. In the second line, we copy some bytes from S2 to S1 begining at offset 2. This means that the first 2 bytes of S1 are now 0x00, and the bytes afterwards are filled by the contents of S2 beginning from the value of RL1 (register long 1) and counting RL3 bytes. This was a bit of a pitfall, since I kind of expected some sort of initialization, but nope.</p>
<p>The VM has multiple flags, but nothing special - sign, carry, overflow and zero are pretty much what you would expect. There is a special one that acts as a trap register for catching or allowing errors, which some jobs use to great extent and which is a bit annoying. </p>
<p>Instructions are of a static length and encoded in two bytes. The first contains an opcode for a instruction, and the second contains information about the addressing mode for the operand. Each addressing mode is encoded in the upper respective lower nibble of the second byte, which effectively limits the operand count to two. Sane choice.</p>
<hr />
<p>There are 15 operand addressing modes. The first four describe registers (regS, regAB, regI, regL). The next four address immediate values (imm8, imm16, imm32, immStr). The fun starts afterwards, where we get into the register indexing stuff. The indexing addressing scheme only operates on string registers, so if you ever find a long register referenced here, you have a decoding error. I&#x27;ll address these operand schemes briefly.</p>
<p><strong>IdxImm:</strong> </p>
<p>Immediate indexing. Used a lot when parsing xsend responses, which come from the car.</p>
<p>Example: move RS1, RS2[2]</p>
<p><strong>IdxReg:</strong> </p>
<p>Indexing using the contents of another register.</p>
<p>Example: move RS1, RS2[RAB2]</p>
<p><strong>IdxRegImm:</strong></p>
<p>This one had me stumped for a bit, since I could not quite imagine why they would implement this. I would guess that it&#x27;s an optimization for smaller code. Imagine you have a result of some operation in a long register RL2. You wish to use that to index a string register and copy data to RS2, but need to add a constant 3 to it (which is a common operation for processing diagnostic payloads). You would need to execute something like this:</p>
<pre><code>move RL3, RL2
add RL3, 3
move RS2, RS1[RL3]</code></pre>
<p>With IdxRegImm, you can do this:</p>
<pre><code>move RS2, RS1[RL2+3]</code></pre>
<p>This saves a few bytes and speeds things up.</p>
<p><strong>IdxImmLenImm:</strong></p>
<p>Indexing using two constants. The first one is the offset, and the second one is the lenght. This is comparable to a memcpy.</p>
<p>Example: move RS1, RS2[3:4]</p>
<p>This would copy 4 bytes to RS1, beginning at index 3 of RS2.</p>
<p><strong>IdxImmLenReg, IdxRegLenImm, IdxRegLenReg</strong></p>
<p>Same as IdxImmLenImm, but different sources of index.</p>
<hr />
<p>After decoding the instructions and operands, the instruction is executed. This is the annoying part, as there are many opcodes that need to be implemented. See my VM for most of them, but I ommited a few because I never encountered them in the wild while dealing with Fxx and Exx vehicles.</p>
<p>The shared memory is accessed by shmset and shmget. Both opcodes expect some access key, which enables multiple shared memories at the same time. I have never seen this used though, and instead an empty string is passed. Other than that, shared memory behaves like a string register.</p>
<p>Tables are a central part of the VM, since a lot of data is stored there. This can include simple strings describing portions of a diagnostic payload, up to complex tables detailing how response values are to be manipulated to get a human readable response.</p>
<p>The jobs itself are located in .prg files, which are obfusicated in a very lazy fashion (why even bother). The file format is not very complicated, see my VM for a parser.</p>
<hr />
<p>I will add more to this overview with time, or whenever somebody asks.</p>
]]></description>
    <pubDate>2020-10-17</pubDate>
  </item>
  
  <item>
    <title>On the topic of browser extension security.</title>
    <link>https://lpcvoid.com/blog/0004_sellout_browserext</link>
    <description><![CDATA[<p>While reading on &#x2F;r&#x2F;firefox today, I stumbled upon <a href="https://old.reddit.com/r/firefox/comments/jbua53/nanoadblocker_nanodefender_is_malware_now/">a discussion</a> about how a browser extension was sold by a developer to a third party. This apparently stems from the original author not having the time for maintaining it anymore (why not just say so and step back then?). I do not want to discuss the handling of this issue here, what I am more interested in is the implication. The extension has north of 100k users, most of which would probably describe themselves as privacy aware. This is probably a valuable target demographic for malicious actors, and so far it&#x27;s looking like the new owners don&#x27;t have the users best interest in mind.<br /></p>
<p>This is too easy. I mean, this time the internet noticed and there was a huge outcry. The problem here centers around distribution and gatekeeping. One of the main arguments Google and other proponents of walled gardens keep bringing up in favor of their stores is security. It&#x27;s supposed to be harder to distribute malicious code over these stores due to a plethora of automatic analysis that they do. Except this time, it apparently didn&#x27;t work and code got pushed successfully. If this doesn&#x27;t work reliably, I&#x27;d much rather install my extensions without going over Google (or Mozilla). If the gatekeepers fail, why even bother. Uploading an extension to the Chrome webstore just requires a zip file with the extension code and assets bundled. As far as I know, there isn&#x27;t even a method to link a repo or commit hash to the submission for verification. So what is it worth to host your extension on github?</p>
<p>Give me an open build service (which I could host myself) which builds extensions, lets me download them in packaged form and then install them manually. Updates can then be optionally enabled by giving the browser a way to automatically check the build service for new releases, and then asking the user for permission to update.</p>
<p>In the future, I&#x27;d also like stringent requirements for large extensions. For one, developers should be required to use some sort of U2F scheme to verify that they indeed did author commits. Just a login isn&#x27;t sufficient here due to how easy it is to transfer.</p>
]]></description>
    <pubDate>2020-10-17</pubDate>
  </item>
  
  <item>
    <title>Using a static site generator now.</title>
    <link>https://lpcvoid.com/blog/0003_blogc</link>
    <description><![CDATA[<p>I designed this site quite a long time ago, and never really used it too much. Then, a few days ago, I decided to start this blog to see if I would enjoy this type of writing. As it turns out, so far I do. It also helps me practice touch typing, which I have recently started learning. Anyhow - since I plan to write at least a post a week, this blog needed to be built on a more solid approach other than copying template html files. I needed a site generator.</p>
<p>I wanted one that was minimal, preferrably written in C, and which supports nice markup features along with not sticking Javascript everywhere. While I am sure that there are many such tools out there, my choice fell on blogc because of its incredibly well written documentation, along with its fast execution speed. </p>
<p>The initial port was trivial, as blogc is built pretty straight forward. You define a template, and then call blogc to compile html from this template. It can use plain text files in order to insert content into posts, which allows me to concentrate on the content when writing, instead of thinking about the way it looks later. The plaintext that was used to generate this post, for example, is located <a href="https://lpcvoid.com/site/blogposts/0003_blogc.txt">here</a>.</p>
<p>Then I wanted to show the last three posts on the index page. First I thought I had run into a problem, since there is no way to limit the number of blocks within the templating language. I emailed the author, Rafael Martins, and he told me about the filtering mechanics that could be used for this purpose. See the Makefile, the magic parameters are <strong>FILTER_PER_PAGE</strong> and <strong>FILTER_PAGE</strong>. There is even documentation about these parameters, but I missed those for some reason, shame on me.</p>
<p>Anyhow, quite happy with this setup so far. Might need a few css tweaks, but time will tell. I can&#x27;t recommend blogc enough.</p>
]]></description>
    <pubDate>2020-10-01</pubDate>
  </item>
  
  <item>
    <title>GrapheneOS is amazing.</title>
    <link>https://lpcvoid.com/blog/0002_grapheneos</link>
    <description><![CDATA[<img src="https://grapheneos.org/logo.png" alt="GrapheneOS logo" class="blogheaderimage" />
<!-- I should probably rehost this image myself. -->
<p>I am not a fan of buying new phones every year. I have been using a HTC 10 since around 2016, and it has served me well so far thanks to custom roms that prolonged its life. The main problem with old phones is security related though, as most manufacturers simply stop patching their firmware at some point (HTC released the last firmware update for this phone in 2018 [version 2.41.401.41]). </p>
<p>I always kind of ignored the issue, since I convinced myself that my threat model does not involve state actors or drive by exploitation, but rather tracking and fingerprinting. I ran the phone on LineageOS and MicroG in order to get the Google spyware off it - but that has its own problems, namely:</p>
<ul>
<li>LineageOS uses userdebug builds, which means it also by default enables a lot of debugging attack surface.</li>
<li>It exposes the root user via adb, which is exactly as scary as it sounds.</li>
<li>It requires an unlocked bootloader in order to not brick your phone, which makes threat persistance easier.</li>
<li>It does not offer any sort of rollback protection and as such it is possible to exploit vulnerabilities that have been dealt with by a certain patch level.</li>
</ul>
<p>Note that this is not an attack on the guys at LineageOS - but I started wondering what a more ideal solution would look like. Thats when somebody on reddit mentioned <a href="https://grapheneos.org/">GrapheneOS</a>. I wont quote their security model here, but in short they take it <em>very</em> seriously, <a href="https://grapheneos.org/faq#security-and-privacy">go check it out</a>. GraphaneOS runs only on Pixel devices, which is the reason I got one a few days ago. I only use fdroid as an appstore, and everything I install must be open source. I&#x27;d recommend you do the same, it&#x27;s worth it. Do not forget to re-lock your bootloader after install!</p>
]]></description>
    <pubDate>2020-09-29</pubDate>
  </item>
  
  <item>
    <title>Moving my page to github pages.</title>
    <link>https://lpcvoid.com/blog/0001_githubpages</link>
    <description><![CDATA[<img src="https://lpcvoid.com/img/GitHub-Mark-120px-plus.png" alt="Github logo" class="blogheaderimage" />
<p>Ever since I made this website, I hosted it on a VPS for a few bucks a month running lighttpd. It was fast and worked well, but the server itself was for another project which I have since then frozen for the time being. I noticed that quite a few people had their personal websites on github and using their hosting feature for exposing it over https. I decided to try it.</p>
<p>The first thing I had to decide is if this move is going to make any reader that stumbles accross here more suspect to tracking than previously. I am an avid opponent of any sort of online tracking, and as such this website does its absolute best to not do this. No cookies, no serverside logging, just free html pages. Would the move to github hinder this? Do they employ tracking?</p>
<p>Prior to moving, I hosted on OVH. I&#x27;ve been doing this for around ten years, and they where always good to me. I have no way of knowing if they track user access to any of their servers, but for the sake of this argument I have to assume they do. Github also has insights avaliable to me, but it does not resolve IP addresses or any data that may be used to track an individual. They can probably do that with their non public logs, but OVH could too, which means it comes down to which entity you like more. </p>
<p>I breifly considered running the site on my server at home, which is exposed to the internet for purposes of having access to my stuff over ssh. But this comes with its own downsides, namely privacy (it would always expose my public IP), and that my residential connection is not a high availability line.</p>
<p>So, after a bit of consideration I moved to github pages. One less server to maintain, since I am lazy like that. An added bonus is that my commits are now public, and whoever reads this can laugh at my HTML&#x2F;CSS mistakes.</p>
]]></description>
    <pubDate>2020-09-28</pubDate>
  </item>
  
</channel>

</rss>
