Ambrose's profile


Learning about systems that scale, occasionally taking apart (physical and digital) things. Currently serving NS under the CyberNSF scheme, until 2022.

PCIe Passthrough and libvirt

A pretty awesome feature of any modern hypervisor is the ability to pass through physical devices like USB and PCIe, without using host drivers. Almost any PCIe device can be passed through, including GPUs. There are many guides online discussing GPU passthrough, including this one by @bryansteiner.

GPUs, however, aren't as easy because they are initialised before the operating system starts. Upon boot, the firmware (sometimes called BIOS) initialises the GPU into VGA mode and starts drawing immediately. Any graphical interface (including the Linux console) will be constantly writing frames to the video card. To free up a GPU for use, you can either go purchase and install an additional GPU or instruct the operating system to stop writing to the current one.

I'm not willing to spend on another GPU on my system, therefore I went the latter route. Most guides online mention using two GPUs, but doing so with one GPU will work too by configuring the system over SSH. I used Fedora Server as

Hear a Bézier Curve

TLDR: Here's the fork

A long long long long long time ago, in Talk.CSS #52, Gao Wei presented a talk on Bézier curves "The Obscurities of  Bézier Curves Explained to My Computer Engineer Friends". She showed uses of Bézier curves in user interface animations, some rather interesting history about it and explained the mathematics behind it. Watch the meetup on Engineers.SG!

Anyway, Hui Jing made a funny comment when Gao Wei was demoing Lea Verou's

I prefer to have this with sound, can someone ah please do a [visualisation] using wavelength and then you can like mmm-MMMMM-mmmm. Then ya easier for me to understand I need to hear. (46:06)

So, a long long long long long time later, I forked Lea Verou's repository and added a checkbox to enable sounds. You can try it out here. Should I make a pull request? 😂

Checkbox so small how to press but I lazy lah

Have fun annoying others in your office with the help of Bézier curves!

Password Storage and Security

It's actually reasonable to store passwords in a database in plaintext, if your threat model excludes the possibility that an attacker could gain access to your database.

However, the reason for hashing/salting passwords is to account for the scenario that an attacker could gain a copy of the database or read-only access to the database. Have I Been Pwned lists many site breaches that have resulted in plaintext passwords being shared or sold. Many of which were due to unprotected databases, backups and poor access controls.

With a site user's password, an attacker can easily:

  • Gain access to the user's account on the site
  • Gain access to the user's accounts on other sites

Note that the first objective is also achievable if the attacker has write access to the accounts database.

Hashing passwords

A simple defence against exposure of the user's password is to apply an unrecoverable operation on the password before storage. A hash function is such an operation. Hash functions produce a digest fr

Modifying macOS Display Resolutions

Some backstory:

I have a Dell U2713HM 27" monitor that has DisplayPort, DVI-D, VGA and HDMI input. I have a dock that only outputs HDMI, so to use my MacBook Pro with the Display I must connect with HDMI. However, the Dell U2713HM can't support its full resolution and frame rate over HDMI. I also previously used SwitchResX but the trial period ended and I am not willing to fork up $23 SGD for something that I can do myself and learn something new in the process.

How a monitor advertises its supported resolutions is by sending EDID metadata to the source. (Read more on Wikipedia) macOS has a feature called EDID overrides that can force a specific display to use a specific resolution. The EDID overrides shipped with macOS is found in /System/Library/Displays/Contents/Resources/Overrides. When a new display is connected, macOS will look in this folder by display vendor and product ID, and if that file exists, it will use the EDID data contained within the file instead of the one sent by th

Domains Are Scary

A lot of our Internet depends on domain names. It's the one source we rely on for verifying authenticity of a website. HTTPS/TLS relies upon users checking the domain of the link they click. I really doubt most users will do so.

This is why Chromium has written guidelines for presenting URLs correctly, to reduce the ability of attacker to present to users a trustworthy-looking URL. There is also an extended document in Chromium source code. However, Chrome hasn't implemented it yet. Disappointing...

Source: Google Chromium Project. The guidelines recommends browsers to show the effective top-level domain, which they call "eTLD".

Using long URLs that confuse and mislead users is one thing, but someone paying slight attention to the URL may notice. An attacker can go even further by obtaining domains that remove a dot or replace a dot with a dash. Most organisations will not spend the effort to buy up all these variations of their domain, therefore it's possible to register one of them and

XSS in Modern Web Apps

Recently, as I was scrolling Twitter, I came across this tweet:

The page he links to demonstrates a very simple vulnerability that is frequently overlooked. This is the source code from his sample modified for brevity:

<a href="<?php
    echo htmlspecialchars($_POST['link'], ENT_QUOTES);

At first glance, most will dismiss this as secure due to the escaping performed. However, HTML links are able to execute JavaScript with the javascript: prefix. This is perfectly fine text and will not get escaped by htmlspecialchars. An example of attack input in the above code will be when link is set to javascript:alert(1).

This is frequently overlooked because it is uncommon for link

Make your own Linux!

