Skip to content
academia.sh

Lesson 08 / 11

Guest and Host System

Measuring escape risk by the number of shared items: the eleven items the guest and host share, the path each item opens and its classification by transit, data, or information effect, the paths closed by turning off optional shares along with their cost in work steps and host memory, and the six paths that cannot be closed while the guest is running.

Published Updated

In this lesson

  • Counts the items the guest and host share and writes how many paths each item opens
  • Splits paths into transit, data, and information effect and measures how many paths each class loses when closed
  • States the cost of closing in two units: changed legitimate work steps and additional host memory
Contents

The previous lesson counted layers stacked on top of one another: guest, hypervisor, host, hardware. In that ordering, each layer carried the one before it. But there is not just a single stack between guest and host; while both run, they use a set of items at the same time. The same scheduler queues both sides’ threads, the same physical memory pages get merged, the disk and network card the guest sees are emulation code running on the host, and on top of that there is a shared folder, a clipboard, and an agent channel opened for convenience.

Escape is the guest boundary being crossed once and something on the host side being reached. This lesson measures escape not as a probability but as the number of shared items: how many items the guest and host share, how many paths each shared item opens, and how many of those paths can be closed. The escape step itself is not written; the path’s name, its effect, and its cost are written. Trying an escape path on a real system, without written authorization and a defined scope, is both illegal and unprofessional.

VR7 — A shared item is a component used jointly with the host while the guest runs; the list on the fictional branch server is a model, the count is real. VR8 — A path is an outcome reached on the host side through a single shared item. VR9 — A path’s effect is one of three classes: transit (running code in the host process), data (touching host data), information (inference only). VR10 — A required item is an item that cannot be closed while the guest is running. VR11 — Cost is written in two units: changed legitimate work steps and additional host memory. VR12 — The branch server has four guests, split into two trust levels.

Shared Items

Shared items are of two kinds. One part exists as long as the guest runs: the guest must obtain processor time, see a disk, and write to a network card, and that disk and network card are also emulation code running on the host. The other part is opened for convenience and can be closed with a single configuration item.

// sharing.mjs — items the guest and host share, and the paths each item opens (MODEL).
// Paths are named on the defense axis; not exploit code, only the path's name and effect are written.
// effect: transit = moving into the host process, data = touching host data, information = inference.

// [item, required?, [[path, effect], ...], [changed legitimate work steps, extra host memory MiB]]
export const SHARING = [
  ['scheduler', true, [["neighboring guest's activity timing", 'information']], [0, 0]],
  ['processor cache', true, [['access pattern from a shared cache line', 'information']], [0, 0]],
  ['clock source', true, [["inferring the host's maintenance window", 'information']], [0, 0]],
  ['disk controller emulation', true, [['transit into the host process via an emulation bug', 'transit']], [0, 0]],
  ['network interface emulation', true, [['transit into the host process via an emulation bug', 'transit'],
    ["guest packet into the host's network stack", 'data']], [0, 0]],
  ['memory page merging', false, [['measuring whether the same page exists', 'information'],
    ['page sharing inferred from write latency', 'information']], [0, 1856]],
  ['display controller emulation', false, [['transit into the host process via an emulation bug', 'transit']], [2, 0]],
  ['shared folder', false, [["writing to the host's file system", 'data'],
    ['a file from the host running inside the guest', 'data']], [2, 0]],
  ['clipboard', false, [["reading the host's clipboard", 'data'], ['passing data from host to guest', 'data']], [1, 0]],
  ['guest agent channel', false, [['running a command in the guest from the host', 'data'],
    ["reading the guest's file system from the host", 'information']], [4, 0]],
  ['USB passthrough', false, [["a plugged-in device reaching the host's driver", 'transit']], [1, 0]],
];
export const EFFECT = ['transit', 'data', 'information']; const WIDTH = { transit: 9, data: 6, information: 13 };
export const pathCount = (list) => list.reduce((t, [, , y]) => t + y.length, 0);
export const effectCount = (list, e) => list.reduce((t, [, , y]) => t + y.filter(([, k]) => k === e).length, 0);

