Skip to content

Commit

Permalink
[24.05] backport fcgiwrap instances fix for local privilege escalatio…
Browse files Browse the repository at this point in the history
…n issue (#331465)
  • Loading branch information
emilazy authored Aug 31, 2024
2 parents 58e942b + 8931f18 commit e2b77fb
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 44 deletions.
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,7 @@
./services/web-servers/caddy/default.nix
./services/web-servers/darkhttpd.nix
./services/web-servers/fcgiwrap.nix
./services/web-servers/fcgiwrap-instances.nix
./services/web-servers/garage.nix
./services/web-servers/hitch/default.nix
./services/web-servers/hydron.nix
Expand Down
15 changes: 7 additions & 8 deletions nixos/modules/services/misc/zoneminder.nix
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,11 @@ in {
];

services = {
fcgiwrap = lib.mkIf useNginx {
enable = true;
preforkProcesses = cfg.cameras;
inherit user group;
fcgiwrap.instances.zoneminder = lib.mkIf useNginx {
process.prefork = cfg.cameras;
process.user = user;
process.group = group;
socket = { inherit (config.services.nginx) user group; };
};

mysql = lib.mkIf cfg.database.createLocally {
Expand All @@ -225,9 +226,7 @@ in {
default = true;
root = "${pkg}/share/zoneminder/www";
listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ];
extraConfig = let
fcgi = config.services.fcgiwrap;
in ''
extraConfig = ''
index index.php;
location / {
Expand Down Expand Up @@ -257,7 +256,7 @@ in {
fastcgi_param HTTP_PROXY "";
fastcgi_intercept_errors on;
fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress};
fastcgi_pass unix:${config.services.fcgiwrap.instances.zoneminder.socket.address};
}
location /cache/ {
Expand Down
103 changes: 75 additions & 28 deletions nixos/modules/services/networking/cgit.nix
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ let

regexLocation = cfg: regexEscape (stripLocation cfg);

mkFastcgiPass = cfg: ''
mkFastcgiPass = name: cfg: ''
${if cfg.nginx.location == "/" then ''
fastcgi_param PATH_INFO $uri;
'' else ''
fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
''
}fastcgi_pass unix:${config.services.fcgiwrap.socketAddress};
}fastcgi_pass unix:${config.services.fcgiwrap.instances."cgit-${name}".socket.address};
'';

cgitrcLine = name: value: "${name}=${
Expand Down Expand Up @@ -72,25 +72,11 @@ let
${cfg.extraConfig}
'';

mkCgitReposDir = cfg:
if cfg.scanPath != null then
cfg.scanPath
else
pkgs.runCommand "cgit-repos" {
preferLocalBuild = true;
allowSubstitutes = false;
} ''
mkdir -p "$out"
${
concatStrings (
mapAttrsToList
(name: value: ''
ln -s ${escapeShellArg value.path} "$out"/${escapeShellArg name}
'')
cfg.repos
)
}
'';
fcgiwrapUnitName = name: "fcgiwrap-cgit-${name}";
fcgiwrapRuntimeDir = name: "/run/${fcgiwrapUnitName name}";
gitProjectRoot = name: cfg: if cfg.scanPath != null
then cfg.scanPath
else "${fcgiwrapRuntimeDir name}/repos";

in
{
Expand Down Expand Up @@ -154,6 +140,30 @@ in
type = types.lines;
default = "";
};

user = mkOption {
description = ''
User to run the cgit service as.
Defaults to "root" for compatibility with legacy setups.
Will default to the unprivileged user "cgit" in NixOS 24.11.
'';
type = types.str;
default = "root";
example = "cgit";
};

group = mkOption {
description = ''
Group to run the cgit service as.
Defaults to "root" for compatibility with legacy setups.
Will default to the unprivileged user "cgit" in NixOS 24.11.
'';
type = types.str;
default = "root";
example = "cgit";
};
};
}));
};
Expand All @@ -165,29 +175,66 @@ in
message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set.";
}) cfgs;

