Compare commits

...

6 Commits
1.28 ... 1.29

Author SHA1 Message Date
Miroslav Lichvar
b5658f4d9c Update NEWS 2013-08-08 15:58:07 +02:00
Miroslav Lichvar
ad58baa13b Drop support for SUBNETS_ACCESSED and CLIENT_ACCESSES commands
Support for the SUBNETS_ACCESSED and CLIENT_ACCESSES commands was
enabled in chronyd, but in chronyc it was always disabled and the
CLIENT_ACCESSES_BY_INDEX command was used instead. As there is no plan
to enable it in the future, remove the support completely.
2013-08-07 14:47:56 +02:00
Miroslav Lichvar
c6fdeeb6bb Don't send uninitialized data in command replies
The RPY_SUBNETS_ACCESSED and RPY_CLIENT_ACCESSES command replies can
contain uninitalized data from stack when the client logging is disabled
or a bad subnet is requested. These commands were never used by chronyc
and they require the client to be authenticated since version 1.25.
2013-08-07 14:46:16 +02:00
Miroslav Lichvar
7712455d9a Fix buffer overflow when processing crafted command packets
When the length of the REQ_SUBNETS_ACCESSED, REQ_CLIENT_ACCESSES
command requests and the RPY_SUBNETS_ACCESSED, RPY_CLIENT_ACCESSES,
RPY_CLIENT_ACCESSES_BY_INDEX, RPY_MANUAL_LIST command replies is
calculated, the number of items stored in the packet is not validated.

A crafted command request/reply can be used to crash the server/client.
Only clients allowed by cmdallow (by default only localhost) can crash
the server.

With chrony versions 1.25 and 1.26 this bug has a smaller security
impact as the server requires the clients to be authenticated in order
to process the subnet and client accesses commands. In 1.27 and 1.28,
however, the invalid calculated length is included also in the
authentication check which may cause another crash.
2013-08-07 13:39:02 +02:00
Miroslav Lichvar
a9a5f98406 Update chrony.conf.example2 2013-08-02 15:43:44 +02:00
Miroslav Lichvar
9ac8f64d89 Don't mention pre 2.2 Linux kernels in documentation 2013-08-02 15:43:44 +02:00
8 changed files with 60 additions and 406 deletions

14
NEWS
View File

@@ -1,3 +1,17 @@
New in version 1.29
===================
Security fixes
--------------
* Fix crash when processing crafted commands (CVE-2012-4502)
(possible with IP addresses allowed by cmdallow and localhost)
* Don't send uninitialized data in SUBNETS_ACCESSED and CLIENT_ACCESSES
replies (CVE-2012-4503) (not used by chronyc)
Other changes
-------------
* Drop support for SUBNETS_ACCESSED and CLIENT_ACCESSES commands
New in version 1.28
===================

29
candm.h
View File

