static void lo_release(struct gendisk *disk, fmode_t mode)
static void __lo_release(struct loop_device *lo)
 {
 	int err;

 	if (atomic_dec_return(&lo->lo_refcnt))
		return;

	mutex_lock(&lo->lo_ctl_mutex);
	if (lo->lo_flags & LO_FLAGS_AUTOCLEAR) {
		/*
		 * In autoclear mode, stop the loop thread
		 * and remove configuration after last close.
		 */
		err = loop_clr_fd(lo);
		if (!err)
			return;
	} else if (lo->lo_state == Lo_bound) {
		/*
		 * Otherwise keep thread (if running) and config,
		 * but flush possible ongoing bios in thread.
		 */
		blk_mq_freeze_queue(lo->lo_queue);
		blk_mq_unfreeze_queue(lo->lo_queue);
	}

 	mutex_unlock(&lo->lo_ctl_mutex);
 }


powerpc/tm: Flush TM only if CPU has TM feature

Commit cd63f3c ("powerpc/tm: Fix saving of TM SPRs in core dump")
added code to access TM SPRs in flush_tmregs_to_thread(). However
flush_tmregs_to_thread() does not check if TM feature is available on
CPU before trying to access TM SPRs in order to copy live state to
thread structures. flush_tmregs_to_thread() is indeed guarded by
CONFIG_PPC_TRANSACTIONAL_MEM but it might be the case that kernel
was compiled with CONFIG_PPC_TRANSACTIONAL_MEM enabled and ran on
a CPU without TM feature available, thus rendering the execution
of TM instructions that are treated by the CPU as illegal instructions.

The fix is just to add proper checking in flush_tmregs_to_thread()
if CPU has the TM feature before accessing any TM-specific resource,
returning immediately if TM is no available on the CPU. Adding
that checking in flush_tmregs_to_thread() instead of in places
where it is called, like in vsr_get() and vsr_set(), is better because
avoids the same problem cropping up elsewhere.

Cc: stable@vger.kernel.org # v4.13+
Fixes: cd63f3c ("powerpc/tm: Fix saving of TM SPRs in core dump")
Signed-off-by: Gustavo Romero <gromero@linux.vnet.ibm.com>
Reviewed-by: Cyril Bur <cyrilbur@gmail.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>

static void flush_tmregs_to_thread(struct task_struct *tsk)
{
	/*
	 * If task is not current, it will have been flushed already to
	 * it's thread_struct during __switch_to().
	 *
	 * A reclaim flushes ALL the state or if not in TM save TM SPRs
 	 * in the appropriate thread structures from live.
 	 */

	if (tsk != current)
 		return;

 	if (MSR_TM_SUSPENDED(mfmsr())) {
		tm_reclaim_current(TM_CAUSE_SIGNAL);
	} else {
		tm_enable();
		tm_save_sprs(&(tsk->thread));
	}
}

In the flush_tmregs_to_thread function in arch/powerpc/kernel/ptrace.c in the Linux kernel before 4.13.5, a guest kernel crash can be triggered from unprivileged userspace during a core dump on a POWER host due to a missing processor feature check and an erroneous use of transactional memory (TM) instructions in the core dump path, leading to a denial of service.


static void flush_tmregs_to_thread(struct task_struct *tsk)
{
	/*
	 * If task is not current, it will have been flushed already to
	 * it's thread_struct during __switch_to().
	 *
	 * A reclaim flushes ALL the state or if not in TM save TM SPRs
 	 * in the appropriate thread structures from live.
 	 */

	if ((!cpu_has_feature(CPU_FTR_TM)) || (tsk != current))
 		return;

 	if (MSR_TM_SUSPENDED(mfmsr())) {
		tm_reclaim_current(TM_CAUSE_SIGNAL);
	} else {
		tm_enable();
		tm_save_sprs(&(tsk->thread));
	}
}


netfilter: add back stackpointer size checks

The rationale for removing the check is only correct for rulesets
generated by ip(6)tables.

In iptables, a jump can only occur to a user-defined chain, i.e.
because we size the stack based on number of user-defined chains we
cannot exceed stack size.

However, the underlying binary format has no such restriction,
and the validation step only ensures that the jump target is a
valid rule start point.

IOW, its possible to build a rule blob that has no user-defined
chains but does contain a jump.

If this happens, no jump stack gets allocated and crash occurs
because no jumpstack was allocated.

Fixes: 7814b6ec6d0d6 ("netfilter: xtables: don't save/restore jumpstack offset")
Reported-by: syzbot+e783f671527912cd9403@syzkaller.appspotmail.com
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

ipt_do_table(struct sk_buff *skb,
	     const struct nf_hook_state *state,
	     struct xt_table *table)
{
	unsigned int hook = state->hook;
	static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
	const struct iphdr *ip;
	/* Initializing verdict to NF_DROP keeps gcc happy. */
	unsigned int verdict = NF_DROP;
	const char *indev, *outdev;
	const void *table_base;
	struct ipt_entry *e, **jumpstack;
	unsigned int stackidx, cpu;
	const struct xt_table_info *private;
	struct xt_action_param acpar;
	unsigned int addend;

	/* Initialization */
	stackidx = 0;
	ip = ip_hdr(skb);
	indev = state->in ? state->in->name : nulldevname;
	outdev = state->out ? state->out->name : nulldevname;
	/* We handle fragments by dealing with the first fragment as
	 * if it was a normal packet.  All other fragments are treated
	 * normally, except that they will NEVER match rules that ask
	 * things we don't know, ie. tcp syn flag or ports).  If the
	 * rule is also a fragment-specific rule, non-fragments won't
	 * match it. */
	acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
	acpar.thoff   = ip_hdrlen(skb);
	acpar.hotdrop = false;
	acpar.state   = state;

	WARN_ON(!(table->valid_hooks & (1 << hook)));
	local_bh_disable();
	addend = xt_write_recseq_begin();
	private = READ_ONCE(table->private); /* Address dependency. */
	cpu        = smp_processor_id();
	table_base = private->entries;
	jumpstack  = (struct ipt_entry **)private->jumpstack[cpu];

	/* Switch to alternate jumpstack if we're being invoked via TEE.
	 * TEE issues XT_CONTINUE verdict on original skb so we must not
	 * clobber the jumpstack.
	 *
	 * For recursion via REJECT or SYNPROXY the stack will be clobbered
	 * but it is no problem since absolute verdict is issued by these.
	 */
	if (static_key_false(&xt_tee_enabled))
		jumpstack += private->stacksize * __this_cpu_read(nf_skb_duplicated);

	e = get_entry(table_base, private->hook_entry[hook]);

	do {
		const struct xt_entry_target *t;
		const struct xt_entry_match *ematch;
		struct xt_counters *counter;

		WARN_ON(!e);
		if (!ip_packet_match(ip, indev, outdev,
		    &e->ip, acpar.fragoff)) {
 no_match:
			e = ipt_next_entry(e);
			continue;
		}

		xt_ematch_foreach(ematch, e) {
			acpar.match     = ematch->u.kernel.match;
			acpar.matchinfo = ematch->data;
			if (!acpar.match->match(skb, &acpar))
				goto no_match;
		}

		counter = xt_get_this_cpu_counter(&e->counters);
		ADD_COUNTER(*counter, skb->len, 1);

		t = ipt_get_target(e);
		WARN_ON(!t->u.kernel.target);

#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
		/* The packet is traced: log it */
		if (unlikely(skb->nf_trace))
			trace_packet(state->net, skb, hook, state->in,
				     state->out, table->name, private, e);
#endif
		/* Standard target? */
		if (!t->u.kernel.target->target) {
			int v;

			v = ((struct xt_standard_target *)t)->verdict;
			if (v < 0) {
				/* Pop from stack? */
				if (v != XT_RETURN) {
					verdict = (unsigned int)(-v) - 1;
					break;
				}
				if (stackidx == 0) {
					e = get_entry(table_base,
					    private->underflow[hook]);
				} else {
					e = jumpstack[--stackidx];
					e = ipt_next_entry(e);
				}
 				continue;
 			}
 			if (table_base + v != ipt_next_entry(e) &&
			    !(e->ip.flags & IPT_F_GOTO))
 				jumpstack[stackidx++] = e;

 			e = get_entry(table_base, v);
 			continue;
		}

		acpar.target   = t->u.kernel.target;
		acpar.targinfo = t->data;

		verdict = t->u.kernel.target->target(skb, &acpar);
		if (verdict == XT_CONTINUE) {
			/* Target might have changed stuff. */
			ip = ip_hdr(skb);
			e = ipt_next_entry(e);
		} else {
			/* Verdict */
			break;
		}
	} while (!acpar.hotdrop);

	xt_write_recseq_end(addend);
	local_bh_enable();

	if (acpar.hotdrop)
		return NF_DROP;
	else return verdict;
}

The netfilter subsystem in the Linux kernel through 4.15.7 mishandles the case of a rule blob that contains a jump but lacks a user-defined chain, which allows local users to cause a denial of service (NULL pointer dereference) by leveraging the CAP_NET_RAW or CAP_NET_ADMIN capability, related to arpt_do_table in net/ipv4/netfilter/arp_tables.c, ipt_do_table in net/ipv4/netfilter/ip_tables.c, and ip6t_do_table in net/ipv6/netfilter/ip6_tables.c.


ipt_do_table(struct sk_buff *skb,
	     const struct nf_hook_state *state,
	     struct xt_table *table)
{
	unsigned int hook = state->hook;
	static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
	const struct iphdr *ip;
	/* Initializing verdict to NF_DROP keeps gcc happy. */
	unsigned int verdict = NF_DROP;
	const char *indev, *outdev;
	const void *table_base;
	struct ipt_entry *e, **jumpstack;
	unsigned int stackidx, cpu;
	const struct xt_table_info *private;
	struct xt_action_param acpar;
	unsigned int addend;

	/* Initialization */
	stackidx = 0;
	ip = ip_hdr(skb);
	indev = state->in ? state->in->name : nulldevname;
	outdev = state->out ? state->out->name : nulldevname;
	/* We handle fragments by dealing with the first fragment as
	 * if it was a normal packet.  All other fragments are treated
	 * normally, except that they will NEVER match rules that ask
	 * things we don't know, ie. tcp syn flag or ports).  If the
	 * rule is also a fragment-specific rule, non-fragments won't
	 * match it. */
	acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
	acpar.thoff   = ip_hdrlen(skb);
	acpar.hotdrop = false;
	acpar.state   = state;

	WARN_ON(!(table->valid_hooks & (1 << hook)));
	local_bh_disable();
	addend = xt_write_recseq_begin();
	private = READ_ONCE(table->private); /* Address dependency. */
	cpu        = smp_processor_id();
	table_base = private->entries;
	jumpstack  = (struct ipt_entry **)private->jumpstack[cpu];

	/* Switch to alternate jumpstack if we're being invoked via TEE.
	 * TEE issues XT_CONTINUE verdict on original skb so we must not
	 * clobber the jumpstack.
	 *
	 * For recursion via REJECT or SYNPROXY the stack will be clobbered
	 * but it is no problem since absolute verdict is issued by these.
	 */
	if (static_key_false(&xt_tee_enabled))
		jumpstack += private->stacksize * __this_cpu_read(nf_skb_duplicated);

	e = get_entry(table_base, private->hook_entry[hook]);

	do {
		const struct xt_entry_target *t;
		const struct xt_entry_match *ematch;
		struct xt_counters *counter;

		WARN_ON(!e);
		if (!ip_packet_match(ip, indev, outdev,
		    &e->ip, acpar.fragoff)) {
 no_match:
			e = ipt_next_entry(e);
			continue;
		}

		xt_ematch_foreach(ematch, e) {
			acpar.match     = ematch->u.kernel.match;
			acpar.matchinfo = ematch->data;
			if (!acpar.match->match(skb, &acpar))
				goto no_match;
		}

		counter = xt_get_this_cpu_counter(&e->counters);
		ADD_COUNTER(*counter, skb->len, 1);

		t = ipt_get_target(e);
		WARN_ON(!t->u.kernel.target);

#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
		/* The packet is traced: log it */
		if (unlikely(skb->nf_trace))
			trace_packet(state->net, skb, hook, state->in,
				     state->out, table->name, private, e);
#endif
		/* Standard target? */
		if (!t->u.kernel.target->target) {
			int v;

			v = ((struct xt_standard_target *)t)->verdict;
			if (v < 0) {
				/* Pop from stack? */
				if (v != XT_RETURN) {
					verdict = (unsigned int)(-v) - 1;
					break;
				}
				if (stackidx == 0) {
					e = get_entry(table_base,
					    private->underflow[hook]);
				} else {
					e = jumpstack[--stackidx];
					e = ipt_next_entry(e);
				}
 				continue;
 			}
 			if (table_base + v != ipt_next_entry(e) &&
			    !(e->ip.flags & IPT_F_GOTO)) {
				if (unlikely(stackidx >= private->stacksize)) {
					verdict = NF_DROP;
					break;
				}
 				jumpstack[stackidx++] = e;
			}

 			e = get_entry(table_base, v);
 			continue;
		}

		acpar.target   = t->u.kernel.target;
		acpar.targinfo = t->data;

		verdict = t->u.kernel.target->target(skb, &acpar);
		if (verdict == XT_CONTINUE) {
			/* Target might have changed stuff. */
			ip = ip_hdr(skb);
			e = ipt_next_entry(e);
		} else {
			/* Verdict */
			break;
		}
	} while (!acpar.hotdrop);

