How to prevent Wine from auto-running .exe files in Fedora
Latest update:
After installing Wine on Fedora 25 I've noticed that I can run
Window executables straight from the bash command line:
$ file -b ~/.wine/drive_c/Program\ Files/Internet\ Explorer/iexplore.exe
PE32+ executable (GUI) x86-64, for MS Windows
$ !$
That iexplore.exe
file is clearly not a ELF, yet the kernel
successfully executes it.
Since the Windows ecosystem is known for its lack of malware &
ransomware, and Wine, in turn, is known for a military-grade,
bulletproof sandbox (i.e., it provides no protection whatsoever), I
find the notion of auto-running very difficult to reconcile with
common sense.
How does it work
If a kernel was compiled w/ CONFIG_BINFMT_MISC
option,
execve(2)
(I'm simplifying here a little) obtains an ability to
delegate the execution of unknown binaries to external processes. The
subsystem that manages the format → interpreter association table is
called binfmt_misc.
binfmt_misc maintains its ephemeral database in the procfs. At the
boot time you mount /proc/sys/fs/binfmt_misc
directory & feed
/proc/sys/fs/binfmt_misc/register
file w/ specially crafted text
lines to create association entries (or rules as systemd docs like
to call them).
E.g., in the good old days before systemd, we would have put
echo :win:M:0:MZ::/usr/bin/wine: > /proc/sys/fs/binfmt_misc/register
somewhere in /etc/rc.local
.
Such a command creates /proc/sys/fs/binfmt_misc/win
file:
enabled
interpreter /usr/bin/wine
flags:
offset 0
magic 4d5a
where the offset & the magic 4d5a (MZ) means the 1st 2 bytes of a
typical Windows executable:
$ hexdump -C -n10 iexplore.exe
00000000 4d 5a 40 00 01 00 00 00 06 00 |MZ@.......|
0000000a
We can be even more fancier & make an extension →
interpreter association, e.g.:
# echo :xv:E::gif::/usr/bin/xv: > /proc/sys/fs/binfmt_misc/register
$ chmod +x slave-ship-daily-schedules.gif
$ ./!$
creates .gif → xv entry, & starts
/usr/bin/xv slave-ship-daily-schedules.gif
under the hood when we try to "run"
the .gif image.
To delete all the entries, we write -1
to
/proc/sys/fs/binfmt_misc/status
file or if we want to delete only
a particular entry:
# echo -1 > /proc/sys/fs/binfmt_misc/xv
The systemd way
systemd doesn't allow us to mingle w/ binfmt_misc subsystem
directly. We ought to write the text lines in the same format
binfmt_misc undestands but put them in special .conf files, where
a separate program systemd-binfmt
expects to find them.
If we provide an rpm for our app, we should put my-app.conf
file in /usr/lib/binfmt.d/
directory during the installation & run
/usr/lib/systemd/systemd-binfmt my-app.conf
in the post-install hook.
The algo systemd-binfmt uses is fairly straightforward. When run w/o
any command line args, it deletes all the existing entries &
recreates the ones specified in the .conf files. If provided w/ the
name of the .conf file (w/o a directory prefix!), it scans the file to
add new entries.
An excerpt from src/binfmt/binfmt.c
(commit 1539a65):
if (argc > optind) {
int i;
for (i = optind; i < argc; i++) {
k = apply_file(argv[i], false);
if (k < 0 && r == 0)
r = k;
}
} else {
_cleanup_strv_free_ char **files = NULL;
char **f;
r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
if (r < 0) {
log_error_errno(r, "Failed to enumerate binfmt.d files: %m");
goto finish;
}
/* Flush out all rules */
write_string_file("/proc/sys/fs/binfmt_misc/status", "-1", 0);
STRV_FOREACH(f, files) {
k = apply_file(*f, true);
if (k < 0 && r == 0)
r = k;
}
}
It sounds fine, until we remember that a typical user (a) doesn't know
much about binfmt-misc kernel module, (b) doesn't know anything about
the standalone systemd-binfmt program, for he uses
systemd-binfmt.service
unit, as in
# systemctl restart systemd-binfmt
That unit (/usr/lib/systemd/system/systemd-binfmt.service
)
incorporates a handful of preconditions. The relevant ones:
ConditionDirectoryNotEmpty=|/lib/binfmt.d
ConditionDirectoryNotEmpty=|/usr/lib/binfmt.d
ConditionDirectoryNotEmpty=|/usr/local/lib/binfmt.d
ConditionDirectoryNotEmpty=|/etc/binfmt.d
ConditionDirectoryNotEmpty=|/run/binfmt.d
When we install Wine, it makes 2 entries in the systemd-binfmt DB. If
we (a) remove the offending package wine-systemd
that contains the
evil /usr/lib/binfmt.d/wine.conf
file & (b) dutifully restart
systemd-binfmt service--no binfmt-misc association entries gets
removed!
# rpm -qf /lib/binfmt.d/wine.conf
wine-systemd-2.3-1.fc25.noarch
# rpm --nodeps -e wine-systemd
# systemctl restart systemd-binfmt
# ls -l /proc/sys/fs/binfmt_misc/
total 0K
--w------- 1 root root 0 Mar 24 20:49 register
-rw-r--r-- 1 root root 0 Mar 25 20:48 status
-rw-r--r-- 1 root root 0 Mar 25 20:58 windows
-rw-r--r-- 1 root root 0 Mar 25 20:58 windowsPE
because systemd will forbid systemd-binfmt to run at all, because all
the conf directories are empty. The remedy is to run systemd-binfmt by
hand:
# /usr/lib/systemd/systemd-binfmt
# ls -l /proc/sys/fs/binfmt_misc/
total 0K
--w------- 1 root root 0 Mar 24 20:49 register
-rw-r--r-- 1 root root 0 Mar 25 20:48 status
You should not manually delete the rpm w/ the evil .conf file for dnf
will reinstall wine-systemd package during the next Wine update. The
recommended systemd-style solution is to create a symlink in
/etc/binfmt.d/
to /dev/null
named wine.conf
& then restart
systemd-binfmt service:
# ln -s /dev/null /etc/binfmt.d/wine.conf
# systemctl restart systemd-binfmt
Tags: ойті
Authors: ag