SUDO_LOGSRV.PROTO(5)

๐Ÿ“ฆ
version 1.9.17_p2 ยท 6 versions
, in alpine 3.24, arch rolling, fedora 43, fedora 44, nixos 26.05, nixpkgs unstable, ubuntu questing, ubuntu resolute, ubuntu stonking

๐Ÿ’– NAME ๐Ÿ’–

sudo_logsrv.proto โ€” Sudo log server protocol, which lives in the File Formats Manual as SUDO_LOGSRV.PROTO(5) ๐Ÿ“šโœจ

๐ŸŒธ DESCRIPTION ๐ŸŒธ

okay so starting with version 1.9.0, sudo can send event and I/O logs off to a log server ๐Ÿ›ฐ๏ธ๐Ÿ”ฅ The protocol is written in Google's Protocol Buffers domain specific language, and the โ€œEXAMPLESโ€ section has the full protocol written out in Protocol Buffers format ๐Ÿ’…

bestie, Protocol Buffers has no built-in way to tell where one message ends and the next begins ๐Ÿ˜ญ So every message gets its wire size sent right before it, as a 32-bit unsigned integer in network byte order. Google calls this โ€œlength-prefix framing,โ€ and it's literally how they suggest handling the lack of message delimiters ๐Ÿซถ

The whole protocol is built on two messages, ClientMessage and ServerMessage, which we'll go through below ๐Ÿฆ‹ The server has to accept messages up to two megabytes in size โœจ The server is allowed to send back an error if the client tries to send something bigger than two megabytes ๐Ÿ™ˆ

๐Ÿ‘‘ Client Messages ๐Ÿ‘‘

A ClientMessage is the container that wraps up every type of message a client can send to the server ๐ŸŽ€

message ClientMessage {
  oneof type {
    AcceptMessage accept_msg = 1;
    RejectMessage reject_msg = 2;
    ExitMessage exit_msg = 3;
    RestartMessage restart_msg = 4;
    AlertMessage alert_msg = 5;
    IoBuffer ttyin_buf = 6;
    IoBuffer ttyout_buf = 7;
    IoBuffer stdin_buf = 8;
    IoBuffer stdout_buf = 9;
    IoBuffer stderr_buf = 10;
    ChangeWindowSize winsize_event = 11;
    CommandSuspend suspend_event = 12;
    ClientHello hello_msg = 13;
  }
}

Here are the different ClientMessage sub-messages the client may send to the server, all explained down below ๐Ÿงš

โฑ๏ธ TimeSpec

message TimeSpec {
    int64 tv_sec = 1;
    int32 tv_nsec = 2;
}

A TimeSpec is basically the same thing as a POSIX struct timespec, with seconds and nanoseconds members ๐Ÿ•ฐ๏ธ The tv_sec member is a 64-bit integer, which means it can handle dates after the year 2038 ๐Ÿ˜Œ๐ŸŒˆ

๐Ÿ“‹ InfoMessage

message InfoMessage {
  message StringList {
    repeated string strings = 1;
  }
  message NumberList {
    repeated int64 numbers = 1;
  }
  string key = 1;
  oneof value {
    int64 numval = 2;
    string strval = 3;
    StringList strlistval = 4;
    NumberList numlistval = 5;
  }
}

An InfoMessage holds info about the user who invoked the command, plus the environment the command runs in, all stored as key-value pairs ๐Ÿ“ The key is always a string. The value can be a 64-bit integer, a string, an array of strings, or an array of 64-bit integers ๐Ÿ’œ The event log data is made up of InfoMessage entries. Check out the โ€œEVENT LOG VARIABLESโ€ section for more deets ๐Ÿ”ฅ

๐Ÿค ClientHello hello_msg

message ClientHello {
  string client_id = 1;
}

A ClientHello message carries client information that can be sent to the server the moment the client first connects ๐Ÿ’–

