cache.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. // cache.go - Dynamic linker cache routines.
  2. // Copyright 2016 Yawning Angel
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a
  5. // copy of this software and associated documentation files (the "Software"),
  6. // to deal in the Software without restriction, including without limitation
  7. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. // and/or sell copies of the Software, and to permit persons to whom the
  9. // Software is furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included
  12. // in all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  15. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  17. // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. // DEALINGS IN THE SOFTWARE.
  21. // Package dynlib provides routines for interacting with the glibc ld.so dynamic
  22. // linker/loader.
  23. package dynlib
  24. import (
  25. "bytes"
  26. "debug/elf"
  27. "encoding/binary"
  28. "fmt"
  29. "io/ioutil"
  30. "os"
  31. "path/filepath"
  32. "runtime"
  33. "sort"
  34. "strings"
  35. )
  36. const (
  37. ldSoCache = "/etc/ld.so.cache"
  38. flagX8664Lib64 = 0x0300
  39. //flagElf = 1
  40. flagElfLibc6 = 3
  41. )
  42. // Debugf is a hook used for redirecting debug output.
  43. var Debugf func(string, ...interface{})
  44. // FilterFunc is a function that implements a filter to allow rejecting
  45. // dependencies when resolving libraries.
  46. type FilterFunc func(string) error
  47. // Quoting from sysdeps/generic/dl-cache.h:
  48. //
  49. // libc5 and glibc 2.0/2.1 use the same format. For glibc 2.2 another
  50. // format has been added in a compatible way:
  51. // The beginning of the string table is used for the new table:
  52. // old_magic
  53. // nlibs
  54. // libs[0]
  55. // ...
  56. // libs[nlibs-1]
  57. // pad, new magic needs to be aligned
  58. // - this is string[0] for the old format
  59. // new magic - this is string[0] for the new format
  60. // newnlibs
  61. // ...
  62. // newlibs[0]
  63. // ...
  64. // newlibs[newnlibs-1]
  65. // string 1
  66. // string 2
  67. // ...
  68. // Cache is a representation of the `ld.so.cache` file.
  69. type Cache struct {
  70. store map[string]cacheEntries
  71. }
  72. // GetLibraryPath returns the path to the given library, if any. This routine
  73. // makes no attempt to disambiguate multiple libraries (eg: via hwcap/search
  74. // path).
  75. func (c *Cache) GetLibraryPath(name string) string {
  76. ents, ok := c.store[name]
  77. if !ok {
  78. return ""
  79. }
  80. return ents[0].value
  81. }
  82. // ResolveLibraries returns a map of library paths and their aliases for a
  83. // given set of binaries, based off the ld.so.cache, libraries known to be
  84. // internal, and a search path.
  85. func (c *Cache) ResolveLibraries(binaries []string, extraLibs []string, ldLibraryPath, fallbackSearchPath string, filterFn FilterFunc) (map[string][]string, error) {
  86. searchPaths := filepath.SplitList(ldLibraryPath)
  87. fallbackSearchPaths := filepath.SplitList(fallbackSearchPath)
  88. libraries := make(map[string]string)
  89. // Breadth-first iteration of all the binaries, and their dependencies.
  90. checkedFile := make(map[string]bool)
  91. checkedLib := make(map[string]bool)
  92. toCheck := binaries
  93. for {
  94. newToCheck := make(map[string]bool)
  95. if len(toCheck) == 0 {
  96. break
  97. }
  98. for _, fn := range toCheck {
  99. if filterFn != nil {
  100. if err := filterFn(fn); err != nil {
  101. return nil, err
  102. }
  103. }
  104. impLibs, err := getLibraries(fn)
  105. if err != nil {
  106. return nil, err
  107. }
  108. debugf("dynlib: %v imports: %v", fn, impLibs)
  109. checkedFile[fn] = true
  110. // The internal libraries also need recursive resolution,
  111. // so just append them to the first binary.
  112. if extraLibs != nil {
  113. debugf("dynlib: Appending extra libs: %v", extraLibs)
  114. impLibs = append(impLibs, extraLibs...)
  115. extraLibs = nil
  116. }
  117. for _, lib := range impLibs {
  118. if checkedLib[lib] {
  119. continue
  120. }
  121. isInPath := func(l string, p []string) string {
  122. for _, d := range p {
  123. maybePath := filepath.Join(d, l)
  124. if fileExists(maybePath) {
  125. return maybePath
  126. }
  127. }
  128. return ""
  129. }
  130. // Look for the library in the various places.
  131. var libPath string
  132. var inLdLibraryPath, inCache, inFallbackPath bool
  133. if libPath = isInPath(lib, searchPaths); libPath != "" {
  134. inLdLibraryPath = true
  135. } else if libPath = c.GetLibraryPath(lib); libPath != "" {
  136. inCache = true
  137. } else if libPath = isInPath(lib, fallbackSearchPaths); libPath != "" {
  138. inFallbackPath = true
  139. } else {
  140. return nil, fmt.Errorf("dynlib: Failed to find library: %v", lib)
  141. }
  142. var libSrc string
  143. switch {
  144. case inLdLibraryPath:
  145. libSrc = "LD_LIBRARY_PATH"
  146. case inCache:
  147. libSrc = "ld.so.conf"
  148. case inFallbackPath:
  149. libSrc = "Filesystem"
  150. }
  151. debugf("dynlib: Found %v (%v).", lib, libSrc)
  152. // Register the library, assuming it's not in what will
  153. // presumably be `LD_LIBRARY_PATH` inside the hugbox.
  154. if !inLdLibraryPath {
  155. libraries[lib] = libPath
  156. }
  157. checkedLib[lib] = true
  158. if !checkedFile[libPath] {
  159. newToCheck[libPath] = true
  160. }
  161. }
  162. }
  163. toCheck = []string{}
  164. for k, _ := range newToCheck {
  165. toCheck = append(toCheck, k)
  166. }
  167. }
  168. // De-dup the libraries map by figuring out what can be symlinked.
  169. ret := make(map[string][]string)
  170. for lib, fn := range libraries {
  171. f, err := filepath.EvalSymlinks(fn)
  172. if err != nil {
  173. return nil, err
  174. }
  175. vec := ret[f]
  176. vec = append(vec, lib)
  177. ret[f] = vec
  178. }
  179. // XXX: This should sanity check to ensure that aliases are distinct.
  180. return ret, nil
  181. }
  182. type cacheEntry struct {
  183. key, value string
  184. flags uint32
  185. osVersion uint32
  186. hwcap uint64
  187. }
  188. type cacheEntries []*cacheEntry
  189. func (e cacheEntries) Len() int {
  190. return len(e)
  191. }
  192. func (e cacheEntries) Less(i, j int) bool {
  193. // Bigger hwcap should come first.
  194. if e[i].hwcap > e[j].hwcap {
  195. return true
  196. }
  197. // Bigger osVersion should come first.
  198. if e[i].osVersion > e[j].osVersion {
  199. return true
  200. }
  201. // Preserve the ordering otherwise.
  202. return i < j
  203. }
  204. func (e cacheEntries) Swap(i, j int) {
  205. e[i], e[j] = e[j], e[i]
  206. }
  207. func getNewLdCache(b []byte) ([]byte, int, error) {
  208. const entrySz = 4 + 4 + 4
  209. // The new format is embedded in the old format, so do some light
  210. // parsing/validation to get to the new format's header.
  211. cacheMagic := []byte{
  212. 'l', 'd', '.', 's', 'o', '-', '1', '.', '7', '.', '0', 0,
  213. }
  214. // old_magic
  215. if !bytes.HasPrefix(b, cacheMagic) {
  216. return nil, 0, fmt.Errorf("dynlib: ld.so.cache has invalid old_magic")
  217. }
  218. off := len(cacheMagic)
  219. b = b[off:]
  220. // nlibs
  221. if len(b) < 4 {
  222. return nil, 0, fmt.Errorf("dynlib: ld.so.cache truncated (nlibs)")
  223. }
  224. nlibs := int(binary.LittleEndian.Uint32(b))
  225. off += 4
  226. b = b[4:]
  227. // libs[nlibs]
  228. nSkip := entrySz * nlibs
  229. if len(b) < nSkip {
  230. return nil, 0, fmt.Errorf("dynlib: ld.so.cache truncated (libs[])")
  231. }
  232. off += nSkip
  233. b = b[nSkip:]
  234. // new_magic is 8 byte aligned.
  235. padLen := (((off+8-1)/8)*8 - off)
  236. if len(b) < padLen {
  237. return nil, 0, fmt.Errorf("dynlib: ld.so.cache truncated (pad)")
  238. }
  239. return b[padLen:], nlibs, nil
  240. }
  241. // LoadCache creates a new system shared library cache usually by loading
  242. // and parsing the `/etc/ld.so.cache` file.
  243. //
  244. // See `sysdeps/generic/dl-cache.h` in the glibc source tree for details
  245. // regarding the format.
  246. func LoadCache() (*Cache, error) {
  247. if !IsSupported() {
  248. return nil, errUnsupported
  249. }
  250. // Certain libc implementations totally lack a ld.so.cache.
  251. _, err := os.Stat(ldSoCache)
  252. if err != nil {
  253. if os.IsNotExist(err) {
  254. return loadCacheFallback()
  255. }
  256. }
  257. return loadCacheGlibc()
  258. }
  259. func loadCacheGlibc() (*Cache, error) {
  260. const entrySz = 4 + 4 + 4 + 4 + 8
  261. ourOsVersion := getOsVersion()
  262. debugf("dynlib: osVersion: %08x", ourOsVersion)
  263. c := new(Cache)
  264. c.store = make(map[string]cacheEntries)
  265. b, err := ioutil.ReadFile(ldSoCache)
  266. if err != nil {
  267. return nil, err
  268. }
  269. // It is likely safe to assume that everyone is running glibc >= 2.2 at
  270. // this point, so extract the "new format" from the "old format".
  271. b, _, err = getNewLdCache(b)
  272. if err != nil {
  273. return nil, err
  274. }
  275. stringTable := b
  276. // new_magic.
  277. cacheMagicNew := []byte{
  278. 'g', 'l', 'i', 'b', 'c', '-', 'l', 'd', '.', 's', 'o', '.', 'c', 'a', 'c',
  279. 'h', 'e', '1', '.', '1',
  280. }
  281. if !bytes.HasPrefix(b, cacheMagicNew) {
  282. return nil, fmt.Errorf("dynlib: ld.so.cache has invalid new_magic")
  283. }
  284. b = b[len(cacheMagicNew):]
  285. // nlibs, len_strings, unused[].
  286. if len(b) < 2*4+5*4 {
  287. return nil, fmt.Errorf("dynlib: ld.so.cache truncated (new header)")
  288. }
  289. nlibs := int(binary.LittleEndian.Uint32(b))
  290. b = b[4:]
  291. lenStrings := int(binary.LittleEndian.Uint32(b))
  292. b = b[4+20:] // Also skip unused[].
  293. rawLibs := b[:nlibs*entrySz]
  294. b = b[len(rawLibs):]
  295. if len(b) != lenStrings {
  296. return nil, fmt.Errorf("dynlib: lenStrings appears invalid")
  297. }
  298. getString := func(idx int) (string, error) {
  299. if idx < 0 || idx > len(stringTable) {
  300. return "", fmt.Errorf("dynlib: string table index out of bounds")
  301. }
  302. l := bytes.IndexByte(stringTable[idx:], 0)
  303. if l == 0 {
  304. return "", nil
  305. }
  306. return string(stringTable[idx : idx+l]), nil
  307. }
  308. // libs[]
  309. var flagCheckFn func(uint32) bool
  310. switch runtime.GOARCH {
  311. case "amd64":
  312. flagCheckFn = func(flags uint32) bool {
  313. const wantFlags = flagX8664Lib64 | flagElfLibc6
  314. return flags&wantFlags == wantFlags
  315. }
  316. // HWCAP is unused on amd64.
  317. default:
  318. panic(errUnsupported)
  319. }
  320. for i := 0; i < nlibs; i++ {
  321. rawE := rawLibs[entrySz*i : entrySz*(i+1)]
  322. e := new(cacheEntry)
  323. e.flags = binary.LittleEndian.Uint32(rawE[0:])
  324. kIdx := int(binary.LittleEndian.Uint32(rawE[4:]))
  325. vIdx := int(binary.LittleEndian.Uint32(rawE[8:]))
  326. e.osVersion = binary.LittleEndian.Uint32(rawE[12:])
  327. e.hwcap = binary.LittleEndian.Uint64(rawE[16:])
  328. e.key, err = getString(kIdx)
  329. if err != nil {
  330. return nil, fmt.Errorf("dynlib: failed to query key: %v", err)
  331. }
  332. e.value, err = getString(vIdx)
  333. if err != nil {
  334. return nil, fmt.Errorf("dynlib: failed to query value: %v", err)
  335. }
  336. // Discard libraries we have no hope of using, either due to
  337. // osVersion, or hwcap.
  338. if ourOsVersion < e.osVersion {
  339. debugf("dynlib: ignoring library: %v (osVersion: %x)", e.key, e.osVersion)
  340. } else if err = ValidateLibraryClass(e.value); err != nil {
  341. debugf("dynlib: ignoring library %v (%v)", e.key, err)
  342. } else if flagCheckFn(e.flags) {
  343. vec := c.store[e.key]
  344. vec = append(vec, e)
  345. c.store[e.key] = vec
  346. } else {
  347. debugf("dynlib: ignoring library: %v (flags: %x, hwcap: %x)", e.key, e.flags, e.hwcap)
  348. }
  349. }
  350. for lib, entries := range c.store {
  351. if len(entries) == 1 {
  352. continue
  353. }
  354. // Sort the entires in order of prefernce similar to what ld-linux.so
  355. // will do.
  356. sort.Sort(entries)
  357. c.store[lib] = entries
  358. paths := []string{}
  359. for _, e := range entries {
  360. paths = append(paths, e.value)
  361. }
  362. debugf("dynlib: debug: Multiple entry: %v: %v", lib, paths)
  363. }
  364. return c, nil
  365. }
  366. func loadCacheFallback() (*Cache, error) {
  367. c := new(Cache)
  368. c.store = make(map[string]cacheEntries)
  369. // The only reason this exists is because some people think that using
  370. // musl-libc is a good idea, so it is tailored for such systems.
  371. machine, searchPaths := archDepsMusl()
  372. for _, path := range searchPaths {
  373. fis, err := ioutil.ReadDir(path)
  374. if err != nil {
  375. debugf("dynlib: failed to read directory '%v': %v", path, err)
  376. continue
  377. }
  378. for _, v := range fis {
  379. // Skip directories.
  380. if v.IsDir() {
  381. continue
  382. }
  383. fn := filepath.Join(path, v.Name())
  384. soname, err := getSoname(fn, machine)
  385. if err != nil {
  386. debugf("dynlib: ignoring file '%v': %v", fn, err)
  387. continue
  388. }
  389. e := &cacheEntry{
  390. key: soname,
  391. value: fn,
  392. }
  393. vec := c.store[e.key]
  394. vec = append(vec, e)
  395. c.store[e.key] = vec
  396. }
  397. }
  398. return c, nil
  399. }
  400. func getSoname(path string, machine elf.Machine) (string, error) {
  401. f, err := elf.Open(path)
  402. if err != nil {
  403. return "", err
  404. }
  405. defer f.Close()
  406. if f.Machine != machine {
  407. return "", fmt.Errorf("machine mismatch (%v)", f.Machine)
  408. }
  409. soNames, err := f.DynString(elf.DT_SONAME)
  410. if err != nil {
  411. return "", err
  412. }
  413. if len(soNames) < 1 {
  414. return "", fmt.Errorf("no DT_SONAME entry")
  415. }
  416. return soNames[0], nil
  417. }
  418. func archDepsMusl() (elf.Machine, []string) {
  419. var (
  420. pathFile string
  421. machine elf.Machine
  422. archPaths []string
  423. )
  424. switch runtime.GOARCH {
  425. case "amd64":
  426. machine = elf.EM_X86_64
  427. pathFile = "/etc/ld-musl-x86_64.path"
  428. archPaths = []string{
  429. "/lib64",
  430. "/usr/lib64",
  431. }
  432. default:
  433. panic(errUnsupported)
  434. }
  435. // Try to load `/etc/ld-musl-{LDSO_ARCH}.path`.
  436. b, err := ioutil.ReadFile(pathFile)
  437. switch err {
  438. case nil:
  439. return machine, strings.FieldsFunc(string(b), func(c rune) bool {
  440. return c == '\n' || c == ':'
  441. })
  442. default:
  443. debugf("dynlib: failed to read '%v': %v", pathFile, err)
  444. }
  445. searchPaths := []string{
  446. // musl's default library search paths.
  447. "/lib",
  448. "/usr/local/lib",
  449. "/usr/lib",
  450. }
  451. searchPaths = append(searchPaths, archPaths...)
  452. return machine, searchPaths
  453. }
  454. func fileExists(f string) bool {
  455. if _, err := os.Lstat(f); err != nil && os.IsNotExist(err) {
  456. // This might be an EPERM, but bubblewrap can have elevated privs,
  457. // so this may succeed. If it doesn't, the error will be caught
  458. // later.
  459. return false
  460. }
  461. return true
  462. }
  463. func debugf(fmt string, args ...interface{}) {
  464. if Debugf != nil {
  465. Debugf(fmt, args...)
  466. }
  467. }