I expanded upon this some more. -fixed some security concerns; fossil ui no longer is a bypass -fixed an issue with access() and setuid logic -the web interface has a place to change the posix id -no need to manually modify tables -changes contained within #ifdefs (exepct on the scheme.c part were I couldn't figure out how)
I can't get this into production fast enough! Maybe someone else feels the need to. ------------- Experiment Howto project name: alpha administrator account alpha-adm project specific fossil binary: /usr/bin/fossil-alpha project user: alpha-user-a Setup user alpha-adm: # useradd -s /bin/bash alpha-adm -m ... Setup user alpha-user-a: # useradd -s /bin/bash -m alpha-user-a ... (make sure these home dir perms are setup the right way - ie not ubuntu defaults) Setup project specfic fossil binary: # cp fossil /usr/local/bin/fossil-alpha # chown alpha-adm:alpha-adm /usr/local/bin/fossil-alpha # chmod u+s /usr/local/bin/fossil-alpha Initialize the project: alpha-adm@host:~$ fossil-alpha init ./alpha.fossil alpha-adm@host:~$ fossil-alpha ui ./alpha.fossil -remove permissions for nobody for demonstration Demonstrate regular fossil binary cannot access the project: alpha-user-a@host:~$ fossil clone /home/alpha-adm/alpha.fossil alpha-clone.fossil fossil: cannot open "/home/alpha-adm/alpha.fossil" for reading Demonstrate project specific fossil binary can not locally access the project for non-admins: alpha-user-a@host:~$ fossil-alpha clone /home/alpha-adm/alpha.fossil alpha-clone.fossil fossil-alpha: cannot open "alpha-clone.fossil" for writing (misleading error. you follow?) I think this is still a security hole. I need to find the code path for local file based urls. Add user to fossil: alpha-adm@host:~$ fossil-alpha ui ./alpha.fossil -add a user alpha-user-a and fill in the posix uid field -grant just clone rights for demo Regular fossil binary over ssh still yields no access: $ fossil clone ssh://alpha-user-a:*@127.0.0.1:29//home/alpha-adm/alpha.fossil?fossil=/usr/local/bin/fossil alpha-clone.fossil ssh -e none -T -p 29 [email protected] Bytes Cards Artifacts Deltas Sent: 53 1 0 0 Received: 218 1 0 0 Sent: 58 1 0 0 Error: Database error: attempt to write a readonly database UPDATE event SET mtime=(SELECT m1 FROM time_fudge WHERE mid=objid) WHERE objid IN (SELECT mid FROM time_fudge);DROP TABLE time_fudge; Received: 218 1 0 0 Total network traffic: 518 bytes sent, 794 bytes received fossil: server returned an error - clone aborted Errors are misleading. user alpha-user-a has now way to read the file /home/alpha-adm/alpha.fossil Project specific fossil with no remote user yields no access: $ fossil clone ssh://alpha-user-a:*@127.0.0.1:29//home/alpha-adm/alpha.fossil?fossil=/usr/local/bin/fossil-alpha alpha-clone.fossil ssh -e none -T -p 29 [email protected] Bytes Cards Artifacts Deltas Sent: 53 1 0 0 Received: 152 3 0 0 Sent: 68 2 0 0 Error: not authorized to clone Received: 152 2 0 0 Total network traffic: 529 bytes sent, 678 bytes received fossil: server returned an error - clone aborted If this worked without error, then you probably forget to remove clone rights for nobody. Successful run: $ REMOTE_USER=alpha-user-a fossil clone ssh://alpha-user-a:*@127.0.0.1:29//home/alpha-adm/alpha.fossil?fossil=/usr/local/bin/fossil-alpha alpha-clone.fossil ssh -e none -T -p 29 [email protected] Bytes Cards Artifacts Deltas Sent: 53 1 0 0 Received: 664 9 3 0 Sent: 58 2 0 0 Received: 634 2 0 0 Total network traffic: 592 bytes sent, 1442 bytes received Closing SSH tunnel: Rebuilding repository meta-data... 100.0% complete... project-id: ad7b7fb1627b88bf2719b8ad674fd54940f3e27b server-id: 9499b9e1f4b95e549a658206902b57534f91c9b3 admin-user: alpha-user-a (password is "db1ac0") No show that access is not all or nothing. user alpha-user-a has only has clone permission, not push: alpha-user-a@host:~$ mkdir checkout alpha-user-a@host:~$ cd checkout/ alpha-user-a@host:~/checkout$ fossil open ../alpha-clone.fossil ... alpha-user-a@host:~/checkout$ echo "good stuff" > goodfile alpha-user-a@host:~/checkout$ fossil add goodfile ... alpha-user-a@host:~/checkout$ REMOTE_USER=alpha-user-a fossil commit Autosync: ssh://alpha-user-a:*@127.0.0.1:29//home/alpha-adm/alpha.fossil?fossil=/usr/local/bin/fossil-alpha ssh -e none -T -p 29 [email protected] ... continue in spite of sync failure (y/N)? y ... # you are done: comment ... fossil: Autosync failed Now we grant alpha-user-a push authority: alpha-adm@host:~$ fossil-alpha ui ./alpha.fossil and try again: alpha-user-a@host:~/checkout$ REMOTE_USER=alpha-user-a fossil push Server: ssh://alpha-user-a:*@127.0.0.1:29//home/alpha-adm/alpha.fossil?fossil=/usr/local/bin/fossil-alpha ssh -e none -T -p 29 [email protected] Bytes Cards Artifacts Deltas Sent: 360 6 0 0 Received: 126 3 0 0 Sent: 716 6 1 1 Received: 32 1 0 0 Total network traffic: 1127 bytes sent, 557 bytes received -cts 10/10/12 7 diffs to play: ============ $ diff -u fossil-src-20120808112557/src/http_transport.c fossil-src-modified/src/http_transport.c --- fossil-src-20120808112557/src/http_transport.c 2012-08-08 06:49:21.000000000 -0500 +++ fossil-src-modified/src/http_transport.c 2012-10-10 15:41:37.736464075 -0500 @@ -80,7 +80,7 @@ ** Read text from sshIn. Zero-terminate and remove trailing ** whitespace. */ -static void sshin_read(char *zBuf, int szBuf){ +static int sshin_read(char *zBuf, int szBuf){ int got; zBuf[0] = 0; got = read(sshIn, zBuf, szBuf-1); @@ -89,6 +89,8 @@ if( got==0 || !fossil_isspace(zBuf[got-1]) ) break; got--; } + + return got; } /* @@ -112,6 +114,7 @@ Blob zCmd; /* The SSH command */ char *zHost; /* The host name to contact */ char *zIn; /* An input line received back from remote */ + const char *zRemoteUser = P("REMOTE_USER"); /* environment variable */ zSsh = db_get("ssh-command", zDefaultSshCmd); blob_init(&zCmd, zSsh, -1); @@ -157,16 +160,36 @@ } blob_reset(&zCmd); +#ifdef FOSSIL_SETUID_SUPPORT + /* Attempt to send our local environment variable over to the server if + ** it is present. + */ + if(zRemoteUser) { + fprintf(sshOut, "export REMOTE_USER=%s\n", zRemoteUser); + fflush(sshOut); + } +#endif + /* Send an "echo" command to the other side to make sure that the ** connection is up and working. */ fprintf(sshOut, "echo test\n"); fflush(sshOut); zIn = fossil_malloc(16000); - sshin_read(zIn, 16000); - if( memcmp(zIn, "test", 4)!=0 ){ + + int read_length, failed = 1; + while((read_length = sshin_read(zIn, 16000)) > 0) + { + if((memcmp(zIn + read_length - 4, "test", 4)) == 0) + { + failed = 0; + break; + } + } + + if( failed) { pclose2(sshIn, sshOut, sshPid); - fossil_fatal("ssh connection failed: [%s]", zIn); + fossil_fatal("ssh connection failed loopback echo test: [%s]", zIn); } fossil_free(zIn); } ============ $ diff -u fossil-src-20120808112557/src/http.c fossil-src-modified/src/http.c --- fossil-src-20120808112557/src/http.c 2012-08-08 06:49:21.000000000 -0500 +++ fossil-src-modified/src/http.c 2012-10-10 15:45:03.278967955 -0500 @@ -39,12 +39,20 @@ const char *zPw; /* The user password */ Blob pw; /* The nonce with user password appended */ Blob sig; /* The signature field */ +#ifdef FOSSIL_SETUID_SUPPORT + const char *zRemoteUser = P("REMOTE_USER"); +#endif blob_zero(pLogin); if( g.urlUser==0 || fossil_strcmp(g.urlUser, "anonymous")==0 ){ return; /* If no login card for users "nobody" and "anonymous" */ } - if( g.urlIsSsh ){ + + if( g.urlIsSsh +#ifdef FOSSIL_SETUID_SUPPORT + && (zRemoteUser == NULL) +#endif + ) { return; /* If no login card for SSH: */ } blob_zero(&nonce); ============ $ diff -u fossil-src-20120808112557/src/login.c fossil-src-modified/src/login.c --- fossil-src-20120808112557/src/login.c 2012-08-08 06:49:21.000000000 -0500 +++ fossil-src-modified/src/login.c 2012-10-10 16:41:42.266463243 -0500 @@ -776,6 +776,11 @@ char *zRemoteAddr; /* Abbreviated IP address of the requestor */ const char *zCap = 0; /* Capability string */ const char *zPublicPages = 0; /* GLOB patterns of public pages */ + const char *zRemoteUser = P("REMOTE_USER"); +#ifdef FOSSIL_SETUID_SUPPORT + uid_t realUserid = getuid(); + uid_t effectiveUserid = geteuid(); +#endif /* Only run this check once. */ if( g.userUid!=0 ) return; @@ -795,12 +800,23 @@ && g.useLocalauth && db_get_int("localauth",0)==0 && P("HTTPS")==0 +#ifdef FOSSIL_SETUID_SUPPORT + && zRemoteUser == NULL +#endif ){ - uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); - g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid); - zCap = "sx"; - g.noPswd = 1; - sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost"); +#ifdef FOSSIL_SETUID_SUPPORT + /* fossil ui in a setuid security model check */ + if(realUserid == effectiveUserid) + { +#endif + uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); + g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid); + zCap = "sx"; + g.noPswd = 1; + sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost"); +#ifdef FOSSIL_SETUID_SUPPORT + } +#endif } /* Check the login cookie to see if it matches a known valid user. @@ -863,13 +879,30 @@ ** then accept the value of REMOTE_USER as the user. */ if( uid==0 ){ - const char *zRemoteUser = P("REMOTE_USER"); if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){ uid = db_int(0, "SELECT uid FROM user WHERE login=%Q" " AND length(cap)>0 AND length(pw)>0", zRemoteUser); } } +#ifdef FOSSIL_SETUID_SUPPORT + /* REMOTE_USER + POSIX security model is in use + ** + */ + if( uid==0 && zRemoteUser) + { + uid = db_int(0, "SELECT uid FROM user WHERE login=%Q" + " AND length(cap)>0", zRemoteUser); + + if( zRemoteUser && uid) { + if( (i64) (realUserid) != db_int64(-1, "SELECT posix_uid FROM user WHERE uid=%d", uid)) + { + uid = 0; + } + } + } +#endif + /* If no user found yet, try to log in as "nobody" */ if( uid==0 ){ uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'"); ============ $ diff -u fossil-src-20120808112557/src/file.c fossil-src-modified/src/file.c --- fossil-src-20120808112557/src/file.c 2012-08-08 06:49:21.000000000 -0500 +++ fossil-src-modified/src/file.c 2012-10-10 15:18:43.938958841 -0500 @@ -29,6 +29,9 @@ #include <string.h> #include <errno.h> #include "file.h" +#ifdef FOSSIL_SETUID_SUPPORT +#include <fcntl.h> +#endif /* ** On Windows, include the Platform SDK header file. @@ -300,9 +303,26 @@ */ int file_access(const char *zFilename, int flags){ char *zMbcs = fossil_utf8_to_mbcs(zFilename); +#ifdef FOSSIL_SETUID_SUPPORT + int fd, open_flags = 0; + if(flags & R_OK) + open_flags |= O_RDONLY; + + if(flags & W_OK) + open_flags |= O_WRONLY; + + fd = open(zMbcs, open_flags); + if(fd) { + close(fd); + return 0; + } else { + return -1; + } +#else int rc = access(zMbcs, flags); fossil_mbcs_free(zMbcs); return rc; +#endif } /* ============ $ diff -u fossil-src-20120808112557/src/setup.c fossil-src-modified/src/setup.c --- fossil-src-20120808112557/src/setup.c 2012-08-08 06:49:21.000000000 -0500 +++ fossil-src-modified/src/setup.c 2012-10-10 17:37:06.168957260 -0500 @@ -295,6 +295,9 @@ int uid; int higherUser = 0; /* True if user being edited is SETUP and the */ /* user doing the editing is ADMIN. Disallow editing */ +#ifdef FOSSIL_SETUID_SUPPORT + int posix_uid = -1; +#endif /* Must have ADMIN privleges to access this page */ @@ -321,7 +324,11 @@ ** modified user record. After writing the user record, redirect ** to the page that displays a list of users. */ - doWrite = cgi_all("login","info","pw") && !higherUser; + doWrite = cgi_all("login","info","pw", +#ifdef FOSSIL_SETUID_SUPPORT + "posixuid" +#endif + ) && !higherUser; if( doWrite ){ char zCap[50]; int i = 0; @@ -375,6 +382,9 @@ zCap[i] = 0; zPw = P("pw"); zLogin = P("login"); +#ifdef FOSSIL_SETUID_SUPPORT + sscanf(P("posixuid"), "%d", &posix_uid); +#endif if( strlen(zLogin)==0 ){ style_header("User Creation Error"); @ <span class="loginError">Empty login not allowed.</span> @@ -402,9 +412,15 @@ } login_verify_csrf_secret(); db_multi_exec( +#ifdef FOSSIL_SETUID_SUPPORT + "REPLACE INTO user(uid,login,info,pw,cap,mtime,posix_uid) " + "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s',now(),%d)", + uid, P("login"), P("info"), zPw, zCap, posix_uid +#else "REPLACE INTO user(uid,login,info,pw,cap,mtime) " "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s',now())", uid, P("login"), P("info"), zPw, zCap +#endif ); if( atoi(PD("all","0"))>0 ){ Blob sql; @@ -425,8 +441,14 @@ " info=%Q," " cap=%Q," " mtime=now()" +#ifdef FOSSIL_SETUID_SUPPORT + " posix_uid=%d" +#endif " WHERE login=%Q;", - zLogin, P("pw"), zLogin, P("info"), zCap, + zLogin, P("pw"), zLogin, P("info"), zCap, +#ifdef FOSSIL_SETUID_SUPPORT + posix_uid, +#endif zOldLogin ); login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr); @@ -457,6 +479,9 @@ zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); +#ifdef FOSSIL_SETUID_SUPPORT + posix_uid = db_int(-1, "SELECT posix_uid FROM user WHERE uid=%d", uid); +#endif if( strchr(zCap, 'a') ) oaa = " checked=\"checked\""; if( strchr(zCap, 'b') ) oab = " checked=\"checked\""; if( strchr(zCap, 'c') ) oac = " checked=\"checked\""; @@ -545,6 +570,13 @@ @ <td class="usetupEditLabel">Login:</td> @ <td><input type="text" name="login" value="%h(zLogin)" /></td> @ </tr> +#ifdef FOSSIL_SETUID_SUPPORT + @ </tr> + @ <tr> + @ <td class="usetupEditLabel">POSIX UID:</td> + @ <td><input type="text" name="posixuid" value="%d(posix_uid)" /></td> + @ </tr> +#endif @ <tr> @ <td class="usetupEditLabel">Contact Info:</td> @ <td><input type="text" name="info" size="40" value="%h(zInfo)" /></td> ============ $ diff -u fossil-src-20120808112557/src/schema.c fossil-src-modified/src/schema.c --- fossil-src-20120808112557/src/schema.c 2012-08-08 06:49:21.000000000 -0500 +++ fossil-src-modified/src/schema.c 2012-10-10 17:49:10.356484035 -0500 @@ -120,7 +120,8 @@ @ cexpire DATETIME, -- Time when cookie expires @ info TEXT, -- contact information @ mtime DATE, -- last change. seconds since 1970 -@ photo BLOB -- JPEG image of this user +@ photo BLOB, -- JPEG image of this user +@ posix_uid INTEGER -- used with setuid @ ); @ @ -- The VAR table holds miscellanous information about the repository. ============ $ diff -u fossil-src-20120808112557/src/rebuild.c fossil-src-modified/src/rebuild.c --- fossil-src-20120808112557/src/rebuild.c 2012-08-08 06:49:21.000000000 -0500 +++ fossil-src-modified/src/rebuild.c 2012-10-10 17:41:17.978979405 -0500 @@ -104,6 +104,9 @@ " info TEXT,\n" " mtime DATE,\n" " photo BLOB\n" +#ifdef FOSSIL_SETUID_SUPPORT + " posix_uid INTEGER\n" +#endif ");" "INSERT OR IGNORE INTO user" " SELECT uid, login, pw, cap, cookie," ============ www.thomasstover.com _______________________________________________ fossil-users mailing list [email protected] http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users