Building your own Linux OS from scratch is no dark magic, believe me! As long as you feel comfortable using a command line, it isn't such a daunting task, only requiring a fair amount of patience.

We'll be setting up an environment, compiling the kernel, userspace tools, a root filesystem and then test booting it. I'll assume you already run Linux on a machine, or in a Virtual Machine. Let's dive right in!


We'll first install the programs necessary for building. All these tools should be available in the major Linux distributions, but I'll only give the commands to install them on Debian/Ubuntu and Alpine Linux.

To compile the kernel, we need:

debian$ sudo apt install build-essential xz-utils libncurses5-dev bison flex bc perl libelf-dev libssl-dev  linux-headers-generic
alpine$ apk add apk add alpine-sdk xz ncurses-dev bison flex bc perl libelf-dev openssl-dev linux-headers findutils


First we have to fetch the source.

$ KERNEL_VERSION=linux-4.18.6
$ wget https://cdn.kernel

Setting up a Ad-Hoc network

Ad-Hoc networks are useful when you just need a connection between a few machines with Wireless networking and no routers. It's an underused feature, but sometimes may be useful.

Setting up an Ad-Hoc network is pretty simple. In macOS, open the wireless panel in the menu bar and select "Create Network..."


After that, set your network name and choose any channel.


Your status bar should show this icon Screenshot-2018-08-22-at-7.43.14-PM and you'll be connected to your Ad-Hoc network.


Let's also configure it in Ubuntu 18.04 using netplan and iw.


  version: 2
  renderer: networkd
        "Ubuntu Machine":
          mode: adhoc
          password: password123

Using the iw command:

iw interface set type ibss
ip link set interface up
iw interface ibss join "Ubuntu Machine" 2442

In Windows 10, the command prompt has to be used to run the following commands:

netsh wlan set hostednetwork mode=allow ssid=Ahhh key=pass

Networking on the cheap

It is not hard to get your hands dirty on computer networking basics and operating networking equipment. I've been running my own home network for the past 4 years and messing around with IPv6, VLANs and multiple networks, all without expensive racked routers or switches.


The most important device you'll need is a router. Any computer with at least one Ethernet port is already a router. You can even use an old laptop as a router. (It comes with a free keyboard and mouse too!)

Alternatively, if you have an old consumer router and access point combination (wireless router) that comes from your ISP, it too can be used as an advanced router.

Usually, your computer or wireless router won't support advanced features like VLANs and have restrictive configuration options, so software is the next step in getting these devices bend to your will. There are a ton of router operating systems that you can install onto devices, or if you want to spend the extra effort, you can also use a Linux dis

Infrastructure 2017: Configuring CoreOS and Kubernetes

With the new infrastructure, I wanted the entire setup to be reproducible from a set of configuration files and scripts. This would mean that I can restore a fresh state anytime in the future, and provide a way to track changes in configuration. This also means that I can quickly spawn another Kubernetes setup to test new features safely.

Before I created installation automation scripts, I spent a while learning about how Kubernetes works by manually running the generic multi-node scripts Kubernetes provides, and failing repeatedly. A while back Sudharshan gave me one of his old OEM desktops, and it became really useful for testing Kubernetes installs. CoreOS is best installed using a Ignition configuration file. It's a JSON file with a specific format that CoreOS would read on the first boot and install the appropriate files or configurations specified. Usually, it is preferred to write these files in YML and then transpile them to JSON with a special tool. A great thing about CoreOS

Quick start to HTTPS with Caddy

Caddy is a easy-to-use web server and reverse proxy. You can use it to enable HTTPS on your self-hosted app with little effort.

To start off, first download caddy for your platform. Place the executable in a nice folder. We'll call that your working directory.

Now in the same folder, you will need to write a Caddyfile, which is just a text file. Open your text editor and paste this:

:2015 {
    proxy / localhost:8080

Save it as Caddyfile without any file extension.

This will start a normal HTTP server at port 2015 and proxy all requests to your app at port 8080.

Now, in the terminal or command prompt, cd into the working directory and run Caddy. On Windows you would type caddy.exe and on macOS or Linux ./caddy

You can now visit localhost:2015 to check that everything works.

Next, you have to get a domain to point to your server. You can get free domains from, or use your dynamic DNS provider. You can test that your domain works by visiting over mobile

Infrastructure 2017: A new DNS setup

Previously, DNS has been a pain to maintain. I was using a cloud DNS service, so for every subdomain, I would have to log on to CloudNS and use their web interface to update DNS records. This made it hard to switch DNS providers and easily edit records. And their three domain limit made it impossible to add my other domains, thus I had to run another DNS server. I made the decision to run BIND, but I had to run a service to update my zone files when my IP address changes because I didn't purchase a static IP.

To simplify my complex setup, I decided to host all my DNS locally and solve the dynamic IP problem at once. I made the switch from BIND to CoreDNS due to its extensibility. To ensure that parent DNS servers are always pointing to the right IP, I got a couple of free domains (an example is "") and pointed it to HE's DNS service, where I set up dynamic DNS updates. In my local zone files, I could also use CNAME records to point to the same d