How I Got Xfinity Stream to Work on Linux (a Tale of Widevine, Chrome OS, and a Patched glibc
)
(If you want to skip to the tutorial, click here.)
If your household uses Xfinity for cable, you have almost definitely heard of Xfinity Stream at some point. If you haven’t, Xfinity Stream is a service that allows you to access Xfinity’s on-demand TV over the internet. This can be useful if you want to watch something on Xfinity’s on-demand TV but do not want to go to your actual TV.
This is nice because some media still goes to on-demand before its respective streaming service (if it even has one). And of course, if you are already paying for Xfinity, it is nice not to have to pay for another streaming service if you can avoid it.
However, it has some limitations as well, mainly that you can only access it on your local Xfinity network… and its completely arbitrary Linux block. That’s right, if you have ever tried to load Xfinity Stream on Linux, you will have seen this lovely error message:
And if you look up that error message, you will find a common theme: Xfinity Stream does not support Linux.
Also of note is that Xfinity Stream will refuse to load at all on Google Chrome without changing your user-agent, but it will load on Firefox, just for it to fail when you try to watch anything.
How does it block Linux?
One of the first methods I tried to use to bypass this block was changing my user-agent… just for the block to persist anyways. Which raised the question “how does Xfinity know I am running Linux?” Because if I can trick it into thinking I am running Windows or MacOS, then it should not be able to block me anymore.
So I dug into Firefox’s network logs, which led me to the source of the error:
This POST
request was generated using the MediaKeySession.generateRequest()
API, which isn turn used the browser’s built-in Widevine CDM to generate a DRM license request. At this point, I had formed a hypothesis: the Linux build of the Widevine CDM was embedding metadata into this request which Xfinity was using to block Linux.
How do I bypass that?
Unfortunately, just patching the Widevine CDM’s binary is beyond my skill set, especially since it is ridiculously obfuscated.
However, I eventually came to a realization: Xfinity Stream probably does not block Chrome OS, and Chrome OS is basically GNU/Linux internally. So I formed a plan: extract Chrome OS’s Widevine CDM from a recovery image, load it into Firefox, and try to use Xfinity Stream.
It did not work, at all.
The Widevine CDM immediately crashed, and when I tried to load it into a standalone program using dlopen()
, it caused a segmentation fault before it even finished loading. This incredibly vague error stumped me for a while until I found this incredibly helpful GitHub issue from a project that was trying to do something similar.
This GitHub issue revealed the problem: Chrome OS was not exactly GNU/Linux. Namely, it was complied with a feature that was not included in my version of glibc
that Google had patched into their version: the DT_RELR
relocation format.
With some more research, I learned that glibc
had actually merged support for this feature in version 2.36! So I loaded Ubuntu 22.10 on a USB flash drive and tried loading Chrome OS’s Widevine CDM into my testing program again.
And it still did not work.
This time the error was slightly more helpful though: DT_RELR without GLIBC_ABI_DT_RELR dependency
.
What in the world is a GLIBC_ABI_DT_RELR
dependency?
It turns out, the glibc
maintainers actually agreed with my earlier assessment: immediately causing a segmentation fault on older versions of glibc
just because a program used the DT_RELR
relocation format was not good behavior.
They decided that for every binary compiled with the DT_RELR
relocation format, the linker would include a version dependency called GLIBC_ABI_DT_RELR
on libc.so.6
. This would cause any program using the DT_RELR
relocation format to give a more sane error on older versions of glibc
: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_ABI_DT_RELR' not found (required by ./program)
.
However, the glibc
maintainers also decided to implement this functionality in reverse as well: glibc
refuses to load any binary containing the DT_RELR
relocation format that does not also contain the GLIBC_ABI_DT_RELR
version dependency. I do not know why they did this, but this is what caused the previous error. This is because Chrome OS’s toolchain evidently predates GLIBC_ABI_DT_RELR
so it was never included when linking the Widevine CDM.
Originally, I tried to patch the Widevine CDM binary to include the GLIBC_ABI_DT_RELR
version dependency using LIEF. However, the resulting binary was invalid and caused a segmentation fault when started. Ultimately, I decided to instead remove the check itself from 'glibc
manually.
The GLIBC_ABI_DT_RELR
check was quite trivial to patch out of glibc
(although waiting for glibc
to compile was certainly tedious). And once done, I was able to load up Firefox with Chrome OS’s Widevine CDM… and Xfinity Stream finally worked on Linux!
How do I actually do this?
- Install a Linux distribution that contains
glibc
v2.36 or higher. I would also recommend taking a backup or using a more disposable installation medium such as a USB flash drive or a VM.- You could use an older version of
glibc
if you really want to, but you would have to patch in support for theDT_RELR
relocation format yourself.
- You could use an older version of
- Use this script to patch
glibc
(this script is Debian-specific, but it should not be too hard to adapt it to other distributions):#!/bin/sh set -e # Create Working Directory rm -rf glibc-work mkdir glibc-work cd glibc-work # Download Sources apt-get source glibc cd glibc-* # Patch patch -p1 << EOF --- glibc-master-old/elf/dl-version.c +++ glibc-master/elf/dl-version.c @@ -362,7 +362,7 @@ /* When there is a DT_VERNEED entry with libc.so on DT_NEEDED, issue an error if there is a DT_RELR entry without GLIBC_ABI_DT_RELR dependency. */ - if (dyn != NULL + if (0 && dyn != NULL && map->l_info[DT_NEEDED] != NULL && map->l_info[DT_RELR] != NULL && __glibc_unlikely (!map->l_dt_relr_ref)) EOF # Commit Patch EDITOR='/bin/true' dpkg-source -q --commit . disable-GLIBC_ABI_DT_RELR-check.patch # Update Package Version EMAIL='example@example.com' dch -n 'Disable GLIBC_ABI_DT_RELR Check' # Disable Testing export DEB_BUILD_OPTIONS=nocheck # Build dpkg-buildpackage -us -uc # Install sudo apt-get install -y ../*.deb
- Download an x86_64 Chrome OS recovery image. I used the stable version of
atlas
, which was Chrome OS v107 at the time of writing. - Extract
/opt/google/chrome/WidevineCdm/_platform_specific/cros_x64/libwidevinecdm.so
from the image. - Browser-specific steps:
- Google Chrome
- Copy
libwidevinecdm.so
to/opt/google/chrome/WidevineCdm/_platform_specific/linux_x64/libwidevinecdm.so
. - You will need to use the Chrome Developer Tools user-agent switcher when using Xfinity Stream. Other user-agent switcher extensions do not appear to work.
- Copy
- Firefox
- Install a non-Snap/Flatpak version of Firefox. This is needed because we need Firefox (and therefore the Widevine CDM) to use the host’s
glibc
. - If you are using the Firefox build from
ppa:mozillateam/ppa
, you will also need to modify Firefox’s AppArmor configuration so that the Widevine CDM does not crash.
- Install a non-Snap/Flatpak version of Firefox. This is needed because we need Firefox (and therefore the Widevine CDM) to use the host’s
- Google Chrome
- Use Xfinity Stream!
- You will need to re-patch
glibc
every time your distribution releases an updated version.
Conclusion
All in all, this whole situation is ridiculous. It should not be this much harder to access media legally than it is to pirate. It is insane how much effort we as a species waste on this DRM that does not stop anybody. If I wanted to pirate anything, I could probably pull up a high-resolution copy in under an hour with minimal effort. But instead, because I tried to access this media the legal way, I have to either go through this whole complicated process or set up an entirely different operating system solely for that purpose. That is so unbelievably silly. The only reason I can remotely justify the time spent on this is because it was more enjoyable than watching whatever it was I was originally planning to watch!
In all honesty, if you want to use Xfinity Stream, I would recommend just setting up a Windows VM. It will probably be much more resistant to whatever nonsense Xfinity or Google try to pull in the future. But if you would rather not watch it at all than set up a Windows VM like me, well here is your method.