	xt_write_recseq_end(addend);
	local_bh_enable();

	if (acpar.hotdrop)
		return NF_DROP;
	else return verdict;
}


unsigned int arpt_do_table(struct sk_buff *skb,
			   const struct nf_hook_state *state,
			   struct xt_table *table)
{
	unsigned int hook = state->hook;
	static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
	unsigned int verdict = NF_DROP;
	const struct arphdr *arp;
	struct arpt_entry *e, **jumpstack;
	const char *indev, *outdev;
	const void *table_base;
	unsigned int cpu, stackidx = 0;
	const struct xt_table_info *private;
	struct xt_action_param acpar;
	unsigned int addend;

	if (!pskb_may_pull(skb, arp_hdr_len(skb->dev)))
		return NF_DROP;

	indev = state->in ? state->in->name : nulldevname;
	outdev = state->out ? state->out->name : nulldevname;

	local_bh_disable();
	addend = xt_write_recseq_begin();
	private = READ_ONCE(table->private); /* Address dependency. */
	cpu     = smp_processor_id();
	table_base = private->entries;
	jumpstack  = (struct arpt_entry **)private->jumpstack[cpu];

	/* No TEE support for arptables, so no need to switch to alternate
	 * stack.  All targets that reenter must return absolute verdicts.
	 */
	e = get_entry(table_base, private->hook_entry[hook]);

	acpar.state   = state;
	acpar.hotdrop = false;

	arp = arp_hdr(skb);
	do {
		const struct xt_entry_target *t;
		struct xt_counters *counter;

		if (!arp_packet_match(arp, skb->dev, indev, outdev, &e->arp)) {
			e = arpt_next_entry(e);
			continue;
		}

		counter = xt_get_this_cpu_counter(&e->counters);
		ADD_COUNTER(*counter, arp_hdr_len(skb->dev), 1);

		t = arpt_get_target_c(e);

		/* Standard target? */
		if (!t->u.kernel.target->target) {
			int v;

			v = ((struct xt_standard_target *)t)->verdict;
			if (v < 0) {
				/* Pop from stack? */
				if (v != XT_RETURN) {
					verdict = (unsigned int)(-v) - 1;
					break;
				}
				if (stackidx == 0) {
					e = get_entry(table_base,
						      private->underflow[hook]);
				} else {
					e = jumpstack[--stackidx];
					e = arpt_next_entry(e);
				}
				continue;
 			}
 			if (table_base + v
 			    != arpt_next_entry(e)) {
 				jumpstack[stackidx++] = e;
 			}

			e = get_entry(table_base, v);
			continue;
		}

		acpar.target   = t->u.kernel.target;
		acpar.targinfo = t->data;
		verdict = t->u.kernel.target->target(skb, &acpar);

		if (verdict == XT_CONTINUE) {
			/* Target might have changed stuff. */
			arp = arp_hdr(skb);
			e = arpt_next_entry(e);
		} else {
			/* Verdict */
			break;
		}
	} while (!acpar.hotdrop);
	xt_write_recseq_end(addend);
	local_bh_enable();

	if (acpar.hotdrop)
		return NF_DROP;
	else
		return verdict;
}


unsigned int arpt_do_table(struct sk_buff *skb,
			   const struct nf_hook_state *state,
			   struct xt_table *table)
{
	unsigned int hook = state->hook;
	static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
	unsigned int verdict = NF_DROP;
	const struct arphdr *arp;
	struct arpt_entry *e, **jumpstack;
	const char *indev, *outdev;
	const void *table_base;
	unsigned int cpu, stackidx = 0;
	const struct xt_table_info *private;
	struct xt_action_param acpar;
	unsigned int addend;

	if (!pskb_may_pull(skb, arp_hdr_len(skb->dev)))
		return NF_DROP;

	indev = state->in ? state->in->name : nulldevname;
	outdev = state->out ? state->out->name : nulldevname;

	local_bh_disable();
	addend = xt_write_recseq_begin();
	private = READ_ONCE(table->private); /* Address dependency. */
	cpu     = smp_processor_id();
	table_base = private->entries;
	jumpstack  = (struct arpt_entry **)private->jumpstack[cpu];

	/* No TEE support for arptables, so no need to switch to alternate
	 * stack.  All targets that reenter must return absolute verdicts.
	 */
	e = get_entry(table_base, private->hook_entry[hook]);

	acpar.state   = state;
	acpar.hotdrop = false;

	arp = arp_hdr(skb);
	do {
		const struct xt_entry_target *t;
		struct xt_counters *counter;

		if (!arp_packet_match(arp, skb->dev, indev, outdev, &e->arp)) {
			e = arpt_next_entry(e);
			continue;
		}

		counter = xt_get_this_cpu_counter(&e->counters);
		ADD_COUNTER(*counter, arp_hdr_len(skb->dev), 1);

		t = arpt_get_target_c(e);

		/* Standard target? */
		if (!t->u.kernel.target->target) {
			int v;

			v = ((struct xt_standard_target *)t)->verdict;
			if (v < 0) {
				/* Pop from stack? */
				if (v != XT_RETURN) {
					verdict = (unsigned int)(-v) - 1;
					break;
				}
				if (stackidx == 0) {
					e = get_entry(table_base,
						      private->underflow[hook]);
				} else {
					e = jumpstack[--stackidx];
					e = arpt_next_entry(e);
				}
				continue;
 			}
 			if (table_base + v
 			    != arpt_next_entry(e)) {
				if (unlikely(stackidx >= private->stacksize)) {
					verdict = NF_DROP;
					break;
				}
 				jumpstack[stackidx++] = e;
 			}

			e = get_entry(table_base, v);
			continue;
		}

		acpar.target   = t->u.kernel.target;
		acpar.targinfo = t->data;
		verdict = t->u.kernel.target->target(skb, &acpar);

		if (verdict == XT_CONTINUE) {
			/* Target might have changed stuff. */
			arp = arp_hdr(skb);
			e = arpt_next_entry(e);
		} else {
			/* Verdict */
			break;
		}
	} while (!acpar.hotdrop);
	xt_write_recseq_end(addend);
	local_bh_enable();

	if (acpar.hotdrop)
		return NF_DROP;
	else
		return verdict;
}


ip6t_do_table(struct sk_buff *skb,
	      const struct nf_hook_state *state,
	      struct xt_table *table)
{
	unsigned int hook = state->hook;
	static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
	/* Initializing verdict to NF_DROP keeps gcc happy. */
	unsigned int verdict = NF_DROP;
	const char *indev, *outdev;
	const void *table_base;
	struct ip6t_entry *e, **jumpstack;
	unsigned int stackidx, cpu;
	const struct xt_table_info *private;
	struct xt_action_param acpar;
	unsigned int addend;

	/* Initialization */
	stackidx = 0;
	indev = state->in ? state->in->name : nulldevname;
	outdev = state->out ? state->out->name : nulldevname;
	/* We handle fragments by dealing with the first fragment as
	 * if it was a normal packet.  All other fragments are treated
	 * normally, except that they will NEVER match rules that ask
	 * things we don't know, ie. tcp syn flag or ports).  If the
	 * rule is also a fragment-specific rule, non-fragments won't
	 * match it. */
	acpar.hotdrop = false;
	acpar.state   = state;

	WARN_ON(!(table->valid_hooks & (1 << hook)));

	local_bh_disable();
	addend = xt_write_recseq_begin();
	private = READ_ONCE(table->private); /* Address dependency. */
	cpu        = smp_processor_id();
	table_base = private->entries;
	jumpstack  = (struct ip6t_entry **)private->jumpstack[cpu];

	/* Switch to alternate jumpstack if we're being invoked via TEE.
	 * TEE issues XT_CONTINUE verdict on original skb so we must not
	 * clobber the jumpstack.
	 *
	 * For recursion via REJECT or SYNPROXY the stack will be clobbered
	 * but it is no problem since absolute verdict is issued by these.
	 */
	if (static_key_false(&xt_tee_enabled))
		jumpstack += private->stacksize * __this_cpu_read(nf_skb_duplicated);

	e = get_entry(table_base, private->hook_entry[hook]);

	do {
		const struct xt_entry_target *t;
		const struct xt_entry_match *ematch;
		struct xt_counters *counter;

		WARN_ON(!e);
		acpar.thoff = 0;
		if (!ip6_packet_match(skb, indev, outdev, &e->ipv6,
		    &acpar.thoff, &acpar.fragoff, &acpar.hotdrop)) {
 no_match:
			e = ip6t_next_entry(e);
			continue;
		}

		xt_ematch_foreach(ematch, e) {
			acpar.match     = ematch->u.kernel.match;
			acpar.matchinfo = ematch->data;
			if (!acpar.match->match(skb, &acpar))
				goto no_match;
		}

		counter = xt_get_this_cpu_counter(&e->counters);
		ADD_COUNTER(*counter, skb->len, 1);

		t = ip6t_get_target_c(e);
		WARN_ON(!t->u.kernel.target);

#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
		/* The packet is traced: log it */
		if (unlikely(skb->nf_trace))
			trace_packet(state->net, skb, hook, state->in,
				     state->out, table->name, private, e);
#endif
		/* Standard target? */
		if (!t->u.kernel.target->target) {
			int v;

			v = ((struct xt_standard_target *)t)->verdict;
			if (v < 0) {
				/* Pop from stack? */
				if (v != XT_RETURN) {
					verdict = (unsigned int)(-v) - 1;
					break;
				}
				if (stackidx == 0)
					e = get_entry(table_base,
					    private->underflow[hook]);
				else
					e = ip6t_next_entry(jumpstack[--stackidx]);
				continue;
 			}
 			if (table_base + v != ip6t_next_entry(e) &&
 			    !(e->ipv6.flags & IP6T_F_GOTO)) {
 				jumpstack[stackidx++] = e;
 			}

			e = get_entry(table_base, v);
			continue;
		}

		acpar.target   = t->u.kernel.target;
		acpar.targinfo = t->data;

		verdict = t->u.kernel.target->target(skb, &acpar);
		if (verdict == XT_CONTINUE)
			e = ip6t_next_entry(e);
		else
			/* Verdict */
			break;
	} while (!acpar.hotdrop);

	xt_write_recseq_end(addend);
	local_bh_enable();

	if (acpar.hotdrop)
		return NF_DROP;
	else return verdict;
}


ip6t_do_table(struct sk_buff *skb,
	      const struct nf_hook_state *state,
	      struct xt_table *table)
{
	unsigned int hook = state->hook;
	static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
	/* Initializing verdict to NF_DROP keeps gcc happy. */
	unsigned int verdict = NF_DROP;
	const char *indev, *outdev;
	const void *table_base;
	struct ip6t_entry *e, **jumpstack;
	unsigned int stackidx, cpu;
	const struct xt_table_info *private;
	struct xt_action_param acpar;
	unsigned int addend;

	/* Initialization */
	stackidx = 0;
	indev = state->in ? state->in->name : nulldevname;
	outdev = state->out ? state->out->name : nulldevname;
	/* We handle fragments by dealing with the first fragment as
	 * if it was a normal packet.  All other fragments are treated
	 * normally, except that they will NEVER match rules that ask
	 * things we don't know, ie. tcp syn flag or ports).  If the
	 * rule is also a fragment-specific rule, non-fragments won't
	 * match it. */
	acpar.hotdrop = false;
	acpar.state   = state;

	WARN_ON(!(table->valid_hooks & (1 << hook)));

	local_bh_disable();
	addend = xt_write_recseq_begin();
	private = READ_ONCE(table->private); /* Address dependency. */
	cpu        = smp_processor_id();
	table_base = private->entries;
	jumpstack  = (struct ip6t_entry **)private->jumpstack[cpu];

	/* Switch to alternate jumpstack if we're being invoked via TEE.
	 * TEE issues XT_CONTINUE verdict on original skb so we must not
	 * clobber the jumpstack.
	 *
	 * For recursion via REJECT or SYNPROXY the stack will be clobbered
	 * but it is no problem since absolute verdict is issued by these.
	 */
	if (static_key_false(&xt_tee_enabled))
		jumpstack += private->stacksize * __this_cpu_read(nf_skb_duplicated);

	e = get_entry(table_base, private->hook_entry[hook]);