services.fcgiwrap.enable = true;
warnings = flatten (flip mapAttrsToList cfgs (inst: cfg:
optional (cfg.user == "root") ''
`services.cgit.${inst}` is configured to run as root.
This has security implications. See advisory: https://discourse.nixos.org/t/51419
It is recommended to set an unprivileged user explicitly.
This default user will be set to "cgit" in NixOS 24.11.
''
));

users = mkMerge (flip mapAttrsToList cfgs (_: cfg: {
users.${cfg.user} = {
isSystemUser = true;
inherit (cfg) group;
};
groups.${cfg.group} = { };
}));

services.fcgiwrap.instances = flip mapAttrs' cfgs (name: cfg:
nameValuePair "cgit-${name}" {
process = { inherit (cfg) user group; };
socket = { inherit (config.services.nginx) user group; };
}
);

systemd.services = flip mapAttrs' cfgs (name: cfg:
nameValuePair (fcgiwrapUnitName name)
(mkIf (cfg.repos != { }) {
serviceConfig.RuntimeDirectory = fcgiwrapUnitName name;
preStart = ''
GIT_PROJECT_ROOT=${escapeShellArg (gitProjectRoot name cfg)}
mkdir -p "$GIT_PROJECT_ROOT"
cd "$GIT_PROJECT_ROOT"
${concatLines (flip mapAttrsToList cfg.repos (name: repo: ''
ln -s ${escapeShellArg repo.path} ${escapeShellArg name}
''))}
'';
}
));

services.nginx.enable = true;

