@@ -27,6 +27,7 @@ const (
2727 selinuxDir = "/etc/selinux/"
2828 selinuxUsersDir = "contexts/users"
2929 defaultContexts = "contexts/default_contexts"
30+ failsafeContext = "contexts/failsafe_context"
3031 selinuxConfig = selinuxDir + "config"
3132 selinuxfsMount = "/sys/fs/selinux"
3233 selinuxTypeTag = "SELINUXTYPE"
@@ -57,6 +58,7 @@ type defaultSECtx struct {
5758 userRdr io.Reader
5859 verifier func (string ) error
5960 defaultRdr io.Reader
61+ failsafeRdr io.Reader
6062 user , level , scon string
6163}
6264
@@ -1181,6 +1183,117 @@ func dupSecOpt(src string) ([]string, error) {
11811183 return dup , nil
11821184}
11831185
1186+ // checkGroup returns true if group's GID is in the list of GIDs gids.
1187+ func checkGroup (group string , gids []string , lookupGroup func (string ) (* user.Group , error )) bool {
1188+ grp , err := lookupGroup (group )
1189+ if err != nil {
1190+ return false
1191+ }
1192+
1193+ for _ , gid := range gids {
1194+ if grp .Gid == gid {
1195+ return true
1196+ }
1197+ }
1198+ return false
1199+ }
1200+
1201+ // getSeUserFromReader reads the seusers file: https://www.man7.org/linux/man-pages/man5/seusers.5.html
1202+ func getSeUserFromReader (username string , gids []string , r io.Reader , lookupGroup func (string ) (* user.Group , error )) (seUser string , level string , err error ) {
1203+ var defaultSeUser , defaultLevel string
1204+ var groupSeUser , groupLevel string
1205+
1206+ lineNum := - 1
1207+ scanner := bufio .NewScanner (r )
1208+ for scanner .Scan () {
1209+ line := scanner .Text ()
1210+ lineNum ++
1211+
1212+ // remove any trailing comments, then extra whitespace
1213+ parts := strings .SplitN (line , "#" , 2 )
1214+ line = strings .TrimSpace (parts [0 ])
1215+ if line == "" {
1216+ continue
1217+ }
1218+
1219+ parts = strings .SplitN (line , ":" , 3 )
1220+ if len (parts ) < 2 {
1221+ return "" , "" , fmt .Errorf ("line %d: malformed line" , lineNum )
1222+ }
1223+ userField := parts [0 ]
1224+ if userField == "" {
1225+ return "" , "" , fmt .Errorf ("line %d: user_id or group_id is empty" , lineNum )
1226+ }
1227+ seUserField := parts [1 ]
1228+ if seUserField == "" {
1229+ return "" , "" , fmt .Errorf ("line %d: seuser_id is empty" , lineNum )
1230+ }
1231+ var levelField string
1232+ // level is optional
1233+ if len (parts ) > 2 {
1234+ levelField = parts [2 ]
1235+ }
1236+
1237+ // we found a match, return it
1238+ if userField == username {
1239+ return seUserField , levelField , nil
1240+ }
1241+
1242+ // if the first field starts with '%' it's a group, check if
1243+ // the user is a member of that group and set the group
1244+ // SELinux user and level if so
1245+ if userField [0 ] == '%' && groupSeUser == "" {
1246+ if checkGroup (userField [1 :], gids , lookupGroup ) {
1247+ groupSeUser = seUserField
1248+ groupLevel = levelField
1249+ }
1250+ } else if userField == "__default__" && defaultSeUser == "" {
1251+ defaultSeUser = seUserField
1252+ defaultLevel = levelField
1253+ }
1254+ }
1255+ if err := scanner .Err (); err != nil {
1256+ return "" , "" , fmt .Errorf ("failed to read seusers file: %w" , err )
1257+ }
1258+
1259+ if groupSeUser != "" {
1260+ return groupSeUser , groupLevel , nil
1261+ }
1262+ if defaultSeUser != "" {
1263+ return defaultSeUser , defaultLevel , nil
1264+ }
1265+
1266+ return "" , "" , fmt .Errorf ("could not find SELinux user for %q login" , username )
1267+ }
1268+
1269+ // getSeUserByName returns an SELinux user and MLS level that is
1270+ // mapped to a given Linux user.
1271+ func getSeUserByName (username string ) (seUser string , level string , err error ) {
1272+ seUsersConf := filepath .Join (policyRoot (), "seusers" )
1273+ confFile , err := os .Open (seUsersConf )
1274+ if err != nil {
1275+ return "" , "" , fmt .Errorf ("failed to open seusers file: %w" , err )
1276+ }
1277+ defer confFile .Close ()
1278+
1279+ usr , err := user .Lookup (username )
1280+ if err != nil {
1281+ return "" , "" , fmt .Errorf ("failed to lookup user %q" , username )
1282+ }
1283+ gids , err := usr .GroupIds ()
1284+ if err != nil {
1285+ return "" , "" , fmt .Errorf ("failed to find user %q's groups" , username )
1286+ }
1287+ gids = append ([]string {usr .Gid }, gids ... )
1288+
1289+ seUser , level , err = getSeUserFromReader (username , gids , confFile , user .LookupGroup )
1290+ if err != nil {
1291+ return "" , "" , fmt .Errorf ("failed to parse seusers file: %w" , err )
1292+ }
1293+
1294+ return seUser , level , nil
1295+ }
1296+
11841297// findUserInContext scans the reader for a valid SELinux context
11851298// match that is verified with the verifier. Invalid contexts are
11861299// skipped. It returns a matched context or an empty string if no
@@ -1238,6 +1351,33 @@ func findUserInContext(context Context, r io.Reader, verifier func(string) error
12381351 return "" , nil
12391352}
12401353
1354+ // getFailsafeContext returns the context in the failsafe_context file:
1355+ // https://www.man7.org/linux/man-pages/man5/failsafe_context.5.html
1356+ func getFailsafeContext (context Context , r io.Reader , verifier func (string ) error ) (string , error ) {
1357+ conn := make ([]byte , 256 )
1358+ limReader := io .LimitReader (r , int64 (len (conn )))
1359+ _ , err := limReader .Read (conn )
1360+ if err != nil {
1361+ return "" , fmt .Errorf ("failed to read failsafe context: %w" , err )
1362+ }
1363+
1364+ conn = bytes .TrimSpace (conn )
1365+ toConns := strings .SplitN (string (conn ), ":" , 4 )
1366+ if len (toConns ) != 3 {
1367+ return "" , nil
1368+ }
1369+
1370+ context ["role" ] = toConns [0 ]
1371+ context ["type" ] = toConns [1 ]
1372+
1373+ outConn := context .get ()
1374+ if err := verifier (outConn ); err != nil {
1375+ return "" , nil
1376+ }
1377+
1378+ return outConn , nil
1379+ }
1380+
12411381func getDefaultContextFromReaders (c * defaultSECtx ) (string , error ) {
12421382 if c .verifier == nil {
12431383 return "" , ErrVerifierNil
@@ -1254,7 +1394,7 @@ func getDefaultContextFromReaders(c *defaultSECtx) (string, error) {
12541394
12551395 conn , err := findUserInContext (context , c .userRdr , c .verifier )
12561396 if err != nil {
1257- return "" , err
1397+ return "" , fmt . Errorf ( "failed to read %q's user context file: %w" , c . user , err )
12581398 }
12591399
12601400 if conn != "" {
@@ -1263,7 +1403,16 @@ func getDefaultContextFromReaders(c *defaultSECtx) (string, error) {
12631403
12641404 conn , err = findUserInContext (context , c .defaultRdr , c .verifier )
12651405 if err != nil {
1266- return "" , err
1406+ return "" , fmt .Errorf ("failed to read default user context file: %w" , err )
1407+ }
1408+
1409+ if conn != "" {
1410+ return conn , nil
1411+ }
1412+
1413+ conn , err = getFailsafeContext (context , c .failsafeRdr , c .verifier )
1414+ if err != nil {
1415+ return "" , fmt .Errorf ("failed to read failsafe_context: %w" , err )
12671416 }
12681417
12691418 if conn != "" {
@@ -1277,24 +1426,32 @@ func getDefaultContextWithLevel(user, level, scon string) (string, error) {
12771426 userPath := filepath .Join (policyRoot (), selinuxUsersDir , user )
12781427 fu , err := os .Open (userPath )
12791428 if err != nil {
1280- return "" , err
1429+ return "" , fmt . Errorf ( "failed to open %q's user context file: %w" , user , err )
12811430 }
12821431 defer fu .Close ()
12831432
12841433 defaultPath := filepath .Join (policyRoot (), defaultContexts )
12851434 fd , err := os .Open (defaultPath )
12861435 if err != nil {
1287- return "" , err
1436+ return "" , fmt . Errorf ( "failed to open default user context file: %w" , err )
12881437 }
12891438 defer fd .Close ()
12901439
1440+ failsafePath := filepath .Join (policyRoot (), failsafeContext )
1441+ fs , err := os .Open (failsafePath )
1442+ if err != nil {
1443+ return "" , fmt .Errorf ("failed to open failsafe user context file: %w" , err )
1444+ }
1445+ defer fs .Close ()
1446+
12911447 c := defaultSECtx {
1292- user : user ,
1293- level : level ,
1294- scon : scon ,
1295- userRdr : fu ,
1296- defaultRdr : fd ,
1297- verifier : securityCheckContext ,
1448+ user : user ,
1449+ level : level ,
1450+ scon : scon ,
1451+ userRdr : fu ,
1452+ defaultRdr : fd ,
1453+ failsafeRdr : fs ,
1454+ verifier : securityCheckContext ,
12981455 }
12991456
13001457 return getDefaultContextFromReaders (& c )
0 commit comments