	do {
		const struct xt_entry_target *t;
		const struct xt_entry_match *ematch;
		struct xt_counters *counter;

		WARN_ON(!e);
		acpar.thoff = 0;
		if (!ip6_packet_match(skb, indev, outdev, &e->ipv6,
		    &acpar.thoff, &acpar.fragoff, &acpar.hotdrop)) {
 no_match:
			e = ip6t_next_entry(e);
			continue;
		}

		xt_ematch_foreach(ematch, e) {
			acpar.match     = ematch->u.kernel.match;
			acpar.matchinfo = ematch->data;
			if (!acpar.match->match(skb, &acpar))
				goto no_match;
		}

		counter = xt_get_this_cpu_counter(&e->counters);
		ADD_COUNTER(*counter, skb->len, 1);

		t = ip6t_get_target_c(e);
		WARN_ON(!t->u.kernel.target);

#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
		/* The packet is traced: log it */
		if (unlikely(skb->nf_trace))
			trace_packet(state->net, skb, hook, state->in,
				     state->out, table->name, private, e);
#endif
		/* Standard target? */
		if (!t->u.kernel.target->target) {
			int v;

			v = ((struct xt_standard_target *)t)->verdict;
			if (v < 0) {
				/* Pop from stack? */
				if (v != XT_RETURN) {
					verdict = (unsigned int)(-v) - 1;
					break;
				}
				if (stackidx == 0)
					e = get_entry(table_base,
					    private->underflow[hook]);
				else
					e = ip6t_next_entry(jumpstack[--stackidx]);
				continue;
 			}
 			if (table_base + v != ip6t_next_entry(e) &&
 			    !(e->ipv6.flags & IP6T_F_GOTO)) {
				if (unlikely(stackidx >= private->stacksize)) {
					verdict = NF_DROP;
					break;
				}
 				jumpstack[stackidx++] = e;
 			}

			e = get_entry(table_base, v);
			continue;
		}

		acpar.target   = t->u.kernel.target;
		acpar.targinfo = t->data;

		verdict = t->u.kernel.target->target(skb, &acpar);
		if (verdict == XT_CONTINUE)
			e = ip6t_next_entry(e);
		else
			/* Verdict */
			break;
	} while (!acpar.hotdrop);

	xt_write_recseq_end(addend);
	local_bh_enable();

	if (acpar.hotdrop)
		return NF_DROP;
	else return verdict;
}


smtp: use the upload buffer size for scratch buffer malloc

... not the read buffer size, as that can be set smaller and thus cause
a buffer overflow! CVE-2018-0500

Reported-by: Peter Wu
Bug: https://curl.haxx.se/docs/adv_2018-70a2.html

CURLcode Curl_smtp_escape_eob(struct connectdata *conn, const ssize_t nread)
{
  /* When sending a SMTP payload we must detect CRLF. sequences making sure
     they are sent as CRLF.. instead, as a . on the beginning of a line will
     be deleted by the server when not part of an EOB terminator and a
     genuine CRLF.CRLF which isn't escaped will wrongly be detected as end of
     data by the server
  */
  ssize_t i;
  ssize_t si;
  struct Curl_easy *data = conn->data;
  struct SMTP *smtp = data->req.protop;
  char *scratch = data->state.scratch;
  char *newscratch = NULL;
  char *oldscratch = NULL;
  size_t eob_sent;

  /* Do we need to allocate a scratch buffer? */
   if(!scratch || data->set.crlf) {
     oldscratch = scratch;

    scratch = newscratch = malloc(2 * data->set.buffer_size);
     if(!newscratch) {
       failf(data, "Failed to alloc scratch buffer!");

       return CURLE_OUT_OF_MEMORY;
     }
   }

   /* Have we already sent part of the EOB? */
   eob_sent = smtp->eob;

  /* This loop can be improved by some kind of Boyer-Moore style of
     approach but that is saved for later... */
  for(i = 0, si = 0; i < nread; i++) {
    if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) {
      smtp->eob++;

      /* Is the EOB potentially the terminating CRLF? */
      if(2 == smtp->eob || SMTP_EOB_LEN == smtp->eob)
        smtp->trailing_crlf = TRUE;
      else
        smtp->trailing_crlf = FALSE;
    }
    else if(smtp->eob) {
      /* A previous substring matched so output that first */
      memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent);
      si += smtp->eob - eob_sent;

      /* Then compare the first byte */
      if(SMTP_EOB[0] == data->req.upload_fromhere[i])
        smtp->eob = 1;
      else
        smtp->eob = 0;

      eob_sent = 0;

      /* Reset the trailing CRLF flag as there was more data */
      smtp->trailing_crlf = FALSE;
    }

    /* Do we have a match for CRLF. as per RFC-5321, sect. 4.5.2 */
    if(SMTP_EOB_FIND_LEN == smtp->eob) {
      /* Copy the replacement data to the target buffer */
      memcpy(&scratch[si], &SMTP_EOB_REPL[eob_sent],
             SMTP_EOB_REPL_LEN - eob_sent);
      si += SMTP_EOB_REPL_LEN - eob_sent;
      smtp->eob = 0;
      eob_sent = 0;
    }
    else if(!smtp->eob)
      scratch[si++] = data->req.upload_fromhere[i];
  }

  if(smtp->eob - eob_sent) {
    /* A substring matched before processing ended so output that now */
    memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent);
    si += smtp->eob - eob_sent;
  }

  /* Only use the new buffer if we replaced something */
  if(si != nread) {
    /* Upload from the new (replaced) buffer instead */
    data->req.upload_fromhere = scratch;

    /* Save the buffer so it can be freed later */
    data->state.scratch = scratch;

    /* Free the old scratch buffer */
    free(oldscratch);

    /* Set the new amount too */
    data->req.upload_present = si;
  }
  else
    free(newscratch);

  return CURLE_OK;
}

Curl_smtp_escape_eob in lib/smtp.c in curl 7.54.1 to and including curl 7.60.0 has a heap-based buffer overflow that might be exploitable by an attacker who can control the data that curl transmits over SMTP with certain settings (i.e., use of a nonstandard --limit-rate argument or CURLOPT_BUFFERSIZE value).


CURLcode Curl_smtp_escape_eob(struct connectdata *conn, const ssize_t nread)
{
  /* When sending a SMTP payload we must detect CRLF. sequences making sure
     they are sent as CRLF.. instead, as a . on the beginning of a line will
     be deleted by the server when not part of an EOB terminator and a
     genuine CRLF.CRLF which isn't escaped will wrongly be detected as end of
     data by the server
  */
  ssize_t i;
  ssize_t si;
  struct Curl_easy *data = conn->data;
  struct SMTP *smtp = data->req.protop;
  char *scratch = data->state.scratch;
  char *newscratch = NULL;
  char *oldscratch = NULL;
  size_t eob_sent;

  /* Do we need to allocate a scratch buffer? */
   if(!scratch || data->set.crlf) {
     oldscratch = scratch;

    scratch = newscratch = malloc(2 * UPLOAD_BUFSIZE);
     if(!newscratch) {
       failf(data, "Failed to alloc scratch buffer!");

       return CURLE_OUT_OF_MEMORY;
     }
   }
  DEBUGASSERT(UPLOAD_BUFSIZE >= nread);

   /* Have we already sent part of the EOB? */
   eob_sent = smtp->eob;

  /* This loop can be improved by some kind of Boyer-Moore style of
     approach but that is saved for later... */
  for(i = 0, si = 0; i < nread; i++) {
    if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) {
      smtp->eob++;

      /* Is the EOB potentially the terminating CRLF? */
      if(2 == smtp->eob || SMTP_EOB_LEN == smtp->eob)
        smtp->trailing_crlf = TRUE;
      else
        smtp->trailing_crlf = FALSE;
    }
    else if(smtp->eob) {
      /* A previous substring matched so output that first */
      memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent);
      si += smtp->eob - eob_sent;

      /* Then compare the first byte */
      if(SMTP_EOB[0] == data->req.upload_fromhere[i])
        smtp->eob = 1;
      else
        smtp->eob = 0;

      eob_sent = 0;

      /* Reset the trailing CRLF flag as there was more data */
      smtp->trailing_crlf = FALSE;
    }

    /* Do we have a match for CRLF. as per RFC-5321, sect. 4.5.2 */
    if(SMTP_EOB_FIND_LEN == smtp->eob) {
      /* Copy the replacement data to the target buffer */
      memcpy(&scratch[si], &SMTP_EOB_REPL[eob_sent],
             SMTP_EOB_REPL_LEN - eob_sent);
      si += SMTP_EOB_REPL_LEN - eob_sent;
      smtp->eob = 0;
      eob_sent = 0;
    }
    else if(!smtp->eob)
      scratch[si++] = data->req.upload_fromhere[i];
  }

  if(smtp->eob - eob_sent) {
    /* A substring matched before processing ended so output that now */
    memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent);
    si += smtp->eob - eob_sent;
  }

  /* Only use the new buffer if we replaced something */
  if(si != nread) {
    /* Upload from the new (replaced) buffer instead */
    data->req.upload_fromhere = scratch;

    /* Save the buffer so it can be freed later */
    data->state.scratch = scratch;

    /* Free the old scratch buffer */
    free(oldscratch);

    /* Set the new amount too */
    data->req.upload_present = si;
  }
  else
    free(newscratch);

  return CURLE_OK;
}


properly initialize data structure for SOAP parsing in ParseNameValue()

topelt field was not properly initialized.

should fix #268

ParseNameValue(const char * buffer, int bufsize,
                struct NameValueParserData * data)
 {
 	struct xmlparser parser;
	data->l_head = NULL;
	data->portListing = NULL;
	data->portListingLength = 0;
 	/* init xmlparser object */
 	parser.xmlstart = buffer;
 	parser.xmlsize = bufsize;
	parser.data = data;
	parser.starteltfunc = NameValueParserStartElt;
	parser.endeltfunc = NameValueParserEndElt;
	parser.datafunc = NameValueParserGetData;
	parser.attfunc = 0;
	parsexml(&parser);
}

Uninitialized stack variable vulnerability in NameValueParserEndElt (upnpreplyparse.c) in miniupnpd < 2.0 allows an attacker to cause Denial of Service (Segmentation fault and Memory Corruption) or possibly have unspecified other impact


ParseNameValue(const char * buffer, int bufsize,
                struct NameValueParserData * data)
 {
 	struct xmlparser parser;
	memset(data, 0, sizeof(struct NameValueParserData));
 	/* init xmlparser object */
 	parser.xmlstart = buffer;
 	parser.xmlsize = bufsize;
	parser.data = data;
	parser.starteltfunc = NameValueParserStartElt;
	parser.endeltfunc = NameValueParserEndElt;
	parser.datafunc = NameValueParserGetData;
	parser.attfunc = 0;
	parsexml(&parser);
}


wildmidi_lib.c (WildMidi_Open, WildMidi_OpenBuffer): refuse to proceed if less then 18 bytes of input

Fixes bug #178.

WM_SYMBOL midi *WildMidi_Open(const char *midifile) {
    uint8_t *mididata = NULL;
    uint32_t midisize = 0;
    uint8_t mus_hdr[] = { 'M', 'U', 'S', 0x1A };
    uint8_t xmi_hdr[] = { 'F', 'O', 'R', 'M' };
    midi * ret = NULL;

    if (!WM_Initialized) {
        _WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_INIT, NULL, 0);
        return (NULL);
    }
    if (midifile == NULL) {
        _WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID_ARG, "(NULL filename)", 0);
        return (NULL);
    }

     if ((mididata = (uint8_t *) _WM_BufferFile(midifile, &midisize)) == NULL) {
         return (NULL);
     }
     if (memcmp(mididata,"HMIMIDIP", 8) == 0) {
         ret = (void *) _WM_ParseNewHmp(mididata, midisize);
     } else if (memcmp(mididata, "HMI-MIDISONG061595", 18) == 0) {
        ret = (void *) _WM_ParseNewHmi(mididata, midisize);
    } else if (memcmp(mididata, mus_hdr, 4) == 0) {
        ret = (void *) _WM_ParseNewMus(mididata, midisize);
    } else if (memcmp(mididata, xmi_hdr, 4) == 0) {
        ret = (void *) _WM_ParseNewXmi(mididata, midisize);
    } else {
        ret = (void *) _WM_ParseNewMidi(mididata, midisize);
    }
    free(mididata);

    if (ret) {
        if (add_handle(ret) != 0) {
            WildMidi_Close(ret);
            ret = NULL;
        }
    }

    return (ret);
}

The WildMidi_Open function in WildMIDI since commit d8a466829c67cacbb1700beded25c448d99514e5 allows remote attackers to cause a denial of service (heap-based buffer overflow and application crash) or possibly have unspecified other impact via a crafted file.


