Skip to content
Permalink
Browse files

Merge pull request #32 from cruise-automation/collin/capability_ext2_…

…sqfs

capability support for ext2 and squashfs
  • Loading branch information
crmulliner committed Feb 27, 2020
2 parents e24ed68 + 31ec779 commit f5764169c822f33c05791b45881d606d3ee5d4d0
@@ -7,6 +7,8 @@ Always update Version in Makefile

### Added
- NEW support for Capabilities
- NEW Capability support for ext2/3/4 and squashfs
- NEW Selinux support for SquashFS

### Changed
- _check.py_ cleaned up a bit, avoiding using `shell=True` in subprocess invocations.
@@ -26,6 +26,7 @@ release: build
testsetup:
gunzip -c test/test.img.gz >test/test.img
gunzip -c test/ubifs.img.gz >test/ubifs.img
gunzip -c test/cap_ext2.img.gz >test/cap_ext2.img
sudo setcap cap_net_admin+p test/test.cap.file
getcap test/test.cap.file

@@ -10,7 +10,8 @@ FwAnalyzer relies on [e2tools](https://github.com/crmulliner/e2tools/) for ext f
[squashfs-tools](https://github.com/plougher/squashfs-tools) for SquashFS filesystems, and
[ubi_reader](https://github.com/crmulliner/ubi_reader) for UBIFS filesystems.
[cpio](https://www.gnu.org/software/cpio/) for cpio archives.
SELinux/xattr support for ext2/3/4 images requires a patched version of [e2tools](https://github.com/crmulliner/e2tools/).
SELinux/Capability support for ext2/3/4 images requires a patched version of [e2tools](https://github.com/crmulliner/e2tools/).
SELinux/Capability support for SquashFS images requires a patched version of [squashfs-tools](https://github.com/crmulliner/squashfs-tools/).

![fwanalyzer](images/fwanalyzer.png)

@@ -96,13 +97,15 @@ The global config is used to define some general parameters.
The `FsType` (filesystem type) field selects the backend that is used to access the files in the image. The supported options for FsType are:

- `dirfs`: to read files from a directory on the host running fwanalyzer, supports Capabilities (supported FsTypeOptions are: N/A)
- `extfs`: to read ext2/3/4 filesystem images (supported FsTypeOptions are: `selinux`)
- `squashfs`: to read SquashFS filesystem images (supported FsTypeOptions are: N/A)
- `extfs`: to read ext2/3/4 filesystem images (supported FsTypeOptions are: `selinux` and `capabilities`)
- `squashfs`: to read SquashFS filesystem images (supported FsTypeOptions are: `securityinfo`)
- `ubifs`: to read UBIFS filesystem images (supported FsTypeOptions are: N/A)
- `vfatfs`: to read VFat filesystem images (supported FsTypeOptions are: N/A)
- `cpiofs`: to read cpio archives (supported FsTypeOptions are: `fixdirs`)

The FsTypeOptions allow tuning of the FsType driver.
- `securityinfo`: will enable selinux and capability support for SquashFS images
- `capabilities`: will enable capability support when reading ext filesystem images
- `selinux`: will enable selinux support when reading ext filesystem images
- `fixdirs`: will attempt to work around a cpio issue where a file exists in a directory while there is no entry for the directory itself

@@ -117,17 +117,21 @@ func NewFromConfig(imagepath string, cfgdata string) *Analyzer {
var fsp fsparser.FsParser
// Set the parser based on the FSType in the config
if strings.EqualFold(config.GlobalConfig.FSType, "extfs") {
fsp = extparser.New(imagepath, config.GlobalConfig.FSTypeOptions == "selinux")
fsp = extparser.New(imagepath,
strings.Contains(config.GlobalConfig.FSTypeOptions, "selinux"),
strings.Contains(config.GlobalConfig.FSTypeOptions, "capabilities"))
} else if strings.EqualFold(config.GlobalConfig.FSType, "dirfs") {
fsp = dirparser.New(imagepath)
} else if strings.EqualFold(config.GlobalConfig.FSType, "vfatfs") {
fsp = vfatparser.New(imagepath)
} else if strings.EqualFold(config.GlobalConfig.FSType, "squashfs") {
fsp = squashfsparser.New(imagepath)
fsp = squashfsparser.New(imagepath,
strings.Contains(config.GlobalConfig.FSTypeOptions, "securityinfo"))
} else if strings.EqualFold(config.GlobalConfig.FSType, "ubifs") {
fsp = ubifsparser.New(imagepath)
} else if strings.EqualFold(config.GlobalConfig.FSType, "cpiofs") {
fsp = cpioparser.New(imagepath, config.GlobalConfig.FSTypeOptions == "fixdirs")
fsp = cpioparser.New(imagepath,
strings.Contains(config.GlobalConfig.FSTypeOptions, "fixdirs"))
} else {
panic("Cannot find an appropriate parser: " + config.GlobalConfig.FSType)
}
@@ -25,31 +25,39 @@ import (
"strconv"
"strings"

"github.com/cruise-automation/fwanalyzer/pkg/capability"
"github.com/cruise-automation/fwanalyzer/pkg/fsparser"
)

type Ext2Parser struct {
fileinfoReg *regexp.Regexp
selinux bool
imagepath string
fileinfoReg *regexp.Regexp
regexString string
selinux bool
capabilities bool
imagepath string
}

const (
e2ToolsCp = "e2cp"
e2ToolsLs = "e2ls"
)

func New(imagepath string, selinux bool) *Ext2Parser {
func New(imagepath string, selinux, capabilities bool) *Ext2Parser {
parser := &Ext2Parser{
// 365 120777 0 0 7 12-Jul-2018 10:15 true
fileinfoReg: regexp.MustCompile(
`^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+-\w+-\d+)\s+(\d+:\d+)\s+(\S+)`),
imagepath: imagepath,
selinux: false,
regexString: `^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+-\w+-\d+)\s+(\d+:\d+)\s+(\S+)`,
imagepath: imagepath,
selinux: false,
capabilities: false,
}

if selinux && seLinuxSupported() {
parser.enableSeLinux()
}
if capabilities && capabilitiesSupported() {
parser.enableCapabilities()
}
parser.fileinfoReg = regexp.MustCompile(parser.regexString)
return parser
}

@@ -60,11 +68,23 @@ func (e *Ext2Parser) ImageName() string {
func (e *Ext2Parser) enableSeLinux() {
// with selinux support (-Z)
// 2600 100750 0 2000 1041 1-Jan-2009 03:00 init.environ.rc u:object_r:rootfs:s0
e.fileinfoReg = regexp.MustCompile(
`^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+-\w+-\d+)\s+(\d+:\d+)\s+(\S+)\s+(\S+)`)
// `^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+-\w+-\d+)\s+(\d+:\d+)\s+(\S+)\s+(\S+)`)

// append selinux part
e.regexString = e.regexString + `\s+(\S+)`
e.selinux = true
}

func (e *Ext2Parser) enableCapabilities() {
// with capabilites support (-C)
// 2600 100750 0 2000 1041 1-Jan-2009 03:00 init.environ.rc 0x2000001,0x0,0x0,0x0,0x0
// `^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+-\w+-\d+)\s+(\d+:\d+)\s+(\S+)\s+(\S+)`)

// append capability part
e.regexString = e.regexString + `\s+(\S+)`
e.capabilities = true
}

func (e *Ext2Parser) parseFileLine(line string) fsparser.FileInfo {
res := e.fileinfoReg.FindAllStringSubmatch(line, -1)
var fi fsparser.FileInfo
@@ -81,6 +101,15 @@ func (e *Ext2Parser) parseFileLine(line string) fsparser.FileInfo {
fi.SELinuxLabel = fsparser.SELinuxNoLabel
}

if e.capabilities {
idx := 9
if e.selinux {
idx = 10
}
if res[0][idx] != "-" {
fi.Capabilities, _ = capability.New(res[0][idx])
}
}
return fi
}

@@ -91,6 +120,9 @@ func (e *Ext2Parser) getDirList(dirpath string, ignoreDot bool) ([]fsparser.File
if e.selinux {
params += "Z"
}
if e.capabilities {
params += "C"
}
out, err := exec.Command(e2ToolsLs, params, arg).CombinedOutput()
if err != nil {
// do NOT print file not found error
@@ -162,3 +194,13 @@ func seLinuxSupported() bool {
fmt.Fprintln(os.Stderr, "extparser: selinux not supported by your version of e2ls")
return false
}

func capabilitiesSupported() bool {
out, _ := exec.Command(e2ToolsLs).CombinedOutput()
// look for C (capability support) in "Usage: e2ls [-acDfilrtZC][-d dir] file"
if strings.Contains(string(out), "C") {
return true
}
fmt.Fprintln(os.Stderr, "extparser: capabilities not supported by your version of e2ls")
return false
}
@@ -18,6 +18,7 @@ package extparser

import (
"os"
"strings"
"testing"
)

@@ -26,7 +27,7 @@ var e *Ext2Parser
func TestMain(t *testing.T) {
testImage := "../../test/test.img"

e = New(testImage, false)
e = New(testImage, false, false)

if e.ImageName() != testImage {
t.Errorf("ImageName returned bad name")
@@ -115,3 +116,21 @@ func TestGetFileInfo(t *testing.T) {
}
}
}

func TestCap(t *testing.T) {
testImage := "../../test/cap_ext2.img"

e = New(testImage, false, true)

if e.ImageName() != testImage {
t.Errorf("ImageName returned bad name")
}

fi, err := e.GetFileInfo("/test")
if err != nil {
t.Error(err)
}
if !strings.EqualFold(fi.Capabilities[0], "cap_net_admin+p") {
t.Errorf("Capabilities %s don't match", fi.Capabilities)
}
}
@@ -27,6 +27,7 @@ import (
"strconv"
"strings"

"github.com/cruise-automation/fwanalyzer/pkg/capability"
"github.com/cruise-automation/fwanalyzer/pkg/fsparser"
)

@@ -35,15 +36,12 @@ const (
cpCmd = "cp"
)

var (
// drwxr-xr-x administrator/administrator 66 2019-04-08 18:49 squashfs-root
fileLineRegex = regexp.MustCompile(`^([A-Za-z-]+)\s+([\-\.\w]+|\d+)/([\-\.\w]+|\d+)\s+(\d+)\s+(\d+-\d+-\d+)\s+(\d+:\d+)\s+(.*)$`)
)

// SquashFSParser parses SquashFS filesystem images.
type SquashFSParser struct {
imagepath string
files map[string][]fsparser.FileInfo
fileLineRegex *regexp.Regexp
imagepath string
files map[string][]fsparser.FileInfo
securityInfo bool
}

func uidForUsername(username string) (int, error) {
@@ -131,11 +129,25 @@ func getExtractFile(dirpath string) (string, error) {
return extractFile.Name(), nil
}

func (s *SquashFSParser) enableSecurityInfo() {
// drwxr-xr-x administrator/administrator 66 2019-04-08 18:49 squashfs-root - -
s.fileLineRegex = regexp.MustCompile(`^([A-Za-z-]+)\s+([\-\.\w]+|\d+)/([\-\.\w]+|\d+)\s+(\d+)\s+(\d+-\d+-\d+)\s+(\d+:\d+)\s+([\S ]+)\t(\S+)\s+(\S)`)
s.securityInfo = true
}

// New returns a new SquashFSParser instance for the given image file.
func New(imagepath string) *SquashFSParser {
func New(imagepath string, securityInfo bool) *SquashFSParser {
parser := &SquashFSParser{
imagepath: imagepath,
// drwxr-xr-x administrator/administrator 66 2019-04-08 18:49 squashfs-root
fileLineRegex: regexp.MustCompile(`^([A-Za-z-]+)\s+([\-\.\w]+|\d+)/([\-\.\w]+|\d+)\s+(\d+)\s+(\d+-\d+-\d+)\s+(\d+:\d+)\s+(.*)$`),
imagepath: imagepath,
securityInfo: false,
}

if securityInfo && securityInfoSupported() {
parser.enableSecurityInfo()
}

return parser
}

@@ -147,12 +159,12 @@ func normalizePath(filepath string) (dir string, name string) {
return
}

func parseFileLine(line string) (string, fsparser.FileInfo, error) {
func (s *SquashFSParser) parseFileLine(line string) (string, fsparser.FileInfo, error) {
// TODO(jlarimer): add support for reading xattrs. unsquashfs can read
// and write xattrs, but it doesn't display them when just listing files.
var fi fsparser.FileInfo
dirpath := ""
res := fileLineRegex.FindStringSubmatch(line)
res := s.fileLineRegex.FindStringSubmatch(line)
if res == nil {
return dirpath, fi, fmt.Errorf("Can't match line %s\n", line)
}
@@ -183,6 +195,14 @@ func parseFileLine(line string) (string, fsparser.FileInfo, error) {
} else {
dirpath, fi.Name = normalizePath(res[7])
}

if s.securityInfo {
if res[8] != "-" {
fi.Capabilities, _ = capability.New(res[8])
}
fi.SELinuxLabel = res[9]
}

return dirpath, fi, nil
}

@@ -192,14 +212,19 @@ func (s *SquashFSParser) loadFileList() error {
}
s.files = make(map[string][]fsparser.FileInfo)

out, err := exec.Command(unsquashfsCmd, "-d", "", "-ll", s.imagepath).CombinedOutput()
args := []string{"-d", "", "-lln", s.imagepath}
if s.securityInfo {
args = append([]string{"-llS"}, args...)
}

out, err := exec.Command(unsquashfsCmd, args...).CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "getDirList: %s", err)
return err
}
lines := strings.Split(string(out), "\n")
for _, line := range lines {
path, fi, err := parseFileLine(line)
path, fi, err := s.parseFileLine(line)
if err == nil {
dirfiles := s.files[path]
dirfiles = append(dirfiles, fi)
@@ -288,3 +313,13 @@ func (f *SquashFSParser) Supported() bool {
_, err = exec.LookPath(cpCmd)
return err == nil
}

func securityInfoSupported() bool {
out, _ := exec.Command(unsquashfsCmd).CombinedOutput()
// look for -ll[S] (securityInfo support) in output
if strings.Contains(string(out), "-ll[S]") {
return true
}
fmt.Fprintln(os.Stderr, "squashfsparser: security info (selinux + capabilities) not supported by your version of unsquashfs")
return false
}

0 comments on commit f576416

Please sign in to comment.
You can’t perform that action at this time.