On Fri, Jun 26, 2020 at 10:23:42AM +0200, Martin Pieuchot wrote:
> On 25/06/20(Thu) 19:56, Vitaliy Makkoveev wrote:
> > Updated diff. 
> > 
> > OpenBSD uses 16 bit counter for allocate interface indexes. So we can't
> > store index in session and be sure if_get(9) returned `ifnet' is our
> > original `ifnet'.
> 
> Why not?  The point of if_get(9) is to be sure.  If that doesn't work
> for whatever reason then the if_get(9) interface has to be fixed.  Which
> case doesn't work for you?  Do you have a reproducer?  
> 
> How does sessions stay around if their corresponding interface is
> destroyed?

We have `pipexinq' and `pipexoutq' which can store pointers to session.
pipexintr() process these queues. pipexintr() and
pipex_destroy_session() are *always* different context. This mean we
*can't* free pipex(4) session without be sure there is no reference to
this session in `pipexinq' or `pipexoutq'. Elsewhere this will cause use
afret free issue. Look please at net/pipex.c:846. The way pppx(4)
destroy sessions is wrong. While pppac(4) destroys sessions by
pipex_iface_fini() it's also wrong. Because we don't check `pipexinq'
and `pipexoutq' state. I'am said it again and again.

Look at net/pipex.c:777

Static int
pipex_ppp_enqueue(struct mbuf *m0, struct pipex_session *session,
    struct mbuf_queue *mq)
{
        m0->m_pkthdr.ph_cookie = session; /* [1] */
        /* XXX need to support other protocols */
        m0->m_pkthdr.ph_ppp_proto = PPP_IP;

        if (mq_enqueue(mq, m0) != 0) /* [2] */
                return (1);

        schednetisr(NETISR_PIPEX); /* [3] */

        return (0);
}

and net/pipex.c:643

Static int
pipex_destroy_session(struct pipex_session *session)
{
        struct radix_node *rn;

        /* remove from radix tree and hash chain */
        NET_ASSERT_LOCKED();

        if (!in_nullhost(session->ip_address.sin_addr)) {
                rn = rn_delete(&session->ip_address, &session->ip_netmask,
                    pipex_rd_head4, (struct radix_node *)session);
                KASSERT(rn != NULL);
        }

        pipex_unlink_session(session);
        pool_put(&pipex_session_pool, session); /* [4] */

        return (0);
}

and net/pipex.c:720

void
pipexintr(void)
{
        struct pipex_session *pkt_session;
        u_int16_t proto;
        struct mbuf *m;
        struct mbuf_list ml;

        /* ppp output */
        mq_delist(&pipexoutq, &ml);
        while ((m = ml_dequeue(&ml)) != NULL) {
                pkt_session = m->m_pkthdr.ph_cookie; /* [5] */
                /* ... */
                pipex_ppp_output(m, pkt_session, proto);
                /* [7] */
        }
        /* ... */
        mq_delist(&pipexinq, &ml);
        while ((m = ml_dequeue(&ml)) != NULL) {
                pkt_session = m->m_pkthdr.ph_cookie; /* [6] */
                /* ... */
                pipex_ppp_input(m, pkt_session, 0);
                /* [8] */
        }
}

and net/pipex.c:808

Static void
pipex_timer(void *ignored_arg)
{
        struct pipex_session *session, *session_tmp;

        timeout_add_sec(&pipex_timer_ch, pipex_prune);

        NET_LOCK();
        /* walk through */
        LIST_FOREACH_SAFE(session, &pipex_session_list, session_list,
            session_tmp) {
                switch (session->state) {
                /* ... */
                case PIPEX_STATE_CLOSED:
                        /*
                         * mbuf queued in pipexinq or pipexoutq may have a
                         * refererce to this session.
                         */
                        /* [9] */
                        if (!mq_empty(&pipexinq) || !mq_empty(&pipexoutq))
                                continue;

                        pipex_destroy_session(session);
                        break;
                /* ... */
        }

1. You pass pointer to mbuf
2. You enqueue this mbuf to `pipexinq' or `pipexoutq'
3. You call task_add();

Is it * always * garanteed that netisr() will be called here? What
will be happened if netisr will be called after [4]?

4. You destroy session.

5. You use memory pointed by `pkt_session' It's netisr context. Are you
shure `ph_cookie' is still valid?
6. You use memory pointed by `pkt_session' It's netisr context. Are you
shure `ph_cookie' is still valid?

Also we are going to move pipexintr() out of KERNEL_LOCK() and
NET_LOCK(). This means pipex_destroy_session() and pipexintr() will be
executed simultaneously. How to protect `ph_cookie'?

I guess to use reference counters for pipex(4) sessions. We will grab
extra reference before [1] and release this reference at [7] or [8].

I see absolutele *no* reason to sleep at [9]. So refcnt_finalize() is
not applicable here. I want to use reference counters in the way of
file(9). This means session can live after userland destroy it because
it has reference in `pipexinq' or `pipexoutq'.

But userland will not only release it's reference to session. It will
destroy `ifnet' too. And you will have session referenced by `pipexinq'
or `pipexoutq' but it's `ifnet' is already destoyed.

The diff I posted should prevent this.

you can run something like the command below:
while true ; do ifconfig tun0 create ; ifconfig tun0 destroy ; done

This command will overflow interface index counters and it will be start
from null. It's ok. It will not overwrite indexes for existing `ifnet's.
But if you are store session index as you suggested to store it's
absolutely *NOT* true that if_get(9) will return NULL if the session you
want to get is done.

The case is:
1. The session is in pipexinq' or `pipexoutq'.
2. pppx(4) or pppac(4) already release its reference and corresponding
        `ifnet'.
3. pipexintr() want to access to this session erferenced by index:
         ifp = if_get(session->if_index);
         `ifp' can be NULL here, you are lucky.
         or NOT NULL here, that means you grab `ifnet' owned by someone
         else.

The way I suggest to go albsolutely exclude this case.

Reply via email to