WM_SYMBOL midi *WildMidi_Open(const char *midifile) {
    uint8_t *mididata = NULL;
    uint32_t midisize = 0;
    uint8_t mus_hdr[] = { 'M', 'U', 'S', 0x1A };
    uint8_t xmi_hdr[] = { 'F', 'O', 'R', 'M' };
    midi * ret = NULL;

    if (!WM_Initialized) {
        _WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_INIT, NULL, 0);
        return (NULL);
    }
    if (midifile == NULL) {
        _WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID_ARG, "(NULL filename)", 0);
        return (NULL);
    }

     if ((mididata = (uint8_t *) _WM_BufferFile(midifile, &midisize)) == NULL) {
         return (NULL);
     }
    if (midisize < 18) {
        _WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);
        return (NULL);
    }
     if (memcmp(mididata,"HMIMIDIP", 8) == 0) {
         ret = (void *) _WM_ParseNewHmp(mididata, midisize);
     } else if (memcmp(mididata, "HMI-MIDISONG061595", 18) == 0) {
        ret = (void *) _WM_ParseNewHmi(mididata, midisize);
    } else if (memcmp(mididata, mus_hdr, 4) == 0) {
        ret = (void *) _WM_ParseNewMus(mididata, midisize);
    } else if (memcmp(mididata, xmi_hdr, 4) == 0) {
        ret = (void *) _WM_ParseNewXmi(mididata, midisize);
    } else {
        ret = (void *) _WM_ParseNewMidi(mididata, midisize);
    }
    free(mididata);

    if (ret) {
        if (add_handle(ret) != 0) {
            WildMidi_Close(ret);
            ret = NULL;
        }
    }

    return (ret);
}


f2fs: fix a dead loop in f2fs_fiemap()

A dead loop can be triggered in f2fs_fiemap() using the test case
as below:

	...
	fd = open();
	fallocate(fd, 0, 0, 4294967296);
	ioctl(fd, FS_IOC_FIEMAP, fiemap_buf);
	...

It's caused by an overflow in __get_data_block():
	...
	bh->b_size = map.m_len << inode->i_blkbits;
	...
map.m_len is an unsigned int, and bh->b_size is a size_t which is 64 bits
on 64 bits archtecture, type conversion from an unsigned int to a size_t
will result in an overflow.

In the above-mentioned case, bh->b_size will be zero, and f2fs_fiemap()
will call get_data_block() at block 0 again an again.

Fix this by adding a force conversion before left shift.

Signed-off-by: Wei Fang <fangwei1@huawei.com>
Acked-by: Chao Yu <yuchao0@huawei.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>

static int __get_data_block(struct inode *inode, sector_t iblock,
			struct buffer_head *bh, int create, int flag,
			pgoff_t *next_pgofs)
{
	struct f2fs_map_blocks map;
	int err;

	map.m_lblk = iblock;
	map.m_len = bh->b_size >> inode->i_blkbits;
	map.m_next_pgofs = next_pgofs;

	err = f2fs_map_blocks(inode, &map, create, flag);
 	if (!err) {
 		map_bh(bh, inode->i_sb, map.m_pblk);
 		bh->b_state = (bh->b_state & ~F2FS_MAP_FLAGS) | map.m_flags;
		bh->b_size = map.m_len << inode->i_blkbits;
 	}
 	return err;
 }

The __get_data_block function in fs/f2fs/data.c in the Linux kernel before 4.11 allows local users to cause a denial of service (integer overflow and loop) via crafted use of the open and fallocate system calls with an FS_IOC_FIEMAP ioctl.


static int __get_data_block(struct inode *inode, sector_t iblock,
			struct buffer_head *bh, int create, int flag,
			pgoff_t *next_pgofs)
{
	struct f2fs_map_blocks map;
	int err;

	map.m_lblk = iblock;
	map.m_len = bh->b_size >> inode->i_blkbits;
	map.m_next_pgofs = next_pgofs;

	err = f2fs_map_blocks(inode, &map, create, flag);
 	if (!err) {
 		map_bh(bh, inode->i_sb, map.m_pblk);
 		bh->b_state = (bh->b_state & ~F2FS_MAP_FLAGS) | map.m_flags;
		bh->b_size = (u64)map.m_len << inode->i_blkbits;
 	}
 	return err;
 }


perf/core: Fix the perf_cpu_time_max_percent check

Use "proc_dointvec_minmax" instead of "proc_dointvec" to check the input
value from user-space.

If not, we can set a big value and some vars will overflow like
"sysctl_perf_event_sample_rate" which will cause a lot of unexpected
problems.

Signed-off-by: Tan Xiaojun <tanxiaojun@huawei.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: <acme@kernel.org>
Cc: <alexander.shishkin@linux.intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Vince Weaver <vincent.weaver@maine.edu>
Link: http://lkml.kernel.org/r/1487829879-56237-1-git-send-email-tanxiaojun@huawei.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>

int perf_cpu_time_max_percent_handler(struct ctl_table *table, int write,
 				void __user *buffer, size_t *lenp,
 				loff_t *ppos)
 {
	int ret = proc_dointvec(table, write, buffer, lenp, ppos);

 	if (ret || !write)
 		return ret;

	if (sysctl_perf_cpu_time_max_percent == 100 ||
	    sysctl_perf_cpu_time_max_percent == 0) {
		printk(KERN_WARNING
		       "perf: Dynamic interrupt throttling disabled, can hang your system!\n");
		WRITE_ONCE(perf_sample_allowed_ns, 0);
	} else {
		update_perf_cpu_limits();
	}

	return 0;
}

The perf_cpu_time_max_percent_handler function in kernel/events/core.c in the Linux kernel before 4.11 allows local users to cause a denial of service (integer overflow) or possibly have unspecified other impact via a large value, as demonstrated by an incorrect sample-rate calculation.


int perf_cpu_time_max_percent_handler(struct ctl_table *table, int write,
 				void __user *buffer, size_t *lenp,
 				loff_t *ppos)
 {
	int ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);

 	if (ret || !write)
 		return ret;

	if (sysctl_perf_cpu_time_max_percent == 100 ||
	    sysctl_perf_cpu_time_max_percent == 0) {
		printk(KERN_WARNING
		       "perf: Dynamic interrupt throttling disabled, can hang your system!\n");
		WRITE_ONCE(perf_sample_allowed_ns, 0);
	} else {
		update_perf_cpu_limits();
	}

	return 0;
}


f2fs: fix race condition in between free nid allocator/initializer

In below concurrent case, allocated nid can be loaded into free nid cache
and be allocated again.

Thread A				Thread B
- f2fs_create
 - f2fs_new_inode
  - alloc_nid
   - __insert_nid_to_list(ALLOC_NID_LIST)
					- f2fs_balance_fs_bg
					 - build_free_nids
					  - __build_free_nids
					   - scan_nat_page
					    - add_free_nid
					     - __lookup_nat_cache
 - f2fs_add_link
  - init_inode_metadata
   - new_inode_page
    - new_node_page
     - set_node_addr
 - alloc_nid_done
  - __remove_nid_from_list(ALLOC_NID_LIST)
					     - __insert_nid_to_list(FREE_NID_LIST)

This patch makes nat cache lookup and free nid list operation being atomical
to avoid this race condition.

Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
Signed-off-by: Chao Yu <yuchao0@huawei.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>

 static bool add_free_nid(struct f2fs_sb_info *sbi, nid_t nid, bool build)
 {
 	struct f2fs_nm_info *nm_i = NM_I(sbi);
	struct free_nid *i;
 	struct nat_entry *ne;
	int err;

 	/* 0 nid should not be used */
 	if (unlikely(nid == 0))
 		return false;

	if (build) {
		/* do not add allocated nids */
		ne = __lookup_nat_cache(nm_i, nid);
		if (ne && (!get_nat_flag(ne, IS_CHECKPOINTED) ||
				nat_get_blkaddr(ne) != NULL_ADDR))
			return false;
	}
 	i = f2fs_kmem_cache_alloc(free_nid_slab, GFP_NOFS);
 	i->nid = nid;
 	i->state = NID_NEW;

	if (radix_tree_preload(GFP_NOFS)) {
		kmem_cache_free(free_nid_slab, i);
		return true;
	}

 	spin_lock(&nm_i->nid_list_lock);
 	err = __insert_nid_to_list(sbi, i, FREE_NID_LIST, true);
 	spin_unlock(&nm_i->nid_list_lock);
 	radix_tree_preload_end();
	if (err) {
 		kmem_cache_free(free_nid_slab, i);
		return true;
	}
	return true;
 }

The add_free_nid function in fs/f2fs/node.c in the Linux kernel before 4.12 does not properly track an allocated nid, which allows local users to cause a denial of service (race condition) or possibly have unspecified other impact via concurrent threads.


 static bool add_free_nid(struct f2fs_sb_info *sbi, nid_t nid, bool build)
 {
 	struct f2fs_nm_info *nm_i = NM_I(sbi);
	struct free_nid *i, *e;
 	struct nat_entry *ne;
	int err = -EINVAL;
	bool ret = false;

 	/* 0 nid should not be used */
 	if (unlikely(nid == 0))
 		return false;

 	i = f2fs_kmem_cache_alloc(free_nid_slab, GFP_NOFS);
 	i->nid = nid;
 	i->state = NID_NEW;

	if (radix_tree_preload(GFP_NOFS))
		goto err;

 	spin_lock(&nm_i->nid_list_lock);

	if (build) {
		/*
		 *   Thread A             Thread B
		 *  - f2fs_create
		 *   - f2fs_new_inode
		 *    - alloc_nid
		 *     - __insert_nid_to_list(ALLOC_NID_LIST)
		 *                     - f2fs_balance_fs_bg
		 *                      - build_free_nids
		 *                       - __build_free_nids
		 *                        - scan_nat_page
		 *                         - add_free_nid
		 *                          - __lookup_nat_cache
		 *  - f2fs_add_link
		 *   - init_inode_metadata
		 *    - new_inode_page
		 *     - new_node_page
		 *      - set_node_addr
		 *  - alloc_nid_done
		 *   - __remove_nid_from_list(ALLOC_NID_LIST)
		 *                         - __insert_nid_to_list(FREE_NID_LIST)
		 */
		ne = __lookup_nat_cache(nm_i, nid);
		if (ne && (!get_nat_flag(ne, IS_CHECKPOINTED) ||
				nat_get_blkaddr(ne) != NULL_ADDR))
			goto err_out;

		e = __lookup_free_nid_list(nm_i, nid);
		if (e) {
			if (e->state == NID_NEW)
				ret = true;
			goto err_out;
		}
	}
	ret = true;
 	err = __insert_nid_to_list(sbi, i, FREE_NID_LIST, true);
err_out:
 	spin_unlock(&nm_i->nid_list_lock);
 	radix_tree_preload_end();
err:
	if (err)
 		kmem_cache_free(free_nid_slab, i);
	return ret;
 }


DBUS notifications could crash the scheduler (Issue #5143)

- scheduler/ipp.c: Make sure requesting-user-name string is valid UTF-8.

add_job(cupsd_client_t  *con,		/* I - Client connection */
	cupsd_printer_t *printer,	/* I - Destination printer */
	mime_type_t     *filetype)	/* I - First print file type, if any */
{
  http_status_t	status;			/* Policy status */
  ipp_attribute_t *attr,		/* Current attribute */
		*auth_info;		/* auth-info attribute */
  const char	*mandatory;		/* Current mandatory job attribute */
  const char	*val;			/* Default option value */
  int		priority;		/* Job priority */
  cupsd_job_t	*job;			/* Current job */
  char		job_uri[HTTP_MAX_URI];	/* Job URI */
  int		kbytes;			/* Size of print file */
  int		i;			/* Looping var */
  int		lowerpagerange;		/* Page range bound */
  int		exact;			/* Did we have an exact match? */
  ipp_attribute_t *media_col,		/* media-col attribute */
		*media_margin;		/* media-*-margin attribute */
  ipp_t		*unsup_col;		/* media-col in unsupported response */
  static const char * const readonly[] =/* List of read-only attributes */
  {
    "date-time-at-completed",
    "date-time-at-creation",
    "date-time-at-processing",
    "job-detailed-status-messages",
    "job-document-access-errors",
    "job-id",
    "job-impressions-completed",
    "job-k-octets-completed",
    "job-media-sheets-completed",
    "job-pages-completed",
    "job-printer-up-time",
    "job-printer-uri",
    "job-state",
    "job-state-message",
    "job-state-reasons",
    "job-uri",
    "number-of-documents",
    "number-of-intervening-jobs",
    "output-device-assigned",
    "time-at-completed",
    "time-at-creation",
    "time-at-processing"
  };

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_job(%p[%d], %p(%s), %p(%s/%s))",
                  con, con->number, printer, printer->name,
		  filetype, filetype ? filetype->super : "none",
		  filetype ? filetype->type : "none");

 /*
  * Check remote printing to non-shared printer...
  */

  if (!printer->shared &&
      _cups_strcasecmp(con->http->hostname, "localhost") &&
      _cups_strcasecmp(con->http->hostname, ServerName))
  {
    send_ipp_status(con, IPP_NOT_AUTHORIZED,
                    _("The printer or class is not shared."));
    return (NULL);
  }

 /*
  * Check policy...
  */

  auth_info = ippFindAttribute(con->request, "auth-info", IPP_TAG_TEXT);

  if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK)
  {
    send_http_error(con, status, printer);
    return (NULL);
  }
  else if (printer->num_auth_info_required == 1 &&
           !strcmp(printer->auth_info_required[0], "negotiate") &&
           !con->username[0])
  {
    send_http_error(con, HTTP_UNAUTHORIZED, printer);
    return (NULL);
  }
