]> ruderich.org/simon Gitweb - nsscash/nsscash.git/commitdiff
Check size limits and abort if they are violated
authorSimon Ruderich <simon@ruderich.org>
Tue, 11 Jun 2019 09:05:08 +0000 (11:05 +0200)
committerSimon Ruderich <simon@ruderich.org>
Tue, 11 Jun 2019 09:05:08 +0000 (11:05 +0200)
Also document them in the README and add tests.

README
group.go
nss/tests/gr.c
nss/tests/pw.c
passwd.go

diff --git a/README b/README
index a6d40b51b51ac234ecbd5477db36a0a3e5028fc8..1b8a6c35650fed3627f8753bc46559521f84d37f 100644 (file)
--- a/README
+++ b/README
@@ -33,6 +33,15 @@ Nsscash is very careful when deploying the changes:
   permitted and will not be written to disk. This is designed to prevent the
   accidental loss of all users/groups on a system.
 
+The passwd/group files have the following size restrictions:
+- maximum number of entries: '2^64-1' (uint64_t)
+- maximum passwd entry size: 65543 bytes (including newline)
+- maximum group entry size: 65535 bytes (including newline, only one member)
+- maximum members per group: depends on the user name length,
+                             with 9 bytes per user: 5460 users
+- `nsscash` checks for these restrictions and aborts with an error if they are
+  violated
+
 nsscash is licensed under AGPL version 3 or later.
 
 [1] https://github.com/google/nsscache
index 126ebb0dc349e0127c4ff14f3b61f48cb99cc737..4ca213a0edfabc48fd7a95e4280509c4547c9d5c 100644 (file)
--- a/group.go
+++ b/group.go
@@ -26,6 +26,7 @@ import (
        "sort"
        "strconv"
        "strings"
+       "math"
 
        "github.com/pkg/errors"
 )
@@ -130,6 +131,11 @@ func SerializeGroup(g Group) ([]byte, error) {
        }
        // And the group members concatenated as above
        data.Write(mems.Bytes())
+       // Ensure the offsets can fit the length of this entry
+       if data.Len() > math.MaxUint16 {
+               return nil, fmt.Errorf("group too large to serialize: %v, %v",
+                       data.Len(), g)
+       }
        size := uint16(data.Len())
 
        var res bytes.Buffer // serialized result
index 3b2f994d5b6b60250e3c39ea06785c127586fefa..487e673b2d84f3363f8363f50ed9a9d9893a289a 100644 (file)
@@ -23,6 +23,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include "../cash_nss.h"
 
@@ -319,10 +320,115 @@ static void test_getgrnam(void) {
     assert(rename("tests/group.nsscash.tmp", "tests/group.nsscash") == 0);
 }
 
