| // Package modules provides fucntionality to install and sign Linux kernel modules. | |
| package modules | |
| import ( | |
| "bytes" | |
| "encoding/binary" | |
| "io" | |
| "io/ioutil" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "strings" | |
| log "github.com/golang/glog" | |
| "github.com/pkg/errors" | |
| "cos.googlesource.com/cos/tools.git/src/pkg/utils" | |
| ) | |
| const ( | |
| // PKEYIDPKCS7 is a constant defined in https://github.com/torvalds/linux/blob/master/scripts/sign-file.c | |
| PKEYIDPKCS7 = byte(2) | |
| // magicNumber is a constant defined in https://github.com/torvalds/linux/blob/master/scripts/sign-file.c | |
| magicNumber = "~Module signature appended~\n" | |
| SecondaryKeyring = "%keyring:.secondary_trusted_keys" | |
| IMAKeyring = "%keyring:.ima" | |
| ) | |
| var ( | |
| execCommand = exec.Command | |
| ) | |
| type Module struct { | |
| Name string | |
| Path string | |
| Deps []*Module | |
| SkipNotFound bool | |
| } | |
| // LoadModule loads a given kernel module to kernel. | |
| func LoadModule(module *Module, moduleParams ModuleParameters) error { | |
| if module.SkipNotFound { | |
| if exists, _ := utils.CheckFileExists(module.Path); !exists { | |
| log.V(2).Infof("skipping module %s as it does not exist", module.Name) | |
| return nil | |
| } | |
| } | |
| loaded, err := isModuleLoaded(module.Name) | |
| if err != nil { | |
| return errors.Wrapf(err, "failed to load module %s (%s)", module.Name, module.Path) | |
| } | |
| if loaded { | |
| return nil | |
| } | |
| // load Module dependencies before module | |
| for _, dep := range module.Deps { | |
| if err := LoadModule(dep, moduleParams); err != nil { | |
| return err | |
| } | |
| } | |
| if err := loadModule(module.Path, moduleParams[module.Name]); err != nil { | |
| return errors.Wrapf(err, "failed to load module %s (%s)", module.Name, module.Path) | |
| } | |
| return nil | |
| } | |
| // UpdateHostLdCache updates the ld cache on host. | |
| func UpdateHostLdCache(hostRootDir, moduleLibDir string) error { | |
| log.Info("Updating host's ld cache") | |
| ldPath := filepath.Join(hostRootDir, "/etc/ld.so.conf") | |
| f, err := os.OpenFile(ldPath, os.O_APPEND|os.O_WRONLY, 0644) | |
| if err != nil { | |
| return errors.Wrapf(err, "failed to open %s", ldPath) | |
| } | |
| defer f.Close() | |
| if _, err := f.WriteString(moduleLibDir + "\n"); err != nil { | |
| return errors.Wrapf(err, `failed to write "%s" to %s`, moduleLibDir, ldPath) | |
| } | |
| if err := execCommand("ldconfig", "-r", hostRootDir).Run(); err != nil { | |
| return errors.Wrapf(err, "failed to run `ldconfig -r %s`", hostRootDir) | |
| } | |
| return nil | |
| } | |
| // LoadPublicKey loads the given public key to system keyring. | |
| func LoadPublicKey(keyName, keyPath, keyring string) error { | |
| log.Infof("Loading %s to keyring %s", keyName, keyring) | |
| keyBytes, err := ioutil.ReadFile(keyPath) | |
| if err != nil { | |
| return errors.Wrapf(err, "failed to read key %s", keyPath) | |
| } | |
| cmd := execCommand("/bin/keyctl", "padd", "asymmetric", keyName, keyring) | |
| cmd.Stdin = bytes.NewBuffer(keyBytes) | |
| if err := cmd.Run(); err != nil { | |
| return errors.Wrapf(err, "failed to load %s to keyring %s", keyName, keyring) | |
| } | |
| log.Infof("Successfully load key %s into keyring %s.", keyName, keyring) | |
| return nil | |
| } | |
| // AppendSignature appends a raw PKCS#7 signature to the end of a given kernel module. | |
| // This is basically the Go implementation of `scripts/sign-file -s` in Linux upstream. | |
| func AppendSignature(outfilePath, modulefilePath, sigfilePath string) error { | |
| tempFile, err := ioutil.TempFile("", "tempFile") | |
| if err != nil { | |
| return errors.Wrap(err, "failed to create temp file") | |
| } | |
| defer os.Remove(tempFile.Name()) | |
| defer tempFile.Close() | |
| // Copy bytes of kernel module into the temp file. | |
| modulefile, err := os.Open(modulefilePath) | |
| if err != nil { | |
| return errors.Wrapf(err, "failed to open file %s", modulefilePath) | |
| } | |
| defer modulefile.Close() | |
| _, err = io.Copy(tempFile, modulefile) | |
| if err != nil { | |
| return errors.Wrap(err, "failed to copy file") | |
| } | |
| // Append bytes of module signature into the temp file. | |
| sigfile, err := os.Open(sigfilePath) | |
| if err != nil { | |
| return errors.Wrapf(err, "failed to open file %s", sigfilePath) | |
| } | |
| defer sigfile.Close() | |
| sigSize, err := io.Copy(tempFile, sigfile) | |
| if err != nil { | |
| return errors.Wrap(err, "failed to copy file") | |
| } | |
| // Append the marker and the PKCS#7 message. | |
| // moduleSignature is the struct module_signature defined in | |
| // https://github.com/torvalds/linux/blob/master/scripts/sign-file.c | |
| moduleSignature := [12]byte{} | |
| // moduleSignature[2] is the id_type of struct module_signature | |
| moduleSignature[2] = PKEYIDPKCS7 | |
| // moduleSignature[8:12] is the sig_len of struct module_signature. | |
| // Using BigEndian as the sig_len should be in network byte order. | |
| binary.BigEndian.PutUint32(moduleSignature[8:12], uint32(sigSize)) | |
| _, err = tempFile.Write(moduleSignature[:]) | |
| if err != nil { | |
| return errors.Wrapf(err, "failed to write to file %s", tempFile.Name()) | |
| } | |
| _, err = tempFile.Write([]byte(magicNumber)) | |
| if err != nil { | |
| return errors.Wrapf(err, "failed to write to file %s", tempFile.Name()) | |
| } | |
| if err := tempFile.Close(); err != nil { | |
| return errors.Wrapf(err, "failed to close file %s", tempFile.Name()) | |
| } | |
| // Finally, move the outfile to specified location. | |
| // It overwrites the original module file if we are appending in place. | |
| if err := utils.MoveFile(tempFile.Name(), outfilePath); err != nil { | |
| return errors.Wrapf(err, "failed to rename file from %s to %s", tempFile.Name(), outfilePath) | |
| } | |
| return nil | |
| } | |
| func isModuleLoaded(moduleName string) (bool, error) { | |
| out, err := execCommand("lsmod").Output() | |
| if err != nil { | |
| return false, errors.Wrap(err, "failed to run command `lsmod`") | |
| } | |
| for _, line := range strings.Split(string(out), "\n") { | |
| fields := strings.Fields(line) | |
| if len(fields) > 0 && fields[0] == moduleName { | |
| return true, nil | |
| } | |
| } | |
| return false, nil | |
| } | |
| func loadModule(modulePath string, moduleParams []string) error { | |
| insmodArgs := append([]string{modulePath}, moduleParams...) | |
| cmd := execCommand("insmod", insmodArgs...) | |
| log.Infof("loading module: %v", cmd) | |
| if err := cmd.Run(); err != nil { | |
| return errors.Wrapf(err, "failed to run command `insmod %v`", insmodArgs) | |
| } | |
| return nil | |
| } |