#ifdef HAVE_SSL
  else if (auth_info && !con->http->tls &&
           !httpAddrLocalhost(con->http->hostaddr))
  {
   /*
    * Require encryption of auth-info over non-local connections...
    */

    send_http_error(con, HTTP_UPGRADE_REQUIRED, printer);
    return (NULL);
  }
#endif /* HAVE_SSL */

 /*
  * See if the printer is accepting jobs...
  */

  if (!printer->accepting)
  {
    send_ipp_status(con, IPP_NOT_ACCEPTING,
                    _("Destination \"%s\" is not accepting jobs."),
                    printer->name);
    return (NULL);
  }

 /*
  * Validate job template attributes; for now just document-format,
  * copies, job-sheets, number-up, page-ranges, mandatory attributes, and
  * media...
  */

  for (i = 0; i < (int)(sizeof(readonly) / sizeof(readonly[0])); i ++)
  {
    if ((attr = ippFindAttribute(con->request, readonly[i], IPP_TAG_ZERO)) != NULL)
    {
      ippDeleteAttribute(con->request, attr);

      if (StrictConformance)
      {
	send_ipp_status(con, IPP_BAD_REQUEST, _("The '%s' Job Status attribute cannot be supplied in a job creation request."), readonly[i]);
	return (NULL);
      }

      cupsdLogMessage(CUPSD_LOG_INFO, "Unexpected '%s' Job Status attribute in a job creation request.", readonly[i]);
    }
  }

  if (printer->pc)
  {
    for (mandatory = (char *)cupsArrayFirst(printer->pc->mandatory);
	 mandatory;
	 mandatory = (char *)cupsArrayNext(printer->pc->mandatory))
    {
      if (!ippFindAttribute(con->request, mandatory, IPP_TAG_ZERO))
      {
       /*
	* Missing a required attribute...
	*/

	send_ipp_status(con, IPP_CONFLICT,
			_("The \"%s\" attribute is required for print jobs."),
			mandatory);
	return (NULL);
      }
    }
  }

  if (filetype && printer->filetypes &&
      !cupsArrayFind(printer->filetypes, filetype))
  {
    char	mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2];
					/* MIME media type string */

    snprintf(mimetype, sizeof(mimetype), "%s/%s", filetype->super,
             filetype->type);

    send_ipp_status(con, IPP_DOCUMENT_FORMAT,
                    _("Unsupported format \"%s\"."), mimetype);

    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE,
                 "document-format", NULL, mimetype);

    return (NULL);
  }

  if ((attr = ippFindAttribute(con->request, "copies",
                               IPP_TAG_INTEGER)) != NULL)
  {
    if (attr->values[0].integer < 1 || attr->values[0].integer > MaxCopies)
    {
      send_ipp_status(con, IPP_ATTRIBUTES, _("Bad copies value %d."),
                      attr->values[0].integer);
      ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER,
	            "copies", attr->values[0].integer);
      return (NULL);
    }
  }

  if ((attr = ippFindAttribute(con->request, "job-sheets",
                               IPP_TAG_ZERO)) != NULL)
  {
    if (attr->value_tag != IPP_TAG_KEYWORD &&
        attr->value_tag != IPP_TAG_NAME)
    {
      send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-sheets value type."));
      return (NULL);
    }

    if (attr->num_values > 2)
    {
      send_ipp_status(con, IPP_BAD_REQUEST,
                      _("Too many job-sheets values (%d > 2)."),
		      attr->num_values);
      return (NULL);
    }

    for (i = 0; i < attr->num_values; i ++)
      if (strcmp(attr->values[i].string.text, "none") &&
          !cupsdFindBanner(attr->values[i].string.text))
      {
	send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-sheets value \"%s\"."),
			attr->values[i].string.text);
	return (NULL);
      }
  }

  if ((attr = ippFindAttribute(con->request, "number-up",
                               IPP_TAG_INTEGER)) != NULL)
  {
    if (attr->values[0].integer != 1 &&
        attr->values[0].integer != 2 &&
        attr->values[0].integer != 4 &&
        attr->values[0].integer != 6 &&
        attr->values[0].integer != 9 &&
        attr->values[0].integer != 16)
    {
      send_ipp_status(con, IPP_ATTRIBUTES, _("Bad number-up value %d."),
                      attr->values[0].integer);
      ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER,
	            "number-up", attr->values[0].integer);
      return (NULL);
    }
  }

  if ((attr = ippFindAttribute(con->request, "page-ranges",
                               IPP_TAG_RANGE)) != NULL)
  {
    for (i = 0, lowerpagerange = 1; i < attr->num_values; i ++)
    {
      if (attr->values[i].range.lower < lowerpagerange ||
	  attr->values[i].range.lower > attr->values[i].range.upper)
      {
	send_ipp_status(con, IPP_BAD_REQUEST,
	                _("Bad page-ranges values %d-%d."),
	                attr->values[i].range.lower,
			attr->values[i].range.upper);
	return (NULL);
      }

      lowerpagerange = attr->values[i].range.upper + 1;
    }
  }

 /*
  * Do media selection as needed...
  */

  if (!ippFindAttribute(con->request, "PageRegion", IPP_TAG_ZERO) &&
      !ippFindAttribute(con->request, "PageSize", IPP_TAG_ZERO) &&
      _ppdCacheGetPageSize(printer->pc, con->request, NULL, &exact))
  {
    if (!exact &&
        (media_col = ippFindAttribute(con->request, "media-col",
	                              IPP_TAG_BEGIN_COLLECTION)) != NULL)
    {
      send_ipp_status(con, IPP_OK_SUBST, _("Unsupported margins."));

      unsup_col = ippNew();
      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
                                           "media-bottom-margin",
					   IPP_TAG_INTEGER)) != NULL)
        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
	              "media-bottom-margin", media_margin->values[0].integer);

      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
                                           "media-left-margin",
					   IPP_TAG_INTEGER)) != NULL)
        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
	              "media-left-margin", media_margin->values[0].integer);

      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
                                           "media-right-margin",
					   IPP_TAG_INTEGER)) != NULL)
        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
	              "media-right-margin", media_margin->values[0].integer);

      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
                                           "media-top-margin",
					   IPP_TAG_INTEGER)) != NULL)
        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
	              "media-top-margin", media_margin->values[0].integer);

      ippAddCollection(con->response, IPP_TAG_UNSUPPORTED_GROUP, "media-col",
                       unsup_col);
      ippDelete(unsup_col);
    }
  }

 /*
  * Make sure we aren't over our limit...
  */

  if (MaxJobs && cupsArrayCount(Jobs) >= MaxJobs)
    cupsdCleanJobs();

  if (MaxJobs && cupsArrayCount(Jobs) >= MaxJobs)
  {
    send_ipp_status(con, IPP_NOT_POSSIBLE, _("Too many active jobs."));
    return (NULL);
  }

  if ((i = check_quotas(con, printer)) < 0)
  {
    send_ipp_status(con, IPP_NOT_POSSIBLE, _("Quota limit reached."));
    return (NULL);
  }
  else if (i == 0)
  {
    send_ipp_status(con, IPP_NOT_AUTHORIZED, _("Not allowed to print."));
    return (NULL);
  }

 /*
  * Create the job and set things up...
  */

  if ((attr = ippFindAttribute(con->request, "job-priority",
                               IPP_TAG_INTEGER)) != NULL)
    priority = attr->values[0].integer;
  else
  {
    if ((val = cupsGetOption("job-priority", printer->num_options,
                             printer->options)) != NULL)
      priority = atoi(val);
    else
      priority = 50;

    ippAddInteger(con->request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority",
                  priority);
  }

  if ((attr = ippFindAttribute(con->request, "job-name", IPP_TAG_ZERO)) == NULL)
    ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, "Untitled");
  else if ((attr->value_tag != IPP_TAG_NAME &&
            attr->value_tag != IPP_TAG_NAMELANG) ||
           attr->num_values != 1)
  {
    send_ipp_status(con, IPP_ATTRIBUTES,
                    _("Bad job-name value: Wrong type or count."));
    if ((attr = ippCopyAttribute(con->response, attr, 0)) != NULL)
      attr->group_tag = IPP_TAG_UNSUPPORTED_GROUP;
    return (NULL);
  }
  else if (!ippValidateAttribute(attr))
  {
    send_ipp_status(con, IPP_ATTRIBUTES, _("Bad job-name value: %s"),
                    cupsLastErrorString());
    if ((attr = ippCopyAttribute(con->response, attr, 0)) != NULL)
      attr->group_tag = IPP_TAG_UNSUPPORTED_GROUP;
     return (NULL);
   }

   if ((job = cupsdAddJob(priority, printer->name)) == NULL)
   {
     send_ipp_status(con, IPP_INTERNAL_ERROR,
                    _("Unable to add job for destination \"%s\"."),
		    printer->name);
    return (NULL);
  }

  job->dtype   = printer->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE);
  job->attrs   = con->request;
  job->dirty   = 1;
  con->request = ippNewRequest(job->attrs->request.op.operation_id);

  cupsdMarkDirty(CUPSD_DIRTY_JOBS);

   add_job_uuid(job);
   apply_printer_defaults(printer, job);

  attr = ippFindAttribute(job->attrs, "requesting-user-name", IPP_TAG_NAME);
   if (con->username[0])
   {
     cupsdSetString(&job->username, con->username);

    if (attr)
      ippSetString(job->attrs, &attr, 0, con->username);
  }
  else if (attr)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG,
                    "add_job: requesting-user-name=\"%s\"",
                    attr->values[0].string.text);

    cupsdSetString(&job->username, attr->values[0].string.text);
  }
  else
    cupsdSetString(&job->username, "anonymous");

  if (!attr)
    ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME,
                 "job-originating-user-name", NULL, job->username);
  else
  {
    ippSetGroupTag(job->attrs, &attr, IPP_TAG_JOB);
    ippSetName(job->attrs, &attr, "job-originating-user-name");
  }

  if (con->username[0] || auth_info)
  {
    save_auth_info(con, job, auth_info);

   /*
    * Remove the auth-info attribute from the attribute data...
    */

    if (auth_info)
      ippDeleteAttribute(job->attrs, auth_info);
  }

  if ((attr = ippFindAttribute(con->request, "job-name", IPP_TAG_NAME)) != NULL)
    cupsdSetString(&(job->name), attr->values[0].string.text);

  if ((attr = ippFindAttribute(job->attrs, "job-originating-host-name",
                               IPP_TAG_ZERO)) != NULL)
  {
   /*
    * Request contains a job-originating-host-name attribute; validate it...
    */

    if (attr->value_tag != IPP_TAG_NAME ||
        attr->num_values != 1 ||
        strcmp(con->http->hostname, "localhost"))
    {
     /*
      * Can't override the value if we aren't connected via localhost.
      * Also, we can only have 1 value and it must be a name value.
      */

      ippDeleteAttribute(job->attrs, attr);
      ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-originating-host-name", NULL, con->http->hostname);
    }
    else
      ippSetGroupTag(job->attrs, &attr, IPP_TAG_JOB);
  }
  else
  {
   /*
    * No job-originating-host-name attribute, so use the hostname from
    * the connection...
    */

    ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME,
        	 "job-originating-host-name", NULL, con->http->hostname);
  }

  ippAddOutOfBand(job->attrs, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-completed");
  ippAddDate(job->attrs, IPP_TAG_JOB, "date-time-at-creation", ippTimeToDate(time(NULL)));
  ippAddOutOfBand(job->attrs, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-processing");
  ippAddOutOfBand(job->attrs, IPP_TAG_JOB, IPP_TAG_NOVALUE, "time-at-completed");
  ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", time(NULL));
  ippAddOutOfBand(job->attrs, IPP_TAG_JOB, IPP_TAG_NOVALUE, "time-at-processing");

 /*
  * Add remaining job attributes...
  */

  ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
  job->state = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_ENUM,
                             "job-state", IPP_JOB_STOPPED);
  job->state_value = (ipp_jstate_t)job->state->values[0].integer;
  job->reasons = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
                              "job-state-reasons", NULL, "job-incoming");
  job->impressions = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions-completed", 0);
  job->sheets = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER,
                              "job-media-sheets-completed", 0);
  ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL,
               printer->uri);

  if ((attr = ippFindAttribute(job->attrs, "job-k-octets", IPP_TAG_INTEGER)) != NULL)
    attr->values[0].integer = 0;
  else
    ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-k-octets", 0);

  if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
                               IPP_TAG_KEYWORD)) == NULL)
    attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
  if (!attr)
  {
    if ((val = cupsGetOption("job-hold-until", printer->num_options,
                             printer->options)) == NULL)
      val = "no-hold";

    attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
                        "job-hold-until", NULL, val);
  }

  if (printer->holding_new_jobs)
  {
   /*
    * Hold all new jobs on this printer...
    */

    if (attr && strcmp(attr->values[0].string.text, "no-hold"))
      cupsdSetJobHoldUntil(job, ippGetString(attr, 0, NULL), 0);
    else
      cupsdSetJobHoldUntil(job, "indefinite", 0);

    job->state->values[0].integer = IPP_JOB_HELD;
    job->state_value              = IPP_JOB_HELD;

    ippSetString(job->attrs, &job->reasons, 0, "job-held-on-create");
  }
  else if (attr && strcmp(attr->values[0].string.text, "no-hold"))
  {
   /*
    * Hold job until specified time...
    */

    cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0);

    job->state->values[0].integer = IPP_JOB_HELD;
    job->state_value              = IPP_JOB_HELD;

    ippSetString(job->attrs, &job->reasons, 0, "job-hold-until-specified");
  }
  else if (job->attrs->request.op.operation_id == IPP_CREATE_JOB)
  {
    job->hold_until               = time(NULL) + MultipleOperationTimeout;
    job->state->values[0].integer = IPP_JOB_HELD;
    job->state_value              = IPP_JOB_HELD;
  }
  else
  {
    job->state->values[0].integer = IPP_JOB_PENDING;
    job->state_value              = IPP_JOB_PENDING;

    ippSetString(job->attrs, &job->reasons, 0, "none");
  }

  if (!(printer->type & CUPS_PRINTER_REMOTE) || Classification)
  {
   /*
    * Add job sheets options...
    */

    if ((attr = ippFindAttribute(job->attrs, "job-sheets",
                                 IPP_TAG_ZERO)) == NULL)
    {
      cupsdLogMessage(CUPSD_LOG_DEBUG,
                      "Adding default job-sheets values \"%s,%s\"...",
                      printer->job_sheets[0], printer->job_sheets[1]);

      attr = ippAddStrings(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-sheets",
                           2, NULL, NULL);
      ippSetString(job->attrs, &attr, 0, printer->job_sheets[0]);
      ippSetString(job->attrs, &attr, 1, printer->job_sheets[1]);
    }

    job->job_sheets = attr;

   /*
    * Enforce classification level if set...
    */

    if (Classification)
    {
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "Classification=\"%s\", ClassifyOverride=%d",
                      Classification ? Classification : "(null)",
		      ClassifyOverride);

      if (ClassifyOverride)
      {
        if (!strcmp(attr->values[0].string.text, "none") &&
	    (attr->num_values == 1 ||
	     !strcmp(attr->values[1].string.text, "none")))
        {
	 /*
          * Force the leading banner to have the classification on it...
	  */

          ippSetString(job->attrs, &attr, 0, Classification);

	  cupsdLogJob(job, CUPSD_LOG_NOTICE, "CLASSIFICATION FORCED "
	                		     "job-sheets=\"%s,none\", "
					     "job-originating-user-name=\"%s\"",
	              Classification, job->username);
	}
	else if (attr->num_values == 2 &&
	         strcmp(attr->values[0].string.text,
		        attr->values[1].string.text) &&
		 strcmp(attr->values[0].string.text, "none") &&
		 strcmp(attr->values[1].string.text, "none"))
        {
	 /*
	  * Can't put two different security markings on the same document!
	  */

          ippSetString(job->attrs, &attr, 1, attr->values[0].string.text);

	  cupsdLogJob(job, CUPSD_LOG_NOTICE, "CLASSIFICATION FORCED "
	                		     "job-sheets=\"%s,%s\", "
					     "job-originating-user-name=\"%s\"",
		      attr->values[0].string.text,
		      attr->values[1].string.text, job->username);
	}
	else if (strcmp(attr->values[0].string.text, Classification) &&
	         strcmp(attr->values[0].string.text, "none") &&
		 (attr->num_values == 1 ||
	          (strcmp(attr->values[1].string.text, Classification) &&
	           strcmp(attr->values[1].string.text, "none"))))
        {
	  if (attr->num_values == 1)
            cupsdLogJob(job, CUPSD_LOG_NOTICE,
			"CLASSIFICATION OVERRIDDEN "
			"job-sheets=\"%s\", "
			"job-originating-user-name=\"%s\"",
	                attr->values[0].string.text, job->username);
          else
            cupsdLogJob(job, CUPSD_LOG_NOTICE,
			"CLASSIFICATION OVERRIDDEN "
			"job-sheets=\"%s,%s\",fffff "
			"job-originating-user-name=\"%s\"",
			attr->values[0].string.text,
			attr->values[1].string.text, job->username);
        }
      }
      else if (strcmp(attr->values[0].string.text, Classification) &&
               (attr->num_values == 1 ||
	       strcmp(attr->values[1].string.text, Classification)))
      {
       /*
        * Force the banner to have the classification on it...
	*/

        if (attr->num_values > 1 &&
	    !strcmp(attr->values[0].string.text, attr->values[1].string.text))
	{
          ippSetString(job->attrs, &attr, 0, Classification);
          ippSetString(job->attrs, &attr, 1, Classification);
	}
        else
	{
          if (attr->num_values == 1 ||
	      strcmp(attr->values[0].string.text, "none"))
            ippSetString(job->attrs, &attr, 0, Classification);

          if (attr->num_values > 1 &&
	      strcmp(attr->values[1].string.text, "none"))
	    ippSetString(job->attrs, &attr, 1, Classification);
        }

        if (attr->num_values > 1)
	  cupsdLogJob(job, CUPSD_LOG_NOTICE,
		      "CLASSIFICATION FORCED "
		      "job-sheets=\"%s,%s\", "
		      "job-originating-user-name=\"%s\"",
		      attr->values[0].string.text,
		      attr->values[1].string.text, job->username);
        else
	  cupsdLogJob(job, CUPSD_LOG_NOTICE,
		      "CLASSIFICATION FORCED "
		      "job-sheets=\"%s\", "
		      "job-originating-user-name=\"%s\"",
		      Classification, job->username);
      }
    }

   /*
    * See if we need to add the starting sheet...
    */

    if (!(printer->type & CUPS_PRINTER_REMOTE))
    {
      cupsdLogJob(job, CUPSD_LOG_INFO, "Adding start banner page \"%s\".",
		  attr->values[0].string.text);

      if ((kbytes = copy_banner(con, job, attr->values[0].string.text)) < 0)
      {
        cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE,
	                 "Aborting job because the start banner could not be "
			 "copied.");
        return (NULL);
      }

      cupsdUpdateQuota(printer, job->username, 0, kbytes);
    }
  }
  else if ((attr = ippFindAttribute(job->attrs, "job-sheets",
                                    IPP_TAG_ZERO)) != NULL)
    job->job_sheets = attr;

 /*
  * Fill in the response info...
  */

  httpAssembleURIf(HTTP_URI_CODING_ALL, job_uri, sizeof(job_uri), "ipp", NULL,
                   con->clientname, con->clientport, "/jobs/%d", job->id);
  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL,
               job_uri);

  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);

  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state",
                job->state_value);
  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_TEXT, "job-state-message", NULL, "");
  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, "job-state-reasons",
               NULL, job->reasons->values[0].string.text);

  con->response->request.status.status_code = IPP_OK;

 /*
  * Add any job subscriptions...
  */

  add_job_subscriptions(con, job);

 /*
  * Set all but the first two attributes to the job attributes group...
  */

  for (attr = job->attrs->attrs->next->next; attr; attr = attr->next)
    attr->group_tag = IPP_TAG_JOB;

 /*
  * Fire the "job created" event...
  */

  cupsdAddEvent(CUPSD_EVENT_JOB_CREATED, printer, job, "Job created.");

 /*
  * Return the new job...
  */

  return (job);
}

