/* result is pinned on successful return */ int __pmGetPDU(int fd, int mode, int timeout, __pmPDU **result) { int need; int len; static int maxsize = PDU_CHUNK; char *handle; __pmPDU *pdubuf; __pmPDU *pdubuf_prev; __pmPDUHdr *php; if ((pdubuf = __pmFindPDUBuf(maxsize)) == NULL) return -oserror(); /* First read - try to read the header */ len = pduread(fd, (void *)pdubuf, sizeof(__pmPDUHdr), HEADER, timeout); php = (__pmPDUHdr *)pdubuf; if (len < (int)sizeof(__pmPDUHdr)) { if (len == -1) { if (__pmSocketClosed()) { len = 0; } else { char errmsg[PM_MAXERRMSGLEN]; __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: len=%d: %s", fd, len, pmErrStr_r(-oserror(), errmsg, sizeof(errmsg))); } } else if (len >= (int)sizeof(php->len)) { /* * Have part of a PDU header. Enough for the "len" * field to be valid, but not yet all of it - save * what we have received and try to read some more. * Note this can only happen once per PDU, so the * ntohl() below will _only_ be done once per PDU. */ goto check_read_len; /* continue, do not return */ } else if (len == PM_ERR_TIMEOUT) { __pmUnpinPDUBuf(pdubuf); return PM_ERR_TIMEOUT; } else if (len < 0) { char errmsg[PM_MAXERRMSGLEN]; __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: len=%d: %s", fd, len, pmErrStr_r(len, errmsg, sizeof(errmsg))); __pmUnpinPDUBuf(pdubuf); return PM_ERR_IPC; } else if (len > 0) { __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: bad len=%d", fd, len); __pmUnpinPDUBuf(pdubuf); return PM_ERR_IPC; } /* * end-of-file with no data */ __pmUnpinPDUBuf(pdubuf); return 0; } check_read_len: php->len = ntohl(php->len); if (php->len < (int)sizeof(__pmPDUHdr)) { /* * PDU length indicates insufficient bytes for a PDU header * ... looks like DOS attack like PV 935490 */ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d illegal PDU len=%d in hdr", fd, php->len); __pmUnpinPDUBuf(pdubuf); return PM_ERR_IPC; } else if (mode == LIMIT_SIZE && php->len > ceiling) { /* * Guard against denial of service attack ... don't accept PDUs * from clients that are larger than 64 Kbytes (ceiling) * (note, pmcd and pmdas have to be able to _send_ large PDUs, * e.g. for a pmResult or instance domain enquiry) */ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d bad PDU len=%d in hdr exceeds maximum client PDU size (%d)", fd, php->len, ceiling); __pmUnpinPDUBuf(pdubuf); return PM_ERR_TOOBIG; } if (len < php->len) { /* * need to read more ... */ int tmpsize; int have = len; PM_INIT_LOCKS(); PM_LOCK(__pmLock_libpcp); if (php->len > maxsize) { tmpsize = PDU_CHUNK * ( 1 + php->len / PDU_CHUNK); maxsize = tmpsize; } else tmpsize = maxsize; PM_UNLOCK(__pmLock_libpcp); pdubuf_prev = pdubuf; if ((pdubuf = __pmFindPDUBuf(tmpsize)) == NULL) { __pmUnpinPDUBuf(pdubuf_prev); return -oserror(); } memmove((void *)pdubuf, (void *)php, len); __pmUnpinPDUBuf(pdubuf_prev); php = (__pmPDUHdr *)pdubuf; need = php->len - have; handle = (char *)pdubuf; /* block until all of the PDU is received this time */ len = pduread(fd, (void *)&handle[len], need, BODY, timeout); if (len != need) { if (len == PM_ERR_TIMEOUT) { __pmUnpinPDUBuf(pdubuf); return PM_ERR_TIMEOUT; } else if (len < 0) { char errmsg[PM_MAXERRMSGLEN]; __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d data read: len=%d: %s", fd, len, pmErrStr_r(-oserror(), errmsg, sizeof(errmsg))); } else __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d data read: have %d, want %d, got %d", fd, have, need, len); /* * only report header fields if you've read enough bytes */ if (len > 0) have += len; if (have >= (int)(sizeof(php->len)+sizeof(php->type)+sizeof(php->from))) __pmNotifyErr(LOG_ERR, "__pmGetPDU: PDU hdr: len=0x%x type=0x%x from=0x%x", php->len, (unsigned)ntohl(php->type), (unsigned)ntohl(php->from)); else if (have >= (int)(sizeof(php->len)+sizeof(php->type))) __pmNotifyErr(LOG_ERR, "__pmGetPDU: PDU hdr: len=0x%x type=0x%x", php->len, (unsigned)ntohl(php->type)); __pmUnpinPDUBuf(pdubuf); return PM_ERR_IPC; } } *result = (__pmPDU *)php; php->type = ntohl((unsigned int)php->type); if (php->type < 0) { /* * PDU type is bad ... could be a possible mem leak attack like * https://bugzilla.redhat.com/show_bug.cgi?id=841319 */ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d illegal PDU type=%d in hdr", fd, php->type); __pmUnpinPDUBuf(pdubuf); return PM_ERR_IPC; } php->from = ntohl((unsigned int)php->from); #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_PDU) { int j; char *p; int jend = PM_PDU_SIZE(php->len); char strbuf[20]; /* clear the padding bytes, lest they contain garbage */ p = (char *)*result + php->len; while (p < (char *)*result + jend*sizeof(__pmPDU)) *p++ = '~'; /* buffer end */ if (mypid == -1) mypid = (int)getpid(); fprintf(stderr, "[%d]pmGetPDU: %s fd=%d len=%d from=%d", mypid, __pmPDUTypeStr_r(php->type, strbuf, sizeof(strbuf)), fd, php->len, php->from); for (j = 0; j < jend; j++) { if ((j % 8) == 0) fprintf(stderr, "\n%03d: ", j); fprintf(stderr, "%8x ", (*result)[j]); } putc('\n', stderr); } #endif if (php->type >= PDU_START && php->type <= PDU_FINISH) __pmPDUCntIn[php->type-PDU_START]++; /* * Note php points into the PDU buffer pdubuf that remains pinned * and php is returned via the result parameter ... see the * thread-safe comments above */ return php->type; }
int __pmtracegetPDU(int fd, int timeout, __pmTracePDU **result) { int need, len; char *handle; static int maxsize = TRACE_PDU_CHUNK; __pmTracePDU *pdubuf; __pmTracePDU *pdubuf_prev; __pmTracePDUHdr *php; /* * This stuff is a little tricky. What we try to do is read() * an amount of data equal to the largest PDU we have (or are * likely to have) seen thus far. In the majority of cases * this returns exactly one PDU's worth, i.e. read() returns * a length equal to php->len. * * For this to work, we have a special "mode" of -1 * to pduread() which means read, but return after the * first read(), rather than trying to read up to the request * length with multiple read()s, which would of course "hang" * after the first PDU arrived. * * We need to handle the following tricky cases: * 1. We get _more_ than we need for a single PDU -- happens * when PDU's arrive together. This requires "moreinput" * to handle leftovers here (it gets even uglier if we * have part, but not all of the second PDU). * 2. We get _less_ than we need for a single PDU -- this * requires at least another read(), and possibly acquiring * another pdubuf and doing a memcpy() for the partial PDU * from the earlier call. */ if (__pmtracemoreinput(fd)) { /* some leftover from last time ... handle -> start of PDU */ pdubuf = more[fd].pdubuf; len = more[fd].len; __pmtracenomoreinput(fd); } else { if ((pdubuf = __pmtracefindPDUbuf(maxsize)) == NULL) return -oserror(); len = pduread(fd, (void *)pdubuf, maxsize, -1, timeout); } php = (__pmTracePDUHdr *)pdubuf; if (len < (int)sizeof(__pmTracePDUHdr)) { if (len == -1) { if (oserror() == ECONNRESET|| oserror() == ETIMEDOUT || oserror() == ENETDOWN || oserror() == ENETUNREACH || oserror() == EHOSTDOWN || oserror() == EHOSTUNREACH || oserror() == ECONNREFUSED) /* * failed as a result of pmdatrace exiting and the * connection being reset, or as a result of the kernel * ripping down the connection (most likely because the * host at the other end just took a dive) * * treat this like end of file on input * * from irix/kern/fs/nfs/bds.c seems like all of the * following are peers here: * ECONNRESET (pmdatrace terminated?) * ETIMEDOUT ENETDOWN ENETUNREACH EHOSTDOWN EHOSTUNREACH * ECONNREFUSED * peers for bds but not here: * ENETRESET ENONET ESHUTDOWN (cache_fs only?) * ECONNABORTED (accept, user req only?) * ENOTCONN (udp?) * EPIPE EAGAIN (nfs, bds & ..., but not ip or tcp?) */ len = 0; else fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: %s", fd, osstrerror()); } else if (len > 0) fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: len=%d, not %d?", fd, len, (int)sizeof(__pmTracePDUHdr)); else if (len == PMTRACE_ERR_TIMEOUT) return PMTRACE_ERR_TIMEOUT; else if (len < 0) fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: %s", fd, pmtraceerrstr(len)); return len ? PMTRACE_ERR_IPC : 0; } php->len = ntohl(php->len); if (php->len < 0) { fprintf(stderr, "__pmtracegetPDU: fd=%d illegal len=%d in hdr\n", fd, php->len); return PMTRACE_ERR_IPC; } if (len == php->len) /* return below */ ; else if (len > php->len) { /* * read more than we need for this one, save it up for next time */ handle = (char *)pdubuf; moreinput(fd, (__pmTracePDU *)&handle[php->len], len - php->len); } else { int tmpsize; /* * need to read more ... */ __pmtracepinPDUbuf(pdubuf); pdubuf_prev = pdubuf; if (php->len > maxsize) tmpsize = TRACE_PDU_CHUNK * ( 1 + php->len / TRACE_PDU_CHUNK); else tmpsize = maxsize; if ((pdubuf = __pmtracefindPDUbuf(tmpsize)) == NULL) { __pmtraceunpinPDUbuf(pdubuf_prev); return -oserror(); } if (php->len > maxsize) maxsize = tmpsize; memmove((void *)pdubuf, (void *)php, len); __pmtraceunpinPDUbuf(pdubuf_prev); php = (__pmTracePDUHdr *)pdubuf; need = php->len - len; handle = (char *)pdubuf; /* block until all of the PDU is received this time */ len = pduread(fd, (void *)&handle[len], need, 0, timeout); if (len != need) { if (len == PMTRACE_ERR_TIMEOUT) return PMTRACE_ERR_TIMEOUT; if (len < 0) fprintf(stderr, "__pmtracegetPDU: error (%d) fd=%d: %s\n", (int)oserror(), fd, osstrerror()); else fprintf(stderr, "__pmtracegetPDU: len=%d, not %d? (fd=%d)\n", len, need, fd); fprintf(stderr, "hdr: len=0x%08x type=0x%08x from=0x%08x\n", php->len, (int)ntohl(php->type), (int)ntohl(php->from)); return PMTRACE_ERR_IPC; } } *result = (__pmTracePDU *)php; php->type = ntohl(php->type); php->from = ntohl(php->from); #ifdef PMTRACE_DEBUG if (__pmstate & PMTRACE_STATE_PDU) { int j; int jend = (int)(php->len+(int)sizeof(__pmTracePDU)-1)/(int)sizeof(__pmTracePDU); char *p; /* for Purify ... */ p = (char *)*result + php->len; while (p < (char *)*result + jend*sizeof(__pmTracePDU)) *p++ = '~'; /* buffer end */ fprintf(stderr, "[%" FMT_PID "]__pmtracegetPDU: %s fd=%d len=%d from=%d", (pid_t)getpid(), pdutypestr(php->type), fd, php->len, php->from); for (j = 0; j < jend; j++) { if ((j % 8) == 0) fprintf(stderr, "\n%03d: ", j); fprintf(stderr, "%8x ", (*result)[j]); } putc('\n', stderr); } #endif return php->type; }