Skip to content

Commit

Permalink
nixos/espanso-capdacoverride: init module
Browse files Browse the repository at this point in the history
  • Loading branch information
pitkling committed Nov 20, 2024
1 parent 1e93f06 commit 22e99a9
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 0 deletions.
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,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);
}
}

0 comments on commit 22e99a9

Please sign in to comment.