Comment 17 for bug 1231091

Revision history for this message
Gordon N. Squash (thesquash) wrote (last edit ):

I'm pretty sure I discovered the bug, and I think I know how to rectify it too.
Please note that this was a tricky bug to find and it made my head spin for a
while; it was only recently when I figured this out from start to end. Here's
what I know now:

I discovered that Ubiquity's greeter (`maybe-ubiquity`) starts two D-Busses:
the normal session bus, which non-root programs communicate over for
pretty much everything (including accessibility communications), and the
special accessibility bus, which is only used by root programs like Ubiquity.
The accessibility bus uses a special configuration file which allows root to
use the bus (normally root would be banned from the bus); that configuration
file is stored at /usr/share/defaults/at-spi2/accessibility.conf:

   [...]
   <policy context="default">
     <!-- Allow root to connect -->
     <allow user="root"/>
     <!-- Allow everything to be sent -->
     <allow send_destination="*" eavesdrop="true"/>
     <!-- Allow everything to be received -->
     <allow eavesdrop="true"/>
     <!-- Allow anyone to own anything -->
     <allow own="*"/>
   </policy>
   [...]

Depending on the version of Ubuntu in use, the exact application which requests
the accessibility bus in the first place (and thus the application which
requests the bus to be launched) differs. On stock Ubuntu, it is a portion of
the GNOME Settings Daemon; on Ubuntu MATE, it is consistently the Ubiquity
panel. Either way, which application starts the bus doesn't matter, as long
as an application starts it.

When an application requests the bus be started, the D-Bus daemon requests that
SystemD launches the `at-spi-bus-launcher` program. This program starts
another, separate D-Bus daemon (separate from the main session bus, that is),
and tells this new D-Bus daemon to use the aforementioned configuration file.
The bus launcher also requests that the new D-Bus daemon inform the bus
launcher about how the new D-Bus daemon can be addressed and contacted; the
applications need to know about this bus and they need to know how to
communicate on it. The D-Bus daemon tells the bus launcher how to contact it
when the D-Bus daemon has initialized itself fully.

Once the bus launcher knows how to contact the accessibility bus, the bus
launcher is supposed to set an *X11 atom* on the root window of the X session
which Ubiquity is using. The atom applied to the root window means that any
application, even Ubiquity, can quickly and easily find out how to communicate
over the accessibility bus. Here's an example of the atom:

   $ DISPLAY=":0" xprop -root AT_SPI_BUS
   AT_SPI_BUS(STRING) = "unix:abstract=/tmp/dbus-jvfzJSsTTp,guid=e1dc67ddfb5b08287b1b9676612460d8"

This means that the accessibility bus can be reached using a UNIX domain socket
(so it can be accessed only on the local system); a proper call to "bind" to
the socket at `/tmp/dbus-jvfzJSsTTp` will connect to the accessibility bus
(though the `abstract` part means that the socket is not visible in the file
system; this is a Linux-only feature, and you'll sometimes see `path` instead,
which means the socket can be viewed as part of the file system); and in order
to verify that the application is authenticated to access the bus, it must pass
the globally unique identifier `e1dc67ddfb5b08287b1b9676612460d8` as proof that
it's not an external attacker.

Sounds straightforward, right? Wrong. How does the bus launcher know what the
address of the *X server* is? Sure, there is the `DISPLAY` environment
variable, and that is what the bus launcher tries to get access to; but in
general, on UNIX one process cannot get access to an environment variable
unless it inherited that variable from its parent.

So who's the parent of the bus launcher? You'd think it would be the Ubiquity
panel, for example, or the GNOME Settings Daemon, since those programs
*requested* the accessibility bus in the first place. Not so; remember how I
said that *the Ubiquity panel requests* that *D-Bus requests* that *SystemD
launches* the accessibility bus? (Yes, it really *is* that big of a game of
Telephone.) But the D-Bus session bus daemon doesn't know what the contents
of the X11 `DISPLAY` variable are -- I verified this -- much less does SystemD
know that. So how is the accessibility bus supposed to know what X11 display
to use??

This one took a lot of digging. Ultimately, it was just luck that I came
across the `/usr/bin/ubiquity-dm` program, which holds the key (excerpted from
line 407 et seq.):

           if (
               osextras.find_on_path("kwin") or
               osextras.find_on_path("kwin_x11") or
               osextras.find_on_path("gnome-shell")
           ) and osextras.find_on_path("dbus-update-activation-environment"):
               subprocess.Popen(
                   [
                       "dbus-update-activation-environment",
                       "--verbose",
                       "--systemd",
                       "DISPLAY",
                   ],
                   stdin=null,
                   stdout=logfile,
                   stderr=logfile,
                   preexec_fn=self.drop_privileges,
               )

Okay, what's going on here? Let's break this down: If KWin or the GNOME Shell
are installed, and this program called `dbus-update-activation-environment` is
also installed, then run this `dbus-update-activation-environment` program --
with a parameter of `DISPLAY`.

Huh. Does that sound familiar? The Ubiquity panel asks D-Bus to *activate*
the accessibility bus (I verified that is the proper terminology). I looked
up this interesting command, and I confirmed that this command tells D-Bus what
environment variables to provide to activated services. In other words, this
command tells D-Bus to launch any future services (such as the accessibility
bus launcher) with an extra environment variable: `DISPLAY`! So this is how
the bus launcher finds the X11 display, adds the atom, and informs other
applications (notably Ubiquity) how to access the accessibility bus.

But one little problem remains. Did you notice that the above only applies to
stock Ubuntu or Kubuntu, or a strange "spin" of Ubuntu which includes at least
either KWin or the GNOME Shell? Did you notice there was nothing for, for
example, a distribution like Ubuntu MATE which doesn't include either program?

That's right, that's where the bug lay. I tried changing `gnome-shell` to
`geenome-shell` (that does not exist) on stock Ubuntu, and Ubiquity wouldn't
communicate with the screen reader anymore -- just like Ubuntu MATE. And I
tried changing that to `marco` on Ubuntu MATE, and it worked!

So who introduced this bug, and why? I don't know. What I do know is that,
after I looked briefly through the list of commits to the Ubiquity repo on
Launchpad, I noticed a change from January which basically stated "Add screen
reading capabilities to Kubuntu" (see https://git.launchpad.net/ubuntu/+source/ubiquity/commit/bin/ubiquity-dm?id=365ce53aa2beb6dad17234aaa5789a68c2220438). Well, excuse me. Do we really need
to add support for each derivative of Ubuntu separately? This program
(`ubiquity-dm`) already assumes that we have a GUI (as opposed to the server
installer, which has no GUI). Can't we just remove the `if kwin ...
gnome-shell` stuff so that all derivatives of Ubuntu can benefit? I think
the answer is "yes", based on my research into the commit history of the
repo.

And as soon as I can, I shall start a merge request and get this patched up
ASAP. We don't need any more visually impaired users suffering from this
tricky bug.

I have not declared victory yet; I shall declare it only after someone else
confirms what I have written, and the patch is applied. But I'm still pretty
happy right now that I think I've found the problem, anyway.

That was a long rant, but I hope I didn't leave anything out!

**EDIT:** I just opened a merge request to resolve this: https://code.launchpad.net/~thesquash/ubuntu/+source/ubiquity/+git/ubiquity/+merge/407632