package cap import ( "bufio" "errors" "strconv" "strings" ) // String converts a capability Value into its canonical text // representation. func (v Value) String() string { name, ok := names[v] if ok { return name } // Un-named capabilities are referred to numerically (in decimal). return strconv.Itoa(int(v)) } // FromName converts a named capability Value to its binary // representation. func FromName(name string) (Value, error) { startUp.Do(multisc.cInit) v, ok := bits[name] if ok { if v >= Value(words*32) { return 0, ErrBadValue } return v, nil } i, err := strconv.Atoi(name) if err != nil { return 0, err } if i >= 0 && i < int(words*32) { return Value(i), nil } return 0, ErrBadValue } const ( eBin uint = (1 << Effective) pBin = (1 << Permitted) iBin = (1 << Inheritable) ) var combos = []string{"", "e", "p", "ep", "i", "ei", "ip", "eip"} // histo generates a histogram of flag state combinations. func (c *Set) histo(bins []int, patterns []uint, from, limit Value) uint { for v := from; v < limit; v++ { b := uint(v & 31) u, bit, err := bitOf(0, v) if err != nil { break } x := uint((c.flat[u][Effective]&bit)>>b) * eBin x |= uint((c.flat[u][Permitted]&bit)>>b) * pBin x |= uint((c.flat[u][Inheritable]&bit)>>b) * iBin bins[x]++ patterns[uint(v)] = x } // Note, in the loop, we use >= to pick the smallest value for // m with the highest bin value. That is ties break towards // m=0. m := uint(7) for t := m; t > 0; { t-- if bins[t] >= bins[m] { m = t } } return m } // String converts a full capability Set into a single short readable // string representation (which may contain spaces). See the // cap.FromText() function for an explanation of its return values. // // Note (*cap.Set).String() may evolve to generate more compact // strings representing the a given Set over time, but it should // maintain compatibility with the libcap:cap_to_text() function for // any given release. Further, it will always be an inverse of // cap.FromText(). func (c *Set) String() string { if c == nil || len(c.flat) == 0 { return "" } bins := make([]int, 8) patterns := make([]uint, maxValues) c.mu.RLock() defer c.mu.RUnlock() // Note, in order to have a *Set pointer, startUp.Do(cInit) // must have been called which sets maxValues. m := c.histo(bins, patterns, 0, Value(maxValues)) // Background state is the most popular of the named bits. vs := []string{"=" + combos[m]} for i := uint(8); i > 0; { i-- if i == m || bins[i] == 0 { continue } var list []string for j, p := range patterns { if p != i { continue } list = append(list, Value(j).String()) } x := strings.Join(list, ",") var y, z string if cf := i & ^m; cf != 0 { op := "+" if len(vs) == 1 && vs[0] == "=" { // Special case "= foo+..." == "foo=...". // Prefer because it vs = nil op = "=" } y = op + combos[cf] } if cf := m & ^i; cf != 0 { z = "-" + combos[cf] } vs = append(vs, x+y+z) } // The unnamed bits can only add to the above named ones since // unnamed ones are always defaulted to lowered. uBins := make([]int, 8) uPatterns := make([]uint, 32*words) c.histo(uBins, uPatterns, Value(maxValues), 32*Value(words)) for i := uint(7); i > 0; i-- { if uBins[i] == 0 { continue } var list []string for j, p := range uPatterns { if p != i { continue } list = append(list, Value(j).String()) } vs = append(vs, strings.Join(list, ",")+"+"+combos[i]) } return strings.Join(vs, " ") } // ErrBadText is returned if the text for a capability set cannot be parsed. var ErrBadText = errors.New("bad text") // FromText converts the canonical text representation for a Set into // a freshly allocated Set. // // The format follows the following pattern: a set of space separated // sequences. Each sequence applies over the previous sequence to // build up a Set. The format of a sequence is: // // [comma list of cap_values][[ops][flags]]* // // Examples: // // "all=ep" // "cap_chown,cap_setuid=ip cap_setuid+e" // "=p cap_setpcap-p+i" // // Here "all" refers to all named capabilities known to the hosting // kernel, and "all" is assumed if no capabilities are listed before // an "=". // // The ops values, "=", "+" and "-" imply "reset and raise", "raise" // and "lower" respectively. The "e", "i" and "p" characters // correspond to the capabilities of the corresponding Flag: "e" // (Effective); "i" (Inheritable); "p" (Permitted). // // This syntax is overspecified and there are many ways of building // the same final Set state. Any sequence that includes a '=' resets // the accumulated state of all Flags ignoring earlier sequences. On // each of the following lines we give three or more examples of ways // to specify a common Set. The last entry on each line is the one // generated by (*cap.Set).String() from that Set. // // "=p all+ei" "all=pie" "=pi all+e" "=eip" // // "cap_setuid=p cap_chown=i" "cap_chown=ip-p" "cap_chown=i" // // "cap_chown=-p" "all=" "cap_setuid=pie-pie" "=" // // Note: FromText() is tested at release time to completely match the // import ability of the libcap:cap_from_text() function. func FromText(text string) (*Set, error) { c := NewSet() scanner := bufio.NewScanner(strings.NewReader(text)) scanner.Split(bufio.ScanWords) chunks := 0 for scanner.Scan() { chunks++ // Parsing for xxx([-+=][eip]+)+ t := scanner.Text() i := strings.IndexAny(t, "=+-") if i < 0 { return nil, ErrBadText } var vs []Value sep := t[i] if vals := t[:i]; vals == "all" { for v := Value(0); v < Value(maxValues); v++ { vs = append(vs, v) } } else if vals != "" { for _, name := range strings.Split(vals, ",") { v, err := FromName(name) if err != nil { return nil, ErrBadText } vs = append(vs, v) } } else if sep != '=' { if vals == "" { // Only "=" supports ""=="all". return nil, ErrBadText } } else if j := i + 1; j+1 < len(t) { switch t[j] { case '+': sep = 'P' i++ case '-': sep = 'M' i++ } } i++ // There are 5 ways to set: =, =+, =-, +, -. We call // the 2nd and 3rd of these 'P' and 'M'. for { // read [eip]+ setting flags. var fE, fP, fI bool for ok := true; ok && i < len(t); i++ { switch t[i] { case 'e': fE = true case 'i': fI = true case 'p': fP = true default: ok = false } if !ok { break } } if !(fE || fI || fP) { if sep != '=' { return nil, ErrBadText } } switch sep { case '=', 'P', 'M', '+': if sep != '+' { c.Clear() if sep == 'M' { break } } if keep := len(vs) == 0; keep { if sep != '=' { return nil, ErrBadText } c.forceFlag(Effective, fE) c.forceFlag(Permitted, fP) c.forceFlag(Inheritable, fI) break } // =, + and P for specific values are left. if fE { c.SetFlag(Effective, true, vs...) } if fP { c.SetFlag(Permitted, true, vs...) } if fI { c.SetFlag(Inheritable, true, vs...) } case '-': if fE { c.SetFlag(Effective, false, vs...) } if fP { c.SetFlag(Permitted, false, vs...) } if fI { c.SetFlag(Inheritable, false, vs...) } } if i == len(t) { break } switch t[i] { case '+', '-': sep = t[i] i++ default: return nil, ErrBadText } } } if chunks == 0 { return nil, ErrBadText } return c, nil }