Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/espanso: fix wayland problems due to missing capabilities #328890

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
./programs/ecryptfs.nix
./programs/environment.nix
./programs/envision.nix
./programs/espanso-capdacoverride
./programs/evince.nix
./programs/extra-container.nix
./programs/fcast-receiver.nix
Expand Down
60 changes: 60 additions & 0 deletions nixos/modules/programs/espanso-capdacoverride/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
config,
lib,
pkgs,
...
}:

with lib;
{
meta = {
maintainers = with maintainers; [ pitkling ];
};

options = {
programs.espanso.capdacoverride = {
enable = (mkEnableOption "espanso-wayland overlay with DAC_OVERRIDE capability") // {
default = false;
extraDescription = ''
Creates an espanso binary with the DAC_OVERRIDE capability (via `security.wrappers`) and overlays `pkgs.espanso-wayland` such that self-forks call the capability-enabled binary.
Required for `pkgs.espanso-wayland` to work correctly if not run with root privileges.
'';
};

package = mkOption {
type = types.package // {
check = package: types.package.check package && (builtins.elem "wayland" package.buildFeatures);
description =
types.package.description
+ " for espanso with wayland support (`package.builtFeatures` must contain `\"wayland\"`)";
};
default = pkgs._espanso-wayland-orig;
defaultText = "pkgs.espanso-wayland (before applying the overlay)";
description = "The espanso-wayland package used as the base to generate the capability-enabled package.";
};
};
};

config =
let
cfg = config.programs.espanso.capdacoverride;
in
mkIf cfg.enable {
nixpkgs.overlays = [
(final: prev: {
_espanso-wayland-orig = prev.espanso-wayland;
espanso-wayland = pkgs.callPackage ./espanso-capdacoverride.nix {
capDacOverrideWrapperDir = "${config.security.wrapperDir}";
espanso = cfg.package;
};
})
];

security.wrappers."${pkgs.espanso-wayland.meta.mainProgram}" = {
source = "${getExe pkgs.espanso-wayland}";
capabilities = "cap_dac_override+p";
owner = "root";
group = "root";
};
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
autoPatchelfHook,
capDacOverrideWrapperDir,
espanso,
patchelfUnstable, # have to use patchelfUnstable to support --rename-dynamic-symbols
stdenv,
}:
let
inherit (espanso) version;
pname = "${espanso.pname}-capdacoverride";

wrapperLibName = "wrapper-lib.so";
wrapperLibSource = "wrapper-lib.c";

# On Wayland, Espanso requires the DAC_OVERRIDE capability. One can create a wrapper binary with this
# capability using the `config.security.wrappers.<name>` framework. However, this is not enough: the
# capability is required by a worker process of Espanso created by forking `/proc/self/exe`, which points
# to the executable **without** the DAC_OVERRIDE capability. Thus, we inject a wrapper library into Espanso
# that redirects requests to `/proc/self/exe` to the binary with the proper capabilities.
wrapperLib = stdenv.mkDerivation {
name = "${pname}-${version}-wrapper-lib";

src = builtins.path {
name = "${pname}-${version}-wrapper-lib-source";
path = ./.;
filter = path: type: baseNameOf path == wrapperLibSource;
};

postPatch = ''
substitute ${wrapperLibSource} lib.c --subst-var-by to "${capDacOverrideWrapperDir}/espanso"
cc -fPIC -shared lib.c -o ${wrapperLibName}
'';

installPhase = ''
runHook preInstall
install -D -t $out/lib ${wrapperLibName}
runHook postInstall
'';
};
in
espanso.overrideAttrs (previousAttrs: {
inherit pname;

buildInputs = previousAttrs.buildInputs ++ [ wrapperLib ];

nativeBuildInputs = previousAttrs.nativeBuildInputs ++ [
autoPatchelfHook
patchelfUnstable
];

postInstall =
''
echo readlink readlink_wrapper > readlink_name_map
patchelf \
--rename-dynamic-symbols readlink_name_map \
--add-needed ${wrapperLibName} \
"$out/bin/espanso"
''
+ previousAttrs.postInstall;
})
20 changes: 20 additions & 0 deletions nixos/modules/programs/espanso-capdacoverride/wrapper-lib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static const char from[] = "/proc/self/exe";
static const char to[] = "@to@";

ssize_t readlink_wrapper(const char *restrict path, char *restrict buf, size_t bufsize) {
if (strcmp(path, from) == 0) {
printf("readlink_wrapper.c: Resolving readlink call to '%s' to '%s'\n", from, to);
size_t to_length = strlen(to);
if (to_length > bufsize) {
to_length = bufsize;
}
memcpy(buf, to, to_length);
return to_length;
} else {
return readlink(path, buf, bufsize);
}
}