Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 29 Mar 2014 10:56:32 +0800
From:      David Xu <davidxu@freebsd.org>
To:        Don Lewis <truckman@FreeBSD.org>
Cc:        mjguzik@gmail.com, mjg@FreeBSD.org, svn-src-all@FreeBSD.org, svn-src-head@FreeBSD.org, src-committers@FreeBSD.org, kostikbel@gmail.com
Subject:   Re: svn commit: r263755 - head/sys/kern
Message-ID:  <53363660.3080404@freebsd.org>
In-Reply-To: <201403281613.s2SGDKpk010871@gw.catspoiler.org>
References:  <201403281613.s2SGDKpk010871@gw.catspoiler.org>

next in thread | previous in thread | raw e-mail | index | archive | help

On 2014/03/29 00:13, Don Lewis wrote:
> On 28 Mar, David Xu wrote:
>> On 2014/03/28 06:31, Don Lewis wrote:
>>> On 27 Mar, Konstantin Belousov wrote:
>>>> On Thu, Mar 27, 2014 at 04:05:12PM +0100, Mateusz Guzik wrote:
>>>>> On Thu, Mar 27, 2014 at 03:58:19PM +0100, Mateusz Guzik wrote:
>>>>>> On Thu, Mar 27, 2014 at 04:46:57PM +0800, David Xu wrote:
>>>>>>> On 2014/03/27 16:37, Mateusz Guzik wrote:
>>>>>>>> On Thu, Mar 27, 2014 at 03:45:17PM +0800, David Xu wrote:
>>>>>>>>> I think the async process pointer can be cleared when a process exits
>>>>>>>>> by registering an event handler. please see attached patch.
>>>>>>>>>
>>>>>>>> Sure, but I'm not very fond of this solution.
>>>>>>>>
>>>>>>>> This is a rather obscure bug you wont hit unless you explicitly try,
>>>>>>>> and even then you need root privs by default.
>>>>>>>>
>>>>>>> OK, but I don't like the bug exists in kernel. It is not obscure for me,
>>>>>>> I can run "shutdown now" command, and insert a device, and then the
>>>>>>> kernel will write garbage data into freed memory space.
>>>>>>>
>>>>>> Not sure what you mean. devd does not use this feature, and even if it
>>>>>> did async_proc is cleared on close, which happens while signal delivery
>>>>>> is still legal.
>>>>>>
>>>>>> That said, you are not going to encounter this bug unless you code
>>>>>> something up to specifically trigger it.
>>>>>>
>>>>>> fwiw, I think we could axe this feature if there was no way to fix it
>>>>>> without introducing a check for every process.
>>>>>>
>>>>>>>> As such writing a callback function which will be executed for all exiting
>>>>>>>> processes seems unjustified for me.
>>>>>>>>
>>>>>>>> Ideally we would get some mechanism which would allow to register
>>>>>>>> callbacks for events related to given entity. Then it could be used to
>>>>>>>> provide a "call this function when process p exits", amongst other things.
>>>>>>>>
>>>>>>> Yes, but the callback itself is cheap enough and is not worth to be
>>>>>>> per-entity entry.
>>>>>>>
>>>>>> There is other code in the kernel which would benefit from such
>>>>>> functionality - dev/syscons/scmouse, dev/vt/vt_core.c, aio and possibly
>>>>>> more.
>>>>>>
>>>>>> As such I think this is worth pursuing.
>>>>>>
>>>>> We can hack around this one the way the other code is doing - apart from
>>>>> from proc pointer you store pid and then compare result of pfind(pid).
>>>>>
>>>>> This is still buggy as both proc and pid pointer can be recycled and end
>>>>> up being the same (but you have an entrirely new process).
>>>>>
>>>>> However, then in absolutely worst cae you send SIGIO to incorrect
>>>>> process, always an existing process so no more corruption.
>>>>>
>>>>> Would you be ok with such hack for the time being?
>>>> Isn't p_sigiolist and fsetown(9) already provide the neccessary registration
>>>> and cleanup on the process exit ?  The KPI might require some generalization,
>>>> but I think that the mechanism itself is enough.
>>> That's the correct mechanism, but it's not being used here.
>>>
>>> Something like the following untested patch should do the trick:
>>>
>>> Index: sys/kern/subr_bus.c
>>> ===================================================================
>>> --- sys/kern/subr_bus.c	(revision 263289)
>>> +++ sys/kern/subr_bus.c	(working copy)
>>> @@ -402,7 +402,7 @@
>>>    	struct cv cv;
>>>    	struct selinfo sel;
>>>    	struct devq devq;
>>> -	struct proc *async_proc;
>>> +	struct sigio *sigio;
>>>    } devsoftc;
>>>
>>>    static struct cdev *devctl_dev;
>>> @@ -425,7 +425,7 @@
>>>    	/* move to init */
>>>    	devsoftc.inuse = 1;
>>>    	devsoftc.nonblock = 0;
>>> -	devsoftc.async_proc = NULL;
>>> +	funsetown(&devsoftc.sigio);
>>>    	return (0);
>>>    }
>>>
>>> @@ -436,7 +436,7 @@
>>>    	mtx_lock(&devsoftc.mtx);
>>>    	cv_broadcast(&devsoftc.cv);
>>>    	mtx_unlock(&devsoftc.mtx);
>>> -	devsoftc.async_proc = NULL;
>>> +	funsetown(&devsoftc.sigio);
>>>    	return (0);
>>>    }
>>>
>>> @@ -492,9 +492,8 @@
>>>    		return (0);
>>>    	case FIOASYNC:
>>>    		if (*(int*)data)
>>> -			devsoftc.async_proc = td->td_proc;
>>> -		else
>>> -			devsoftc.async_proc = NULL;
>>> +			return (fsetown(td->td_proc->p_pid, &devsoftc.sigio));
>>> +		funsetown(&devsoftc.sigio);
>>>    		return (0);
>>>
>>>    		/* (un)Support for other fcntl() calls. */
>>> @@ -546,7 +545,6 @@
>>>    devctl_queue_data_f(char *data, int flags)
>>>    {
>>>    	struct dev_event_info *n1 = NULL, *n2 = NULL;
>>> -	struct proc *p;
>>>
>>>    	if (strlen(data) == 0)
>>>    		goto out;
>>> @@ -576,12 +574,8 @@
>>>    	cv_broadcast(&devsoftc.cv);
>>>    	mtx_unlock(&devsoftc.mtx);
>>>    	selwakeup(&devsoftc.sel);
>>> -	p = devsoftc.async_proc;
>>> -	if (p != NULL) {
>>> -		PROC_LOCK(p);
>>> -		kern_psignal(p, SIGIO);
>>> -		PROC_UNLOCK(p);
>>> -	}
>>> +	if (devsoftc.sigio != NULL)
>>> +		pgsigio(&devsoftc.sigio, SIGIO, 0);
>>>    	return;
>>>    out:
>>>    	/*
>>>
>>>
>> I have tweaked it a bit, is this okay ?
>>
>> # HG changeset patch
>> # Parent 53b614ff2cae108f27e4475989d3a86997017268
>>
>> diff -r 53b614ff2cae sys/kern/subr_bus.c
>> --- a/sys/kern/subr_bus.c	Thu Mar 27 10:03:50 2014 +0800
>> +++ b/sys/kern/subr_bus.c	Fri Mar 28 14:22:29 2014 +0800
>> @@ -391,11 +391,12 @@
>>    	int	inuse;
>>    	int	nonblock;
>>    	int	queued;
>> +	int	async;
>>    	struct mtx mtx;
>>    	struct cv cv;
>>    	struct selinfo sel;
>>    	struct devq devq;
>> -	struct proc *async_proc;
>> +	struct sigio *sigio;
>>    } devsoftc;
>>
>>    static struct cdev *devctl_dev;
>> @@ -422,7 +423,8 @@
>>    	/* move to init */
>>    	devsoftc.inuse = 1;
>>    	devsoftc.nonblock = 0;
>> -	devsoftc.async_proc = NULL;
>> +	devsoftc.async = 0;
>> +	devsoftc.sigio = NULL;
>>    	mtx_unlock(&devsoftc.mtx);
>>    	return (0);
>>    }
>> @@ -433,8 +435,9 @@
>>
>>    	mtx_lock(&devsoftc.mtx);
>>    	devsoftc.inuse = 0;
>> -	devsoftc.async_proc = NULL;
>> +	devsoftc.async = 0;
>>    	cv_broadcast(&devsoftc.cv);
>> +	funsetown(&devsoftc.sigio);
>>    	mtx_unlock(&devsoftc.mtx);
>>    	return (0);
>>    }
>> @@ -490,33 +493,21 @@
>>    			devsoftc.nonblock = 0;
>>    		return (0);
>>    	case FIOASYNC:
>> -		/*
>> -		 * FIXME:
>> -		 * Since this is a simple assignment there is no guarantee that
>> -		 * devsoftc.async_proc consumers will get a valid pointer.
>> -		 *
>> -		 * Example scenario where things break (processes A and B):
>> -		 * 1. A opens devctl
>> -		 * 2. A sends fd to B
>> -		 * 3. B sets itself as async_proc
>> -		 * 4. B exits
>> -		 *
>> -		 * However, normally this requires root privileges and the only
>> -		 * in-tree consumer does not behave in a dangerous way so the
>> -		 * issue is not critical.
>> -		 */
>>    		if (*(int*)data)
>> -			devsoftc.async_proc = td->td_proc;
>> +			devsoftc.async = 1;
>>    		else
>> -			devsoftc.async_proc = NULL;
>> +			devsoftc.async = 0;
>> +		return (0);
>> +	case FIOSETOWN:
>> +		return fsetown(*(int *)data, &devsoftc.sigio);
>> +	case FIOGETOWN:
>> +		*(int *)data = fgetown(&devsoftc.sigio);
>>    		return (0);
>>
>>    		/* (un)Support for other fcntl() calls. */
>>    	case FIOCLEX:
>>    	case FIONCLEX:
>>    	case FIONREAD:
>> -	case FIOSETOWN:
>> -	case FIOGETOWN:
>>    	default:
>>    		break;
>>    	}
>> @@ -560,7 +551,6 @@
>>    devctl_queue_data_f(char *data, int flags)
>>    {
>>    	struct dev_event_info *n1 = NULL, *n2 = NULL;
>> -	struct proc *p;
>>
>>    	if (strlen(data) == 0)
>>    		goto out;
>> @@ -590,13 +580,8 @@
>>    	cv_broadcast(&devsoftc.cv);
>>    	mtx_unlock(&devsoftc.mtx);
>>    	selwakeup(&devsoftc.sel);
>> -	/* XXX see a comment in devioctl */
>> -	p = devsoftc.async_proc;
>> -	if (p != NULL) {
>> -		PROC_LOCK(p);
>> -		kern_psignal(p, SIGIO);
>> -		PROC_UNLOCK(p);
>> -	}
>> +	if (devsoftc.async && devsoftc.sigio != NULL)
>> +		pgsigio(&devsoftc.sigio, SIGIO, 0);
>>    	return;
>>    out:
>>    	/*
>>
>>
> That makes it work more like the other users of fsetown(), which is
> probably a good thing.  The downside is that two syscalls are needed to
> activate it, which I was trying to avoid that with my patch.  I noticed
> that logopen() in subr_log.c unconditionally calls fsetown(), which
> would avoid the need for an extra syscall.

This is incompatible with POSIX, at least "man 2 open" does not say
a process opened the file will immediately be the owner. Two syscalls
are necessary, because fsetown is expensive, so using a light weight
fioasync to temporally disable and enable it is more flexible.

>   That also avoids the direct
> manipulation of the pointer in your patch, which makes me nervous about
> the possibility of a leak.
fsetown() does already guard the leak problem.

> I wonder if FIOASYNC should fail if
> 	td->td_proc != devsoftc.sigio.sio_proc
> (or the equivalent for other instances) to prevent a process from
> maniuplating the async flag for a device "owned" by another process.  I
> think this check would need to be wrapped in SIGIO_LOCK()/SIGIO_UNLOCK()
> to be safe.
>
>
>
most code in the kernel do not check ownership, I can not find one
in kern/ sub-directory.




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?53363660.3080404>