What if your DVD recorder sets bogus permissions to your DVDs?

My dad has a DVD recorder (a Panasonic DMR-EX769) which he uses to record TV programs to watch them later. That device includes an internal hard disk so the recorded shows can later be burnt in a DVD. Unfortunately such DVDs cannot be played in Ubuntu. Why? Because the recorder sets bogus permissions to the directories: all directories only have the read permission set. When the user (my dad) inserts the DVD into the reader, Ubuntu correctly mounts the DVD and grants ownership of its files and directories to the user. But since the permissions of the directories inside the DVD are wrong, the user itself cannot enter any directory, effectively preventing the user to be able to use that DVD. The net effect is that the DVD cannot be played.

DVDs use the UDF. This format seems to include support for permissions. According to some comments I read when looking information about this, Windows seems to ignore them (although I have not verified this fact by myself). But the UDF driver in the Linux kernel seems to honour these permissions, even if they happen to be meaningless.

This issue affects Ubuntu (and probably other Linux distributions) according to these two bug reports: #10550 and #635499.

Technically speaking this is not a blocker since it can be worked around by being superuser (using sudo and friends) or mounting the DVD passing a mode and dmode option at mount-time. This may work if you just want to retrieve your files from the DVD but it is ludicrous if your goal was as mundane as trying to play a DVD.

An expert Linux user is now thinking just remount the DVD with the appropiate mode and dmode options, no big deal. Ok, I tried this first, but, you know, Linux (or whoever is responsible for this bit) happily ignores you. So the only way to change the permissions is first unmounting and then mounting again: -o remount does not work for that.

For Ubuntu 14.04 LTS, the problem lies in package udisks2. This package provides a DBus service for disk management, including automounting facilities when a new disk is inserted (i.e. a DVD or a USB pendrive). One of the applications provided by this package, udisksctl, can mount and unmount drives for you (which is very nice and handy). As a security measure it filters out unsafe mount-options (those that would let the user of this tool elevate its privileges). Sadly, both mode and dmode are filtered, so it is not possible to use that tool. So we have to resort with old-school mount tool (which is what udisks is internally calling, probably).

Abovementioned bug report #635499 includes a comment with a (more or less) crude patch in the source code of udisks (version 1) which tries to dynamically add mode and dmode flags when a readonly DVD is about to be mounted. The patch cannot be applied directly to udisks2 but it gives an idea of the defaults used by udisks and how it filters out options in udisksctl.

So our goal is make a minimal patch to udisk2 so it always passes mode=0400 and dmode=0500 when it mounts an UDF filesystem. This will force the files to have r-------- permissions (only the owner can read the file) and directories r-x------ (only the owner can read and enter the directory). Note that this patch is obviously not realistic for general consumption since some systems can use UDF filesystems in a read/write fashion (according to Wikipedia for DVD-RW with packet writing). This patch would effectively prevent that. But, this is not the scenario of my dad, who only wants to be able the DVDs with films he recorded from the TV.

Setup your environment

Before anything, let's create some working directory and do everything therein.

$ mkdir Builds
$ cd Builds

Now we need to install a tool that we will need later when building the package.

$ sudo apt-get install fakeroot

Now we have to be sure that the package will be buildable. Since Ubuntu is a Debian derivative it uses apt for package managing. This nice tool includes an option to install all the build dependencies of another package. This is, all the (development) packages you are going to need to successfully build the package. This may install a ton or just a few packages into your system, depending on its current installation state.

$ sudo apt-get build-dep udisks2
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
  autoconf automake autopoint autotools-dev build-essential debhelper
  dh-apparmor dh-autoreconf docbook docbook-dsssl docbook-to-man docbook-xml
  docbook-xsl dpkg-dev g++ g++-4.8 gir1.2-polkit-1.0 gnome-common
  gobject-introspection gtk-doc-tools intltool jade libacl1-dev
  libatasmart-dev libattr1-dev libencode-locale-perl libffi-dev
  libfile-listing-perl libgirepository1.0-dev libglib2.0-dev libgudev-1.0-dev
  libhtml-parser-perl libhtml-tagset-perl libhtml-tree-perl
  libhttp-cookies-perl libhttp-date-perl libhttp-message-perl
  libhttp-negotiate-perl libio-html-perl liblwp-mediatypes-perl
  liblwp-protocol-https-perl libnet-http-perl libpcre3-dev libpcrecpp0
  libpolkit-agent-1-dev libpolkit-gobject-1-dev libsigsegv2 libsp1c2
  libstdc++-4.8-dev libtool libwww-perl libwww-robotrules-perl
  libxml-parser-perl m4 po-debconf python-mako python-markupsafe sgml-data sp
  xsltproc zlib1g-dev
