NixOS / nixpkgs

Nix Packages collection & NixOS
MIT License
17.35k stars 13.59k forks source link

tcb: shadow hardening #109457

Open Izorkin opened 3 years ago

Izorkin commented 3 years ago

Project description The tcb package contains core components of our tcb suite implementing the alternative password shadowing scheme on Owl.

More information:

I could only do the tcb package - https://github.com/NixOS/nixpkgs/compare/master...Izorkin:shadow-tcb Need add patch to shadow-utils - https://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/shadow-utils/ and integrate to /etc/pam.d/system-auth and /etc/nsswitch.conf

stale[bot] commented 3 years ago

I marked this as stale due to inactivity. → More info

Izorkin commented 1 year ago

How to automatically load libnss_tcb.so.2 library from tcb package. When run passwd, library is only found in glibc package:

strace -ff passwd 2>&1 | grep libnss
openat(AT_FDCWD, "/nix/store/4nlgxhb09sdr51nc9hdm8az5b08vzkgx-glibc-2.35-163/lib/libnss_tcb.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

cc @Ma27 @lovesegfault @vcunat @mweinelt @trofi @ajs124 @Mic92

Mic92 commented 1 year ago

This link says: https://web.archive.org/web/20140923054517/http://felinemenace.org/~andrewg/configuring_gentoo_to_use_openwall_tcb/ it needs to be configured via nss modules and nscd would take care of loading it.

Izorkin commented 1 year ago

@Mic92 i have already set these parameters, already described in more detail here - https://github.com/openwall/tcb/issues/14

Izorkin commented 1 year ago

Draft patch:

diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index b004072813b..8d7f87d3875 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -125,7 +125,7 @@ with lib;
     system.nssDatabases = {
       passwd = mkBefore [ "files" ];
       group = mkBefore [ "files" ];
-      shadow = mkBefore [ "files" ];
+      shadow = mkBefore (if config.security.tcb.enable then [ "tcb" ] else [ "files" ]);
       hosts = mkMerge [
         (mkOrder 998 [ "files" ])
         (mkOrder 1499 [ "dns" ])
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index af7fd4f712c..fd35a8350ae 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -280,6 +280,7 @@
   ./security/rtkit.nix
   ./security/sudo.nix
   ./security/systemd-confinement.nix
+  ./security/tcb.nix
   ./security/tpm2.nix
   ./security/wrappers/default.nix
   ./services/admin/meshcentral.nix
diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix
index fab809f279a..bed7f286bdc 100644
--- a/nixos/modules/programs/shadow.nix
+++ b/nixos/modules/programs/shadow.nix
@@ -40,7 +40,10 @@ let
       # users to change their account GECOS information.
       # This should be made configurable.
       #CHFN_RESTRICT frwh
-
+    '' + optionalString config.security.tcb.enable ''
+      USE_TCB yes
+      TCB_AUTH_GROUP yes
+      TCB_SYMLINKS NO
     '';

   mkSetuidRoot = source:
@@ -50,6 +53,13 @@ let
       inherit source;
     };

+  mkSetuidShadow = source:
+    { setgid = true;
+      owner = "root";
+      group = "shadow";
+      inherit source;
+    };
+
 in

 {
@@ -123,7 +133,10 @@ in
       newgidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newgidmap";
     } // lib.optionalAttrs config.users.mutableUsers {
       chsh   = mkSetuidRoot "${pkgs.shadow.out}/bin/chsh";
+    } // lib.optionalAttrs (config.users.mutableUsers && (!config.security.tcb.enable)) {
       passwd = mkSetuidRoot "${pkgs.shadow.out}/bin/passwd";
+    } // lib.optionalAttrs (config.users.mutableUsers && config.security.tcb.enable) {
+      passwd = mkSetuidShadow "${pkgs.shadow.out}/bin/passwd";
     };
   };
 }
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 273bc796341..2d0efe689ce 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -491,8 +491,15 @@ let
           # The required pam_unix.so module has to come after all the sufficient modules
           # because otherwise, the account lookup will fail if the user does not exist
           # locally, for example with MySQL- or LDAP-auth.
+          optionalString (!config.security.tcb.enable)
           ''
             account required pam_unix.so
+          '' +
+          optionalString config.security.tcb.enable
+          ''
+            account required ${pkgs.tcb}/lib/security/pam_tcb.so shadow fork helper=/run/wrappers/bin/tcb_chkpwd audit debug
+          '' +
+          ''

             # Authentication management.
           '' +