client_id : A free-form client description. This usually includes the name and version of the client implementation โœจ

Hmm, that label line above is just the field name; see the bullets below for the same info in a cleaner way:

  • client_id โ€” a free-form client description ๐ŸŽ€ This usually includes the name and version of the client implementation ๐Ÿ’…

โœ… AcceptMessage accept_msg

message AcceptMessage {
  TimeSpec submit_time = 1;
  repeated InfoMessage info_msgs = 2;
  bool expect_iobufs = 3;
}

An AcceptMessage gets sent by the client when the security policy says a command is allowed ๐Ÿฅณ It has these members:

  • submit_time โ€” the wall clock time when the command was submitted to the security policy โฐ
  • info_msgs โ€” an array of InfoMessage describing the user who submitted the command plus the command's execution environment ๐Ÿงพ This info is used to make an event log entry, and the server might also use it to figure out where and how the I/O log gets stored ๐Ÿ—‚๏ธ
  • expect_iobufs โ€” set to true if the server should expect IoBuffer messages to follow (for I/O logging), or false if the server should only store the event log ๐Ÿซง
bestie, if an AcceptMessage is sent, the client must NOT send a RejectMessage or RestartMessage ๐Ÿšซ๐Ÿ’€

๐Ÿ™… RejectMessage reject_msg

message RejectMessage {
  TimeSpec submit_time = 1;
  string reason = 2;
  repeated InfoMessage info_msgs = 3;
}

A RejectMessage gets sent by the client when the security policy denies a command ๐Ÿ˜ค It has these members:

  • submit_time โ€” the wall clock time when the command was submitted to the security policy โฐ
  • reason โ€” the reason the security policy gave for denying the command ๐Ÿ™ƒ
  • info_msgs โ€” an array of InfoMessage describing the user who submitted the command plus the command's execution environment ๐Ÿงพ This info is used to make an event log entry.
if a RejectMessage is sent, the client must NOT send an AcceptMessage or RestartMessage ๐Ÿ™…โ€โ™€๏ธ

๐Ÿ ExitMessage exit_msg

message ExitMessage {
  TimeSpec run_time = 1;
  int32 exit_value = 2;
  bool dumped_core = 3;
  string signal = 4;
  string error = 5;
}

An ExitMessage gets sent by the client once the command has exited or been killed by a signal ๐Ÿชฆโœจ It has these members:

  • run_time โ€” the total elapsed time since the command started, measured with a monotonic clock where possible โณ This is NOT the wall clock time.
  • exit_value โ€” the command's exit value, in the range 0โ€“255 ๐Ÿ”ข
  • dumped_core โ€” true if the command was terminated by a signal and dumped core ๐Ÿ’ฅ
  • signal โ€” if the command was terminated by a signal, this is set to the name of the signal without the leading โ€œSIGโ€. For example: INT, TERM, KILL, SEGV ๐Ÿ”ช
  • error โ€” a message from the client saying the command was terminated unexpectedly because of an error ๐Ÿ˜ญ

When doing I/O logging, the client should wait for a commit_point that matches the final IoBuffer before closing the connection, unless the final commit_point has already been received ๐Ÿซถ Don't dip early, bestie ๐Ÿ™

๐Ÿ” RestartMessage restart_msg

message RestartMessage {
  string log_id = 1;
  TimeSpec resume_point = 2;
}

A RestartMessage gets sent by the client to pick up sending an existing I/O log that got interrupted earlier ๐Ÿ”„ It has these members:

  • log_id โ€” the server-side name for an I/O log that the server previously sent to the client ๐Ÿท๏ธ This might be a path name on the server, or some other kind of server-side identifier.
  • resume_point โ€” the point in time to resume the I/O log after ๐Ÿ•ฐ๏ธ This is a TimeSpec showing how much time has passed since the command started, NOT the wall clock time. The resume_point should match a commit_point the server already sent to the client. If the server gets a RestartMessage with a resume_point it has never seen before, an error gets returned to the client and the connection gets dropped ๐Ÿ˜ต