The add_job function in scheduler/ipp.c in CUPS before 2.2.6, when D-Bus support is enabled, can be crashed by remote attackers by sending print jobs with an invalid username, related to a D-Bus notification.


add_job(cupsd_client_t  *con,		/* I - Client connection */
	cupsd_printer_t *printer,	/* I - Destination printer */
	mime_type_t     *filetype)	/* I - First print file type, if any */
{
  http_status_t	status;			/* Policy status */
  ipp_attribute_t *attr,		/* Current attribute */
		*auth_info;		/* auth-info attribute */
  const char	*mandatory;		/* Current mandatory job attribute */
  const char	*val;			/* Default option value */
  int		priority;		/* Job priority */
  cupsd_job_t	*job;			/* Current job */
  char		job_uri[HTTP_MAX_URI];	/* Job URI */
  int		kbytes;			/* Size of print file */
  int		i;			/* Looping var */
  int		lowerpagerange;		/* Page range bound */
  int		exact;			/* Did we have an exact match? */
  ipp_attribute_t *media_col,		/* media-col attribute */
		*media_margin;		/* media-*-margin attribute */
  ipp_t		*unsup_col;		/* media-col in unsupported response */
  static const char * const readonly[] =/* List of read-only attributes */
  {
    "date-time-at-completed",
    "date-time-at-creation",
    "date-time-at-processing",
    "job-detailed-status-messages",
    "job-document-access-errors",
    "job-id",
    "job-impressions-completed",
    "job-k-octets-completed",
    "job-media-sheets-completed",
    "job-pages-completed",
    "job-printer-up-time",
    "job-printer-uri",
    "job-state",
    "job-state-message",
    "job-state-reasons",
    "job-uri",
    "number-of-documents",
    "number-of-intervening-jobs",
    "output-device-assigned",
    "time-at-completed",
    "time-at-creation",
    "time-at-processing"
  };

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_job(%p[%d], %p(%s), %p(%s/%s))",
                  con, con->number, printer, printer->name,
		  filetype, filetype ? filetype->super : "none",
		  filetype ? filetype->type : "none");

 /*
  * Check remote printing to non-shared printer...
  */

  if (!printer->shared &&
      _cups_strcasecmp(con->http->hostname, "localhost") &&
      _cups_strcasecmp(con->http->hostname, ServerName))
  {
    send_ipp_status(con, IPP_NOT_AUTHORIZED,
                    _("The printer or class is not shared."));
    return (NULL);
  }

 /*
  * Check policy...
  */

  auth_info = ippFindAttribute(con->request, "auth-info", IPP_TAG_TEXT);

  if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK)
  {
    send_http_error(con, status, printer);
    return (NULL);
  }
  else if (printer->num_auth_info_required == 1 &&
           !strcmp(printer->auth_info_required[0], "negotiate") &&
           !con->username[0])
  {
    send_http_error(con, HTTP_UNAUTHORIZED, printer);
    return (NULL);
  }
#ifdef HAVE_SSL
  else if (auth_info && !con->http->tls &&
           !httpAddrLocalhost(con->http->hostaddr))
  {
   /*
    * Require encryption of auth-info over non-local connections...
    */

    send_http_error(con, HTTP_UPGRADE_REQUIRED, printer);
    return (NULL);
  }
