Last 10 blog posts

Thanks to Moritz Naumann who found the issues and wrote a very useful report, I fixed a number of Cross Site Scripting vulnerabilities on https://debtags.debian.org.

The core of the issue was code like this in a Django view:

def pkginfo_view(request, name):
    pkg = bmodels.Package.by_name(name)
    if pkg is None:
        return http.HttpResponseNotFound("Package %s was not found" % name)
    # …

The default content-type of HttpResponseNotFound is text/html, and the string passed is the raw HTML with clearly no escaping, so this allows injection of arbitrary HTML/<script> code in the name variable.

I was so used to Django doing proper auto-escaping that I missed this place in which it can't do that.

There are various things that can be improved in that code.

One could introduce escaping (and while one's at it, migrate the old % to format):

from django.utils.html import escape

def pkginfo_view(request, name):
    pkg = bmodels.Package.by_name(name)
    if pkg is None:
        return http.HttpResponseNotFound("Package {} was not found".format(escape(name)))
    # …

Alternatively, set content_type to text/plain:

def pkginfo_view(request, name):
    pkg = bmodels.Package.by_name(name)
    if pkg is None:
        return http.HttpResponseNotFound("Package {} was not found".format(name), content_type="text/plain")
    # …

Even better, raise Http404:

from django.utils.html import escape

def pkginfo_view(request, name):
    pkg = bmodels.Package.by_name(name)
    if pkg is None:
        raise Http404(f"Package {name} was not found")
    # …

Even better, use standard shortcuts and model functions if possible:

from django.shortcuts import get_object_or_404

def pkginfo_view(request, name):
    pkg = get_object_or_404(bmodels.Package, name=name)
    # …

And finally, though not security related, it's about time to switch to class-based views:

class PkgInfo(TemplateView):
    template_name = "reports/package.html"

    def get_context_data(self, **kw):
        ctx = super().get_context_data(**kw)
        ctx["pkg"] = get_object_or_404(bmodels.Package, name=self.kwargs["name"])
    # …
        return ctx

I proceeded with a review of the other Django sites I maintain in case I reproduced this mistake also there.

Several sites have started disabling paste in input fields, mostly password fields, but also other fields for no apparent reason.

Random links on the topic:

  • https://developers.google.com/web/tools/lighthouse/audits/password-pasting
  • https://www.ncsc.gov.uk/blog-post/let-them-paste-passwords
  • https://www.troyhunt.com/the-cobra-effect-that-is-disabling/
  • https://www.wired.com/2015/07/websites-please-stop-blocking-password-managers-2015/

This said, I am normally uneasy about copy-pasting passwords, as any X window can sniff the clipboard contents at any time, and I like password managers like impass that would type it for you instead of copying it to the clipboard.

However, today I got out way more frustrated than I could handle after illing in 17-digits nonsensical, always-slightly-different INPS payment codelines inside input fields that disabled paste for no reason whatsoever (they are not secret).

I thought "never again", I put together some code from impass and wmctrl and created xtypeinto:

$ ./xtypeinto --help
usage: xtypeinto [-h] [--verbose] [--debug] [string]

Type text into a window

positional arguments:
  string         string to type (default: stdin)

optional arguments:
  -h, --help     show this help message and exit
  --verbose, -v  verbose output
  --debug        debug output

Pass a string to xtypeinto as an argument, or as standard input.

xtypeinto will show a crosshair to pick a window, and the text will be typed into that window.

Please make sure that you focus on the right field before running xtypeinto, to make sure things are typed where you need them.

The LineageOS updater notified me that there will be no more updates for LineageOS 14, because now development on my phone happens on LineageOS 16, so I set aside some time and carefully followed the upgrade instructions.

I now have a phone with Lineageos 16, but the whole modem subsystem does not work.

Advice on #lineageos was that "the wiki instructions are often a bit generic.. offical thread often has the specific details".

Official thread is here, and the missing specific detail was "Make sure you had Samsung's Oreo firmware bootloader and modem before installing this.".

It looks like nothing ever installed firmware updates, since the Android that came with my phone ages ago. I can either wipe everything and install a stock android to let it do the upgrade, then replace it with LineageOS, or try a firmware upgrade.

This link has instructions for firmware upgrades using haimdall, which is in Debian, instead of Odin, which is in Windows.

Finding firmwares is embarassing. They only seem to be available from links on shady download sites, or commercial sites run by who knows whom. I verify sha256sums on LineageOS images, F-Droid has reproducible builds, but at the base of this wonderful stack there's going to be a blob downloaded off some forum on the internet.

In this case, this link points to some collection of firmware blobs.

I downloaded the pack and identified the ones for my phone, then unpacked the tar files and uncompressed the lz4 blobs.

With heimdall, I identified the mapping from partition names to blob names:

heimdall print-pit --no-reboot

Then I did the flashing:

heimdall flash --resume --RADIO modem.bin --CM cm.bin --PARAM param.bin --BOOTLOADER sboot.bin

The first time flashing didn't work, and I got stuck in download mode. This explains how to get out of download mode (power + volume down for 10s).

Second attempt worked fine, and now I have a working phone again:

heimdall flash --RADIO modem.bin --CM cm.bin --PARAM param.bin --BOOTLOADER sboot.bin

«Bullshit is unavoidable whenever circumstances require someone to talk without knowing what he is talking about. Thus the production of bullshit is stimulated whenever a person’s obligations or opportunities to speak about some topic are more excessive than his knowledge of the facts that are relevant to that topic.

This discrepancy is common in public life, where people are frequently impelled— whether by their own propensities or by the demands of others—to speak extensively about matters of which they are to some degree ignorant.

Closely related instances arise from the widespread conviction that it is the responsibility of a citizen in a democracy to have opinions about everything, or at least everything that pertains to the conduct of his country’s affairs.

The lack of any significant connection between a person’s opinions and his apprehension of reality will be even more severe, needless to say, for someone who believes it his responsibility, as a conscientious moral agent, to evaluate events and conditions in all parts of the world.»

(From Harry G. Frankfurt's On Bullshit)

Opinion Sort

In a world where it is more important to have a quick opinion than a thorough understanding, I propose this novel sorting algoritihm.

def opinion_sort(list: List[Any], post: Callable[List]):
    """
    list: a list of elements to sort in place
    post: a callable that requires a sorted list as input and does
          proper error checking, as they should do
    """
    if list[0] > list[1]:
        swap(list[0], list[1])
    while True:
        try:
            # Assert opinion: "It is a sorted list!"
            post(list)
        except NotSortedException as e:
            # Someone disagrees, and they have a good point
            swap(list[e.unsorted_idx_1], list[e.unsorted_idx_2])
        else:
            break
    # The list is now sorted, and the callable has to agree

This algorithm is the most efficient sorting algorithm, because it can sort a list by only looking at the first two elements.

I might have accidentally forked live-wrapper.

I sometimes need to build Debian live iso images for work, and some time ago got into an inconvenient situation in which live-wrapper required software not available in Debian anymore, and there was no obvious replacement for it, so I forked it and tried to forward-port things and fill the gaps.

Over time this kind of grew: I ported it to python3, removed difficult dependencies, added several new features that I needed, and removed several that I didn't need.

I recently had a chance to document the result, which makes it good enough to be announced, so here it is. The README has an introduction and links to documentation, recipes and examples.

I'm not actively maintaining this except when work requires, so if there's anything extra you need for it, the best way to get it is via a merge request.

I'm not sure how much of live-wrapper is still left in the fork. If anyone starts using it, we should probably look into a new name.