if (import.meta.url === `file://${process.argv[1]}`) {
  const s = (x, n) => String(x).padStart(n);
  const T = pathCount(SHARING);
  console.log(`${SHARING.length} shared items, ${T} paths; average per item `
    + `${(T / SHARING.length).toFixed(2)} paths`);
  console.log(`\n${'item'.padEnd(30)}${'type'.padEnd(14)}${s('paths', 7)}${s('transit', 9)}${s('data', 6)}`
    + `${s('information', 13)}${s('extra steps', 13)}${s('extra memory', 14)}`);
  for (const k of SHARING) {
    const [ad, required, y, [adim, mib]] = k;
    console.log(`${ad.padEnd(30)}${(required ? 'required' : 'optional').padEnd(14)}${s(y.length, 7)}`
      + EFFECT.map((e) => s(effectCount([k], e), WIDTH[e])).join('')
      + `${s(required ? '-' : adim, 13)}${s(required ? '-' : mib, 14)}`);
  }
  for (const [ad, f] of [['required total', true], ['optional total', false]]) {
    const g = SHARING.filter(([, z]) => z === f);
    const [adim, mib] = g.reduce(([a, m], [, , , [x, y]]) => [a + x, m + y], [0, 0]);
    console.log(`${ad.padEnd(44)}${s(pathCount(g), 7)}` + EFFECT.map((e) => s(effectCount(g, e), WIDTH[e])).join('')
      + `${s(f ? '-' : adim, 13)}${s(f ? '-' : mib, 14)}`);
  }
}
11 shared items, 16 paths; average per item 1.45 paths

item                          type            paths  transit  data  information  extra steps  extra memory
scheduler                     required            1        0     0            1            -             -
processor cache               required            1        0     0            1            -             -
clock source                  required            1        0     0            1            -             -
disk controller emulation     required            1        1     0            0            -             -
network interface emulation   required            2        1     1            0            -             -
memory page merging           optional            2        0     0            2            0          1856
display controller emulation  optional            1        1     0            0            2             0
shared folder                 optional            2        0     2            0            2             0
clipboard                     optional            2        0     2            0            1             0
guest agent channel           optional            2        0     1            1            4             0
USB passthrough               optional            1        1     0            0            1             0
required total                                    6        2     1            3            -             -
optional total                                   10        2     5            3           10          1856

Eleven items open sixteen paths; the average per item is 1.45. That average alone is a warning: closing a shared item does not always close one path — five of the eleven carry two, as does leaving one open.

The effect columns split the list in two. The five required items open six paths, and three of those are pure information inference: when the neighbor runs, which memory page is shared, the host’s maintenance window. Two are transit, and both come from a piece of emulation code. Since the guest must see a disk and a network card, those two pieces of emulation code run on the host in every run; these two transit paths are not a closable item.

The six optional items open ten paths and the distribution flips: five of six data paths are here. The clipboard, the shared folder, and the agent channel — all three items opened to make one person’s work easier — carry five of the six host-data paths. The right two columns write the cost of that: ten work steps and 1,856 MiB of host memory.

What Closing Closes, and What It Leaves

The run below closes the optional items one by one — first the one that opens the most paths, and on a tie the cheaper one — and writes the closed path together with its cost at each step.

// closing.mjs — optional shares are closed one by one; the closed path and its cost are counted together.
import { SHARING, EFFECT, pathCount, effectCount } from './sharing.mjs';

const T = pathCount(SHARING);
const optional = SHARING.filter(([, z]) => !z)
  .sort((a, b) => b[2].length - a[2].length || a[3][0] - b[3][0]);   // most paths first, then the cheaper one
const s = (x, n) => String(x).padStart(n);
console.log(`${'closed item'.padEnd(30)}${s('closed', 8)}${s('total', 7)}${s('remaining', 11)}`
  + `${s('extra steps', 13)}${s('extra memory', 14)}`);
let closed = 0, steps = 0, mem = 0;
for (const [ad, , y, [a, m]] of optional) {
  closed += y.length; steps += a; mem += m;
  console.log(`${ad.padEnd(30)}${s(y.length, 8)}${s(closed, 7)}${s(T - closed, 11)}${s(steps, 13)}${s(mem, 14)}`);
}

const remaining = SHARING.filter(([, z]) => z);
console.log(`\nclosable ${optional.length}/${SHARING.length} items, closed ${closed}/${T} paths `
  + `(${((100 * closed) / T).toFixed(1)}%); cost ${steps} work steps and ${mem} MiB of host memory`);