if a RestartMessage is sent, the client must NOT send an AcceptMessage or RejectMessage ๐Ÿšซ

๐Ÿšจ AlertMessage alert_msg

message AlertMessage {
  TimeSpec alert_time = 1;
  string reason = 2;
  repeated InfoMessage info_msgs = 3;
}

An AlertMessage gets sent by the client to flag a problem the security policy spotted while the command was running, one that should be saved in the event log ๐Ÿ‘€ It has these members:

  • alert_time โ€” the wall clock time when the alert happened โฐ
  • reason โ€” the reason for the alert ๐Ÿ“ฃ
  • info_msgs โ€” an optional array of InfoMessage describing the user who submitted the command plus the command's execution environment ๐Ÿงพ This info is used to make an event log entry.

๐ŸŽž๏ธ IoBuffer ttyin_buf | ttyout_buf | stdin_buf | stdout_buf | stderr_buf

message IoBuffer {
  TimeSpec delay = 1;
  bytes data = 2;
}

An IoBuffer represents data from terminal input, terminal output, standard input, standard output, or standard error ๐Ÿ–ฅ๏ธ It has these members:

  • delay โ€” the elapsed time since the last record, as a TimeSpec โฑ๏ธ The delay should be calculated with a monotonic clock where possible.
  • data โ€” the binary I/O log data from terminal input, terminal output, standard input, standard output, or standard error ๐Ÿซง

๐Ÿ“ ChangeWindowSize winsize_event

message ChangeWindowSize {
  TimeSpec delay = 1;
  int32 rows = 2;
  int32 cols = 3;
}

A ChangeWindowSize message gets sent by the client whenever the terminal running the command changes size ๐ŸชŸ It has these members:

  • delay โ€” the elapsed time since the last record, as a TimeSpec โฑ๏ธ The delay should be calculated with a monotonic clock where possible.
  • rows โ€” the new number of terminal rows ๐Ÿ“
  • cols โ€” the new number of terminal columns ๐Ÿ“

โธ๏ธ CommandSuspend suspend_event

message CommandSuspend {
  TimeSpec delay = 1;
  string signal = 2;
}

A CommandSuspend message gets sent by the client when the command is either suspended or resumed ๐Ÿซ  It has these members:

  • delay โ€” the elapsed time since the last record, as a TimeSpec โฑ๏ธ The delay should be calculated with a monotonic clock where possible.
  • signal โ€” the signal name without the leading โ€œSIGโ€. For example: STOP, TSTP, CONT ๐Ÿšฆ

โœจ Server Messages โœจ

okay so a ServerMessage is the container that wraps up every single message type the server can send to a client ๐Ÿ’Œ

message ServerMessage {
  oneof type {
    ServerHello hello = 1;
    TimeSpec commit_point = 2;
    string log_id = 3;
    string error = 4;
    string abort = 5;
  }
}

the sub-messages the server can send to the client are broken down below, so let's go ๐Ÿงš

ServerHello hello

message ServerHello {
  string server_id = 1;
  string redirect = 2;
  repeated string servers = 3;
  bool subcommands = 4;
}

this is the server info that shows up the moment a client first connects ๐ŸŽ€ it has these members:

  • server_id โ€” a free-form server description, usually the name and version of the implementation running on the log server ๐Ÿ’… this one is always there, no exceptions
  • redirect โ€” a host and port separated by a colon (โ€˜โ€™), which the client should connect to instead ๐Ÿฆ‹ the host can be a host name, an IPv4 address, or an IPv6 address in square brackets. this is how server load balancing can happen โœจ and heads up: the server disconnects right after sending the ServerHello when it includes a redirect ๐Ÿ’€
  • servers โ€” a list of other known log servers ๐ŸŒธ this is what makes log server redundancy work, and it lets the client find every other log server just by connecting to one it already knows. you can leave this out when there's only a single log server
  • subcommands โ€” if this is set, the server supports logging extra commands during a session ๐Ÿ”ฅ the client may send an AcceptMessage or RejectMessage when sudo is running in intercept mode. in that mode, commands spawned from the initial command sudo authorized are subject to policy restrictions and/or get logged. if subcommands is false, the client must not try to log additional commands ๐Ÿ™ˆ