0 upgraded, 61 newly installed, 0 to remove and 6 not upgraded.
Need to get 20.6 MB of archives.
After this operation, 97.7 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
...

This may take a few minutes. The next step is getting the source. Again we will use apt for this.

$ apt-get source udisks2
Reading package lists... Done
Building dependency tree       
Reading state information... Done
NOTICE: 'udisks2' packaging is maintained in the 'Git' version control system at:
git://git.debian.org/git/pkg-utopia/udisks2.git
Need to get 924 kB of source archives.
Get:1 http://es.archive.ubuntu.com/ubuntu/ trusty/main udisks2 2.1.3-1 (dsc) [2496 B]
Get:2 http://es.archive.ubuntu.com/ubuntu/ trusty/main udisks2 2.1.3-1 (tar) [910 kB]
Get:3 http://es.archive.ubuntu.com/ubuntu/ trusty/main udisks2 2.1.3-1 (diff) [11.7 kB]
Fetched 924 kB in 1s (512 kB/s)
gpgv: Signature made Mon Mar 10 10:44:35 2014 CET using RSA key ID AFE11347
gpgv: Can't check signature: public key not found
dpkg-source: warning: failed to verify signature on ./udisks2_2.1.3-1.dsc
dpkg-source: info: extracting udisks2 in udisks2-2.1.3
dpkg-source: info: unpacking udisks2_2.1.3.orig.tar.bz2
dpkg-source: info: unpacking udisks2_2.1.3-1.debian.tar.xz
dpkg-source: info: applying mount_in_media.patch
dpkg-source: info: applying unsupported_acls.patch

apt-get source downloads the code, unpacks it and applies any patches of the distribution (in this case there are two patches).

$ ls
udisks2-2.1.3  udisks2_2.1.3-1.debian.tar.xz  udisks2_2.1.3-1.dsc  udisks2_2.1.3.orig.tar.bz2

Build the current package

The next step is to make sure we can build the existing version of the package. This may take a few minutes.

$ cd udisks2-2.1.3
$ dpkg-buildpackage -us -uc
... gibberish ...
dpkg-deb: building package `udisks2' in `../udisks2_2.1.3-1_amd64.deb'.
dpkg-deb: building package `udisks2-doc' in `../udisks2-doc_2.1.3-1_all.deb'.
dpkg-deb: building package `libudisks2-0' in `../libudisks2-0_2.1.3-1_amd64.deb'.
dpkg-deb: building package `libudisks2-dev' in `../libudisks2-dev_2.1.3-1_amd64.deb'.
dpkg-deb: building package `gir1.2-udisks-2.0' in `../gir1.2-udisks-2.0_2.1.3-1_amd64.deb'.
 dpkg-genchanges  >../udisks2_2.1.3-1_amd64.changes
dpkg-genchanges: including full source code in upload
 dpkg-source --after-build udisks2-2.1.3
dpkg-buildpackage: full upload (original source is included)

Now the packages should have been built in the upper directory.

$ ls  ../*.deb
../gir1.2-udisks-2.0_2.1.3-1_amd64.deb  ../udisks2-doc_2.1.3-1_all.deb
../libudisks2-0_2.1.3-1_amd64.deb       ../udisks2_2.1.3-1_amd64.deb
../libudisks2-dev_2.1.3-1_amd64.deb

These are the packages files that Ubuntu uses to install software. But now they are not interesting to us since they still do not do what we want. Let's remove the packages first, to make sure that later we install the new ones.

$ rm -vf ../*.deb
removed '../gir1.2-udisks-2.0_2.1.3-1_amd64.deb'
removed '../libudisks2-0_2.1.3-1_amd64.deb'
removed '../libudisks2-dev_2.1.3-1_amd64.deb'
removed '../udisks2-doc_2.1.3-1_all.deb'
removed '../udisks2_2.1.3-1_amd64.deb'

Use the source, Luke

Time to hack! We will modify the file src/udiskslinuxfilesystem.c using our favorite editor. Around line 300 we will see the following.

298
299
300
301
/* ---------------------- udf -------------------- */

static const gchar *udf_defaults[] = { "uid=", "gid=", "iocharset=utf8", "umask=0077", NULL };
static const gchar *udf_allow[] = { "iocharset=", "umask=", NULL };

The first array is the default parameters that udisks uses when mounting a filesystem. The second array are the parameters allowed by udisksctl in its mount subcommand. Let's change these two arrays to