#endif /* HAVE_SSL */

 /*
  * See if the printer is accepting jobs...
  */

  if (!printer->accepting)
  {
    send_ipp_status(con, IPP_NOT_ACCEPTING,
                    _("Destination \"%s\" is not accepting jobs."),
                    printer->name);
    return (NULL);
  }

 /*
  * Validate job template attributes; for now just document-format,
  * copies, job-sheets, number-up, page-ranges, mandatory attributes, and
  * media...
  */

  for (i = 0; i < (int)(sizeof(readonly) / sizeof(readonly[0])); i ++)
  {
    if ((attr = ippFindAttribute(con->request, readonly[i], IPP_TAG_ZERO)) != NULL)
    {
      ippDeleteAttribute(con->request, attr);

      if (StrictConformance)
      {
	send_ipp_status(con, IPP_BAD_REQUEST, _("The '%s' Job Status attribute cannot be supplied in a job creation request."), readonly[i]);
	return (NULL);
      }

      cupsdLogMessage(CUPSD_LOG_INFO, "Unexpected '%s' Job Status attribute in a job creation request.", readonly[i]);
    }
  }

  if (printer->pc)
  {
    for (mandatory = (char *)cupsArrayFirst(printer->pc->mandatory);
	 mandatory;
	 mandatory = (char *)cupsArrayNext(printer->pc->mandatory))
    {
      if (!ippFindAttribute(con->request, mandatory, IPP_TAG_ZERO))
      {
       /*
	* Missing a required attribute...
	*/

	send_ipp_status(con, IPP_CONFLICT,
			_("The \"%s\" attribute is required for print jobs."),
			mandatory);
	return (NULL);
      }
    }
  }

  if (filetype && printer->filetypes &&
      !cupsArrayFind(printer->filetypes, filetype))
  {
    char	mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2];
					/* MIME media type string */

    snprintf(mimetype, sizeof(mimetype), "%s/%s", filetype->super,
             filetype->type);

    send_ipp_status(con, IPP_DOCUMENT_FORMAT,
                    _("Unsupported format \"%s\"."), mimetype);

    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE,
                 "document-format", NULL, mimetype);

    return (NULL);
  }

  if ((attr = ippFindAttribute(con->request, "copies",
                               IPP_TAG_INTEGER)) != NULL)
  {
    if (attr->values[0].integer < 1 || attr->values[0].integer > MaxCopies)
    {
      send_ipp_status(con, IPP_ATTRIBUTES, _("Bad copies value %d."),
                      attr->values[0].integer);
      ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER,
	            "copies", attr->values[0].integer);
      return (NULL);
    }
  }

  if ((attr = ippFindAttribute(con->request, "job-sheets",
                               IPP_TAG_ZERO)) != NULL)
  {
    if (attr->value_tag != IPP_TAG_KEYWORD &&
        attr->value_tag != IPP_TAG_NAME)
    {
      send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-sheets value type."));
      return (NULL);
    }

    if (attr->num_values > 2)
    {
      send_ipp_status(con, IPP_BAD_REQUEST,
                      _("Too many job-sheets values (%d > 2)."),
		      attr->num_values);
      return (NULL);
    }

    for (i = 0; i < attr->num_values; i ++)
      if (strcmp(attr->values[i].string.text, "none") &&
          !cupsdFindBanner(attr->values[i].string.text))
      {
	send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-sheets value \"%s\"."),
			attr->values[i].string.text);
	return (NULL);
      }
  }

  if ((attr = ippFindAttribute(con->request, "number-up",
                               IPP_TAG_INTEGER)) != NULL)
  {
    if (attr->values[0].integer != 1 &&
        attr->values[0].integer != 2 &&
        attr->values[0].integer != 4 &&
        attr->values[0].integer != 6 &&
        attr->values[0].integer != 9 &&
        attr->values[0].integer != 16)
    {
      send_ipp_status(con, IPP_ATTRIBUTES, _("Bad number-up value %d."),
                      attr->values[0].integer);
      ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER,
	            "number-up", attr->values[0].integer);
      return (NULL);
    }
  }

  if ((attr = ippFindAttribute(con->request, "page-ranges",
                               IPP_TAG_RANGE)) != NULL)
  {
    for (i = 0, lowerpagerange = 1; i < attr->num_values; i ++)
    {
      if (attr->values[i].range.lower < lowerpagerange ||
	  attr->values[i].range.lower > attr->values[i].range.upper)
      {
	send_ipp_status(con, IPP_BAD_REQUEST,
	                _("Bad page-ranges values %d-%d."),
	                attr->values[i].range.lower,
			attr->values[i].range.upper);
	return (NULL);
      }

      lowerpagerange = attr->values[i].range.upper + 1;
    }
  }

 /*
  * Do media selection as needed...
  */

  if (!ippFindAttribute(con->request, "PageRegion", IPP_TAG_ZERO) &&
      !ippFindAttribute(con->request, "PageSize", IPP_TAG_ZERO) &&
      _ppdCacheGetPageSize(printer->pc, con->request, NULL, &exact))
  {
    if (!exact &&
        (media_col = ippFindAttribute(con->request, "media-col",
	                              IPP_TAG_BEGIN_COLLECTION)) != NULL)
    {
      send_ipp_status(con, IPP_OK_SUBST, _("Unsupported margins."));

      unsup_col = ippNew();
      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
                                           "media-bottom-margin",
					   IPP_TAG_INTEGER)) != NULL)
        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
	              "media-bottom-margin", media_margin->values[0].integer);

      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
                                           "media-left-margin",
					   IPP_TAG_INTEGER)) != NULL)
        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
	              "media-left-margin", media_margin->values[0].integer);

      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
                                           "media-right-margin",
					   IPP_TAG_INTEGER)) != NULL)
        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
	              "media-right-margin", media_margin->values[0].integer);

      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
                                           "media-top-margin",
					   IPP_TAG_INTEGER)) != NULL)
        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
	              "media-top-margin", media_margin->values[0].integer);

      ippAddCollection(con->response, IPP_TAG_UNSUPPORTED_GROUP, "media-col",
                       unsup_col);
      ippDelete(unsup_col);
    }
  }

 /*
  * Make sure we aren't over our limit...
  */

  if (MaxJobs && cupsArrayCount(Jobs) >= MaxJobs)
    cupsdCleanJobs();

  if (MaxJobs && cupsArrayCount(Jobs) >= MaxJobs)
  {
    send_ipp_status(con, IPP_NOT_POSSIBLE, _("Too many active jobs."));
    return (NULL);
  }

  if ((i = check_quotas(con, printer)) < 0)
  {
    send_ipp_status(con, IPP_NOT_POSSIBLE, _("Quota limit reached."));
    return (NULL);
  }
  else if (i == 0)
  {
    send_ipp_status(con, IPP_NOT_AUTHORIZED, _("Not allowed to print."));
    return (NULL);
  }

 /*
  * Create the job and set things up...
  */

  if ((attr = ippFindAttribute(con->request, "job-priority",
                               IPP_TAG_INTEGER)) != NULL)
    priority = attr->values[0].integer;
  else
  {
    if ((val = cupsGetOption("job-priority", printer->num_options,
                             printer->options)) != NULL)
      priority = atoi(val);
    else
      priority = 50;

    ippAddInteger(con->request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority",
                  priority);
  }

  if ((attr = ippFindAttribute(con->request, "job-name", IPP_TAG_ZERO)) == NULL)
    ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, "Untitled");
  else if ((attr->value_tag != IPP_TAG_NAME &&
            attr->value_tag != IPP_TAG_NAMELANG) ||
           attr->num_values != 1)
  {
    send_ipp_status(con, IPP_ATTRIBUTES,
                    _("Bad job-name value: Wrong type or count."));
    if ((attr = ippCopyAttribute(con->response, attr, 0)) != NULL)
      attr->group_tag = IPP_TAG_UNSUPPORTED_GROUP;
    return (NULL);
  }
  else if (!ippValidateAttribute(attr))
  {
    send_ipp_status(con, IPP_ATTRIBUTES, _("Bad job-name value: %s"),
                    cupsLastErrorString());
    if ((attr = ippCopyAttribute(con->response, attr, 0)) != NULL)
      attr->group_tag = IPP_TAG_UNSUPPORTED_GROUP;
     return (NULL);
   }

  attr = ippFindAttribute(con->request, "requesting-user-name", IPP_TAG_NAME);

  if (attr && !ippValidateAttribute(attr))
  {
    send_ipp_status(con, IPP_ATTRIBUTES, _("Bad requesting-user-name value: %s"), cupsLastErrorString());
    if ((attr = ippCopyAttribute(con->response, attr, 0)) != NULL)
      attr->group_tag = IPP_TAG_UNSUPPORTED_GROUP;
    return (NULL);
  }

   if ((job = cupsdAddJob(priority, printer->name)) == NULL)
   {
     send_ipp_status(con, IPP_INTERNAL_ERROR,
                    _("Unable to add job for destination \"%s\"."),
		    printer->name);
    return (NULL);
  }

  job->dtype   = printer->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE);
  job->attrs   = con->request;
  job->dirty   = 1;
  con->request = ippNewRequest(job->attrs->request.op.operation_id);

  cupsdMarkDirty(CUPSD_DIRTY_JOBS);

   add_job_uuid(job);
   apply_printer_defaults(printer, job);

   if (con->username[0])
   {
     cupsdSetString(&job->username, con->username);

    if (attr)
      ippSetString(job->attrs, &attr, 0, con->username);
  }
  else if (attr)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG,
                    "add_job: requesting-user-name=\"%s\"",
                    attr->values[0].string.text);

    cupsdSetString(&job->username, attr->values[0].string.text);
  }
  else
    cupsdSetString(&job->username, "anonymous");

  if (!attr)
    ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME,
                 "job-originating-user-name", NULL, job->username);
  else
  {
    ippSetGroupTag(job->attrs, &attr, IPP_TAG_JOB);
    ippSetName(job->attrs, &attr, "job-originating-user-name");
  }

  if (con->username[0] || auth_info)
  {
    save_auth_info(con, job, auth_info);

   /*
    * Remove the auth-info attribute from the attribute data...
    */

    if (auth_info)
      ippDeleteAttribute(job->attrs, auth_info);
  }

  if ((attr = ippFindAttribute(con->request, "job-name", IPP_TAG_NAME)) != NULL)
    cupsdSetString(&(job->name), attr->values[0].string.text);

  if ((attr = ippFindAttribute(job->attrs, "job-originating-host-name",
                               IPP_TAG_ZERO)) != NULL)
  {
   /*
    * Request contains a job-originating-host-name attribute; validate it...
    */

    if (attr->value_tag != IPP_TAG_NAME ||
        attr->num_values != 1 ||
        strcmp(con->http->hostname, "localhost"))
    {
     /*
      * Can't override the value if we aren't connected via localhost.
      * Also, we can only have 1 value and it must be a name value.
      */

      ippDeleteAttribute(job->attrs, attr);
      ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-originating-host-name", NULL, con->http->hostname);
    }
    else
      ippSetGroupTag(job->attrs, &attr, IPP_TAG_JOB);
  }
  else
  {
   /*
    * No job-originating-host-name attribute, so use the hostname from
    * the connection...
    */

    ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME,
        	 "job-originating-host-name", NULL, con->http->hostname);
  }

  ippAddOutOfBand(job->attrs, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-completed");
  ippAddDate(job->attrs, IPP_TAG_JOB, "date-time-at-creation", ippTimeToDate(time(NULL)));
  ippAddOutOfBand(job->attrs, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-processing");
  ippAddOutOfBand(job->attrs, IPP_TAG_JOB, IPP_TAG_NOVALUE, "time-at-completed");
  ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", time(NULL));
  ippAddOutOfBand(job->attrs, IPP_TAG_JOB, IPP_TAG_NOVALUE, "time-at-processing");

 /*
  * Add remaining job attributes...
  */

  ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
  job->state = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_ENUM,
                             "job-state", IPP_JOB_STOPPED);
  job->state_value = (ipp_jstate_t)job->state->values[0].integer;
  job->reasons = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
                              "job-state-reasons", NULL, "job-incoming");
  job->impressions = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions-completed", 0);
  job->sheets = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER,
                              "job-media-sheets-completed", 0);
  ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL,
               printer->uri);

  if ((attr = ippFindAttribute(job->attrs, "job-k-octets", IPP_TAG_INTEGER)) != NULL)
    attr->values[0].integer = 0;
  else
    ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-k-octets", 0);

  if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
                               IPP_TAG_KEYWORD)) == NULL)
    attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
  if (!attr)
  {
    if ((val = cupsGetOption("job-hold-until", printer->num_options,
                             printer->options)) == NULL)
      val = "no-hold";

    attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
                        "job-hold-until", NULL, val);
  }

  if (printer->holding_new_jobs)
  {
   /*
    * Hold all new jobs on this printer...
    */

    if (attr && strcmp(attr->values[0].string.text, "no-hold"))
      cupsdSetJobHoldUntil(job, ippGetString(attr, 0, NULL), 0);
    else
      cupsdSetJobHoldUntil(job, "indefinite", 0);

    job->state->values[0].integer = IPP_JOB_HELD;
    job->state_value              = IPP_JOB_HELD;

    ippSetString(job->attrs, &job->reasons, 0, "job-held-on-create");
  }
  else if (attr && strcmp(attr->values[0].string.text, "no-hold"))
  {
   /*
    * Hold job until specified time...
    */

    cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0);

    job->state->values[0].integer = IPP_JOB_HELD;
    job->state_value              = IPP_JOB_HELD;

    ippSetString(job->attrs, &job->reasons, 0, "job-hold-until-specified");
  }
  else if (job->attrs->request.op.operation_id == IPP_CREATE_JOB)
  {
    job->hold_until               = time(NULL) + MultipleOperationTimeout;
    job->state->values[0].integer = IPP_JOB_HELD;
    job->state_value              = IPP_JOB_HELD;
  }
  else
  {
    job->state->values[0].integer = IPP_JOB_PENDING;
    job->state_value              = IPP_JOB_PENDING;

    ippSetString(job->attrs, &job->reasons, 0, "none");
  }

  if (!(printer->type & CUPS_PRINTER_REMOTE) || Classification)
  {
   /*
    * Add job sheets options...
    */

    if ((attr = ippFindAttribute(job->attrs, "job-sheets",
                                 IPP_TAG_ZERO)) == NULL)
    {
      cupsdLogMessage(CUPSD_LOG_DEBUG,
                      "Adding default job-sheets values \"%s,%s\"...",
                      printer->job_sheets[0], printer->job_sheets[1]);

      attr = ippAddStrings(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-sheets",
                           2, NULL, NULL);
      ippSetString(job->attrs, &attr, 0, printer->job_sheets[0]);
      ippSetString(job->attrs, &attr, 1, printer->job_sheets[1]);
    }

    job->job_sheets = attr;

   /*
    * Enforce classification level if set...
    */

    if (Classification)
    {
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "Classification=\"%s\", ClassifyOverride=%d",
                      Classification ? Classification : "(null)",
		      ClassifyOverride);

      if (ClassifyOverride)
      {
        if (!strcmp(attr->values[0].string.text, "none") &&
	    (attr->num_values == 1 ||
	     !strcmp(attr->values[1].string.text, "none")))
        {
	 /*
          * Force the leading banner to have the classification on it...
	  */

          ippSetString(job->attrs, &attr, 0, Classification);

	  cupsdLogJob(job, CUPSD_LOG_NOTICE, "CLASSIFICATION FORCED "
	                		     "job-sheets=\"%s,none\", "
					     "job-originating-user-name=\"%s\"",
	              Classification, job->username);
	}
	else if (attr->num_values == 2 &&
	         strcmp(attr->values[0].string.text,
		        attr->values[1].string.text) &&
		 strcmp(attr->values[0].string.text, "none") &&
		 strcmp(attr->values[1].string.text, "none"))
        {
	 /*
	  * Can't put two different security markings on the same document!
	  */

          ippSetString(job->attrs, &attr, 1, attr->values[0].string.text);

	  cupsdLogJob(job, CUPSD_LOG_NOTICE, "CLASSIFICATION FORCED "
	                		     "job-sheets=\"%s,%s\", "
					     "job-originating-user-name=\"%s\"",
		      attr->values[0].string.text,
		      attr->values[1].string.text, job->username);
	}
	else if (strcmp(attr->values[0].string.text, Classification) &&
	         strcmp(attr->values[0].string.text, "none") &&
		 (attr->num_values == 1 ||
	          (strcmp(attr->values[1].string.text, Classification) &&
	           strcmp(attr->values[1].string.text, "none"))))
        {
	  if (attr->num_values == 1)
            cupsdLogJob(job, CUPSD_LOG_NOTICE,
			"CLASSIFICATION OVERRIDDEN "
			"job-sheets=\"%s\", "
			"job-originating-user-name=\"%s\"",
	                attr->values[0].string.text, job->username);
          else
            cupsdLogJob(job, CUPSD_LOG_NOTICE,
			"CLASSIFICATION OVERRIDDEN "
			"job-sheets=\"%s,%s\",fffff "
			"job-originating-user-name=\"%s\"",
			attr->values[0].string.text,
			attr->values[1].string.text, job->username);
        }
      }
      else if (strcmp(attr->values[0].string.text, Classification) &&
               (attr->num_values == 1 ||
	       strcmp(attr->values[1].string.text, Classification)))
      {
       /*
        * Force the banner to have the classification on it...
	*/

        if (attr->num_values > 1 &&
	    !strcmp(attr->values[0].string.text, attr->values[1].string.text))
	{
          ippSetString(job->attrs, &attr, 0, Classification);
          ippSetString(job->attrs, &attr, 1, Classification);
	}
        else
	{
          if (attr->num_values == 1 ||
	      strcmp(attr->values[0].string.text, "none"))
            ippSetString(job->attrs, &attr, 0, Classification);

          if (attr->num_values > 1 &&
	      strcmp(attr->values[1].string.text, "none"))
	    ippSetString(job->attrs, &attr, 1, Classification);
        }

        if (attr->num_values > 1)
	  cupsdLogJob(job, CUPSD_LOG_NOTICE,
		      "CLASSIFICATION FORCED "
		      "job-sheets=\"%s,%s\", "
		      "job-originating-user-name=\"%s\"",
		      attr->values[0].string.text,
		      attr->values[1].string.text, job->username);
        else
	  cupsdLogJob(job, CUPSD_LOG_NOTICE,
		      "CLASSIFICATION FORCED "
		      "job-sheets=\"%s\", "
		      "job-originating-user-name=\"%s\"",
		      Classification, job->username);
      }
    }

   /*
    * See if we need to add the starting sheet...
    */

    if (!(printer->type & CUPS_PRINTER_REMOTE))
    {
      cupsdLogJob(job, CUPSD_LOG_INFO, "Adding start banner page \"%s\".",
		  attr->values[0].string.text);

      if ((kbytes = copy_banner(con, job, attr->values[0].string.text)) < 0)
      {
        cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE,
	                 "Aborting job because the start banner could not be "
			 "copied.");
        return (NULL);
      }

      cupsdUpdateQuota(printer, job->username, 0, kbytes);
    }
  }
  else if ((attr = ippFindAttribute(job->attrs, "job-sheets",
                                    IPP_TAG_ZERO)) != NULL)
    job->job_sheets = attr;

 /*
  * Fill in the response info...
  */

  httpAssembleURIf(HTTP_URI_CODING_ALL, job_uri, sizeof(job_uri), "ipp", NULL,
                   con->clientname, con->clientport, "/jobs/%d", job->id);
  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL,
               job_uri);

  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);

  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state",
                job->state_value);
  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_TEXT, "job-state-message", NULL, "");
  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, "job-state-reasons",
               NULL, job->reasons->values[0].string.text);

  con->response->request.status.status_code = IPP_OK;

 /*
  * Add any job subscriptions...
  */

  add_job_subscriptions(con, job);

 /*
  * Set all but the first two attributes to the job attributes group...
  */

  for (attr = job->attrs->attrs->next->next; attr; attr = attr->next)
    attr->group_tag = IPP_TAG_JOB;

 /*
  * Fire the "job created" event...
  */

  cupsdAddEvent(CUPSD_EVENT_JOB_CREATED, printer, job, "Job created.");

 /*
  * Return the new job...
  */

  return (job);
}