TimeSpec commit_point

a periodic timestamp the server sends to say when I/O log buffers have been committed to storage ๐Ÿ•ฐ๏ธ it's not sent after every single IoBuffer. it shows up at an interval the server configures itself โญ when the server gets an ExitMessage, it replies with a commit_point that matches the last IoBuffer it received, right before it closes the connection ๐Ÿ’œ

string log_id

the server-side ID of the I/O log being stored ๐Ÿ—‚๏ธ it gets sent back in response to an AcceptMessage where expect_iobufs is true.

string error

a fatal server-side error ๐Ÿ˜ญ the server closes the connection right after sending it.

string abort

an abort message basically tells the client to kill the command and end the session ๐Ÿ›‘ the server can use it to enforce a bit of simple server-side policy. the server closes the connection right after sending it.


โœจ Protocol flow of control โœจ

this is the order things are supposed to happen in, bestie ๐Ÿ“‹

  1. The client connects to the first available server ๐Ÿ”Œ if the client is set up to use TLS, a TLS handshake gets attempted.
  2. The client sends ClientHello ๐Ÿ‘‹ this is currently optional, but it lets the server spot a non-TLS connection on the TLS port.
  3. The server sends ServerHello ๐Ÿ’–
  4. The client replies with either AcceptMessage, RejectMessage, or RestartMessage ๐Ÿซถ
  5. If the client sent an AcceptMessage with expect_iobufs set, the server creates a new I/O log and replies with a log_id ๐ŸŽ€
  6. The client sends zero or more IoBuffer messages ๐ŸŒŠ
  7. The server periodically replies to IoBuffer messages with a commit_point โฐ
  8. The client sends an ExitMessage when the command exits or gets killed ๐Ÿ
  9. The server sends the final commit_point if one is still pending โœจ
  10. The server closes the connection. After receiving the final commit_point, the client shuts down its side of the TLS connection (if TLS is in use) and closes the connection ๐Ÿ‘‹
  11. The server shuts down its side of the TLS connection (if TLS is in use) and closes the connection ๐Ÿ’…

at any point, the server may send an error or abort message to the client, and then the server closes the connection ๐Ÿ˜ฌ if the client gets an abort message, it should terminate the running command. no drama, just do it ๐Ÿ›‘


โœจ EVENT LOG VARIABLES โœจ

the AcceptMessage, AlertMessage and RejectMessage classes each contain an array of InfoMessage. that array should hold info about the user who submitted the command, plus info about the execution environment of the command if it was accepted ๐ŸŒˆ

some variables have a client, run, or submit prefix ๐Ÿงšโ€โ™€๏ธ these prefixes clear up confusion for variables that could apply to the client program, the user submitting the command, or the command being run. variables with a client prefix are about the program making the connection to the log server, like sudo. variables with a run prefix are about the command the user asked to run. variables with a submit prefix are about the user submitting the request (the user running sudo) ๐Ÿ’œ

the following InfoMessage entries are required ๐Ÿ”’

  • command (string) โ€” the command that was submitted
  • runuser (string) โ€” the name of the user the command was run as
  • submithost (string) โ€” the name of the host the command was submitted on
  • submituser (string) โ€” the name of the user submitting the command