console.log(`${'effect'.padEnd(13)}${s('initial paths', 15)}${s('remaining paths', 17)}`);
for (const e of EFFECT)
  console.log(`${e.padEnd(13)}${s(effectCount(SHARING, e), 15)}${s(effectCount(remaining, e), 17)}`);
console.log(`while the guest runs, ${remaining.length} items cannot be closed, leaving ${pathCount(remaining)} paths`);

// Layout: four fictional guests on the same host, two trust levels.
const GUESTS = [['aggregator', 'high'], ['validator', 'high'], ['field portal', 'low'], ['test', 'low']];
const pairs = GUESTS.flatMap((a, i) => GUESTS.slice(i + 1).map((b) => [a[1], b[1]]));
const mixed = pairs.filter(([a, b]) => a !== b).length;
console.log(`\nsingle host: ${GUESTS.length} guests, ${pairs.length} neighbor pairs, ${mixed} pairs from different levels`);
console.log(`separate host per level: ${pairs.length - mixed} pairs, 0 from different levels; `
  + `remaining paths still ${pathCount(remaining)}, cost: 1 more machine`);
closed item                     closed  total  remaining  extra steps  extra memory
memory page merging                  2      2         14            0          1856
clipboard                            2      4         12            1          1856
shared folder                        2      6         10            3          1856
guest agent channel                  2      8          8            7          1856
USB passthrough                      1      9          7            8          1856
display controller emulation         1     10          6           10          1856

closable 6/11 items, closed 10/16 paths (62.5%); cost 10 work steps and 1856 MiB of host memory
effect         initial paths  remaining paths
transit                    4                2
data                       6                1
information                6                3
while the guest runs, 5 items cannot be closed, leaving 6 paths

single host: 4 guests, 6 neighbor pairs, 4 pairs from different levels
separate host per level: 2 pairs, 0 from different levels; remaining paths still 6, cost: 1 more machine

When six items are closed, ten of the sixteen paths close and six remain. The first row looks free at first glance: memory page merging closes two information paths and the changed work step count is zero. The cost is not in the workflow but in memory — 1,856 MiB of host memory, because merged pages are now kept separately. An item whose cost looks like zero usually has its cost in another column.

The effect table shows the real gain. Data paths drop from six to one; the single remaining data path is the one carried by the network emulation. So five out of six paths touching host data were inside the closable convenience items. Transit paths drop from four to two, information paths from six to three. The total cost is ten work steps: file transfer is done over the network, closing and reopening procedures are run by hand, and reading field devices moves to the branch client.

The remaining six paths are this lesson’s boundary. As long as the guest runs, a scheduler, a processor cache, a clock source, a disk, and a network interface are shared. These cannot be closed, because closing them means the guest does not run either. What remains is to ask how readily those six paths operate.

Items Required per Path

Up to this point, every path was matched with a single shared item. Read that way, it looks as if the share’s existence is the path’s only condition; for most paths that is not true. The run below gives each path the extra conditions required on top of the share and works out how many items must be true at the same time to cross the boundary.

// conditions.mjs — number of items that must be true at the same time, for each path (MODEL).
// The shared item's existence is one item; the list below gives it EXTRA conditions.
import { SHARING } from './sharing.mjs';

const EXTRA = {
  "neighboring guest's activity timing": [],
  'access pattern from a shared cache line': ['being scheduled onto the same core as the neighbor'],
  "inferring the host's maintenance window": [],
  'transit into the host process via an emulation bug': ['an unpatched emulation bug'],
  "guest packet into the host's network stack": ['the host interface being bridged to the guest network'],
  'measuring whether the same page exists': ['identical page content in the neighbor'],
  'page sharing inferred from write latency': ['identical page content in the neighbor'],
  "writing to the host's file system": ['the mount being writable'],
  'a file from the host running inside the guest': ['execute permission in the guest', 'the file being opened'],
  "reading the host's clipboard": [], 'passing data from host to guest': [],
  'running a command in the guest from the host': ['agent privilege on the host side'],
  "reading the guest's file system from the host": ['agent privilege on the host side'],
  "a plugged-in device reaching the host's driver": ['physical access', 'an unpatched driver bug'],
};
// [required items, path name, from a required item?]
const PATHS = SHARING.flatMap(([, z, y]) => y.map(([ad]) => [1 + EXTRA[ad].length, ad, z]));