@@ -298,22 +298,10 @@ typedef struct {
uint32_t bits_specd;
} REQ_SubnetsAccessed_Subnet;
#define MAX_SUBNETS_ACCESSED 8
typedef struct {
uint32_t n_subnets;
REQ_SubnetsAccessed_Subnet subnets[MAX_SUBNETS_ACCESSED];
} REQ_SubnetsAccessed;
/* This is based on the response size rather than the
request size */
#define MAX_CLIENT_ACCESSES 8
typedef struct {
uint32_t n_clients;
IPAddr client_ips[MAX_CLIENT_ACCESSES];
} REQ_ClientAccesses;
typedef struct {
uint32_t first_index;
uint32_t n_indices;
@@ -370,7 +358,8 @@ typedef struct {
modify polltarget, modify maxdelaydevratio, reselect, reselectdistance
Version 5 : auth data moved to the end of the packet to allow hashes with
different sizes, extended sources, tracking and activity reports
different sizes, extended sources, tracking and activity reports, dropped
subnets accessed and client accesses
*/
#define PROTO_VERSION_NUMBER 5
@@ -426,8 +415,6 @@ typedef struct {
REQ_RTCReport rtcreport;
REQ_TrimRTC trimrtc;
REQ_CycleLogs cyclelogs;
REQ_SubnetsAccessed subnets_accessed;
REQ_ClientAccesses client_accesses;
REQ_ClientAccessesByIndex client_accesses_by_index;
REQ_ManualList manual_list;
REQ_ManualDelete manual_delete;
@@ -582,11 +569,6 @@ typedef struct {
uint32_t bitmap[8];
} RPY_SubnetsAccessed_Subnet;
typedef struct {
uint32_t n_subnets;
RPY_SubnetsAccessed_Subnet subnets[MAX_SUBNETS_ACCESSED];
} RPY_SubnetsAccessed;
typedef struct {
IPAddr ip;
uint32_t client_hits;
@@ -598,11 +580,6 @@ typedef struct {
uint32_t last_cmd_hit_ago;
} RPY_ClientAccesses_Client;
typedef struct {
uint32_t n_clients;
RPY_ClientAccesses_Client clients[MAX_CLIENT_ACCESSES];
} RPY_ClientAccesses;
typedef struct {
uint32_t n_indices; /* how many indices there are in the server's table */
uint32_t next_index; /* the index 1 beyond those processed on this call */
@@ -656,8 +633,6 @@ typedef struct {
RPY_Tracking tracking;
RPY_Sourcestats sourcestats;
RPY_Rtc rtc;
RPY_SubnetsAccessed subnets_accessed;
RPY_ClientAccesses client_accesses;
RPY_ClientAccessesByIndex client_accesses_by_index;
RPY_ManualList manual_list;
RPY_Activity activity;

View File

@@ -125,9 +125,7 @@ different quirks in its behaviour.
The software is known to work in the following environments:
@itemize @bullet
@item Linux on i386, x86_64 and PowerPC architectures. The software is known
to work on Linux 2.0.x and newer. Prior to 2.0.31, the real time clock can't
be used.
@item Linux 2.2 and newer
@item NetBSD
@item BSD/386
@@ -826,18 +824,10 @@ compiled into the kernel). An estimate is made of the RTC error at a
particular RTC second, and the rate at which the RTC gains or loses time
relative to true time.
The RTC is fully supported in 2.2, 2.4 and 2.6 kernels.
On 2.6 kernels, if your motherboard has a HPET, you need to enable the
On 2.6 and later kernels, if your motherboard has a HPET, you need to enable the
@samp{HPET_EMULATE_RTC} option in your kernel configuration. Otherwise, chrony
will not be able to interact with the RTC device and will give up using it.
For kernels in the 2.0 series prior to 2.0.32, the kernel was set up to
trim the RTC every 11 minutes. This would be disasterous for
@code{chronyd} -- there is no reliable way of synchronising with this
trimming. For this reason, @code{chronyd} only supports the RTC in 2.0
kernels from v2.0.32 onwards.
When the computer is powered down, the measurement histories for all the
NTP servers are saved to files (if the @code{dumponexit} directive is
specified in the configuration file), and the RTC tracking information
@@ -2637,8 +2627,7 @@ conditions apply:
@enumerate 1
@item
You are running Linux version 2.2.x or 2.4.x (for any value of x), or v2.0.x
with x>=32.
You are running Linux version 2.2.x or later.
@item
You have compiled the kernel with extended real-time clock support

204
client.c
View File

@@ -1371,7 +1371,8 @@ submit_request(CMD_Request *request, CMD_Reply *reply, int *reply_auth_ok)
read_length = recvfrom_status;
expected_length = PKL_ReplyLength(reply);
bad_length = (read_length < expected_length);
bad_length = (read_length < expected_length ||
expected_length < offsetof(CMD_Reply, data));
bad_sender = (where_from.u.sa_family != his_addr.u.sa_family ||
(where_from.u.sa_family == AF_INET &&
(where_from.in4.sin_addr.s_addr != his_addr.in4.sin_addr.s_addr ||
@@ -1990,207 +1991,6 @@ process_cmd_rtcreport(char *line)
/* ================================================== */
#if 0
/* This is a previous attempt at implementing the clients command. It
could be re-instated sometime as a way of looking at all clients in a
particular subnet. The problem with it is that is requires at least 5
round trips to the server even if the server only has one client to
report. */
typedef struct XSubnetToDo {
struct XSubnetToDo *next;
unsigned long ip;
unsigned long bits;
} SubnetToDo;
static void
process_cmd_clients(char *line)
{
CMD_Request request;
CMD_Reply reply;
SubnetToDo *head, *todo, *tail, *p, *next_node, *new_node;
int i, j, nets_looked_up, clients_looked_up;
int word;
unsigned long mask;
unsigned long ip, bits;
unsigned long client_hits;
unsigned long peer_hits;
unsigned long cmd_hits_auth;
unsigned long cmd_hits_normal;
unsigned long cmd_hits_bad;
unsigned long last_ntp_hit_ago;
unsigned long last_cmd_hit_ago;
char hostname_buf[50];
int n_replies;
head = todo = MallocNew(SubnetToDo);
todo->next = NULL;
/* Set up initial query = root subnet */
todo->ip = 0;
todo->bits = 0;
tail = todo;
do {
request.command = htons(REQ_SUBNETS_ACCESSED);
/* Build list of subnets to examine */
i=0;
p=todo;
while((i < MAX_SUBNETS_ACCESSED) &&
p &&
(p->bits < 32)) {
request.data.subnets_accessed.subnets[i].ip = htonl(p->ip);
request.data.subnets_accessed.subnets[i].bits_specd = htonl(p->bits);
p = p->next;
i++;
}
nets_looked_up = i;
if (nets_looked_up == 0) {
/* No subnets need examining */
break;
}
request.data.subnets_accessed.n_subnets = htonl(nets_looked_up);
if (request_reply(&request, &reply, RPY_SUBNETS_ACCESSED, 0)) {
n_replies = ntohl(reply.data.subnets_accessed.n_subnets);
for (j=0; j<n_replies; j++) {
ip = ntohl(reply.data.subnets_accessed.subnets[j].ip);
bits = ntohl(reply.data.subnets_accessed.subnets[j].bits_specd);
for (i=0; i<256; i++) {
word = i/32;
mask = 1UL << (i%32);
if (ntohl(reply.data.subnets_accessed.subnets[j].bitmap[word]) & mask) {
/* Add this subnet to the todo list */
new_node = MallocNew(SubnetToDo);
new_node->next = NULL;
new_node->bits = bits + 8;
new_node->ip = ip | (i << (24 - bits));
tail->next = new_node;
tail = new_node;
#if 0
printf("%08lx %2d %3d %08lx\n", ip, bits, i, new_node->ip);
#endif
}
}
}
/* Skip the todo pointer forwards by the number of nets looked
up. Can't do this earlier, because we might have to point
at the next layer of subnets that have only just been
concatenated to the linked list. */
for (i=0; i<nets_looked_up; i++) {
todo = todo->next;
}
}
} else {
return;
}
} while (1); /* keep going until all subnets have been expanded,
down to single nodes */
/* Now the todo list consists of client records */
request.command = htons(REQ_CLIENT_ACCESSES);
#if 0
printf("%d %d\n", sizeof (RPY_ClientAccesses_Client), offsetof(CMD_Reply, data.client_accesses.clients));
#endif
printf("Hostname Client Peer CmdAuth CmdNorm CmdBad LstN LstC\n"
"========================= ====== ====== ====== ====== ====== ==== ====\n");
do {
i = 0;
p = todo;
while ((i < MAX_CLIENT_ACCESSES) &&
p) {
request.data.client_accesses.client_ips[i] = htonl(p->ip);
p = p->next;
i++;
}
clients_looked_up = i;
if (clients_looked_up == 0) {
/* No more clients to do */
break;
}
request.data.client_accesses.n_clients = htonl(clients_looked_up);
if (request_reply(&request, &reply, RPY_CLIENT_ACCESSES, 0)) {
n_replies = ntohl(reply.data.client_accesses.n_clients);
for (j=0; j<n_replies; j++) {
ip = ntohl(reply.data.client_accesses.clients[j].ip);
if (ip != 0UL) {
/* ip == 0 implies that the node could not be found in
the daemon's tables; we shouldn't ever generate this
case, but ignore it if we do. (In future there might
be a protocol to reset the client logging; if another
administrator runs that while we're doing the clients
command, there will be a race condition that could
cause this). */
client_hits = ntohl(reply.data.client_accesses.clients[j].client_hits);
peer_hits = ntohl(reply.data.client_accesses.clients[j].peer_hits);
cmd_hits_auth = ntohl(reply.data.client_accesses.clients[j].cmd_hits_auth);
cmd_hits_normal = ntohl(reply.data.client_accesses.clients[j].cmd_hits_normal);
cmd_hits_bad = ntohl(reply.data.client_accesses.clients[j].cmd_hits_bad);
last_ntp_hit_ago = ntohl(reply.data.client_accesses.clients[j].last_ntp_hit_ago);
last_cmd_hit_ago = ntohl(reply.data.client_accesses.clients[j].last_cmd_hit_ago);
if (no_dns) {
snprintf(hostname_buf, sizeof(hostname_buf),
"%s", UTI_IPToDottedQuad(ip));
} else {
DNS_IPAddress2Name(ip, hostname_buf, sizeof(hostname_buf));
hostname_buf[25] = 0;
}
printf("%-25s %6d %6d %6d %6d %6d ",
hostname_buf,
client_hits, peer_hits,
cmd_hits_auth, cmd_hits_normal, cmd_hits_bad);
print_seconds(last_ntp_hit_ago);
printf(" ");
print_seconds(last_cmd_hit_ago);
printf("\n");
}
}
/* Skip the todo pointer forwards by the number of nets looked
up. Can't do this earlier, because we might have to point
at the next layer of subnets that have only just been
concatenated to the linked list. */
for (i=0; i<clients_looked_up; i++) {
todo = todo->next;
}
}
} while (1);
cleanup:
for (p = head; p; ) {
next_node = p->next;
Free(p);
p = next_node;
}
}
#endif
/* New implementation of clients command */
static int
process_cmd_clients(char *line)
{

156
cmdmon.c
View File

@@ -1479,107 +1479,6 @@ handle_cyclelogs(CMD_Request *rx_message, CMD_Reply *tx_message)
/* ================================================== */
#define FLIPL(X) ((X) = htonl(X))
static void
handle_subnets_accessed(CMD_Request *rx_message, CMD_Reply *tx_message)
{
int i, j;
unsigned long ns, bits_specd;
IPAddr ip;
CLG_Status result;
ns = ntohl(rx_message->data.subnets_accessed.n_subnets);
tx_message->status = htons(STT_SUCCESS);
tx_message->reply = htons(RPY_SUBNETS_ACCESSED);
tx_message->data.subnets_accessed.n_subnets = htonl(ns);
for (i=0; i<ns; i++) {
UTI_IPNetworkToHost(&rx_message->data.subnets_accessed.subnets[i].ip, &ip);
bits_specd = ntohl(rx_message->data.subnets_accessed.subnets[i].bits_specd);
UTI_IPHostToNetwork(&ip, &tx_message->data.subnets_accessed.subnets[i].ip);
tx_message->data.subnets_accessed.subnets[i].bits_specd = htonl(bits_specd);
result = CLG_GetSubnetBitmap(&ip, bits_specd, tx_message->data.subnets_accessed.subnets[i].bitmap);
switch (result) {
case CLG_SUCCESS:
case CLG_EMPTYSUBNET:
/* Flip endianness of each 4 byte word. Don't care if subnet
is empty - just return an all-zero bitmap. */
for (j=0; j<8; j++) {
FLIPL(tx_message->data.subnets_accessed.subnets[i].bitmap[j]);
}
break;
case CLG_BADSUBNET:
tx_message->status = htons(STT_BADSUBNET);
return;
case CLG_INACTIVE:
tx_message->status = htons(STT_INACTIVE);
return;
default:
assert(0);
break;
}
}
}
/* ================================================== */
static void
handle_client_accesses(CMD_Request *rx_message, CMD_Reply *tx_message)
{
CLG_Status result;
RPT_ClientAccess_Report report;
unsigned long nc;
IPAddr ip;
int i;
struct timeval now;
LCL_ReadCookedTime(&now, NULL);
nc = ntohl(rx_message->data.client_accesses.n_clients);
tx_message->status = htons(STT_SUCCESS);
tx_message->reply = htons(RPY_CLIENT_ACCESSES);
tx_message->data.client_accesses.n_clients = htonl(nc);
printf("%d %d\n", (int)sizeof(RPY_ClientAccesses_Client), (int)offsetof(CMD_Reply, data.client_accesses.clients));
for (i=0; i<nc; i++) {
UTI_IPNetworkToHost(&rx_message->data.client_accesses.client_ips[i], &ip);
UTI_IPHostToNetwork(&ip, &tx_message->data.client_accesses.clients[i].ip);
result = CLG_GetClientAccessReportByIP(&ip, &report, now.tv_sec);
switch (result) {
case CLG_SUCCESS:
tx_message->data.client_accesses.clients[i].client_hits = htonl(report.client_hits);
tx_message->data.client_accesses.clients[i].peer_hits = htonl(report.peer_hits);
tx_message->data.client_accesses.clients[i].cmd_hits_auth = htonl(report.cmd_hits_auth);
tx_message->data.client_accesses.clients[i].cmd_hits_normal = htonl(report.cmd_hits_normal);
tx_message->data.client_accesses.clients[i].cmd_hits_bad = htonl(report.cmd_hits_bad);
tx_message->data.client_accesses.clients[i].last_ntp_hit_ago = htonl(report.last_ntp_hit_ago);
tx_message->data.client_accesses.clients[i].last_cmd_hit_ago = htonl(report.last_cmd_hit_ago);
printf("%s %lu %lu %lu %lu %lu %lu %lu\n", UTI_IPToString(&ip), report.client_hits, report.peer_hits, report.cmd_hits_auth, report.cmd_hits_normal, report.cmd_hits_bad, report.last_ntp_hit_ago, report.last_cmd_hit_ago);
break;
case CLG_EMPTYSUBNET:
/* Signal back to the client that this single client address
was unknown */
ip.family = IPADDR_UNSPEC;
UTI_IPHostToNetwork(&ip, &tx_message->data.client_accesses.clients[i].ip);
break;
case CLG_INACTIVE:
tx_message->status = htons(STT_INACTIVE);
return;
default:
assert(0);
break;
}
}
}
/* ================================================== */
static void
handle_client_accesses_by_index(CMD_Request *rx_message, CMD_Reply *tx_message)
{
@@ -1781,29 +1680,10 @@ read_from_cmd_socket(void *anything)
}
read_length = status;
expected_length = PKL_CommandLength(&rx_message);
rx_command = ntohs(rx_message.command);
LCL_ReadRawTime(&now);
LCL_CookTime(&now, &cooked_now, NULL);
tx_message.version = PROTO_VERSION_NUMBER;
tx_message.pkt_type = PKT_TYPE_CMD_REPLY;
tx_message.res1 = 0;
tx_message.res2 = 0;
tx_message.command = rx_message.command;
tx_message.sequence = rx_message.sequence;
tx_message.reply = htons(RPY_NULL);
tx_message.number = htons(1);
tx_message.total = htons(1);
tx_message.pad1 = 0;
tx_message.utoken = htonl(utoken);
/* Set this to a default (invalid) value. This protects against the
token field being set to an arbitrary value if we reject the
message, e.g. due to the host failing the access check. */
tx_message.token = htonl(0xffffffffUL);
memset(&tx_message.auth, 0, sizeof(tx_message.auth));
switch (where_from.u.sa_family) {
case AF_INET:
remote_ip.family = IPADDR_INET4;
@@ -1830,7 +1710,14 @@ read_from_cmd_socket(void *anything)
allowed = ADF_IsAllowed(access_auth_table, &remote_ip) || localhost;
if (read_length < offsetof(CMD_Request, data) ||
/* Message size sanity check */
if (read_length >= offsetof(CMD_Request, data)) {
expected_length = PKL_CommandLength(&rx_message);
} else {
expected_length = 0;
}
if (expected_length < offsetof(CMD_Request, data) ||
rx_message.pkt_type != PKT_TYPE_CMD_REQUEST ||
rx_message.res1 != 0 ||
rx_message.res2 != 0) {
@@ -1842,6 +1729,25 @@ read_from_cmd_socket(void *anything)
return;
}
rx_command = ntohs(rx_message.command);
tx_message.version = PROTO_VERSION_NUMBER;
tx_message.pkt_type = PKT_TYPE_CMD_REPLY;
tx_message.res1 = 0;
tx_message.res2 = 0;
tx_message.command = rx_message.command;
tx_message.sequence = rx_message.sequence;
tx_message.reply = htons(RPY_NULL);
tx_message.number = htons(1);
tx_message.total = htons(1);
tx_message.pad1 = 0;
tx_message.utoken = htonl(utoken);
/* Set this to a default (invalid) value. This protects against the
token field being set to an arbitrary value if we reject the
message, e.g. due to the host failing the access check. */
tx_message.token = htonl(0xffffffffUL);
memset(&tx_message.auth, 0, sizeof(tx_message.auth));
if (rx_message.version != PROTO_VERSION_NUMBER) {
tx_message.status = htons(STT_NOHOSTACCESS);
if (!LOG_RateLimited()) {
@@ -2233,14 +2139,6 @@ read_from_cmd_socket(void *anything)
handle_cyclelogs(&rx_message, &tx_message);
break;
case REQ_SUBNETS_ACCESSED:
handle_subnets_accessed(&rx_message, &tx_message);
break;
case REQ_CLIENT_ACCESSES:
handle_client_accesses(&rx_message, &tx_message);
break;
case REQ_CLIENT_ACCESSES_BY_INDEX:
handle_client_accesses_by_index(&rx_message, &tx_message);
break;

View File

@@ -15,8 +15,8 @@ driftfile /var/lib/chrony/drift
rtcsync
# In first three updates step the system clock instead of slew
# if the adjustment is larger than 100 seconds.
makestep 100 3
# if the adjustment is larger than 10 seconds.
makestep 10 3
# Allow NTP client access from local network.
#allow 192.168/16

View File

@@ -68,7 +68,7 @@ support hardware reference clocks to your computer, then xntpd will work fine.
Apart from not supporting hardware clocks, chrony will work fine too.
If your computer connects to the 'net for 5 minutes once a day (or something
like that), or you turn your (Linux v2.0) computer off when you're not using
like that), or you turn your Linux computer off when you're not using
it, or you want to use NTP on an isolated network with no hardware clocks in
sight, chrony will work much better for you.
@@ -271,7 +271,7 @@ The program needs to see the definitions of structures used to interact with
the real time clock (via /dev/rtc) and with the adjtimex() system call. Sadly
this has led to a number of compilation problems with newer kernels which have
been increasingly hard to fix in a way that makes the code compilable on all
Linux kernel versions (from 2.0 up anyway, I doubt 1.x still works.) Hopefully
Linux kernel versions. Hopefully
the situation will not deteriorate further with future kernel versions.
Q: I get "Could not open /dev/rtc, Device or resource busy" in my syslog file.

View File

@@ -124,19 +124,9 @@ PKL_CommandLength(CMD_Request *r)
case REQ_CYCLELOGS :
return offsetof(CMD_Request, data.cyclelogs.EOR);
case REQ_SUBNETS_ACCESSED :
{
unsigned long ns;
ns = ntohl(r->data.subnets_accessed.n_subnets);
return (offsetof(CMD_Request, data.subnets_accessed.subnets) +
ns * sizeof(REQ_SubnetsAccessed_Subnet));
}
case REQ_CLIENT_ACCESSES:
{
unsigned long nc;
nc = ntohl(r->data.client_accesses.n_clients);
return (offsetof(CMD_Request, data.client_accesses.client_ips) +
nc * sizeof(unsigned long));
}
/* No longer supported */
return 0;
case REQ_CLIENT_ACCESSES_BY_INDEX:
return offsetof(CMD_Request, data.client_accesses_by_index.EOR);
case REQ_MANUAL_LIST:
@@ -194,29 +184,15 @@ PKL_ReplyLength(CMD_Reply *r)
case RPY_RTC:
return offsetof(CMD_Reply, data.rtc.EOR);
case RPY_SUBNETS_ACCESSED :
{
unsigned long ns = ntohl(r->data.subnets_accessed.n_subnets);
if (r->status == htons(STT_SUCCESS)) {
return (offsetof(CMD_Reply, data.subnets_accessed.subnets) +
ns * sizeof(RPY_SubnetsAccessed_Subnet));
} else {
return offsetof(CMD_Reply, data);
}
}
case RPY_CLIENT_ACCESSES:
{
unsigned long nc = ntohl(r->data.client_accesses.n_clients);
if (r->status == htons(STT_SUCCESS)) {
return (offsetof(CMD_Reply, data.client_accesses.clients) +
nc * sizeof(RPY_ClientAccesses_Client));
} else {
return offsetof(CMD_Reply, data);
}
}
/* No longer supported */
return 0;
case RPY_CLIENT_ACCESSES_BY_INDEX:
{
unsigned long nc = ntohl(r->data.client_accesses_by_index.n_clients);
if (r->status == htons(STT_SUCCESS)) {
if (nc > MAX_CLIENT_ACCESSES)
return 0;
return (offsetof(CMD_Reply, data.client_accesses_by_index.clients) +
nc * sizeof(RPY_ClientAccesses_Client));
} else {
@@ -226,6 +202,8 @@ PKL_ReplyLength(CMD_Reply *r)
case RPY_MANUAL_LIST:
{
unsigned long ns = ntohl(r->data.manual_list.n_samples);
if (ns > MAX_MANUAL_LIST_SAMPLES)
return 0;
if (r->status == htons(STT_SUCCESS)) {
return (offsetof(CMD_Reply, data.manual_list.samples) +
ns * sizeof(RPY_ManualListSample));