@@ -584,9 +591,12 @@ let
                 auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
               ''
             )) +
-          optionalString cfg.unixAuth ''
+          optionalString (cfg.unixAuth && (!config.security.tcb.enable)) ''
             auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
           '' +
+          optionalString (cfg.unixAuth && config.security.tcb.enable) ''
+            auth sufficient ${pkgs.tcb}/lib/security/pam_tcb.so nullok shadow fork helper=/run/wrappers/bin/tcb_chkpwd audit debug
+          '' +
           optionalString cfg.otpwAuth ''
             auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so
           '' +
@@ -605,8 +615,15 @@ let
             auth required pam_deny.so

             # Password management.
+          '' +
+          optionalString (!config.security.tcb.enable)
+          ''
             password sufficient pam_unix.so nullok sha512
           '' +
+          optionalString config.security.tcb.enable
+          ''
+            password sufficient ${pkgs.tcb}/lib/security/pam_tcb.so shadow fork nullok write_to=tcb helper=/run/wrappers/bin/tcb_chkpwd audit debug
+          '' +
           optionalString config.security.pam.enableEcryptfs ''
             password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
           '' +
@@ -638,9 +655,14 @@ let
           optionalString cfg.setEnvironment ''
             session required pam_env.so conffile=/etc/pam/environment readenv=0
           '' +
+          optionalString (!config.security.tcb.enable)
           ''
             session required pam_unix.so
           '' +
+          optionalString config.security.tcb.enable
+          ''
+            session required ${pkgs.tcb}/lib/security/pam_tcb.so helper=/run/wrappers/bin/tcb_chkpwd audit debug
+          '' +
           optionalString cfg.setLoginUid ''
             session ${if config.boot.isContainer then "optional" else "required"} pam_loginuid.so
           '' +
@@ -1236,7 +1258,7 @@ in

     boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];

