Ejemplo n.º 1
0
Archivo: pdu.c Proyecto: rwongone/pcp
/* 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;
}
Ejemplo n.º 2
0
Archivo: pdu.c Proyecto: goodwinos/pcp
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;
}