github.com/google/osv-scalibr@v0.4.1/detector/cis/generic_linux/etcpasswdpermissions/etcpasswdpermissions.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build linux || darwin
    16  
    17  // Package etcpasswdpermissions implements a detector for the "Ensure permissions on /etc/passwd- are configured" CIS check.
    18  package etcpasswdpermissions
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  	"io/fs"
    25  	"os"
    26  	"syscall"
    27  
    28  	"github.com/google/osv-scalibr/detector"
    29  	scalibrfs "github.com/google/osv-scalibr/fs"
    30  	"github.com/google/osv-scalibr/inventory"
    31  	"github.com/google/osv-scalibr/packageindex"
    32  	"github.com/google/osv-scalibr/plugin"
    33  )
    34  
    35  const (
    36  	// Name of the detector.
    37  	Name = "cis/generic-linux/etcpasswdpermissions"
    38  )
    39  
    40  // Detector is a SCALIBR Detector for the CIS check "Ensure permissions on /etc/passwd- are configured"
    41  // from the CIS Distribution Independent Linux benchmarks.
    42  type Detector struct{}
    43  
    44  // New returns a detector.
    45  func New() detector.Detector {
    46  	return &Detector{}
    47  }
    48  
    49  // Name of the detector.
    50  func (Detector) Name() string { return Name }
    51  
    52  // Version of the detector.
    53  func (Detector) Version() int { return 0 }
    54  
    55  // RequiredExtractors returns an empty list as there are no dependencies.
    56  func (Detector) RequiredExtractors() []string { return []string{} }
    57  
    58  // Requirements of the Detector.
    59  func (Detector) Requirements() *plugin.Capabilities { return &plugin.Capabilities{OS: plugin.OSUnix} }
    60  
    61  // Scan starts the scan.
    62  func (d Detector) Scan(ctx context.Context, scanRoot *scalibrfs.ScanRoot, px *packageindex.PackageIndex) (inventory.Finding, error) {
    63  	return d.ScanFS(ctx, scanRoot.FS, px)
    64  }
    65  
    66  // DetectedFinding returns generic vulnerability information about what is detected.
    67  func (d Detector) DetectedFinding() inventory.Finding {
    68  	return d.findingForTarget(nil)
    69  }
    70  
    71  func (Detector) findingForTarget(target *inventory.GenericFindingTargetDetails) inventory.Finding {
    72  	return inventory.Finding{GenericFindings: []*inventory.GenericFinding{{
    73  		Adv: &inventory.GenericFindingAdvisory{
    74  			ID: &inventory.AdvisoryID{
    75  				Publisher: "CIS",
    76  				Reference: "etc-passwd-permissions",
    77  			},
    78  			Title: "Ensure permissions on /etc/passwd are configured",
    79  			Description: "The /etc/passwd file contains user account information that " +
    80  				"is used by many system utilities and therefore must be readable for these " +
    81  				"utilities to operate.",
    82  			Recommendation: "Run the following command to set permissions on /etc/passwd :\n" +
    83  				"# chown root:root /etc/passwd\n" +
    84  				"# chmod 644 /etc/passwd",
    85  			Sev: inventory.SeverityMinimal,
    86  		},
    87  		Target: target,
    88  	}}}
    89  }
    90  
    91  // ScanFS starts the scan from a pseudo-filesystem.
    92  func (d Detector) ScanFS(ctx context.Context, fs fs.FS, px *packageindex.PackageIndex) (inventory.Finding, error) {
    93  	f, err := fs.Open("etc/passwd")
    94  	if err != nil {
    95  		if errors.Is(err, os.ErrNotExist) {
    96  			// File doesn't exist, check not applicable.
    97  			return inventory.Finding{}, nil
    98  		}
    99  		return inventory.Finding{}, err
   100  	}
   101  	defer f.Close()
   102  	info, err := f.Stat()
   103  	if err != nil {
   104  		return inventory.Finding{}, err
   105  	}
   106  
   107  	problems := ""
   108  	if info.Mode().Perm() != 0644 {
   109  		problems = fmt.Sprintf("file permissions %03o, expected 644\n", info.Mode().Perm())
   110  	}
   111  
   112  	stat, ok := info.Sys().(*syscall.Stat_t)
   113  	if !ok {
   114  		return inventory.Finding{}, errors.New("failed to get file ownership info")
   115  	}
   116  
   117  	if stat.Uid != 0 {
   118  		problems += fmt.Sprintf("file owner %d, expected 0/root\n", stat.Uid)
   119  	}
   120  	if stat.Gid != 0 {
   121  		problems += fmt.Sprintf("file group %d, expected 0/root\n", stat.Gid)
   122  	}
   123  
   124  	if len(problems) == 0 {
   125  		return inventory.Finding{}, nil
   126  	}
   127  
   128  	target := &inventory.GenericFindingTargetDetails{Extra: "/etc/passwd: " + problems}
   129  	return d.findingForTarget(target), nil
   130  }