Problem Description
In pkg/unikontainers/utils.go, findQemuDataDir() checks whether /usr/local/share/<name> is a symbolic link. When it is, the function uses os.Readlink() and directly returns the symlink target.
os.Readlink() returns the target exactly as stored in the symlink. If the target is relative, the returned value is therefore also relative to the symlink's parent directory rather than an absolute path.
For example, if:
/usr/local/share/qemu -> ../share/qemu
then os.Readlink("/usr/local/share/qemu") returns:
findQemuDataDir() currently returns that value without resolving it relative to /usr/local/share/.
The returned path is subsequently used by mountsForMonitor() in pkg/unikontainers/rootfs.go as the source of a bind mount:
mounts = append(mounts, bindMount(qDataPath, "/usr/share/qemu", true, true))
The relative path can then be interpreted relative to the process's current working directory rather than the directory containing the symlink. This can cause the subsequent filesystem or mount operation to fail with ENOENT.
Affected Code
File: pkg/unikontainers/utils.go
func findQemuDataDir(basename string) (string, error) {
// First check if the file exists under /usr/local/share
qdPath := filepath.Join("/usr/local/share/", basename)
info, err := os.Lstat(qdPath)
if err != nil {
if !os.IsNotExist(err) {
return "", fmt.Errorf("failed to get info of %s: %w", qdPath, err)
}
// The file does not exist under /usr/local/share
// fallback to the usual path /usr/share/
qdPath = filepath.Join("/usr/share/", basename)
} else {
// The file exists under /usr/local/share, but check if it is a link
if info.Mode()&os.ModeSymlink != 0 {
// It is a link, get the target
qdPath, err = os.Readlink(qdPath)
if err != nil {
return "", fmt.Errorf("failed to get target of %s %w", qdPath, err)
}
}
return qdPath, nil
}
return qdPath, nil
}
The issue is specifically the os.Readlink() path: the returned target is not resolved relative to /usr/local/share/<name> before being returned.
Failure Scenarios
1. Relative symlink target
Given:
/usr/local/share/qemu -> ../share/qemu
findQemuDataDir("qemu") returns:
instead of the path represented by the symlink.
When this value is later used as a filesystem path, its interpretation depends on the process's current working directory.
2. Chained symlinks
os.Readlink() resolves only the first symlink itself. If the target is another symlink, findQemuDataDir() does not resolve the remaining chain.
3. Broken symlink
os.Lstat() succeeds for a dangling symlink because the link itself exists. os.Readlink() also succeeds and returns its target.
As a result, the function does not detect that the target is missing and does not fall back to /usr/share/<name>.
Expected Behavior
findQemuDataDir() should return a path that correctly represents the target of a symlink under /usr/local/share/, independent of the process's current working directory.
Symlink resolution should also correctly handle chained symlinks and detect a broken target so that the existing /usr/share/<name> fallback can be used where appropriate.
Impact
On systems where the QEMU data directory under /usr/local/share/ is represented by a relative symlink, the current behavior can result in an invalid source path being passed to the monitor filesystem setup.
This can prevent the container/unikernel from being created even though the expected QEMU data directory exists and is reachable through the symlink.
Problem Description
In
pkg/unikontainers/utils.go,findQemuDataDir()checks whether/usr/local/share/<name>is a symbolic link. When it is, the function usesos.Readlink()and directly returns the symlink target.os.Readlink()returns the target exactly as stored in the symlink. If the target is relative, the returned value is therefore also relative to the symlink's parent directory rather than an absolute path.For example, if:
then
os.Readlink("/usr/local/share/qemu")returns:findQemuDataDir()currently returns that value without resolving it relative to/usr/local/share/.The returned path is subsequently used by
mountsForMonitor()inpkg/unikontainers/rootfs.goas the source of a bind mount:The relative path can then be interpreted relative to the process's current working directory rather than the directory containing the symlink. This can cause the subsequent filesystem or mount operation to fail with
ENOENT.Affected Code
File:
pkg/unikontainers/utils.goThe issue is specifically the
os.Readlink()path: the returned target is not resolved relative to/usr/local/share/<name>before being returned.Failure Scenarios
1. Relative symlink target
Given:
findQemuDataDir("qemu")returns:instead of the path represented by the symlink.
When this value is later used as a filesystem path, its interpretation depends on the process's current working directory.
2. Chained symlinks
os.Readlink()resolves only the first symlink itself. If the target is another symlink,findQemuDataDir()does not resolve the remaining chain.3. Broken symlink
os.Lstat()succeeds for a dangling symlink because the link itself exists.os.Readlink()also succeeds and returns its target.As a result, the function does not detect that the target is missing and does not fall back to
/usr/share/<name>.Expected Behavior
findQemuDataDir()should return a path that correctly represents the target of a symlink under/usr/local/share/, independent of the process's current working directory.Symlink resolution should also correctly handle chained symlinks and detect a broken target so that the existing
/usr/share/<name>fallback can be used where appropriate.Impact
On systems where the QEMU data directory under
/usr/local/share/is represented by a relative symlink, the current behavior can result in an invalid source path being passed to the monitor filesystem setup.This can prevent the container/unikernel from being created even though the expected QEMU data directory exists and is reachable through the symlink.