+static void test_limits(void) {
+    char large_member[65525];
+    memset(large_member, 'X', sizeof(large_member));
+    large_member[sizeof(large_member)-1] = '\0';
+
+    char many_members[54603]; // 5461 members
+    memset(many_members, 'X', sizeof(many_members));
+    for (int i = 9; i < (int)sizeof(many_members); i += 10) {
+        many_members[i-1] = (char)('A' + i % ('Z' - 'A'));
+        many_members[i] = ',';
+    }
+    many_members[sizeof(many_members)-1] = '\0';
+
+    int r;
+    FILE *fh;
+
+    const char *nsscache_cmd = "../nsscash convert group "
+        "tests/limits tests/limits.nsscash 2> /dev/null";
+
+    // Entries which will not fit in uint16_t, nsscash must abort
+
+    fh = fopen("tests/limits", "w");
+    assert(fh != NULL);
+    r = fprintf(fh, "test:x:42:A%s\n", large_member);
+    assert(r == 65536);
+    r = fclose(fh);
+    assert(r == 0);
+
+    r = system(nsscache_cmd);
+    assert(r != -1);
+    assert(WIFEXITED(r) && WEXITSTATUS(r) == 1);
+
+    fh = fopen("tests/limits", "w");
+    assert(fh != NULL);
+    r = fprintf(fh, "many:x:4711:%s%s\n", many_members, many_members);
+    assert(r == 109217);
+    r = fclose(fh);
+    assert(r == 0);
+
+    r = system(nsscache_cmd);
+    assert(r != -1);
+    assert(WIFEXITED(r) && WEXITSTATUS(r) == 1);
+
+    // Largest entries which will fit
+
+    fh = fopen("tests/limits", "w");
+    assert(fh != NULL);
+    r = fprintf(fh, "test:x:42:%s\n", large_member);
+    assert(r == 65535);
+    r = fprintf(fh, "many:x:4711:%s\n", many_members);
+    assert(r == 54615);
+    r = fclose(fh);
+    assert(r == 0);
+
+    r = system(nsscache_cmd);
+    assert(r != -1);
+    assert(WIFEXITED(r) && WEXITSTATUS(r) == 0);
+
+    r = rename("tests/group.nsscash", "tests/group.nsscash.tmp");
+    assert(r == 0);
+    r = rename("tests/limits.nsscash", "tests/group.nsscash");
+    assert(r == 0);
+
+    // Check if the entry can be retrieved
+
+    struct group g;
+    enum nss_status s;
+    char tmp[sizeof(char **) + 1*sizeof(char *) + 1*sizeof(uint16_t) +
+             4+1 + 1+1 + 65525 + 1];
+    char tmp2[sizeof(char **) + 5462*sizeof(char *) + 5462*sizeof(uint16_t) +
+             4+1 + 1+1 + 54603 + 1];
+    int errnop = 0;
+
+    s = _nss_cash_getgrgid_r(42, &g, tmp, sizeof(tmp), &errnop);
+    assert(s == NSS_STATUS_SUCCESS);
+    assert(!strcmp(g.gr_name, "test"));
+    assert(g.gr_gid == 42);
+    assert(g.gr_mem != NULL);
+    assert(!strcmp(g.gr_mem[0], large_member));
+    assert(g.gr_mem[1] == NULL);
+
+    s = _nss_cash_getgrgid_r(4711, &g, tmp2, sizeof(tmp2), &errnop);
+    assert(s == NSS_STATUS_SUCCESS);
+    assert(!strcmp(g.gr_name, "many"));
+    assert(g.gr_gid == 4711);
+    assert(g.gr_mem != NULL);
+    for (int i = 0; i < 5461-1; i++) {
+        char x[9+1];
+        memset(x, 'X', sizeof(x));
+        x[8] = (char)('A' + (i * 10 + 9) % ('Z' - 'A'));
+        x[9] = '\0';
+        assert(!strcmp(g.gr_mem[i], x));
+    }
+    assert(!strcmp(g.gr_mem[5461-1], "XX"));
+    assert(g.gr_mem[5461] == NULL);
+
+    r = rename("tests/group.nsscash.tmp", "tests/group.nsscash");
+    assert(r == 0);
+
+    r = unlink("tests/limits");
+    assert(r == 0);
+}
+
 int main(void) {
     test_getgrent();
     test_getgrgid();
     test_getgrnam();
 
+    test_limits();
+
     return EXIT_SUCCESS;
 }
index 86e6ae4c93287c06ba771ce156155382b2220124..b832b38d8c3967d19a5b06681fdb1f32b92d1f55 100644 (file)
@@ -23,6 +23,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include "../cash_nss.h"
 
@@ -312,10 +313,89 @@ static void test_getpwnam(void) {
     assert(rename("tests/passwd.nsscash.tmp", "tests/passwd.nsscash") == 0);
 }
 