mlock: fix mlock count can not decrease in race condition

Kefeng reported that when running the follow test, the mlock count in
meminfo will increase permanently:

 [1] testcase
 linux:~ # cat test_mlockal
 grep Mlocked /proc/meminfo
  for j in `seq 0 10`
  do
 	for i in `seq 4 15`
 	do
 		./p_mlockall >> log &
 	done
 	sleep 0.2
 done
 # wait some time to let mlock counter decrease and 5s may not enough
 sleep 5
 grep Mlocked /proc/meminfo

 linux:~ # cat p_mlockall.c
 #include <sys/mman.h>
 #include <stdlib.h>
 #include <stdio.h>

 #define SPACE_LEN	4096

 int main(int argc, char ** argv)
 {
	 	int ret;
	 	void *adr = malloc(SPACE_LEN);
	 	if (!adr)
	 		return -1;

	 	ret = mlockall(MCL_CURRENT | MCL_FUTURE);
	 	printf("mlcokall ret = %d\n", ret);

	 	ret = munlockall();
	 	printf("munlcokall ret = %d\n", ret);

	 	free(adr);
	 	return 0;
	 }

In __munlock_pagevec() we should decrement NR_MLOCK for each page where
we clear the PageMlocked flag.  Commit 1ebb7cc6a583 ("mm: munlock: batch
NR_MLOCK zone state updates") has introduced a bug where we don't
decrement NR_MLOCK for pages where we clear the flag, but fail to
isolate them from the lru list (e.g.  when the pages are on some other
cpu's percpu pagevec).  Since PageMlocked stays cleared, the NR_MLOCK
accounting gets permanently disrupted by this.

Fix it by counting the number of page whose PageMlock flag is cleared.

Fixes: 1ebb7cc6a583 (" mm: munlock: batch NR_MLOCK zone state updates")
Link: http://lkml.kernel.org/r/1495678405-54569-1-git-send-email-xieyisheng1@huawei.com
Signed-off-by: Yisheng Xie <xieyisheng1@huawei.com>
Reported-by: Kefeng Wang <wangkefeng.wang@huawei.com>
Tested-by: Kefeng Wang <wangkefeng.wang@huawei.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Joern Engel <joern@logfs.org>
Cc: Mel Gorman <mgorman@suse.de>
Cc: Michel Lespinasse <walken@google.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Michal Hocko <mhocko@suse.cz>
Cc: Xishi Qiu <qiuxishi@huawei.com>
Cc: zhongjiang <zhongjiang@huawei.com>
Cc: Hanjun Guo <guohanjun@huawei.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

static void __munlock_pagevec(struct pagevec *pvec, struct zone *zone)
 {
 	int i;
 	int nr = pagevec_count(pvec);
	int delta_munlocked;
 	struct pagevec pvec_putback;
 	int pgrescued = 0;

	pagevec_init(&pvec_putback, 0);

	/* Phase 1: page isolation */
	spin_lock_irq(zone_lru_lock(zone));
	for (i = 0; i < nr; i++) {
		struct page *page = pvec->pages[i];

		if (TestClearPageMlocked(page)) {
			/*
			 * We already have pin from follow_page_mask()
			 * so we can spare the get_page() here.
			 */
			if (__munlock_isolate_lru_page(page, false))
 				continue;
 			else
 				__munlock_isolation_failed(page);
 		}

 		/*
		 * We won't be munlocking this page in the next phase
		 * but we still need to release the follow_page_mask()
		 * pin. We cannot do it under lru_lock however. If it's
		 * the last pin, __page_cache_release() would deadlock.
		 */
 		pagevec_add(&pvec_putback, pvec->pages[i]);
 		pvec->pages[i] = NULL;
 	}
	delta_munlocked = -nr + pagevec_count(&pvec_putback);
 	__mod_zone_page_state(zone, NR_MLOCK, delta_munlocked);
 	spin_unlock_irq(zone_lru_lock(zone));

	/* Now we can release pins of pages that we are not munlocking */
	pagevec_release(&pvec_putback);

	/* Phase 2: page munlock */
	for (i = 0; i < nr; i++) {
		struct page *page = pvec->pages[i];

		if (page) {
			lock_page(page);
			if (!__putback_lru_fast_prepare(page, &pvec_putback,
					&pgrescued)) {
				/*
				 * Slow path. We don't want to lose the last
				 * pin before unlock_page()
				 */
				get_page(page); /* for putback_lru_page() */
				__munlock_isolated_page(page);
				unlock_page(page);
				put_page(page); /* from follow_page_mask() */
			}
		}
	}

	/*
	 * Phase 3: page putback for pages that qualified for the fast path
	 * This will also call put_page() to return pin from follow_page_mask()
	 */
	if (pagevec_count(&pvec_putback))
		__putback_lru_fast(&pvec_putback, pgrescued);
}

The __munlock_pagevec function in mm/mlock.c in the Linux kernel before 4.11.4 allows local users to cause a denial of service (NR_MLOCK accounting corruption) via crafted use of mlockall and munlockall system calls.


static void __munlock_pagevec(struct pagevec *pvec, struct zone *zone)
 {
 	int i;
 	int nr = pagevec_count(pvec);
	int delta_munlocked = -nr;
 	struct pagevec pvec_putback;
 	int pgrescued = 0;

	pagevec_init(&pvec_putback, 0);

	/* Phase 1: page isolation */
	spin_lock_irq(zone_lru_lock(zone));
	for (i = 0; i < nr; i++) {
		struct page *page = pvec->pages[i];

		if (TestClearPageMlocked(page)) {
			/*
			 * We already have pin from follow_page_mask()
			 * so we can spare the get_page() here.
			 */
			if (__munlock_isolate_lru_page(page, false))
 				continue;
 			else
 				__munlock_isolation_failed(page);
		} else {
			delta_munlocked++;
 		}

 		/*
		 * We won't be munlocking this page in the next phase
		 * but we still need to release the follow_page_mask()
		 * pin. We cannot do it under lru_lock however. If it's
		 * the last pin, __page_cache_release() would deadlock.
		 */
 		pagevec_add(&pvec_putback, pvec->pages[i]);
 		pvec->pages[i] = NULL;
 	}
 	__mod_zone_page_state(zone, NR_MLOCK, delta_munlocked);
 	spin_unlock_irq(zone_lru_lock(zone));

	/* Now we can release pins of pages that we are not munlocking */
	pagevec_release(&pvec_putback);

	/* Phase 2: page munlock */
	for (i = 0; i < nr; i++) {
		struct page *page = pvec->pages[i];

		if (page) {
			lock_page(page);
			if (!__putback_lru_fast_prepare(page, &pvec_putback,
					&pgrescued)) {
				/*
				 * Slow path. We don't want to lose the last
				 * pin before unlock_page()
				 */
				get_page(page); /* for putback_lru_page() */
				__munlock_isolated_page(page);
				unlock_page(page);
				put_page(page); /* from follow_page_mask() */
			}
		}
	}

	/*
	 * Phase 3: page putback for pages that qualified for the fast path
	 * This will also call put_page() to return pin from follow_page_mask()
	 */
	if (pagevec_count(&pvec_putback))
		__putback_lru_fast(&pvec_putback, pgrescued);
}


static netdev_tx_t hns_nic_net_xmit(struct sk_buff *skb,
 				    struct net_device *ndev)
 {
 	struct hns_nic_priv *priv = netdev_priv(ndev);
	int ret;

 	assert(skb->queue_mapping < ndev->ae_handle->q_num);
	ret = hns_nic_net_xmit_hw(ndev, skb,
				  &tx_ring_data(priv, skb->queue_mapping));
	if (ret == NETDEV_TX_OK) {
		netif_trans_update(ndev);
		ndev->stats.tx_bytes += skb->len;
		ndev->stats.tx_packets++;
	}
	return (netdev_tx_t)ret;
 }

In drivers/net/ethernet/hisilicon/hns/hns_enet.c in the Linux kernel before 4.13, local users can cause a denial of service (use-after-free and BUG) or possibly have unspecified other impact by leveraging differences in skb handling between hns_nic_net_xmit_hw and hns_nic_net_xmit.


static netdev_tx_t hns_nic_net_xmit(struct sk_buff *skb,
 				    struct net_device *ndev)
 {
 	struct hns_nic_priv *priv = netdev_priv(ndev);

 	assert(skb->queue_mapping < ndev->ae_handle->q_num);

	return hns_nic_net_xmit_hw(ndev, skb,
				   &tx_ring_data(priv, skb->queue_mapping));
 }