const s = (x, n) => String(x).padStart(n);
console.log(`${'required items'.padEnd(16)}${s('paths', 6)}   example`);
for (const g of [...new Set(PATHS.map(([n]) => n))].sort()) {
  const k = PATHS.filter(([n]) => n === g);
  console.log(`${String(g).padEnd(16)}${s(k.length, 6)}   ${k[0][1]}`);
}
const required = PATHS.filter(([, , z]) => z);
console.log(`\nrequired items in the ${required.length} paths that cannot be closed: `
  + [...new Set(required.map(([n]) => n))].sort()
    .map((g) => `${g} -> ${required.filter(([n]) => n === g).length} paths`).join(', '));
const secondary = PATHS.flatMap(([, ad]) => EXTRA[ad]);
const counts = [...new Set(secondary)].map((k) => [k, secondary.filter((x) => x === k).length])
  .sort((a, b) => b[1] - a[1]);
console.log(`most frequent extra condition: ${counts[0][0]} (${counts[0][1]} paths); total ${secondary.length} extra conditions, `
  + `${counts.length} distinct items`);
required items   paths   example
1                    4   neighboring guest's activity timing
2                   10   access pattern from a shared cache line
3                    2   a file from the host running inside the guest

required items in the 6 paths that cannot be closed: 1 -> 2 paths, 2 -> 4 paths
most frequent extra condition: an unpatched emulation bug (3 paths); total 14 extra conditions, 10 distinct items

Four of the sixteen paths work with a single item: if the share is open, the path is open, no other condition is required. Those four are the scheduler, the clock source, and the clipboard’s two directions; none of the three depends on a bug or a placement — each exists merely by the share’s definition. Ten paths need two items, two paths need three items.

Two of the six paths that cannot be closed work with a single item, and both are information paths. The remaining four want a second item, and the most frequently required second item is an unpatched emulation bug — the same condition in three paths. This ties the measure of crossing the boundary to patching: on a host where the emulation code is patched, those three paths require two items and the second is not in place. The patch program itself is not this course’s subject, but the number here writes what it changes: it closes the second item of three transit paths.

The Six Paths That Cannot Be Closed

What can be done for a path that cannot be closed is changing where it reaches. The last two rows measure this. While four guests run on the same host, six neighbor pairs form, and four of them are between different trust levels: the guest that aggregates meter readings and the test guest, the validator and the field portal. The remaining six paths work between these four pairs.

Distributing the guests across two separate hosts by trust level closes none of the paths — each host still has six paths — but it drops different-level pairs from four to zero. In the remaining two pairs, the neighbors are already systems that access the same data; the information one neighbor extracts from the other is information it already has within its own boundary. The cost is one machine.

This measurement gives an ordering, not a preference. First the six optional items are closed: ten paths go, at a cost of ten work steps and 1,856 MiB. Then the layout is corrected: no path goes, but the ends of the remaining six paths are tied to the same trust level, at a cost of one machine. There is no third step; as long as the guest runs, those six paths are open, and the defense’s answer there is not closing but watching — which piece of emulation code returns an unexpected error, which guest shows activity during its neighbor’s maintenance window.

Summary

  • The guest and host share eleven items, and these items open sixteen paths; the average per item is 1.45 paths.
  • The five required items open six paths: two transit, one data, three information. The two transit paths come from disk and network emulation and cannot be closed while the guest runs.
  • The six optional items open ten paths and carry five of the six data paths; closing them, data paths drop from six to one, transit from four to two, information from six to three.
  • Cost is written in two columns: ten changed work steps and 1,856 MiB of host memory. Memory page merging’s work-step cost is zero; its cost is in memory.
  • Four of the sixteen paths work with a single item; of the six remaining paths, two need one item and four need two, and the most frequently required second item is an unpatched emulation bug.
  • Splitting the layout by trust level does not change the path count; it drops the different-level neighbor pair count from four to zero, at a cost of one machine.

Next Step

This lesson’s and the previous lesson’s measurement were both about a running guest: which layer it stands beneath, which items it shares with the host. But a virtual machine’s real distinguishing feature does not show up while it runs, but while it is stopped and copied. A guest snapshot can be taken and stored, an image can be cloned thirty times and distributed to thirty machines, a state from a week ago can be returned to in a few seconds. The next lesson measures these three operations in boundary items: how many secret items a clone carries to how many copies, how many sound links a rollback needs as the snapshot chain grows longer, and how many closed vulnerabilities returning to a week ago reopens.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close