+static void test_limits(void) {
+    char gecos[65508];
+    memset(gecos, 'X', sizeof(gecos));
+    gecos[sizeof(gecos)-1] = '\0';
+
+    int r;
+    FILE *fh;
+
+    const char *nsscache_cmd = "../nsscash convert passwd "
+        "tests/limits tests/limits.nsscash 2> /dev/null";
+
+    // Entries which will not fit in uint16_t, nsscash must abort
+
+    fh = fopen("tests/limits", "w");
+    assert(fh != NULL);
+    r = fprintf(fh, "test:xx:42:4711:%s:/home/test:/bin/zsh\n", gecos);
+    assert(r == 65544);
+    r = fclose(fh);
+    assert(r == 0);
+
+    r = system(nsscache_cmd);
+    assert(r != -1);
+    assert(WIFEXITED(r) && WEXITSTATUS(r) == 1);
+
+    fh = fopen("tests/limits", "w");
+    assert(fh != NULL);
+    r = fprintf(fh, "test:%s:42:4711:%s:/home/test:/bin/zsh\n", gecos, gecos);
+    assert(r == 131049);
+    r = fclose(fh);
+    assert(r == 0);
+
+    r = system(nsscache_cmd);
+    assert(r != -1);
+    assert(WIFEXITED(r) && WEXITSTATUS(r) == 1);
+
+    // Largest entry which will fit
+
+    fh = fopen("tests/limits", "w");
+    assert(fh != NULL);
+    r = fprintf(fh, "test:x:42:4711:%s:/home/test:/bin/zsh\n", gecos);
+    assert(r == 65543);
+    r = fclose(fh);
+    assert(r == 0);
+
+    r = system(nsscache_cmd);
+    assert(r != -1);
+    assert(WIFEXITED(r) && WEXITSTATUS(r) == 0);
+
+    r = rename("tests/passwd.nsscash", "tests/passwd.nsscash.tmp");
+    assert(r == 0);
+    r = rename("tests/limits.nsscash", "tests/passwd.nsscash");
+    assert(r == 0);
+
+    // Check if the entry can be retrieved
+
+    struct passwd p;
+    enum nss_status s;
+    char tmp[4+1 + 1+1 + 65508 + 10+1 + 8+1];
+    int errnop = 0;
+
+    s = _nss_cash_getpwuid_r(42, &p, tmp, sizeof(tmp), &errnop);
+    assert(s == NSS_STATUS_SUCCESS);
+    assert(!strcmp(p.pw_name, "test"));
+    assert(!strcmp(p.pw_passwd, "x"));
+    assert(p.pw_uid == 42);
+    assert(p.pw_gid == 4711);
+    assert(!strcmp(p.pw_gecos, gecos));
+    assert(!strcmp(p.pw_dir, "/home/test"));
+    assert(!strcmp(p.pw_shell, "/bin/zsh"));
+
+    r = rename("tests/passwd.nsscash.tmp", "tests/passwd.nsscash");
+    assert(r == 0);
+
+    r = unlink("tests/limits");
+    assert(r == 0);
+}
+
 int main(void) {
     test_getpwent();
     test_getpwuid();
     test_getpwnam();
 
+    test_limits();
+
     return EXIT_SUCCESS;
 }
index 54da63b3f30252f65e5c1717318b683686a037c1..c9f4409c2bbb013f99631d251389f2e8d2e85de9 100644 (file)
--- a/passwd.go
+++ b/passwd.go
@@ -23,6 +23,7 @@ import (
        "encoding/binary"
        "fmt"
        "io"
+       "math"
        "sort"
        "strconv"
        "strings"
@@ -103,6 +104,11 @@ func SerializePasswd(p Passwd) ([]byte, error) {
        offShell := uint16(data.Len())
        data.Write([]byte(p.Shell))
        data.WriteByte(0)
+       // Ensure the offsets can fit the length of this entry
+       if data.Len() > math.MaxUint16 {
+               return nil, fmt.Errorf("passwd too large to serialize: %v, %v",
+                       data.Len(), p)
+       }
        size := uint16(data.Len())
 
        var res bytes.Buffer // serialized result