Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interfaces,dirs: add ldconfig backend #14926

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dirs/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var (
snapDataHomeGlob []string
SnapDownloadCacheDir string
SnapAppArmorDir string
SnapLdconfigDir string
SnapSeccompBase string
SnapSeccompDir string
SnapMountPolicyDir string
Expand Down Expand Up @@ -465,6 +466,7 @@ func SetRootDir(rootdir string) {

SnapDataDir = filepath.Join(rootdir, "/var/snap")
SnapAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "profiles")
SnapLdconfigDir = filepath.Join(rootdir, "/etc/ld.so.conf.d")
SnapDownloadCacheDir = filepath.Join(rootdir, snappyDir, "cache")
SnapSeccompBase = filepath.Join(rootdir, snappyDir, "seccomp")
SnapSeccompDir = filepath.Join(SnapSeccompBase, "bpf")
Expand Down
2 changes: 2 additions & 0 deletions interfaces/backends/backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/dbus"
"github.com/snapcore/snapd/interfaces/kmod"
"github.com/snapcore/snapd/interfaces/ldconfig"
"github.com/snapcore/snapd/interfaces/mount"
"github.com/snapcore/snapd/interfaces/polkit"
"github.com/snapcore/snapd/interfaces/seccomp"
Expand All @@ -45,6 +46,7 @@ func All() []interfaces.SecurityBackend {
&mount.Backend{},
&kmod.Backend{},
&polkit.Backend{},
&ldconfig.Backend{},
}

// TODO use something like:
Expand Down
2 changes: 2 additions & 0 deletions interfaces/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ const (
SecuritySystemd SecuritySystem = "systemd"
// SecurityPolkit identifies the polkit security system.
SecurityPolkit SecuritySystem = "polkit"
// SecurityLdconfig identifies the ldconfig security system.
SecurityLdconfig SecuritySystem = "ldconfig"
)

var isValidBusName = regexp.MustCompile(`^[a-zA-Z_-][a-zA-Z0-9_-]*(\.[a-zA-Z_-][a-zA-Z0-9_-]*)+$`).MatchString
Expand Down
38 changes: 38 additions & 0 deletions interfaces/ifacetest/testiface.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/snapcore/snapd/interfaces/dbus"
"github.com/snapcore/snapd/interfaces/hotplug"
"github.com/snapcore/snapd/interfaces/kmod"
"github.com/snapcore/snapd/interfaces/ldconfig"
"github.com/snapcore/snapd/interfaces/mount"
"github.com/snapcore/snapd/interfaces/polkit"
"github.com/snapcore/snapd/interfaces/seccomp"
Expand Down Expand Up @@ -84,6 +85,13 @@ type TestInterface struct {
KModPermanentPlugCallback func(spec *kmod.Specification, plug *snap.PlugInfo) error
KModPermanentSlotCallback func(spec *kmod.Specification, slot *snap.SlotInfo) error

// Support for interacting with the ldconfig backend.

LdconfigConnectedPlugCallback func(spec *ldconfig.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
LdconfigConnectedSlotCallback func(spec *ldconfig.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
LdconfigPermanentPlugCallback func(spec *ldconfig.Specification, plug *snap.PlugInfo) error
LdconfigPermanentSlotCallback func(spec *ldconfig.Specification, slot *snap.SlotInfo) error

// Support for interacting with the seccomp backend.

SecCompConnectedPlugCallback func(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
Expand Down Expand Up @@ -360,6 +368,36 @@ func (t *TestInterface) KModPermanentSlot(spec *kmod.Specification, slot *snap.S
return nil
}

// Support for interacting with the ldconfig backend.

func (t *TestInterface) LdconfigConnectedPlug(spec *ldconfig.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
if t.LdconfigConnectedPlugCallback != nil {
return t.LdconfigConnectedPlugCallback(spec, plug, slot)
}
return nil
}

func (t *TestInterface) LdconfigConnectedSlot(spec *ldconfig.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
if t.LdconfigConnectedSlotCallback != nil {
return t.LdconfigConnectedSlotCallback(spec, plug, slot)
}
return nil
}

func (t *TestInterface) LdconfigPermanentPlug(spec *ldconfig.Specification, plug *snap.PlugInfo) error {
if t.LdconfigPermanentPlugCallback != nil {
return t.LdconfigPermanentPlugCallback(spec, plug)
}
return nil
}

func (t *TestInterface) LdconfigPermanentSlot(spec *ldconfig.Specification, slot *snap.SlotInfo) error {
if t.LdconfigPermanentSlotCallback != nil {
return t.LdconfigPermanentSlotCallback(spec, slot)
}
return nil
}

// Support for interacting with the dbus backend.

func (t *TestInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
Expand Down
121 changes: 121 additions & 0 deletions interfaces/ldconfig/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package ldconfig

import (
"fmt"
"os"
"strings"

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/timings"
)

// Backend is responsible for maintaining ldconfig cache.
type Backend struct{}

var _ = interfaces.SecurityBackend(&Backend{})

// Initialize does nothing for this backend.
func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error {
return nil
}

// Name returns the name of the backend.
func (b *Backend) Name() interfaces.SecuritySystem {
return "ldconfig"
}

// Setup will make the ldconfig backend generate the needed
// configuration files and re-create the ld cache.
//
// If the method fails it should be re-tried (with a sensible strategy) by the caller.
func (b *Backend) Setup(appSet *interfaces.SnapAppSet, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
snapName := appSet.InstanceName()
// Get the snippets that apply to this snap
spec, err := repo.SnapSpecification(b.Name(), appSet, opts)
if err != nil {
return fmt.Errorf("cannot obtain ldconfig specification for snap %q: %s",
snapName, err)
}

return b.setupLdconfigCache(spec.(*Specification))
}

// Remove removes modules ldconfig files specific to a given snap.
// This method should be called after removing a snap.
//
// If the method fails it should be re-tried (with a sensible strategy) by the caller.
func (b *Backend) Remove(snapName string) error {
// TODO this will not be called for the rootfs case where the system
// has an implicit plug, but needs to be revisited for snaps.
return nil
}

// NewSpecification returns a new specification associated with this backend.
func (b *Backend) NewSpecification(*interfaces.SnapAppSet,
interfaces.ConfinementOptions) interfaces.Specification {
return &Specification{}
}

// SandboxFeatures returns the list of features supported by snapd for ldconfig.
func (b *Backend) SandboxFeatures() []string {
return []string{"mediated-ldconfig"}
}

func (b *Backend) setupLdconfigCache(spec *Specification) error {
ldConfigDir := dirs.SnapLdconfigDir
if err := os.MkdirAll(ldConfigDir, 0755); err != nil {
return fmt.Errorf("cannot create directory for ldconfig files %q: %s", ldConfigDir, err)
}

// TODO this considers only the case when the libraries are exposed to
// the rootfs. For snaps, we will create files in
// /var/lib/snapd/ldconfig/ that will be used to generate a cache
// specific to each snap.

allContent := map[string]osutil.FileState{}
for key, dirs := range spec.libDirs {
content := "# This file is automatically generated by snapd\n"
content += strings.Join(dirs, "\n")
content += "\n"

// File name is snap.<snap_name>.<slot_name>.conf
ldconfigPath := fmt.Sprintf("snap.%s.conf", key)
allContent[ldconfigPath] = &osutil.MemoryFileState{
Content: []byte(content),
Mode: 0644,
}
}
_, _, err := osutil.EnsureDirStateGlobs(ldConfigDir, []string{"snap.*.conf"}, allContent)
if err != nil {
return err
}

// Re-create cache
out, stderr, err := osutil.RunSplitOutput("ldconfig")
if err != nil {
return osutil.OutputErrCombine(out, stderr, err)
}

return nil
}
123 changes: 123 additions & 0 deletions interfaces/ldconfig/backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package ldconfig_test

import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"

. "gopkg.in/check.v1"

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/ifacetest"
"github.com/snapcore/snapd/interfaces/ldconfig"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
)

func Test(t *testing.T) {
TestingT(t)
}

type backendSuite struct {
ifacetest.BackendSuite
ldconfigCmd *testutil.MockCmd
}

var _ = Suite(&backendSuite{})

func (s *backendSuite) SetUpTest(c *C) {
s.Backend = &ldconfig.Backend{}
s.BackendSuite.SetUpTest(c)
c.Assert(s.Repo.AddBackend(s.Backend), IsNil)
s.ldconfigCmd = testutil.MockCommand(c, "ldconfig", "")
}

func (s *backendSuite) TearDownTest(c *C) {
s.ldconfigCmd.Restore()
s.BackendSuite.TearDownTest(c)
}

func (s *backendSuite) TestName(c *C) {
c.Check(s.Backend.Name(), Equals, interfaces.SecurityLdconfig)
}

func checkLdconfigFile(c *C, snapName, slot string, libDirs []string) {
path := filepath.Join(dirs.GlobalRootDir, "etc", "ld.so.conf.d",
fmt.Sprintf("snap.%s.%s.conf", snapName, slot))
content := "# This file is automatically generated by snapd\n"
content += strings.Join(libDirs, "\n")
content += "\n"
c.Assert(path, testutil.FileEquals, content)
}

func (s *backendSuite) TestInstallingCreatesLdconf(c *C) {
s.Iface.LdconfigPermanentSlotCallback = func(spec *ldconfig.Specification,
slot *snap.SlotInfo) error {
spec.AddLibDirs("snap1", "slot1", []string{"/dir1/lib1", "/dir1/lib11"})
spec.AddLibDirs("snap1", "slot2", []string{"/dir1/lib2", "/dir1/lib22"})
spec.AddLibDirs("snap2", "slot1", []string{"/dir2/lib1"})
return nil
}
snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 0)

confDir := filepath.Join(dirs.GlobalRootDir, "etc", "ld.so.conf.d")
libcConfPath := filepath.Join(confDir, "libc.conf")
c.Assert(os.WriteFile(libcConfPath, []byte{}, 0644), IsNil)

checkLdconfigFile(c, "snap1", "slot1", []string{"/dir1/lib1", "/dir1/lib11"})
checkLdconfigFile(c, "snap1", "slot2", []string{"/dir1/lib2", "/dir1/lib22"})
checkLdconfigFile(c, "snap2", "slot1", []string{"/dir2/lib1"})

c.Assert(s.ldconfigCmd.Calls(), DeepEquals, [][]string{
{"ldconfig"},
})
s.ldconfigCmd.ForgetCalls()

// When refreshing a slot is removed
s.Iface.LdconfigPermanentSlotCallback = func(spec *ldconfig.Specification,
slot *snap.SlotInfo) error {
spec.AddLibDirs("snap1", "slot1", []string{"/dir1/lib1", "/dir1/lib11"})
spec.AddLibDirs("snap2", "slot1", []string{"/dir2/lib1"})
return nil
}
s.UpdateSnap(c, snapInfo, interfaces.ConfinementOptions{}, ifacetest.SambaYamlV1, 1)

checkLdconfigFile(c, "snap1", "slot1", []string{"/dir1/lib1", "/dir1/lib11"})
c.Check(filepath.Join(confDir, "snap.snap1.slot2.conf"), testutil.FileAbsent)
checkLdconfigFile(c, "snap2", "slot1", []string{"/dir2/lib1"})

c.Assert(s.ldconfigCmd.Calls(), DeepEquals, [][]string{
{"ldconfig"},
})

// libc config has not been touched
c.Check(libcConfPath, testutil.FilePresent)

s.RemoveSnap(c, snapInfo)
}

func (s *backendSuite) TestSandboxFeatures(c *C) {
c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"mediated-ldconfig"})
}
Loading
Loading