the following InfoMessage entries are recognized, but not required ๐ŸŒธ

  • clientargv (StringList) โ€” the client's original argument vector
  • clientpid (int64) โ€” the client's process ID
  • clientppid (int64) โ€” the client's parent process ID
  • clientsid (int64) โ€” the client's terminal session ID
  • columns (int64) โ€” the number of columns in the terminal
  • lines (int64) โ€” the number of lines in the terminal
  • runargv (StringList) โ€” the argument vector of the command to run
  • runchroot (string) โ€” the root directory of the command to run
  • runcwd (string) โ€” the running command's working directory
  • runenv (StringList) โ€” the running command's environment
  • rungid (int64) โ€” the primary group-ID of the command
  • rungids (NumberList) โ€” the supplementary group-IDs for the command
  • rungroup (string) โ€” the primary group name of the command
  • rungroups (StringList) โ€” the supplementary group names for the command
  • runuid (int64) โ€” the run user's user-ID
  • submitcwd (string) โ€” the submit user's current working directory
  • submitenv (StringList) โ€” the submit user's environment
  • submitgid (int64) โ€” the submit user's primary group-ID
  • submitgids (NumberList) โ€” the submit user's supplementary group-IDs
  • submitgroup (string) โ€” the submitting user's primary group name
  • submitgroups (StringList) โ€” the submit user's supplementary group names
  • submituid (int64) โ€” the submit user's user-ID
  • ttyname (string) โ€” the terminal the command was submitted from

the server must accept other variables that aren't listed above, but it's allowed to ignore them ๐Ÿซถ

โœจ EXAMPLES โœจ

okay so the whole Protocol Buffers description of the log server protocol is right here, written in proto3 syntax, and it's all included below ๐Ÿ’– no skipping, bestie ๐Ÿซถ

syntax = "proto3";

/*
 * Client message to the server.  Messages on the wire are
 * prefixed with a 32-bit size in network byte order.
 */
message ClientMessage {
  oneof type {
    AcceptMessage accept_msg = 1;
    RejectMessage reject_msg = 2;
    ExitMessage exit_msg = 3;
    RestartMessage restart_msg = 4;
    AlertMessage alert_msg = 5;
    IoBuffer ttyin_buf = 6;
    IoBuffer ttyout_buf = 7;
    IoBuffer stdin_buf = 8;
    IoBuffer stdout_buf = 9;
    IoBuffer stderr_buf = 10;
    ChangeWindowSize winsize_event = 11;
    CommandSuspend suspend_event = 12;
  }
}

/* Equivalent of POSIX struct timespec */
message TimeSpec {
    int64 tv_sec = 1;           /* seconds */
    int32 tv_nsec = 2;          /* nanoseconds */
}

/* I/O buffer with keystroke data */
message IoBuffer {
  TimeSpec delay = 1;           /* elapsed time since last record */
  bytes data = 2;               /* keystroke data */
}

/*
 * Key/value pairs, like Privilege Manager struct info.
 * The value may be a number, a string, or a list of strings.
 */
message InfoMessage {
  message StringList {
    repeated string strings = 1;
  }
  message NumberList {
    repeated int64 numbers = 1;
  }
  string key = 1;
  oneof value {
    int64 numval = 2;
    string strval = 3;
    StringList strlistval = 4;
    NumberList numlistval = 5;
  }
}

/*
 * Event log data for command accepted by the policy.
 */
message AcceptMessage {
  TimeSpec submit_time = 1;             /* when command was submitted */
  repeated InfoMessage info_msgs = 2;   /* key,value event log data */
  bool expect_iobufs = 3;               /* true if I/O logging enabled */
}

/*
 * Event log data for command rejected by the policy.
 */
message RejectMessage {
  TimeSpec submit_time = 1;             /* when command was submitted */
  string reason = 2;                    /* reason command was rejected */
  repeated InfoMessage info_msgs = 3;   /* key,value event log data */
}