298
299
300
301
/* ---------------------- udf -------------------- */

static const gchar *udf_defaults[] = { "uid=", "gid=", "iocharset=utf8", "umask=0077", "mode=0400", "dmode=0500", NULL };
static const gchar *udf_allow[] = { "iocharset=", "umask=", "mode=", "dmode=", NULL };

This will set mode=0400 and dmode=0500 by default. We will be able to override them in udisksctl tool also. Save the file and now let's rebuild the package.

Rebuild package

$ dpkg-buildpackage -us -uc
... gibberish ...
dpkg-source: info: local changes detected, the modified files are:
 udisks2-2.1.3/src/udiskslinuxfilesystem.c
dpkg-source: info: you can integrate the local changes with dpkg-source --commit
dpkg-source: error: aborting due to unexpected upstream changes, see /tmp/udisks2_2.1.3-1.diff.YgBM9N
dpkg-buildpackage: error: dpkg-source -b udisks2-2.1.3 gave error exit status 2

dpkg has detected that we changed a file and tells us to make a patch for it. It even tells us what we have to do.

$ dpkg-source --commit
... gibberish ...
dpkg-source: info: local changes detected, the modified files are:
 udisks2-2.1.3/src/udiskslinuxfilesystem.c
Enter the desired patch name: 

It asks us the name of the patch, I used force-udf-permissions (press ENTER key to continue). Now it shows us the path in our $EDITOR. Just save it.

dpkg-source: info: local changes have been recorded in a new patch: udisks2-2.1.3/debian/patches/force-udf-permissions

Now we can build the package. Again this will take a few minutes.

$ dpkg-buildpackage -us -uc
... gibberish ...
dpkg-deb: building package `udisks2' in `../udisks2_2.1.3-1_amd64.deb'.
dpkg-deb: building package `udisks2-doc' in `../udisks2-doc_2.1.3-1_all.deb'.
dpkg-deb: building package `libudisks2-0' in `../libudisks2-0_2.1.3-1_amd64.deb'.
dpkg-deb: building package `libudisks2-dev' in `../libudisks2-dev_2.1.3-1_amd64.deb'.
dpkg-deb: building package `gir1.2-udisks-2.0' in `../gir1.2-udisks-2.0_2.1.3-1_amd64.deb'.
 dpkg-genchanges  >../udisks2_2.1.3-1_amd64.changes
dpkg-genchanges: including full source code in upload
 dpkg-source --after-build udisks2-2.1.3
dpkg-buildpackage: full upload (original source is included)

Now there should be several deb files again, in the upper directory.

$ ls ../*.deb
../gir1.2-udisks-2.0_2.1.3-1_amd64.deb  ../udisks2-doc_2.1.3-1_all.deb
../libudisks2-0_2.1.3-1_amd64.deb       ../udisks2_2.1.3-1_amd64.deb
../libudisks2-dev_2.1.3-1_amd64.deb

Cool!

Installation of the new packages

Let's install the only two that are actually needed (the other ones are development files and development documentation).

$ sudo dpkg -i ../libudisks2-0_2.1.3-1_amd64.deb ../udisks2_2.1.3-1_amd64.deb 
(Reading database ... 236576 files and directories currently installed.)
Preparing to unpack .../libudisks2-0_2.1.3-1_amd64.deb ...
Unpacking libudisks2-0:amd64 (2.1.3-1) over (2.1.3-1) ...
Preparing to unpack ../udisks2_2.1.3-1_amd64.deb ...
Unpacking udisks2 (2.1.3-1) over (2.1.3-1) ...
Setting up libudisks2-0:amd64 (2.1.3-1) ...
Setting up udisks2 (2.1.3-1) ...
Processing triggers for man-db (2.6.7.1-1ubuntu1) ...
Processing triggers for libc-bin (2.19-0ubuntu6.4) ...

Now restart Ubuntu (maybe this is not needed but I'm now too lazy to figure out what would be the minimum required to reload the udisks2 thing). Once logged on, insert a DVD, wait it to be mounted. Now open a terminal and check that the mode and dmode flags were passed:

$ mount
...
/dev/sr0 on /media/rferrer/A DVD HERE type iso9660 (ro,nosuid,nodev,uid=1000,gid=1000,iocharset=utf8,mode=0400,dmode=0500,uhelper=udisks2)

Yeah.

Hold the package

Next time we update Ubuntu, it will overwrite our package, so make sure it is held.

$ sudo apt-mark hold udisks2

Future work

The next step is to figure out how to more or less automate this process when Ubuntu updates a new version of udisks2.

Have a good day.