services.nginx.virtualHosts = mkMerge (mapAttrsToList (_: cfg: {
services.nginx.virtualHosts = mkMerge (mapAttrsToList (name: cfg: {
${cfg.nginx.virtualHost} = {
locations = (
genAttrs'
[ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ]
(name: nameValuePair "= ${stripLocation cfg}/${name}" {
(fileName: nameValuePair "= ${stripLocation cfg}/${fileName}" {
extraConfig = ''
alias ${cfg.package}/cgit/${name};
alias ${cfg.package}/cgit/${fileName};
'';
})
) // {
"~ ${regexLocation cfg}/.+/(info/refs|git-upload-pack)" = {
fastcgiParams = rec {
SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend";
GIT_HTTP_EXPORT_ALL = "1";
GIT_PROJECT_ROOT = mkCgitReposDir cfg;
GIT_PROJECT_ROOT = gitProjectRoot name cfg;
HOME = GIT_PROJECT_ROOT;
};
extraConfig = mkFastcgiPass cfg;
extraConfig = mkFastcgiPass name cfg;
};
"${stripLocation cfg}/" = {
fastcgiParams = {
Expand All @@ -196,7 +243,7 @@ in
HTTP_HOST = "$server_name";
CGIT_CONFIG = mkCgitrc cfg;
};
extraConfig = mkFastcgiPass cfg;
extraConfig = mkFastcgiPass name cfg;
};
};
};
Expand Down
9 changes: 7 additions & 2 deletions nixos/modules/services/networking/smokeping.nix
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ in
};
preStart = ''
mkdir -m 0755 -p ${smokepingHome}/cache ${smokepingHome}/data
chown -R ${cfg.user}:${cfg.user} ${smokepingHome}/{cache,data}
ln -snf ${cfg.package}/htdocs/css ${smokepingHome}/css
ln -snf ${cfg.package}/htdocs/js ${smokepingHome}/js
ln -snf ${cgiHome} ${smokepingHome}/smokeping.fcgi
Expand All @@ -337,7 +338,11 @@ in
};

# use nginx to serve the smokeping web service
services.fcgiwrap.enable = mkIf cfg.webService true;
services.fcgiwrap.instances.smokeping = mkIf cfg.webService {
process.user = cfg.user;
process.group = cfg.user;
socket = { inherit (config.services.nginx) user group; };
};
services.nginx = mkIf cfg.webService {
enable = true;
virtualHosts."smokeping" = {
Expand All @@ -349,7 +354,7 @@ in
locations."/smokeping.fcgi" = {
extraConfig = ''
include ${config.services.nginx.package}/conf/fastcgi_params;
fastcgi_pass unix:${config.services.fcgiwrap.socketAddress};
fastcgi_pass unix:${config.services.fcgiwrap.instances.smokeping.socket.address};
fastcgi_param SCRIPT_FILENAME ${smokepingHome}/smokeping.fcgi;
fastcgi_param DOCUMENT_ROOT ${smokepingHome};
'';
Expand Down
136 changes: 136 additions & 0 deletions nixos/modules/services/web-servers/fcgiwrap-instances.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{ config, lib, pkgs, ... }:

with lib;

let
forEachInstance = f: flip mapAttrs' config.services.fcgiwrap.instances (
name: cfg: nameValuePair "fcgiwrap-${name}" (f cfg)
);

in {
options.services.fcgiwrap.instances = mkOption {
description = "Configuration for fcgiwrap instances.";
default = { };
type = types.attrsOf (types.submodule ({ config, ... }: { options = {
process.prefork = mkOption {
type = types.ints.positive;
default = 1;
description = "Number of processes to prefork.";
};

process.user = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
User as which this instance of fcgiwrap will be run.
Set to `null` (the default) to use a dynamically allocated user.
'';
};

process.group = mkOption {
type = types.nullOr types.str;
default = null;
description = "Group as which this instance of fcgiwrap will be run.";
};

socket.type = mkOption {
type = types.enum [ "unix" "tcp" "tcp6" ];
default = "unix";
description = "Socket type: 'unix', 'tcp' or 'tcp6'.";
};

socket.address = mkOption {
type = types.str;
default = "/run/fcgiwrap-${config._module.args.name}.sock";
example = "1.2.3.4:5678";
description = ''
Socket address.
In case of a UNIX socket, this should be its filesystem path.
'';
};

socket.user = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
User to be set as owner of the UNIX socket.
'';
};

socket.group = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Group to be set as owner of the UNIX socket.
'';
};

socket.mode = mkOption {
type = types.nullOr types.str;
default = if config.socket.type == "unix" then "0600" else null;
defaultText = literalExpression ''
if config.socket.type == "unix" then "0600" else null
'';
description = ''
Mode to be set on the UNIX socket.
Defaults to private to the socket's owner.
'';
};
}; }));
};

config = {
assertions = concatLists (mapAttrsToList (name: cfg: [
{
assertion = cfg.socket.type == "unix" -> cfg.socket.user != null;
message = "Socket owner is required for the UNIX socket type.";
}
{
assertion = cfg.socket.type == "unix" -> cfg.socket.group != null;
message = "Socket owner is required for the UNIX socket type.";
}
{
assertion = cfg.socket.user != null -> cfg.socket.type == "unix";
message = "Socket owner can only be set for the UNIX socket type.";
}
{
assertion = cfg.socket.group != null -> cfg.socket.type == "unix";
message = "Socket owner can only be set for the UNIX socket type.";
}
{
assertion = cfg.socket.mode != null -> cfg.socket.type == "unix";
message = "Socket mode can only be set for the UNIX socket type.";
}
]) config.services.fcgiwrap.instances);

systemd.services = forEachInstance (cfg: {
after = [ "nss-user-lookup.target" ];
wantedBy = optional (cfg.socket.type != "unix") "multi-user.target";

serviceConfig = {
ExecStart = ''
${pkgs.fcgiwrap}/sbin/fcgiwrap ${cli.toGNUCommandLineShell {} ({
c = cfg.process.prefork;
} // (optionalAttrs (cfg.socket.type != "unix") {
s = "${cfg.socket.type}:${cfg.socket.address}";
}))}
'';
} // (if cfg.process.user != null then {
User = cfg.process.user;
Group = cfg.process.group;
} else {
DynamicUser = true;
});
});

systemd.sockets = forEachInstance (cfg: mkIf (cfg.socket.type == "unix") {
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = cfg.socket.address;
SocketUser = cfg.socket.user;
SocketGroup = cfg.socket.group;
SocketMode = cfg.socket.mode;
};
});
};
}
Loading

0 comments on commit e2b77fb

Please sign in to comment.