Documentation
¶
Overview ¶
Package identity answers one question for a local service: which program is on the other end of this connection, and how strongly is that proven.
The second half is the point. A privileged service that grants narrow permissions to named applications - "allow LogViewer to read the System log" - is only as good as its ability to tell that the caller really is LogViewer. When it cannot, the honest outcome is a refusal, not a weaker grant made silently. So every attribute of a Peer arrives welded to a Proof, there is no way to read the value without the proof, and a service can ask CanEver at startup whether the policy it intends to enforce is achievable on this operating system at all.
Nothing here reads caller-supplied identity fields out of a payload. Native stream credentials and, on macOS, the exact XPC message's OS evidence identify the caller. ProofClaimed exists only to name what is being refused.
Usage ¶
// Once, at startup: refuse to run a policy this machine cannot enforce.
if err := identity.CanEver(policy); err != nil {
return fmt.Errorf("this build cannot enforce its own permission model: %w", err)
}
func serveOne(h identity.Handle, at time.Time) error {
defer closeAcceptedPipe(h)
// Windows will not identify the peer until the server has read.
frame, err := readOneBoundedFrame(h) // do not parse it yet
if err != nil { return err }
b, err := identity.Bind(h, &identity.Options{ConnectedAt: at})
if err != nil { return err }
defer b.Close() // release evidence before the accepted pipe closes
if err := b.Check(policy); err != nil { return err }
peer, err := b.Peer() // recheck the captured binding before acting
if err != nil { return err }
return handle(peer, frame)
}
Capture at on the line after accept; see Options.ConnectedAt. The service supplies policy and the pipe/frame helpers above. Binding owns evidence and must be closed; OfHandle and OfConn return snapshots for diagnostics, with no later recheck. The listen subpackage provides framed admission and keeps the binding alive through each call, including XPC requests.
Platforms ¶
Windows named pipes, Linux AF_UNIX, macOS AF_UNIX and macOS XPC are implemented. XPC requires cgo and macOS 12 or newer. Ceiling describes the native stream; CeilingFor selects a transport. Every other GOOS is a seam that returns ErrUnimplemented rather than a weaker answer from a portable fallback.
The three do not answer the same question equally well, and the differences are not details:
- Windows names the process that opened the pipe and cannot verify the code it is running.
- Linux names the user unforgeably and has no general answer to what a program is; without SO_PEERPIDFD (Linux 6.5) it cannot even bind a path to the peer, and reports that at ProofPID rather than hiding it.
- macOS can verify a running program's signature, which neither of the others can, but over a plain socket the audit token that selects which program is resolved from the socket's current owner rather than stamped at connect, capping process-derived evidence at ProofPID. Its XPC path instead binds an active request to the exact message's validated code and remote connection, with a bound path; Package and Code remain unknown.
Read CONTRACT.md before building a permission prompt on any of it. It is the document this package exists to make possible.
Index ¶
- Constants
- Variables
- func AdmitServerQueries(...string) (func() error, error)
- func CanEver(n Need) error
- func CanonicalProgramPath(path string) string
- func CheckElevatedHelper(peer *Peer, self User, program string) error
- func CheckLoopbackOnly(sockets []Socket, owned func(pid int) bool, listener netip.AddrPort) error
- func CheckTreeLoopback(listener netip.AddrPort, contains TreeContains) error
- func FileKey(path string) (string, error)
- func HasShortNameComponent(path string) bool
- func Inside(path, folder string) bool
- func InsideRel(path, folder string) (string, bool, error)
- func LocalPath(path string) error
- func NormalizeSubjectProgram(program string) string
- func ObserveOpens(observe func(path string)) (stop func(), ok bool)
- func OwnedByTree(contains TreeContains) func(pid int) bool
- func PackageFamily(fullName string) (string, bool)
- func PathNameKey(p string) string
- func PathNameWithin(path, folder string) bool
- func PlaceKey(path string) (string, error)
- func ProgramKey(program string) string
- func SameFile(a, b string) (bool, error)
- func SameNormalizedSubjectProgram(a, b string) bool
- func SameNormalizedSubjectProgramKeyed(a, b string, keyA, keyB func() string) bool
- func SamePathName(a, b string) bool
- func SameSubjectProgram(a, b string) bool
- func SubjectProgram(p *Peer, pathProof Proof) (string, error)
- func TreatDriveAsNetwork(letter byte) (restore func())
- func TreatVolumeAsNetwork(folder string) (restore func())
- func UnresolvedShortName(path string) bool
- func ValidSubjectProgram(program string) bool
- func VerifyFile(path string, opts *Options) (Code, Proof, error)
- func Within(path, folder string) (bool, error)
- func WithinRel(path, folder string) (string, bool, error)
- type Attr
- type Binding
- func Bind(h Handle, opts *Options) (*Binding, error)
- func BindConn(c net.Conn, opts *Options) (*Binding, error)
- func BindLoopback(c net.Conn, opts *Options) (*Binding, error)
- func BindLoopbackServer(c net.Conn, opts *Options) (*Binding, error)
- func BindServer(h Handle, opts *Options) (*Binding, error)
- func BindServerConn(c net.Conn, opts *Options) (*Binding, error)
- func BindUnixServer(c net.Conn, opts *Options) (*Binding, error)
- func (b *Binding) Alive() (bool, error)
- func (b *Binding) BoundAt() time.Time
- func (b *Binding) Captured() *Peer
- func (b *Binding) Check(n Need) error
- func (b *Binding) Close() error
- func (b *Binding) EvidenceValidUntil() time.Time
- func (b *Binding) Peer() (*Peer, error)
- func (b *Binding) Shared() *Binding
- type Code
- type Handle
- type Limits
- type Need
- type NotLocalPathError
- type Options
- type Peer
- type Process
- type Proof
- type ProofError
- type Socket
- type Transport
- type TreeContains
- type User
Constants ¶
const PackagedProgramPrefix = "msix:"
PackagedProgramPrefix begins the subject program of a caller with MSIX package identity: msix:<package family name>.
const TransportLoopback = "tcp-loopback"
TransportLoopback names a loopback TCP connection in Peer.Transport and Limits.Transport.
Variables ¶
var ( // ErrNoBinding means this machine has no primitive that ties an // identity to the process that opened the connection. It is not a // weaker answer; it is the absence of one, and a service that needs a // peer bound must refuse the connection. Ceiling().Binding says what is // missing. ErrNoBinding = errors.New("identity: this machine cannot bind a peer to a connection") // ErrPeerGone means there was no live process to bind to. It comes from // [Bind] only. A peer that exits after it has been bound does not // invalidate the binding: "the process that opened this connection was // X" stays true after X exits, and every primitive here pins the process // so that no successor can take its place. Ask [Binding.Alive] when // liveness is what matters. ErrPeerGone = errors.New("identity: there is no live peer to bind to") // ErrPeerPending means an initial Linux loopback lookup found the // kernel socket row but no descriptor holder before any process was // observed or pinned. A dial can // finish before the server accepts. No identity has been captured; a // caller may wait within its existing context before trying this same // connection again. It also matches ErrPeerGone. Other missing-peer, // process-exit and binding-recheck failures do not match ErrPeerPending. ErrPeerPending = fmt.Errorf("%w: no descriptor holder was observed for the connected socket", ErrPeerGone) // ErrPeerMoved means the connection no longer answers for the process // that was captured when it was accepted. ErrPeerMoved = errors.New("identity: the connection has moved to a different program since it was accepted") )
Errors a binding returns instead of an identity.
var ( // ErrRemotePeer means the peer is not on this machine. A Windows named // pipe is reachable over SMB unless the server passes // PIPE_REJECT_REMOTE_CLIENTS, and a remote peer has no local pid at all. ErrRemotePeer = errors.New("identity: peer is not on this machine") // ErrNotServerEnd means the handle is not the end a service accepted: // the client end of a Windows named pipe, or a listening Unix socket. // // Both mistakes answer, rather than fail, if they are not caught. On the // client end of a pipe every question below has an answer and every // answer is about the service itself. SO_PEERCRED on a listening socket // returns the credentials captured at listen(2) - also the service's // own. A service that identified itself and believed it had identified a // caller would authorise everything. ErrNotServerEnd = errors.New("identity: handle is not the end of the connection the service accepted") // ErrUnsupportedConn means the connection is of a kind that carries no // peer credentials, or one whose handle this package cannot reach. ErrUnsupportedConn = errors.New("identity: connection carries no peer credentials") // ErrMustReadFirst means Windows will not identify a named pipe's client // until the server has completed a read on that pipe. It is not enough // for the client to have written; the server must have read. // // The consequence is unpleasant and unavoidable: a service has to accept // and read bytes from a caller it cannot yet name. Read one bounded // frame, identify the peer, and only then decide what the bytes were // allowed to ask for. Do not parse the frame first, do not size a buffer // from it, and above all do not read a name out of it - see // CONTRACT.md, "You must listen before you can ask who is speaking". ErrMustReadFirst = errors.New("identity: windows will not identify a pipe client until the server has read from the pipe") // ErrImpersonationStuck means the operating system refused to undo an // impersonation. It is returned instead of an identity, because a // process that cannot drop a client's token has a privilege escalation, // not a naming problem. The thread that held the token is destroyed // rather than returned to the scheduler; see impersonate_windows.go. ErrImpersonationStuck = errors.New("identity: could not revert impersonation; thread abandoned") // ErrUnimplemented means this platform's answer has not been built yet. // It is returned instead of a weaker answer from a portable fallback, // deliberately: a permission system that silently degrades to "some // process on this machine" is worse than one that will not start. ErrUnimplemented = errors.New("identity: not implemented on this platform") )
Errors that say the channel itself cannot answer the question, as distinct from answering it weakly.
var ErrBeyondLoopback = errors.New("identity: process tree listens beyond loopback")
ErrBeyondLoopback reports a socket of a contained process tree bound to an address other than loopback, or a tree whose own listener could not be observed in the owner tables.
var ErrNotHelper = errors.New("identity: peer is not this account's elevated helper")
ErrNotHelper is a peer CheckElevatedHelper refuses.
var ErrNotLocalFile = errors.New("identity: not an absolute path on a local drive")
ErrNotLocalFile is VerifyFile's refusal of a path it will not verify: a relative, unclean, UNC or device path, a mapped network drive, or a local path whose junction or link leads to one. Opening a network path to show a publisher could reach another machine.
var ErrNotLocalPath = errors.New("identity: the path is not on a local drive")
ErrNotLocalPath is what errors.Is matches for a path identity refuses to open (LocalPath).
var ErrNotProven = errors.New("identity: not proven to the required level")
ErrNotProven is the root of every failure to meet a required proof level. Test for it with errors.Is.
Functions ¶
func AdmitServerQueries ¶ added in v0.5.0
func CanEver ¶
CanEver reports whether a policy is satisfiable on this platform at all, under the best case for every attribute.
Call it at startup, not per connection. The answer does not depend on who is calling, and a service whose permission model rests on a proof this platform cannot produce should refuse to start rather than fail open once per connection - or, worse, decide it can relax the requirement.
func CanonicalProgramPath ¶ added in v0.4.0
CanonicalProgramPath resolves path to the one spelling it treats as the true name of the file it names: on Windows, a short DOS 8.3 name component expands to its long form. A short-name spelling and a long-name spelling of the same program compare equal under CanonicalProgramPath. A path this cannot resolve, including one that names no existing file and one LocalPath refuses, which is never read, comes back unchanged: an exact comparison against it still refuses exactly what it refused before.
func CheckElevatedHelper ¶ added in v0.5.0
CheckElevatedHelper reports whether peer is program running elevated as the account self names [ID-H1]: a Windows user read at kernel with self's SID, elevated, at high integrity or above; a process read at kernel; an image read at bound that SameSubjectProgram names program. It reads no file: the caller decides first that program is a path only administrators can change (ID-D2). A different account, the administrator a standard user's prompt asked for included, is refused, and so is every peer outside Windows.
func CheckLoopbackOnly ¶ added in v0.5.0
CheckLoopbackOnly accepts sockets only when every socket owned by the tree is bound to a loopback address (127.0.0.0/8 or ::1, IPv4-mapped forms unmapped first) and the tree holds the TCP listener at listener. Requiring that listener proves the table was read: an empty table never passes. Membership is asked only where it decides the answer, for sockets beyond loopback and for the expected listener.
func CheckTreeLoopback ¶ added in v0.5.0
func CheckTreeLoopback(listener netip.AddrPort, contains TreeContains) error
CheckTreeLoopback reads the socket owner tables and applies CheckLoopbackOnly to the tree contains describes, whose own listener is listener. It is the Get-NetTCPConnection and Get-NetUDPEndpoint view of one process tree, with membership proved by process id and creation time.
func FileKey ¶ added in v0.5.0
FileKey is a key for a set or map of existing files: the volume serial number and file ID, which two paths share exactly when SameFile reports them one file. A path refused (LocalPath) or unreadable has no key.
func HasShortNameComponent ¶ added in v0.5.0
HasShortNameComponent reports, from the spelling alone, whether a component of path is shaped like a Windows short (8.3) alias. It reads no filesystem. A long name can have that shape too, so true means only that the spelling may read differently once resolved.
func Inside ¶ added in v0.5.0
Inside is Within for a folder that may not exist yet: when folder exists, Within answers. When folder does not exist, nothing can lie in it except a path spelled exactly below it, so path's cleaned spelling must begin with folder's and a separator; no case is folded. Every other failure answers false: either path refused (LocalPath), which is never opened, and a folder or path that cannot be read. A caller for whom false is the permissive answer checks LocalPath itself.
func InsideRel ¶ added in v0.5.0
InsideRel is Inside that returns path's remainder below folder, with the platform's separators, and the error behind a false answer: a *NotLocalPathError (errors.Is ErrNotLocalPath) for a refused path, or the read error when folder exists but the question could not be asked. A caller tells "outside" (nil error) from "not answered" by the error.
func LocalPath ¶ added in v0.5.0
LocalPath reports whether identity's path functions may open path. On Windows that is an absolute path on a local drive letter, given directly or as \\?\X:\, with no component that is a reserved device name (CON, PRN, AUX, NUL, COM0-9, LPT0-9 and their superscript forms, CONIN$, CONOUT$) in any folder and with any extension; a UNC path, a \\.\ or other \\?\ path, an NT object path and a network drive letter are refused. On macOS a path is local when the volume holding it, or its deepest existing folder, reports MNT_LOCAL to statfs within a short deadline; a network volume and one that does not answer are refused; a mount that stops answering refuses only paths spelled on it. On Linux the volume's file system type decides under the same deadline: NFS, SMB/CIFS, Ceph, AFS, 9P and the other network and shared file systems are refused, and a FUSE mount is local only when its type in /proc/self/mountinfo is known local (fuseblk, lxcfs, squashfuse, fuse-overlayfs). Elsewhere every path is local. It reads the spelling, the drive's type or the volume's mount flags or type, and opens nothing on the volume; a refusal is a *NotLocalPathError.
func NormalizeSubjectProgram ¶ added in v0.4.2
NormalizeSubjectProgram gives an ordinary image path the spelling used for caller subjects. MSIX package identities retain their stable family name. Validation remains the caller's responsibility; an unresolvable path keeps its cleaned spelling and does not prove that a file exists. It resolves the path against the filesystem: a store normalizes a subject once, when it writes it, and checks a stored subject on read with ValidSubjectProgram, which reads no filesystem.
func ObserveOpens ¶ added in v0.5.0
ObserveOpens is for tests: off Windows observe is never told of an open. On macOS and Linux a refused path is decided by statfs, which opens nothing; Linux reads /proc/self/mountinfo for a FUSE mount's type.
func OwnedByTree ¶ added in v0.5.0
func OwnedByTree(contains TreeContains) func(pid int) bool
OwnedByTree turns a tree's membership answer into a per-process-id test. It reads the process's creation time and asks contains about that exact process; a process that cannot be opened, has exited or answers an error is not a member.
func PackageFamily ¶ added in v0.3.0
PackageFamily returns the family name of an MSIX package full name, <Name>_<Version>_<Architecture>_<ResourceId>_<PublisherId>, where ResourceId may be empty.
func PathNameKey ¶ added in v0.5.0
PathNameKey keys a set of names for files that may not exist, for the collision checks before placement SamePathName serves: two names have one key exactly when SamePathName reports them one name. A set of existing files keys by FileKey.
func PathNameWithin ¶ added in v0.5.0
PathNameWithin reports whether the name path lies below the name folder, comparing folder's components with path's first components as SamePathName compares names, for the same uses as SamePathName: a path that may not exist yet. Either separator separates components, so a Windows path reads alike on every platform. An existing file's containment is Within's.
func PlaceKey ¶ added in v0.5.0
PlaceKey is a key for a file or folder that may not exist yet, such as a folder an install is about to create. The part of path that exists is replaced by its final path, the kernel's one spelling of it after links, junctions and 8.3 aliases (as Within resolves it); the part still to be created keeps its names, each without the trailing dots and spaces Windows strips when it creates a name. The result is keyed as PathNameKey keys names. The key stays the same as folders on the way are created, so a lock keyed by it serialises work started before and after. A path refused by LocalPath has no key.
func ProgramKey ¶ added in v0.5.0
ProgramKey is the file identity a subject program compares by: FileKey of a path program that exists on a local drive, and "" for a package family, a program that does not exist and one LocalPath refuses, which compare by name alone.
func SameFile ¶ added in v0.5.0
SameFile reports whether a and b name one existing file, by file identity: the volume serial number and file ID (FileKey), as os.SameFile compares them. A junction or symbolic link and its target are one file, and so are an 8.3 alias and its long name; two names the volume keeps apart, such as twins that differ by a KELVIN SIGN or a LONG S, are two. When either side is refused (LocalPath) or cannot be read, SameFile answers false with that error.
func SameNormalizedSubjectProgram ¶ added in v0.5.0
SameNormalizedSubjectProgram compares two subject programs that are already in the spelling NormalizeSubjectProgram gives. Byte-equal names are one program, and a package family is a name and compares only by SamePathName. When both programs exist on a local drive, file identity decides (ProgramKey): a junction and its target, and an 8.3 alias and its long name, are one program, and two files whose names differ only in case, as a case-sensitive folder holds them, are two. When either does not exist, or LocalPath refuses it, such as a program run from a share, the names decide (SamePathName), and nothing is opened for a refused one. Invalid input never matches.
func SameNormalizedSubjectProgramKeyed ¶ added in v0.5.0
SameNormalizedSubjectProgramKeyed is SameNormalizedSubjectProgram with each side's ProgramKey supplied by the caller, so a caller comparing one program with many reads each file identity once. keyA and keyB are called only when the names are not byte-equal.
func SamePathName ¶ added in v0.5.0
SamePathName reports whether a and b are one name for a file that may not exist, under the rule the platform's file system compares names by: on Windows ordinal comparison ignoring case through the system uppercase table (CompareStringOrdinal with bIgnoreCase), the rule NTFS applies; elsewhere the bytes must be equal. Unicode case folding is looser (it folds KELVIN SIGN to k and LONG S to s, which NTFS keeps apart) and decides no name here.
It answers a name question, never "same file": SameFile and Within answer that by file identity, which also sees a junction and an 8.3 alias. It has exactly these uses:
- a rights rule naming a program not yet installed, matched after CanonicalProgramPath (SameSubjectProgram uses SameFile when both exist);
- a collision check before placement, when two names are about to become files and cannot be created exclusively instead;
- a fixed or pinned file name, such as lemond.exe or a pin's program.
func SameSubjectProgram ¶ added in v0.4.2
SameSubjectProgram compares two subject program names. Invalid or relative input never matches, even when both inputs have the same spelling.
func SubjectProgram ¶ added in v0.3.0
SubjectProgram names the program a service's rights, credentials and inference decisions are about. A Windows caller whose package identity is proven at ProofSigned, and whose image lies inside that package's installed folder, is msix:<package family name>: the image path of a packaged app changes with every package version, and the family does not. The folder check is needed because any process of the account can start an arbitrary program with an installed package's identity (Invoke-CommandInDesktopPackage), while it cannot write into the package's folder under WindowsApps. Every other caller, including a descendant of a packaged app, which has no identity of its own, is its clean absolute image path at pathProof or better, canonicalized: a peer launched through a short DOS 8.3 alias names the same subject as one launched through the long spelling of the same file (CanonicalProgramPath).
func TreatDriveAsNetwork ¶ added in v0.5.0
func TreatDriveAsNetwork(letter byte) (restore func())
TreatDriveAsNetwork is for tests: off Windows there are no drive letters, and it marks nothing.
func TreatVolumeAsNetwork ¶ added in v0.5.0
func TreatVolumeAsNetwork(folder string) (restore func())
TreatVolumeAsNetwork is for tests, in this module and in its consumers: until restore is called, LocalPath refuses folder and every path below it as a network volume, as it refuses a volume without MNT_LOCAL on macOS and a network file system on Linux, so a test can show a refusal without mounting a share. It only ever refuses more.
func UnresolvedShortName ¶ added in v0.5.0
UnresolvedShortName reports whether the canonical spelling of path still holds a component shaped like a short (8.3) alias that names no existing file or folder. A store refuses to write such a spelling: once a file appears there, its canonical spelling differs, and a rule stored under the alias would match nothing. Always false off Windows and for a package family.
func ValidSubjectProgram ¶ added in v0.3.0
ValidSubjectProgram reports whether program is a subject program as SubjectProgram names one: msix:<package family name>, or a clean absolute path of at most 4096 bytes without NUL, CR or LF.
func VerifyFile ¶ added in v0.5.0
VerifyFile verifies the code signature of the program file at path, as it is now, and reports who signed it (ID-W4). It is display and policy evidence about a file; a Peer's Code is the evidence about the program a connection is running.
On Windows the path must be an absolute, clean path on a lettered drive that is neither remote nor absent, or VerifyFile returns ErrNotLocalFile without opening it; a junction or link leading off such a drive returns ErrNotLocalFile after the open and before verification. The file is held without write sharing for the verification, so a file another handle holds open for writing returns a sharing violation. The signer is read by path, and the file the handle names must still be at path afterwards, or VerifyFile returns an error. Code and Proof follow a peer's Authenticode rungs: a file Windows accepted is ProofBound with Trusted set, Subject and Issuer name an embedded signer, and Catalog is set when the signature came from a system catalog. Every other platform returns ErrUnimplemented (ID-E4).
func Within ¶ added in v0.5.0
Within reports whether path lies strictly inside folder, by file identity: the deepest existing folder above path is first resolved through links, so a file reached through a junction counts where it actually is, and then each parent of the resolved path is compared with folder as SameFile compares. path itself need not exist, nor the folders still to be created on the way to it; their names stay as written. folder must exist; when either is refused (LocalPath), or folder or every folder above path cannot be read, Within answers false with the error.
Types ¶
type Attr ¶
type Attr[T any] struct { // contains filtered or unexported fields }
Attr is one attribute of a peer together with the strength of the evidence for it. There is deliberately no way to read the value without being handed the proof at the same time: a service that needs a verified signature has to see that it got something weaker instead of silently accepting it.
func (Attr[T]) AtLeast ¶
AtLeast returns the value only if it is proven at least as strongly as min, and otherwise a *ProofError explaining what was available instead.
func (Attr[T]) Get ¶
Get returns the value and its proof together. Use it when the decision depends on the proof; use AtLeast when a fixed minimum is required.
type Binding ¶
type Binding struct {
// contains filtered or unexported fields
}
A Binding is a peer identity captured when the connection was accepted, together with whatever handle the platform offers for asking, later, whether that capture is still true.
It exists because OfHandle answers by looking the peer up at the moment it is called, and a lookup is a race by construction: every platform here resolves a program from a number, and the thing that number refers to can change. A Binding takes the answer once, at the only instant the caller controls, and refuses afterwards rather than re-deriving.
When to call it differs by platform, and the difference is not cosmetic:
- Linux and macOS: at accept, before the service reads a single byte. On macOS this is load-bearing. LOCAL_PEERTOKEN follows the socket's most recent writer, so a byte written by another process before this call is a byte this call believes.
- Windows: after the first read, the same precondition OfHandle has. Nothing is lost by waiting: the client process id is written onto the pipe instance by the kernel when the client opens it and never moves, and a Windows process cannot replace its own image.
func Bind ¶
Bind captures the peer identity of an accepted local connection and keeps the platform handle needed to re-check it.
The returned Binding must be closed. See the type documentation for when to call this, which is not the same on all three platforms.
func BindConn ¶
BindConn is Bind for a net.Conn that is willing to give up its handle.
The returned Binding keeps a reference to that handle and is only valid while c is open: close the Binding before closing c. This is the one place this package holds a descriptor outside raw.Control, and it is unavoidable - a binding that has to be re-checked later cannot re-derive the handle it was taken from.
func BindLoopback ¶ added in v0.3.0
BindLoopback binds the process on the other end of an accepted IPv4 loopback TCP connection, through the operating system's socket-owner table.
A loopback socket carries no peer credentials: the kernel stamps nothing onto the connection that names the process at the other end. What it does keep is a table of the sockets each process owns. The binding looks the client's end of this connection up in that table, pins the process it names, and refuses afterwards when the table stops agreeing:
- Windows: GetExtendedTcpTable with TCP_TABLE_OWNER_PID_CONNECTIONS names the process that created the client socket. The binding opens that process, requires its creation time to predate the connection, and refuses with ErrPeerMoved once the process has exited while the connection is still established, because another process then holds the socket.
- Linux: /proc/net/tcp names the socket inode and its owning uid, and the binding finds the one same-uid process whose descriptor table holds that inode, pins it with pidfd_open and captures its image path. A socket held by two processes, or later held by a different one, is ErrPeerMoved.
- macOS: ErrNoBinding. The recorded identity refusal stands; see LoopbackCeiling.
Call it on the line after accept, before the service reads a byte, and re-check with Binding.Peer before acting. A dialed connection names the listening service; callers proving an engine they dialed should use BindLoopbackServer. The returned Binding must be closed before c.
func BindLoopbackServer ¶ added in v0.5.0
BindLoopbackServer binds the listening process at the remote end of a dialed IPv4 loopback connection. Call it before sending request bytes, check its captured process against the child the caller started, and re-check with Binding.Peer before trusting a reply. It uses the same pinned socket-owner proof as BindLoopback; the endpoint order is already correct for a dialed connection because its remote end is the server socket.
func BindServer ¶ added in v0.2.1
BindServer captures the server of an already connected local CLIENT handle, before application bytes are sent. The caller must establish that the handle belongs to the local transport namespace; this raw-handle primitive does not validate the path used to open it. The caller owns the connection and closes the returned binding before that connection. ConnectedAt must describe the successful dial, when supplied. This identifies a process; the caller still needs independent trust evidence to authorize that process as its server.
func BindServerConn ¶ added in v0.2.1
BindServerConn is BindServer for a connection exposing syscall.Conn.
func BindUnixServer ¶ added in v0.5.0
BindUnixServer binds the listening process at the far end of a dialed AF_UNIX stream connection (ID-T8). A provider that started an engine on a socket path calls it on the line after the dial, before sending a byte, checks the captured process against the engine it started, and rechecks with Binding.Peer before trusting a reply.
- Windows: SIO_AF_UNIX_GETPEERPID on the client socket returns the process id the kernel recorded for the server end. The binding opens that process, requires its creation time to predate the connection, and reads user, path and package through the pinned handle.
- Linux: SO_PEERCRED on a connected client socket names the process that called listen(2); SO_PEERPIDFD pins it, as Bind does for an accepted socket.
- macOS: ErrNoBinding. The client socket's LOCAL_PEERPID and audit token follow the server socket's most recent writer (ID-M1), so no primitive pins the listening process.
The returned Binding keeps the socket's handle and must be closed before c.
func (*Binding) Alive ¶
Alive reports whether the bound process still exists. It is a separate question from whether the identity is still trustworthy, and services that conflate the two end up refusing honest callers that happened to exit.
func (*Binding) Captured ¶
Captured returns the identity as it was at bind time without re-checking it. It is for log lines and for tests that need to compare the two answers. A permission decision uses Binding.Peer.
func (*Binding) Check ¶
Check is Peer.Check against the re-checked capture.
func (*Binding) Close ¶
Close releases the platform handle. A Binding that is not closed leaks a pidfd on Linux and a process handle on Windows, and on Linux a leaked pidfd holds the peer's pid reserved for as long as the service runs.
func (*Binding) EvidenceValidUntil ¶ added in v0.4.0
EvidenceValidUntil is the expiry of captured time-sensitive evidence. It is zero when this binding captured no evidence with a recorded expiry. On Windows it preserves the original Authenticode verdict deadline even when Bind reused a verdict verified for an earlier connection.
func (*Binding) Peer ¶
Peer returns the captured identity, having first confirmed that nothing has contradicted it. It returns ErrPeerMoved instead of a stale answer.
A peer that has merely exited is not a contradiction and is answered normally; see Binding.Alive.
type Code ¶
type Code struct {
// Subject is the name the signing certificate was issued to: the
// publisher, as a person reading a consent prompt would understand it.
Subject string
// Issuer is the certificate authority that vouched for the subject.
// Subject alone is not an identity; anyone can obtain a certificate
// naming themselves anything a CA will accept.
Issuer string
// Trusted is true when the platform's own trust policy accepted the
// signature and the chain, with revocation checking as configured in
// Options. It is the same verdict the Proof beside this Code carries, as
// a boolean for logs: a policy compares the Proof, and a Code with
// Trusted false never sits above ProofUnmet. Status says how it failed.
Trusted bool
// Status is the platform's verdict in its own words, always populated:
// "valid", "no signature", or the failing condition.
Status string
// Catalog is true on Windows when the signature came from a system
// catalog rather than from an embedded signature in the file itself.
// Subject and Issuer then name the signer of the catalog Windows
// accepted, and are empty when a catalog refused the file.
Catalog bool
// Description and OriginalName are the FileDescription and
// OriginalFilename of the program's version resource on Windows, read
// from the held image whose signature Windows accepted. A prompt names a
// signed program by them (VISION 2026-10-01). Both are empty when Trusted
// is false, when the image carries no version resource, and on every
// other platform. They name the program its publisher shipped; the file
// name on disk can be anything.
Description string
OriginalName string
// TeamID is the Apple Developer team identifier out of the validated
// signature on macOS, and empty elsewhere.
//
// It is the field to write a rule against on that platform. Subject is a
// display name a certificate authority was willing to issue and two
// publishers can share one; a team id is assigned by Apple and is the
// stable half of the answer. Platform binaries have none: they validate
// against the system's own anchor instead, which is a different claim
// and should be checked with a requirement rather than a string compare.
TeamID string
}
Code is a code signing identity as some part of the operating system verified it - not as a file on disk claims it.
type Handle ¶
type Handle uintptr
Handle is an operating-system handle for one accepted local connection: on Windows the server end of a named pipe instance, on Unix the file descriptor of a connected socket.
The handle must still be open, and must be the end the service accepted, not the end the client opened. Ownership stays with the caller: nothing in this package closes it.
type Limits ¶
type Limits struct {
Platform string
Best Need
Why map[string]string // "user", "process", "path", "package", "code"
// Transport names the channel these limits are about. The same platform
// answers differently on different channels and a ceiling that omits
// which one is unreadable.
Transport string
// Bindable reports whether [Bind] can tie an identity to the process
// that opened the connection on this machine, as opposed to looking a
// program up from a number afterwards. It is a property of the running
// kernel, not of the GOOS: the same Linux binary answers true on 6.5 and
// false on 4.4.
Bindable bool
// Binding names the primitive behind Bindable, or states what is missing
// when it is false. Always populated.
Binding string
// Stronger names the transport that would prove more than this one, and
// what it would add. Empty when this transport is already the platform's
// best.
Stronger string
}
Limits is what a platform can prove at best, with the reason for each ceiling. Obtained from Ceiling.
func Ceiling ¶
func Ceiling() Limits
Ceiling reports what this platform can prove at best, and why it stops there. It does not depend on any connection.
func CeilingFor ¶ added in v0.4.0
CeilingFor reports the selected transport's capabilities without opening a connection. Unsupported transports return an error, including for empty policy. Ceiling retains its existing meaning: the default native stream transport.
func LoopbackCeiling ¶ added in v0.3.0
func LoopbackCeiling() Limits
LoopbackCeiling reports what this platform can prove at best about the process behind a loopback TCP connection. It does not depend on any connection.
type Need ¶
Need is a policy: the minimum proof a service requires for each attribute before it will act. The zero Need requires nothing.
type NotLocalPathError ¶ added in v0.5.0
NotLocalPathError refuses a path that is not on a local drive. Nothing was opened for it.
func (*NotLocalPathError) Error ¶ added in v0.5.0
func (e *NotLocalPathError) Error() string
func (*NotLocalPathError) Is ¶ added in v0.5.0
func (e *NotLocalPathError) Is(target error) bool
Is makes errors.Is(err, ErrNotLocalPath) true.
type Options ¶
type Options struct {
// ConnectedAt is when the connection was accepted.
//
// Resolution refuses to report anything read out of the peer's pid
// unless it can first open the process and see that it was created
// before this instant. A process that inherited a recycled pid must have
// been created after the connection already existed, so the check
// excludes every one of them.
//
// Leave it zero and the check uses the moment of resolution instead,
// which still excludes any reuse after that moment but not reuse between
// accept and resolution. Set it in the accept loop, on the line after
// accept returns, and the window closes to the width of one accept.
ConnectedAt time.Time
// CheckRevocation asks the platform to contact revocation infrastructure
// while verifying a signature. It is off by default because it makes
// identification depend on the network, and a permission prompt that
// hangs is a permission prompt that gets clicked through.
CheckRevocation bool
// SkipCodeSignature skips signature verification entirely. On Windows
// the first verification for a process costs tens of milliseconds, and
// more for a large signed image; later connections from the same live
// process reuse that verdict (CONTRACT.md). A service that never asks
// about Code should not pay it.
SkipCodeSignature bool
// CodeRequirement is a macOS code signing requirement, such as
//
// anchor apple generic and certificate leaf[subject.OU] = "TEAMID"
//
// It is handed to SecCodeCheckValidity, so the operating system decides
// whether the peer satisfies it. That is worth more than reading
// Code.TeamID and comparing it here: the requirement language covers the
// anchor, the chain and the leaf together, and a comparison written in
// Go can only see what this package chose to extract.
//
// Empty means "verify the signature, tell me what it says, and let me
// decide". Ignored on Windows and Linux, neither of which has anything
// to give it to.
CodeRequirement string
}
Options tunes resolution. The zero value is safe. ConnectedAt is worth setting, and is the only field most callers need.
type Peer ¶
type Peer struct {
// Platform is the GOOS the answer came from. The answer differs per
// platform and a log line that omits which one is unreadable later.
Platform string
// Transport names the channel the identity was taken from: "npipe" on
// Windows, "unix" on Linux and macOS. The same platform gives different
// answers on different transports - on macOS, dramatically so, since an
// XPC peer can be identified to a standard a socket peer cannot - so the
// transport belongs next to the answer.
Transport string
// ObservedAt is when the identity was resolved, which is not when the
// connection was accepted. See Options.ConnectedAt.
ObservedAt time.Time
// User is the security principal the peer runs as.
User Attr[User]
// Process is which process it is.
Process Attr[Process]
// Path is the filesystem path of the running image.
Path Attr[string]
// Package is a package identity where the platform has one: an MSIX
// package full name on Windows, the signing identifier out of a
// validated signature on macOS, a sandbox application id on Linux
// ("flatpak:org.gnome.Calculator", "snap:chromium.chromium"). Unset for
// ordinary executables.
//
// The three are not comparable. The Windows one comes out of a
// certificate the OS validated; the macOS one out of a signature it
// validated; the Linux one out of a file in the peer's own mount
// namespace or a cgroup name, neither of which resists a hostile
// process running as the same user. See CONTRACT.md before writing a
// rule against this field.
Package Attr[string]
// Code is a code signing identity for the image, and never exceeds
// ProofBound on any platform this package supports.
//
// On Windows it is an Authenticode verification of the file the process
// was launched from, which is a weaker statement than it looks. On macOS
// it is a Security framework verification of the running code, which is
// a stronger statement - attached to a weaker binding, because a unix
// socket cannot prove which process it belongs to. On Linux it is always
// unset: the platform has nothing to verify. See CONTRACT.md.
//
// A signature the platform examined and did not accept is reported below
// ProofPID, at ProofInvalid, ProofUnsigned or ProofUnmet, so AtLeast
// refuses it for any policy that implies verification and admits it only
// for a policy that names the lower rung.
Code Attr[Code]
// Notes records everything a reader would otherwise get wrong about this
// particular answer: every place it came out weaker than this platform's
// best, and why. It is for humans, for logs and for tests.
Notes []string
// contains filtered or unexported fields
}
Peer is what a service was able to learn about the program on the other end of an accepted local connection, with the strength of the evidence attached to every part of it separately.
Nothing in a Peer was ever spoken by the peer. There is no field a caller can fill in from a request payload, and no constructor that takes one.
func OfConn ¶
OfConn is OfHandle for a net.Conn that is willing to give up its handle.
Most named-pipe libraries on Windows are not: they wrap the handle in an unexported struct with no accessor. When that is the case OfConn returns ErrUnsupportedConn and the caller must keep hold of the handle it created the pipe instance with and use OfHandle. This is not a workaround to be embarrassed about - the handle is the object the identity belongs to.
func OfHandle ¶
OfHandle identifies the program on the other end of an accepted local connection.
It never blocks on the peer and never reads from the connection itself.
On Windows there is a precondition that is easy to miss and impossible to work around: the server must already have completed a read on the pipe. Windows refuses to impersonate the client of a pipe nothing has been read from, and it is the read that matters, not the client's write. So the sequence is accept, read one bounded frame, identify, and only then decide what that frame was allowed to ask for. Calling OfHandle before the first read returns ErrMustReadFirst.
A returned error means the channel could not answer the question at all - a remote peer, the wrong end of the pipe, a refused impersonation. An answer that is merely weak comes back as a *Peer with low Proof values and a populated Notes, not as an error, because only the caller's policy knows whether weak is fatal.
func (*Peer) Check ¶
Check reports whether this Peer meets the policy. The error names every attribute that fell short, so a service does not learn about its second impossible requirement only after fixing the first.
type Process ¶
type Process struct {
// PID is the process id the kernel recorded for the peer when it
// connected. It names the process that opened the connection, which is
// not necessarily the process writing to it now: handles can be
// inherited and duplicated. See CONTRACT.md.
PID int
// StartTime is when that process was created. It is the thing that
// distinguishes a process from a later one that inherited its number,
// so it belongs in a log next to the pid. Zero when the process could
// not be opened.
StartTime time.Time
// Recycled is true when the pid was proven to belong to a different,
// later process than the one that connected. Nothing resolved from the
// pid is reported when it is set.
Recycled bool
// Generation distinguishes this process from a later one that inherits
// its pid, where the platform keeps a counter for exactly that: the
// pidversion inside a macOS audit token. Zero where the platform has
// none, which is everywhere else - Windows uses StartTime for the same
// job and Linux has neither.
//
// It is not a bound on its own. A pid and a pidversion read together
// describe one process instance consistently; whether that instance is
// the peer is a separate question, and on macOS sockets it is the
// unanswered one. See CONTRACT.md.
Generation uint32
}
Process identifies the process on the other end.
type Proof ¶
type Proof uint8
Proof says how hard it would be for a hostile caller to make one attribute of a Peer lie. It is a total order because a policy has to compare against a minimum, but the steps are qualitatively different, and the documentation of each one says what it actually rests on. Read those before writing a policy; the number alone is not the argument.
From ProofPID upwards a rung says how the value was bound to the peer. The three rungs below it are verdicts, produced only for Code: the platform examined the peer's signature and did not accept it, and the rung says how far it got. They sit below every rung that implies verification, so a policy that requires a verified signature refuses them by comparison alone, and a policy that knowingly admits an unverified caller names the lower rung.
const ( // ProofNone means the attribute was not determined: either the platform // cannot supply it at all, or this particular connection did not carry // it. The value is meaningless and must not be used. Attr refuses to // hand it out. ProofNone Proof = iota // ProofClaimed means the peer said so. This package never produces it // and has no API that accepts it. The level exists to name the thing // being refused, so a service tempted to read an "owner" field out of // the request payload has somewhere to look and be told no. ProofClaimed // ProofInvalid means the platform found a signature and refused it: the // code was modified after signing, the certificate is expired or revoked, // or the chain reaches a root this machine does not trust. It is the // lowest verdict, below ProofUnsigned, because no signature is no claim // and a refused signature is a claim the operating system rejected. The // value carries the platform's wording in Code.Status and no identity. ProofInvalid // ProofUnsigned means the platform looked and found no signature at all. // A policy that admits unsigned callers asks for this rung by name. ProofUnsigned // ProofUnmet means the signature is intact and the platform accepted it, // but the code does not satisfy the requirement the service asked for in // Options.CodeRequirement. It is the strongest verdict short of // verification: intact code, signed by somebody other than who was // required. Nothing is read out of a signature that failed the check it // was given, so the value still carries no identity. Only macOS produces // it; Windows has no requirement language. ProofUnmet // ProofPID means the attribute was read out of the operating system by // process id, after the connection was already up, without checking that // the process behind that id is still the one that connected. It is racy // by construction: pids are reused. Good enough for a log line, never // good enough for a permission decision. ProofPID // ProofBound means the attribute was read through something that ties // it to the process that connected, rather than to a number that // process once had. What that something is differs per platform, and so // does exactly what it excludes: // // - Windows: a process handle whose creation time predates the // connection, which excludes every process that could have // inherited the pid. The reuse race is closed. // - Linux: a pidfd the kernel derived from the connection itself // (SO_PEERPIDFD). While it is open the pid cannot be reassigned, so // /proc read behind it answers about the peer or about nothing. // - macOS: a pid and pidversion from the peer socket, cross-checked // against the connect-time credentials and refused if the process // started after the connection. That excludes pid reuse and any // program spawned to impersonate; it does not exclude a process // that already existed and was handed the socket. See CONTRACT.md. // // The attribute is still only the operating system's record about a // running process (an image path, say), not a statement about code - // except on macOS, where a verified code signature also lands here, // because over a socket the signature is only worth what the binding // above is worth. ProofBound // ProofKernel means the kernel stamped the attribute onto the connection // itself, out of its own records, at the moment the peer connected. The // peer never spoke it and cannot influence it. The access token behind // ImpersonateNamedPipeClient is of this kind, and so is the client // process id the kernel recorded on the pipe; on Linux, so are the uid, // gid and pid in SO_PEERCRED and the LSM label in SO_PEERSEC. // // "At the moment the peer connected" is the load-bearing half. A value // the kernel produces honestly but resolves when asked - LOCAL_PEERPID // and LOCAL_PEERTOKEN on macOS, which follow the peer socket's current // owner - is not of this kind, however official its source. ProofKernel // ProofSigned means the operating system itself validated a code // signature and the identity reported here came out of that validation - // not out of a filename. On Windows only MSIX package identity reaches // this level: the package full name contains a hash of the publisher // identity from the certificate the OS checked when it installed and // launched the package, and no unpackaged process can be given one. // // Authenticode on a plain executable does NOT reach this level, however // good the signature is. See Peer.Code and CONTRACT.md. // // Neither does macOS over a unix socket, which is the surprise in this // package: the Security framework really does verify running code, but // the audit token that selects which code is resolved from the peer // socket's current owner rather than stamped at connect. The transport // that reaches this level on macOS is XPC, whose messages carry a token // the kernel stamped on the sender. This package speaks sockets, so no // platform it supports reports ProofSigned for an ordinary program. ProofSigned )
type ProofError ¶
ProofError reports an attribute that is either unknown or known less firmly than the caller demanded. Why carries the reason the platform gave, which is usually the only actionable part of the message.
func (*ProofError) Error ¶
func (e *ProofError) Error() string
func (*ProofError) Is ¶
func (e *ProofError) Is(target error) bool
type Socket ¶ added in v0.5.0
Socket is one listening TCP socket or bound UDP socket and the process id the operating system's owner table names for it.
func AllSockets ¶ added in v0.5.0
AllSockets reads every TCP and UDP socket with its owner. Only the Windows tables are wired; elsewhere it answers ErrNoBinding.
type Transport ¶ added in v0.4.0
type Transport string
Transport selects the channel whose identity limits are being queried.
type TreeContains ¶ added in v0.5.0
TreeContains answers whether the process with this id and creation time is a member of a contained process tree, for example a supervised job's Contains method. The creation time distinguishes a reused process id.
type User ¶
type User struct {
Kind string // "windows" or "posix"
// Windows.
SID string // e.g. "S-1-5-21-...-1001"
Name string // account name, best effort, may be empty
Domain string // account domain, best effort, may be empty
Elevated bool // the peer's token is elevated
// Integrity is the peer's mandatory integrity level: "untrusted",
// "low", "medium", "high", "system", or "unknown".
//
// It matters more than it looks. Two processes at the same integrity
// level running as the same user are not isolated from each other on
// Windows: either can open the other for full access and write into
// it. So the answer this package gives about a same-user, same-level
// peer identifies a program but does not defend against one. The
// boundary that does hold is a lower level talking to a higher one - a
// medium-integrity process cannot touch a high-integrity one, and an
// AppContainer (low or untrusted) cannot touch either. See CONTRACT.md.
Integrity string
// IntegrityRID is the raw mandatory label RID behind Integrity, for
// comparisons the named levels are too coarse for. Zero when unknown.
IntegrityRID uint32
// Restricted reports a restricted token (CreateRestrictedToken), whose
// restricting SIDs can deny access the account itself has. An unreadable
// flag reads restricted.
Restricted bool
// POSIX.
UID int // -1 when unknown
GID int // -1 when unknown
// SecurityContext is the peer's LSM label on Linux: an SELinux context
// ("unconfined_u:unconfined_r:unconfined_t:s0") or an AppArmor profile
// ("/usr/bin/evince (enforce)"), read from SO_PEERSEC.
//
// It belongs to the principal rather than to the process because that
// is what it is - a label the kernel attaches to a subject at exec, by
// local policy - and it is stamped onto the socket at connect, so it
// carries the same ProofKernel strength as the uid beside it. On an
// AppArmor system it is worth more than the image path: the path names
// a file, the profile names the policy the kernel is actually enforcing
// against the peer. It is not a signature and never becomes one.
//
// Empty when no LSM labels processes on this system, and on every
// platform other than Linux.
SecurityContext string
// AuditSessionID is the macOS audit session the peer belongs to, from
// its audit token. Zero elsewhere. Two processes in the same audit
// session are, roughly, the same login session.
AuditSessionID uint32
}
User is a security principal. Which fields are populated depends on the platform; Kind says which.
func Self ¶ added in v0.5.0
Self returns the principal the operating system attributes to the calling process [ID-S1]: on Windows the primary token's user SID, elevation and mandatory integrity level, on Linux and macOS the effective uid and gid. It reads this process's own token or credentials and never a peer. A service compares a peer's kernel-proven User with it to decide whether the peer holds at least the service's own authority.
Source Files
¶
- bind.go
- bind_linux.go
- filecode.go
- filecode_other.go
- filekey_unix.go
- helper.go
- identity.go
- identity_linux.go
- localpath_linux.go
- localpath_other.go
- localpath_statfs.go
- loopback.go
- loopback_linux.go
- pathidentity.go
- peer.go
- procfs_linux.go
- program.go
- program_other.go
- proof.go
- self.go
- self_other.go
- server.go
- server_admission_other.go
- server_linux.go
- sockets.go
- sockets_other.go
- transport.go
- transport_xpc_other.go
- unix.go
- unixserver.go
- unixserver_linux.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
peerid
command
Command peerid shows what this machine can prove about the program on the other end of a local connection, and then attacks it.
|
Command peerid shows what this machine can prove about the program on the other end of a local connection, and then attacks it. |
|
go
|
|
|
privatedir
Package privatedir makes and checks folders private to this account (identity CONTRACT.md ID-D1): the one helper OA uses for every folder only this account may write, the engine socket root and its launch directories (inference CONTRACT.md INF-EL2), the span record folder (INF-MS7), the Lemonade provider's state and package folders, and the storage reader's digest record (storage-over-local-stores LS-12).
|
Package privatedir makes and checks folders private to this account (identity CONTRACT.md ID-D1): the one helper OA uses for every folder only this account may write, the engine socket root and its launch directories (inference CONTRACT.md INF-EL2), the span record folder (INF-MS7), the Lemonade provider's state and package folders, and the storage reader's digest record (storage-over-local-stores LS-12). |
|
trustedpath
Package trustedpath decides whether a file may be read as content only trusted principals can have written (identity CONTRACT.md ID-D2): the file and every folder above it up to the volume root are owned by a trusted principal, no other principal can write, append to, change the attributes or access of, or delete the file, or delete, rename or change the access of any folder on its path, and no reparse point (link, junction or mount point) stands on the path.
|
Package trustedpath decides whether a file may be read as content only trusted principals can have written (identity CONTRACT.md ID-D2): the file and every folder above it up to the volume root are owned by a trusted principal, no other principal can write, append to, change the attributes or access of, or delete the file, or delete, rename or change the access of any folder on its path, and no reparse point (link, junction or mount point) stands on the path. |
|
internal
|
|
|
integrity
Package integrity reads the mandatory integrity level Windows stamps on a token.
|
Package integrity reads the mandatory integrity level Windows stamps on a token. |
|
Package remote supplies explicitly trusted, mutually authenticated TLS streams for the shared frame protocol.
|
Package remote supplies explicitly trusted, mutually authenticated TLS streams for the shared frame protocol. |