-    security.wrappers = {
+    security.wrappers = mkIf (!config.security.tcb.enable) {
       unix_chkpwd = {
         setuid = true;
         owner = "root";
diff --git a/nixos/modules/security/tcb.nix b/nixos/modules/security/tcb.nix
new file mode 100644
index 00000000000..1c1b550f417
--- /dev/null
+++ b/nixos/modules/security/tcb.nix
@@ -0,0 +1,26 @@
+{ lib, config, pkgs, ... }:
+let
+  cfg = config.security.tcb;
+
+in {
+  meta = {
+    maintainers = [ lib.maintainers.izorkin ];
+  };
+
+  options.security.tcb = {
+    enable = lib.mkEnableOption (lib.mdDoc "Alternative shadow scheme");
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.tcb.bin pkgs.tcb.out ];
+
+    security.wrappers = {
+      tcb_chkpwd = {
+        setgid = true;
+        owner = "root";
+        group = "shadow";
+        source = "${pkgs.tcb}/libexec/chkpwd/tcb_chkpwd";
+      };
+    };
+  };
+}
diff --git a/nixos/tests/shadow.nix b/nixos/tests/shadow.nix
index baa2e5945c0..eae52fb9646 100644
--- a/nixos/tests/shadow.nix
+++ b/nixos/tests/shadow.nix
@@ -12,6 +12,8 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
   nodes.shadow = { pkgs, ... }: {
     environment.systemPackages = [ pkgs.shadow ];

+    security.tcb.enable = true;
+
     users = {
       mutableUsers = true;
       users.emma = {
@@ -45,6 +47,7 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
   testScript = ''
     shadow.wait_for_unit("multi-user.target")
     shadow.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    shadow.wait_until_succeeds("tcb_convert")

     with subtest("Normal login"):
         shadow.send_key("alt-f2")
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 61d70ccc19b..102460cf6f9 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -579,6 +579,9 @@ in {
       render.gid = ids.gids.render;
       sgx.gid = ids.gids.sgx;
       shadow.gid = ids.gids.shadow;
+      auth.gid = ids.gids.auth;
+      chkpwd.gid = ids.gids.chkpwd;
+      sys.gid = ids.gids.sys;
     };

     system.activationScripts.users = {
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 17ea04cb4ec..08aeca76734 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -356,6 +356,9 @@ in
       rstudio-server = 324;
       localtimed = 325;
       automatic-timezoned = 326;
+      # auth = 327; # unused
+      # chkpwd = 328; # unused
+      # sys = 329; # unused

       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!

@@ -666,6 +669,9 @@ in
       rstudio-server = 324;
       localtimed = 325;
       automatic-timezoned = 326;
+      auth = 327;
+      chkpwd = 328;
+      sys = 329;

       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal

And in shadow package, need to activate parameter --enable-tcb.

Izorkin commented 1 year ago

Allow this variant?

diff --git a/pkgs/development/libraries/glibc/default.nix b/pkgs/development/libraries/glibc/default.nix
index 791ac47536f..95a7fcbac2b 100644
--- a/pkgs/development/libraries/glibc/default.nix
+++ b/pkgs/development/libraries/glibc/default.nix
@@ -149,6 +149,7 @@ callPackage ./common.nix { inherit stdenv; } {
       # Work around a Nix bug: hard links across outputs cause a build failure.
       cp $bin/bin/getconf $bin/bin/getconf_
       mv $bin/bin/getconf_ $bin/bin/getconf
+      ln -sf /run/current-system/sw/lib/libnss_tcb.so.2 $out/lib/libnss_tcb.so.2
     '';

     separateDebugInfo = true;
flokli commented 1 year ago

As expressed in https://github.com/NixOS/nixpkgs/pull/207547#issuecomment-1364590045, adding the NSS module to LD_LIBRARY_PATH to get glibc to load it into every process is the wrong approach, and NSS lookups via should ns(n)cd most likely should work.

I asked for confirmation in https://github.com/openwall/tcb/issues/14#issuecomment-1364590368.

As for the draft patch, we should probably see how we can use/extend the existing module options to be able to express this, so this can be properly tested in a VM test.

Izorkin commented 1 year ago

@flokli right now I don't know how to automatically create shadow files in /etc/tcb/${users}/ directorys. without using intermediate step of converting the file /etc/shadow to /etc/tcb/${users}/shadow using the tcb_convert utility.

The suggested build flag didn't help either:

diff --git a/pkgs/tools/security/tcb/default.nix b/pkgs/tools/security/tcb/default.nix
index 63b252be952..35de1dc9ea8 100644
--- a/pkgs/tools/security/tcb/default.nix
+++ b/pkgs/tools/security/tcb/default.nix
@@ -28,6 +28,10 @@ stdenv.mkDerivation rec {
       --replace "INCLUDEDIR = \$(PREFIX)/include" "INCLUDEDIR = $dev/include"
   '';

+  CFLAGS = [
+    "-DENABLE_SETFSUGID"
+  ];
+
   meta = with lib; {
     description = "Alternative password shadowing scheme";
     longDescription = ''
systemctl cat nscd | grep LD_LIBRARY_PATH
Environment="LD_LIBRARY_PATH=/nix/store/07ccvqbj89943b0vwcmqzd9la7jfzka0-systemd-252.1/lib:/nix/store/jci59xwz7pkj4rysn17sjsqzdgkqmvnf-tcb-1.2/lib"

Library libnss_tcb.so.2 not found.

Izorkin commented 1 year ago

To test, I have changed to other nss module:

shadow:    systemd
strace -ff passwd 2>&1 | grep libnss
[pid  4189] openat(AT_FDCWD, "/nix/store/ayfr5l52xkqqjn3n4h9jfacgnchz1z7s-glibc-2.35-224/lib/libnss_systemd.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/nix/store/ayfr5l52xkqqjn3n4h9jfacgnchz1z7s-glibc-2.35-224/lib/libnss_systemd.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

NSCD does not find an existing nss library.

ls -lah /nix/store/07ccvqbj89943b0vwcmqzd9la7jfzka0-systemd-252.1/lib | grep libnss_systemd
-r-xr-xr-x  1 root root 434K Jan  1  1970 libnss_systemd.so.2

Most likely the problem is not with tcb.

flokli commented 1 year ago

Can you convert your test case to a proper VM test? There is a nscd test in nixpkgs, and this stuff usually should work.

flokli commented 1 year ago

It looks like there's no request type in nscd protocol to ask for shadow database entries.

Is the passwd database not sufficient?

Izorkin commented 1 year ago

Can you convert your test case to a proper VM test?

I'll try to think of something with the dough. The required changes have not yet been added to the master branch.

Is the passwd database not sufficient?

TCB manages only the shadow files, which are placed in the /etc/shadow/${username} directory.

Izorkin commented 1 year ago

Can you convert your test case to a proper VM test? There is a nscd test in nixpkgs, and this stuff usually should work.

Updated draft patch - https://github.com/NixOS/nixpkgs/issues/109457#issuecomment-1339020749 Modified the shadow test and using this glibc patch:

diff --git a/pkgs/development/libraries/glibc/default.nix b/pkgs/development/libraries/glibc/default.nix
index 791ac47536f..3e581d2967b 100644
--- a/pkgs/development/libraries/glibc/default.nix
+++ b/pkgs/development/libraries/glibc/default.nix
@@ -149,6 +149,9 @@ callPackage ./common.nix { inherit stdenv; } {
       # Work around a Nix bug: hard links across outputs cause a build failure.
       cp $bin/bin/getconf $bin/bin/getconf_
       mv $bin/bin/getconf_ $bin/bin/getconf
+      # Allow load libnss tcb module.
+      # It is currently not possible to load third party libnss modules from a custom directory.
+      ln -sf /run/current-system/sw/lib/libnss_tcb.so.2 $out/lib/libnss_tcb.so.2
     '';
Izorkin commented 1 year ago

@flokli rechecking with system.nssModules - not working:

diff --git a/nixos/modules/security/tcb.nix b/nixos/modules/security/tcb.nix
index 1c1b550f417..8e28ac5da22 100644
--- a/nixos/modules/security/tcb.nix
+++ b/nixos/modules/security/tcb.nix
@@ -12,7 +12,9 @@ in {
   };

   config = lib.mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.tcb.bin pkgs.tcb.out ];
+    environment.systemPackages = [ pkgs.tcb.bin ];
+
+    system.nssModules = [ pkgs.tcb.out ];

     security.wrappers = {
       tcb_chkpwd = {

Need to find a way to load the libnss module.

Izorkin commented 1 year ago

Debug with LD_DEBUG:

LD_DEBUG=all passwd
...
    224612:     file=libnss_tcb.so.2 [0];  dynamically loaded by /nix/store/1xczacg4m01bgcqhdkh11b96cdbhim5l-glibc-2.35-224/lib/libc.so.6 [0]
    224612:     find library=libnss_tcb.so.2 [0]; searching
    224612:      search cache=/nix/store/1xczacg4m01bgcqhdkh11b96cdbhim5l-glibc-2.35-224/etc/ld.so.cache
    224612:      search path=/nix/store/1xczacg4m01bgcqhdkh11b96cdbhim5l-glibc-2.35-224/lib             (system search path)
    224612:       trying file=/nix/store/1xczacg4m01bgcqhdkh11b96cdbhim5l-glibc-2.35-224/lib/libnss_tcb.so.2
    224612:
    224611:
    224611:     file=libnss_tcb.so.2 [0];  dynamically loaded by /nix/store/1xczacg4m01bgcqhdkh11b96cdbhim5l-glibc-2.35-224/lib/libc.so.6 [0]
    224611:     find library=libnss_tcb.so.2 [0]; searching
    224611:      search cache=/nix/store/1xczacg4m01bgcqhdkh11b96cdbhim5l-glibc-2.35-224/etc/ld.so.cache
    224611:      search path=/nix/store/1xczacg4m01bgcqhdkh11b96cdbhim5l-glibc-2.35-224/lib             (system search path)
    224611:       trying file=/nix/store/1xczacg4m01bgcqhdkh11b96cdbhim5l-glibc-2.35-224/lib/libnss_tcb.so.2
    224611:
passwd: Permission denied
passwd: пароль не изменён
...
solardiz commented 1 year ago

Debug with LD_DEBUG

You cannot fully do that. Please note that LD_* environment variables are not supposed to fully work, if at all, nor to have no side-effects, when used on SUID/SGID programs. So when you enable LD_DEBUG, it may either be ignored or result in the binary running as non-SUID/SGID (and likely failing to work correctly for that reason). In this example, you can probably use LD_DEBUG=all on a copy of passwd that is not SUID/SGID and run the command as root, thus testing its ability to change passwords when run by root. However, you cannot really do that as non-root, thus cannot see what really happens when you're changing a non-root user's password when the command is run by the user.

Izorkin commented 1 year ago

I was able to load the libnss_tcb library via /etc/ld.so.cache. On a normal user, changing the password and logging in also works. It does not want to work through the LD_LIBRARY_PATH=/nix/store/s6w6az734vw7n9fzb51a9adr0xfxlhw6-tcb-1.2/lib variable.

solardiz commented 1 year ago

how to automatically create shadow files in /etc/tcb/${users}/ directorys. without using intermediate step of converting the file /etc/shadow to /etc/tcb/${users}/shadow using the tcb_convert utility.

It's fine to have the intermediate step - with that, you can treat tcb_convert as your tool to create the files. No reason to reimplement functionality that we already have due to tcb_convert. Here's how we do it in Owl's owl-etc package, where the system normally uses tcb right upon installation (due to this logic):

%triggerin -- shadow-utils
function pause()
{
    echo
    echo "Install will continue in 10 seconds..."
    sleep 10
}

# Determine whether the current /etc/shadow matches the initial version
# as provided by this package.
if [ -e /etc/shadow.rpmnew -o ! -e /etc/shadow ]; then
    SHADOW_INITIAL=no
elif [ "`sha1sum < /etc/shadow`" = "%shadow_initial_sha1  -" ]; then
    SHADOW_INITIAL=yes
else
    SHADOW_INITIAL=no
fi

# New install?
if [ $SHADOW_INITIAL = yes -a ! -e /etc/tcb -a \
    ! -e /etc/nsswitch.conf.rpmnew ]; then
    echo "No existing password shadowing found, will use /etc/tcb."
    /sbin/tcb_convert && rm /etc/shadow
# Updating an install that uses tcb?
elif [ \( $SHADOW_INITIAL = yes -o ! -e /etc/shadow \) -a -d /etc/tcb ]; then
    echo "OK, already using /etc/tcb."
    rm -f /etc/shadow
# Updating an install that uses shadow?
elif [ $SHADOW_INITIAL = no -a -f /etc/shadow -a ! -e /etc/tcb ]; then
    if [ -e /etc/nsswitch.conf.rpmnew ]; then
        cat << EOF
This system appears to be using /etc/shadow.  Conversion to /etc/tcb
is desired, but /etc/nsswitch.conf appears to have been modified locally
preventing automatic conversion.  You'll need to either convert this
system to /etc/tcb manually or knowingly keep it with /etc/shadow
(which is also non-default with a number of other configuration files).
Please refer to tcb_convert(8) for instructions.
EOF
    else
        cat << EOF
This system appears to be using /etc/shadow and will now be converted
to /etc/tcb.

EOF
        if /sbin/tcb_convert; then
            echo "tcb_convert succeeded"
            if [ "`%_sbindir/control passwd`" != restricted ]; then
                echo "Setting passwd(1) file modes for tcb"
                %_sbindir/control passwd tcb
                ls -l %_bindir/passwd
            fi
            rm -f /etc/shadow.rpmnew
            mv -v /etc/shadow /etc/shadow-pre-tcb
            chmod -v go-rwx /etc/shadow*
            cat << EOF

The old shadow file and any its backups have been left around - be sure
to remove them once you're positive the conversion has succeeded.
EOF
        else
            cat << EOF
tcb_convert FAILED

Your system may be in an inconsistent state now, please perform the
conversion to /etc/tcb manually.  See tcb_convert(8) for instructions.
EOF
        fi
    fi
    pause
# Updating a misconfigured install?
elif [ $SHADOW_INITIAL = no -a -e /etc/shadow -a -e /etc/tcb ]; then
    cat << EOF
This system appears to be misconfigured: both /etc/shadow and /etc/tcb
exist.  It may be in an inconsistent state now, please complete the
conversion to /etc/tcb manually.  See tcb_convert(8) for instructions.
EOF
    pause
# Possible other misconfigurations.
else
    cat << EOF
This system's local user authentication appears to be misconfigured.
You might need to convert it to /etc/tcb manually, see tcb_convert(8)
for instructions.
EOF
    pause
fi

rm -f /etc/{passwd,shadow,group}.rpmnew
solardiz commented 1 year ago

It does not want to work through the LD_LIBRARY_PATH=/nix/store/s6w6az734vw7n9fzb51a9adr0xfxlhw6-tcb-1.2/lib variable.

Indeed. This is not supposed to work for the same reason I just described. Edit: BTW, I told you so in https://github.com/openwall/tcb/issues/14#issuecomment-1338036737

Izorkin commented 1 year ago

Is there a patch to add tcb support directly to glibc?

solardiz commented 1 year ago

Is there a patch to add tcb support directly to glibc?

Not that I'm aware of. However, musl has it.