/* Message sent by client when command exits. */
/* Might revisit runtime and use end_time instead */
message ExitMessage {
  TimeSpec run_time = 1;        /* total elapsed run time */
  int32 exit_value = 2;         /* 0โ€“255 */
  bool dumped_core = 3;         /* true if command dumped core */
  string signal = 4;            /* signal name if killed by signal */
  string error = 5;             /* if killed due to other error */
}

/* Alert message, policy module-specific. */
message AlertMessage {
  TimeSpec alert_time = 1;              /* time alert message occurred */
  string reason = 2;                    /* policy alert error string */
  repeated InfoMessage info_msgs = 3;   /* key,value event log data */
}

/* Used to restart an existing I/O log on the server. */
message RestartMessage {
  string log_id = 1;            /* ID of log being restarted */
  TimeSpec resume_point = 2;    /* resume point (elapsed time) */
}

/* Window size change event. */
message ChangeWindowSize {
  TimeSpec delay = 1;           /* elapsed time since last record */
  int32 rows = 2;               /* new number of rows */
  int32 cols = 3;               /* new number of columns */
}

/* Command suspend/resume event. */
message CommandSuspend {
  TimeSpec delay = 1;           /* elapsed time since last record */
  string signal = 2;            /* signal that caused suspend/resume */
}

/*
 * Server messages to the client.  Messages on the wire are
 * prefixed with a 32-bit size in network byte order.
 */
message ServerMessage {
  oneof type {
    ServerHello hello = 1;      /* server hello message */
    TimeSpec commit_point = 2;  /* cumulative time of records stored */
    string log_id = 3;          /* ID of server-side I/O log */
    string error = 4;           /* error message from server */
    string abort = 5;           /* abort message, kill command */
  }
}

/* Hello message from server when client connects. */
message ServerHello {
  string server_id = 1;         /* free-form server description */
  string redirect = 2;          /* optional redirect if busy */
  repeated string servers = 3;  /* optional list of known servers */
}

โœจ SEE ALSO โœจ

ily bestie, go check these out too ๐Ÿฆ‹

Protocol Buffers lives at https://protobuf.dev/ ๐Ÿงš


โœจ AUTHORS โœจ

so a whole bunch of people have worked on sudo over the years ๐Ÿ’… this version is mostly code written by:

Todd C. Miller ๐Ÿ‘‘

for the full list of everyone who's contributed (and I mean EVERYONE ๐Ÿ˜ญ), check out the CONTRIBUTORS.md file in the sudo distribution, or https://www.sudo.ws/about/contributors/ ๐ŸŒธ


โœจ BUGS โœจ

if you think you found a bug in sudo_logsrv.proto, you can file a report in the sudo bug database at https://bugzilla.sudo.ws/, or open an issue at https://github.com/sudo-project/sudo/issues ๐Ÿ™ˆ if email is more your thing, you can send messages to the sudo-workers mailing list, https://www.sudo.ws/mailman/listinfo/sudo-workers (public), or to <sudo@sudo.ws> (private) ๐Ÿ’Œ

โš ๏ธ please do NOT report security vulnerabilities through public GitHub issues, Bugzilla, or mailing lists ๐Ÿ’€ instead, email them to <Todd.Miller@sudo.ws>. you're welcome to encrypt your message with PGP if you want, using the key found at https://www.sudo.ws/dist/PGPKEYS ๐Ÿ”

โœจ SUPPORT โœจ

limited free support is available through the sudo-users mailing list ๐ŸŽ€ subscribe or search the archives at https://www.sudo.ws/mailman/listinfo/sudo-users โœจ


โœจ DISCLAIMER โœจ

sudo is provided โ€œAS ISโ€ and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed ๐Ÿ˜ญ for all the details, check the LICENSE.md file distributed with sudo, or https://www.sudo.ws/about/license/ ๐ŸŒˆ


that's a wrap on SUDO_LOGSRV.PROTO(5), from Sudo 1.9.17p2, September 13, 2022 ๐Ÿ’– you're literally a whole main character now ๐Ÿงšโœจ